Skip to content

Instantly share code, notes, and snippets.

@zeffii
Last active August 29, 2015 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zeffii/528ccd234adb297593c9 to your computer and use it in GitHub Desktop.
Save zeffii/528ccd234adb297593c9 to your computer and use it in GitHub Desktop.
outline from edge
'''
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