Last active
August 29, 2015 14:21
-
-
Save zeffii/528ccd234adb297593c9 to your computer and use it in GitHub Desktop.
outline from edge
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
Dealga McArdle (2015) | GPL3 license. | |
''' | |
import math | |
from collections import defaultdict | |
import bpy | |
import bmesh | |
import mathutils | |
from mathutils import Vector, Matrix | |
from mathutils.geometry import convex_hull_2d | |
from mathutils.geometry import intersect_line_line as LineIntersect | |
from mathutils.geometry import intersect_point_line as PtLineIntersect | |
from sverchok.utils.sv_bmesh_utils import pydata_from_bmesh | |
''' | |
known limitations | |
Currently this script assumes the plane of operation is x, y and | |
everything is co-planar. There are two parts in the code which work | |
on x,y axis assumption, they are denoted with a # ***. Both sections | |
can be replaced with code that is axis agnostic, but all points | |
should be co-planar. | |
''' | |
def point_on_edge(p, edge): | |
''' | |
> p: vector | |
> edge: tuple of 2 vectors | |
< returns: True / False if a point happens to lie on an edge | |
''' | |
pt, _percent = PtLineIntersect(p, *edge) | |
on_line = (pt - p).length < 1.0e-5 | |
return on_line and (0.0 <= _percent <= 1.0) | |
def get_intersection(edge1, edge2): | |
''' | |
> takes 2 tuples, each tuple contains 2 vectors | |
< returns the point halfway on line. See intersect_line_line | |
''' | |
[p1, p2], [p3, p4] = edge1, edge2 | |
line = LineIntersect(p1, p2, p3, p4) | |
return ((line[0] + line[1]) / 2) | |
def reprocess_bmesh(bm, radius): | |
def get_rad_angle(a, b): | |
return a.angle(b, math.pi / 2) | |
geom = pydata_from_bmesh(bm) | |
original_verts, original_edges, original_faces = geom | |
new_verts, new_edges, new_faces = [], [], [] | |
new_verts.extend(original_verts) | |
new_edges.extend(original_edges) | |
new_faces.extend(original_faces) | |
if -0.001 < radius < 0.001: | |
return geom | |
verts = [] | |
edges = [] | |
faces = [] | |
bm.verts.ensure_lookup_table() | |
bm.edges.ensure_lookup_table() | |
# first ignore any loose verts, this perhaps optional for safety. OPTION | |
linked_vertices = [v for v in bm.verts if v.link_edges[:]] | |
corner_map = {} | |
c_edge_map = defaultdict(list) | |
edge_map = defaultdict(list) | |
index = len(original_verts) | |
''' find all vertices, used by only one edge ''' | |
single_linked_verts = [v for v in bm.verts if len(v.link_edges[:]) is 1] | |
for v in single_linked_verts: | |
e = v.link_edges[0] | |
this_vert = v | |
other_vert = e.other_vert(v) | |
v1 = this_vert.co | |
v2 = other_vert.co | |
vlength = (v1 - v2).length | |
v3 = v2.lerp(v1, (vlength + radius) / vlength) | |
# calculate new geom | |
axis = Vector((0, 0, 1)) # *** x,y axis co-planar only. | |
mat_rot_1 = Matrix.Rotation(math.radians(90.0), 4, axis) | |
mat_rot_2 = Matrix.Rotation(math.radians(-90.0), 4, axis) | |
v4 = ((v3 - v1) * mat_rot_1) + v3 | |
v5 = ((v3 - v1) * mat_rot_2) + v3 | |
# add new geom | |
new_verts.extend([v4[:], v5[:]]) | |
generated_indices = [index, index + 1] | |
new_edges.extend([generated_indices]) | |
corner_map[v.index] = generated_indices | |
c_edge_map[e.index] = generated_indices | |
index += 2 | |
''' verts shared by more than one edge ''' | |
multi_linked_verts = [v for v in bm.verts if len(v.link_edges[:]) > 1] | |
for v in multi_linked_verts: | |
num_linked_edges = len(v.link_edges) | |
if num_linked_edges is 2: | |
''' polyline bend ''' | |
edge_1 = v.link_edges[0] | |
edge_2 = v.link_edges[1] | |
v1 = edge_1.other_vert(v).co - v.co | |
v2 = edge_2.other_vert(v).co - v.co | |
n1 = v1.normalized() | |
n2 = v2.normalized() | |
half_angle_AB = get_rad_angle(n1, n2) / 2.0 | |
B = math.sin(half_angle_AB) | |
A_ = (1 / B) * radius | |
p3 = n1.lerp(n2, 0.5).normalized() | |
nd3 = (p3 * A_) + v.co | |
new_verts.append(nd3[:]) | |
nd4 = nd3.lerp(v.co, 2) | |
new_verts.append(nd4[:]) | |
# store the newly generated vertex indices associated | |
# with both edges. | |
edge_map[edge_1.index].extend([index, index + 1]) | |
edge_map[edge_2.index].extend([index, index + 1]) | |
index += 2 | |
elif num_linked_edges > 2: | |
''' | |
star junction | |
this whole elif clase # *** | |
''' | |
indices_ve = [(e.other_vert(v).index, e.index) for e in v.link_edges] | |
normalized2d = lambda i: (bm.verts[i].co - v.co).normalized().xy | |
coords_2d = [normalized2d(g[0]) for g in indices_ve] | |
# will return the ordered indices relative to coods_2d | |
arranged_indices = convex_hull_2d(coords_2d) | |
interim_storage = [] | |
num_verts = len(arranged_indices) | |
total_angle = 0 | |
for i in range(num_verts): | |
idx1 = i | |
idx2 = (i + 1) % num_verts | |
v1_index = indices_ve[arranged_indices[idx1]][0] | |
v2_index = indices_ve[arranged_indices[idx2]][0] | |
v1 = bm.verts[v1_index].co - v.co | |
v2 = bm.verts[v2_index].co - v.co | |
n1 = v1.normalized() | |
n2 = v2.normalized() | |
p3 = n1.lerp(n2, 0.5).normalized() | |
full_angle = math.degrees(get_rad_angle(n1, n2)) | |
interim_storage.append([p3, n1, n2, v1, v2, v1_index, v2_index, full_angle]) | |
total_angle += full_angle | |
check_intersections = False | |
rough_total = round(total_angle, 3) | |
print('----', rough_total, total_angle) | |
if rough_total < 360.0: | |
print('fan around {0} has one angle larger than 180 degrees'.format(v.index)) | |
check_intersections = True | |
# test if any of the adjacent edges make up 180 degrees | |
idx_180_1, idx_180_2 = -1, -1 | |
if not check_intersections: | |
# this can't happen if the total is below 360 degrees. | |
# if is below 360, it means one was more than 180 degrees, | |
# therefore another can not also be 180 degrees.. | |
for *xb, full_angle in interim_storage: | |
if round(full_angle, 3) == 180.0: | |
idx_180_1, idx_180_2 = xb[5], xb[6] | |
break | |
# only two edges per fan can have an 180 angle spread, let's print! | |
perform_180_lookup = (idx_180_1 >= 0) | |
if perform_180_lookup: | |
print('between', idx_180_1, idx_180_2, 'angle is 180.0') | |
def do_intersection(snapshot): | |
''' | |
question: do v1_index and v2_index when connected, | |
locally intersect any of the other normalized fan edges? | |
helper: interim_storage: | |
''' | |
p3, n1, n2, v1, v2, v1_index, v2_index, full_angle = snapshot | |
for _snapshot in interim_storage: | |
# test v1_index | |
if snapshot[5] in {_snapshot[5], _snapshot[6]}: | |
return | |
_p3, _n1, _n2, _v1, _v2, _v1_index, _v2_index, _full_angle = snapshot | |
ORIGIN = Vector((0, 0, 0)) | |
fan_edge = [ORIGIN, _n1] | |
p = get_intersection([n1, n2], fan_edge) | |
if point_on_edge(p, fan_edge): | |
return True | |
for snapshot in interim_storage: | |
p3, n1, n2, v1, v2, v1_index, v2_index, full_angle = snapshot | |
# exclude the scenario where this is not the set of edges that is 180 deg. | |
if not perform_180_lookup or not (v1_index == idx_180_1): | |
half_angle_AB = get_rad_angle(n1, n2) / 2.0 | |
B = math.sin(half_angle_AB) | |
A_ = (1 / B) * radius | |
if check_intersections: | |
if do_intersection(snapshot): | |
print(v1_index, v2_index, '-----<<<>>') | |
A_ *= -1 | |
print(A_) | |
nd3 = (p3 * A_) + v.co | |
new_verts.append(nd3[:]) | |
elif perform_180_lookup: | |
# -- make point, from n1 -> rotate 90, | |
# - if | |
# !) the line generated | |
# by this point can be interested by any of the other edge combos | |
# = or = | |
# 2) the line is colinear with any fan edges, | |
# - then rotate 180 deg. | |
# - else, it is already good. | |
... | |
... | |
return new_verts, new_edges, new_faces | |
def sv_main(radius=0.3): | |
verts_out = [] | |
edges_out = [] | |
faces_out = [] | |
verts_new_out = [] | |
edges_new_out = [] | |
faces_new_out = [] | |
in_sockets = [ | |
['s', 'radius', radius] | |
] | |
bm = bmesh.new() | |
objname = "Plane" | |
obj = bpy.data.objects[objname] | |
if obj: | |
bm.from_mesh(obj.data) | |
# return original | |
verts, edges, faces = pydata_from_bmesh(bm) | |
verts_out.append([verts]) | |
edges_out.append([edges]) | |
# return processed | |
verts2, edges2, faces2 = reprocess_bmesh(bm, radius) | |
verts_new_out.append([verts2]) | |
edges_new_out.append([edges2]) | |
bm.free() | |
out_sockets = [ | |
['v', 'verts', verts_out], | |
['s', 'edges', edges_out], | |
['s', 'faces', faces_out], | |
['v', 'verts_new', verts_new_out], | |
['s', 'edges_new', edges_new_out], | |
['s', 'faces_new', faces_new_out] | |
] | |
return in_sockets, out_sockets |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment