def get_perpendicular_point(pt, bl, tl, tr, br): ''' Return the point if it is inside the quad, else, return a point on the border of the quad. This function helps prevent the user from dragging crop handles outside the strip's area. ''' intersects = intersect_point_quad_2d( pt, bl, tl, tr, br) if intersects: return pt elif pt.x <= bl.x and pt.y <= bl.y: return Vector(bl) elif pt.x <= tl.x and pt.y >= tl.y: return Vector(tl) elif pt.x >= tr.x and pt.y >= tr.y: return Vector(tr) elif pt.x >= br.x and pt.y <= br.y: return Vector(br) max_x = max([tr.x, br.x]) min_x = min([tl.x, bl.x]) max_y = max([tl.y, tr.y]) min_y = min([bl.y, br.y]) # pt left of left side if (pt.x <= tl.x or pt.x <= bl.x) and (pt.y >= bl.y and pt.y <= tl.y): right = Vector([max_x, pt.y]) intersection = intersect_line_line_2d(bl, tl, pt, right) # pt right of right side elif (pt.x >= br.x or pt.x >= tr.x) and (pt.y >= br.y and pt.y <= tr.y): left = Vector([min_x, pt.y]) intersection = intersect_line_line_2d(br, tr, pt, left) # pt above top side elif (pt.y >= tl.y or pt.y >= tr.y) and (pt.x >= tl.x and pt.x <= tr.x): bottom = Vector([pt.x, min_y]) intersection = intersect_line_line_2d(tl, tr, pt, bottom) # pt below bottom side elif (pt.y <= bl.y or pt.y <= br.y) and (pt.x >= bl.x and pt.x <= br.x): top = Vector([pt.x, max_y]) intersection = intersect_line_line_2d(bl, br, pt, top) return intersection
def spline_X_shape(shape_ob, spl_index): """ > shape_ob: bezier shape object > spl_index: spline number < returns list of intersections, occuring segment """ shape_list = linear_spline_list(shape_ob) sl = interpolate_all_segments(shape_ob.data.splines[spl_index]) cross = [] # check for self-intersections first sl_length = 0 for i in range(len(sl) - 1): for j in range(i + 2, len(sl) - 1): x = intersect_line_line_2d(sl[i], sl[i + 1], sl[j], sl[j + 1]) if x != None: cross.append( [x, i // shape_ob.data.splines[spl_index].resolution_u, sl_length + (x.to_3d() - sl[i]).length] ) sl_length += (sl[i + 1] - sl[i]).length # then empty active spline from shapelist shape_list[spl_index] = [] # check complete shape for intersection with active spline sl_length = 0 for i in range(len(sl) - 1): for j, spl in enumerate(shape_list): for k, pt in enumerate(spl): for l in range(len(pt) - 1): x = intersect_line_line_2d(sl[i], sl[i + 1], pt[l], pt[l + 1]) if x != None and not ( (sl[i] - pt[l]).length < PRECISION or (sl[i] - pt[l + 1]).length < PRECISION or (sl[i + 1] - pt[l]).length < PRECISION or (sl[i + 1] - pt[l + 1]).length < PRECISION ): cross.append( [ x, i // shape_ob.data.splines[spl_index].resolution_u, sl_length + (x.to_3d() - sl[i]).length, ] ) sl_length += (sl[i + 1] - sl[i]).length cross = sorted([c for c in cross], key=lambda i: i[2]) return [c[0:2] for c in cross]
def spline_X_shape(shape_ob, spl_index): ''' > shape_ob: bezier shape object > spl_index: spline number < returns list of intersections, occuring segment ''' shape_list = linear_spline_list(shape_ob) sl = interpolate_all_segments(shape_ob.data.splines[spl_index]) cross = [] # check for self-intersections first sl_length = 0 for i in range(len(sl) - 1): for j in range(i + 2, len(sl) - 1): x = intersect_line_line_2d(sl[i], sl[i + 1], sl[j], sl[j + 1]) if x != None: cross.append([ x, i // shape_ob.data.splines[spl_index].resolution_u, sl_length + (x.to_3d() - sl[i]).length ]) sl_length += (sl[i + 1] - sl[i]).length # then empty active spline from shapelist shape_list[spl_index] = [] # check complete shape for intersection with active spline sl_length = 0 for i in range(len(sl) - 1): for j, spl in enumerate(shape_list): for k, pt in enumerate(spl): for l in range(len(pt) - 1): x = intersect_line_line_2d(sl[i], sl[i + 1], pt[l], pt[l + 1]) if x != None and not ( (sl[i] - pt[l]).length < PRECISION or (sl[i] - pt[l + 1]).length < PRECISION or (sl[i + 1] - pt[l]).length < PRECISION or (sl[i + 1] - pt[l + 1]).length < PRECISION): cross.append([ x, i // shape_ob.data.splines[spl_index].resolution_u, sl_length + (x.to_3d() - sl[i]).length ]) sl_length += (sl[i + 1] - sl[i]).length cross = sorted([c for c in cross], key=lambda i: i[2]) return [c[0:2] for c in cross]
def point_inside_border(verts,border, scale, offset): min_x = min(border[0][0],border[1][0]) max_x = max(border[0][0],border[1][0]) min_y = min(border[0][1],border[2][1]) max_y = max(border[0][1],border[2][1]) nverts= len(verts) inside = False for v in verts : co = scale*Vector(v) + Vector(offset) if min_x<=co[0]<= max_x and min_y<=co[1]<= max_y : inside = True break if not inside : for i in range(0,nverts) : a = scale*Vector(verts[i-1]) + Vector(offset) b = scale*Vector(verts[i]) + Vector(offset) for j in range(0,4): c = border[j-1] d = border[j] if intersect_line_line_2d(a,b,c,d): inside = True break return inside
def islandIntersectUvIsland(source, target, SourceOffset): # Is 1 point in the box, inside the vertLoops edgeLoopsSource = source[6] # Pretend this is offset edgeLoopsTarget = target[6] # Edge intersect test for ed in edgeLoopsSource: for seg in edgeLoopsTarget: i = geometry.intersect_line_line_2d(seg[0], seg[1], SourceOffset+ed[0], SourceOffset+ed[1], ) if i: return 1 # LINE INTERSECTION # 1 test for source being totally inside target SourceOffset.resize_3d() for pv in source[7]: if pointInIsland(pv+SourceOffset, target[0]): return 2 # SOURCE INSIDE TARGET # 2 test for a part of the target being totally inside the source. for pv in target[7]: if pointInIsland(pv-SourceOffset, source[0]): return 3 # PART OF TARGET INSIDE SOURCE. return 0 # NO INTERSECTION
def process_stroke_split_at_crossings(stroke): strokes = [] stroke = list(stroke) l = len(stroke) cstroke = [stroke.pop()] while stroke: if not stroke[-1]: strokes.append(cstroke) stroke.pop() cstroke = [stroke.pop()] continue p0,p1 = cstroke[-1],stroke[-1] # see if p0-p1 segment crosses any other segment for i in range(len(stroke)-3): q0,q1 = stroke[i+0],stroke[i+1] if q0 is None or q1 is None: continue p = intersect_line_line_2d(p0,p1, q0,q1) if not p: continue if (p-p0).length < 0.000001 or (p-p1).length < 0.000001: continue # intersection! strokes.append(cstroke + [p]) cstroke = [p] # note: inserting None to indicate broken stroke stroke = stroke[:i+1] + [p,None,p] + stroke[i+1:] break else: # no intersections! cstroke.append(stroke.pop()) if cstroke: strokes.append(cstroke) return strokes
def islandIntersectUvIsland(source, target, SourceOffset): # Is 1 point in the box, inside the vertLoops edgeLoopsSource = source[6] # Pretend this is offset edgeLoopsTarget = target[6] # Edge intersect test for ed in edgeLoopsSource: for seg in edgeLoopsTarget: i = geometry.intersect_line_line_2d(seg[0], seg[1], SourceOffset + ed[0], SourceOffset + ed[1], ) if i: return 1 # LINE INTERSECTION # 1 test for source being totally inside target SourceOffset.resize_3d() for pv in source[7]: if pointInIsland(pv + SourceOffset, target[0]): return 2 # SOURCE INSIDE TARGET # 2 test for a part of the target being totally inside the source. for pv in target[7]: if pointInIsland(pv - SourceOffset, source[0]): return 3 # PART OF TARGET INSIDE SOURCE. return 0 # NO INTERSECTION
def process_stroke_split_at_crossings(stroke): strokes = [] stroke = list(stroke) l = len(stroke) cstroke = [stroke.pop()] while stroke: if not stroke[-1]: strokes.append(cstroke) stroke.pop() cstroke = [stroke.pop()] continue p0, p1 = cstroke[-1], stroke[-1] # see if p0-p1 segment crosses any other segment for i in range(len(stroke) - 3): q0, q1 = stroke[i + 0], stroke[i + 1] if q0 is None or q1 is None: continue p = intersect_line_line_2d(p0, p1, q0, q1) if not p: continue if (p - p0).length < 0.000001 or (p - p1).length < 0.000001: continue # intersection! strokes.append(cstroke + [p]) cstroke = [p] # note: inserting None to indicate broken stroke stroke = stroke[:i + 1] + [p, None, p] + stroke[i + 1:] break else: # no intersections! cstroke.append(stroke.pop()) if cstroke: strokes.append(cstroke) return strokes
def checkpt(self, point2d): intersections = 0 for i in range(len(self.poly_points)): pt1 = self.poly_points[i] pt2 = self.poly_points[(i + 1) % len(self.poly_points)] intersect = intersect_line_line_2d(Vector(point2d), Vector([point2d[0], 0]), Vector(pt1), Vector(pt2)) if intersect != None: intersections += 1 return intersections % 2 != 0
def intersect_edges_2d(verts, edges, epsilon): '''Iterate through edges and expose them to intersect_line_line_2d''' verts_in = [Vector(v) for v in verts] ed_lengths = [(verts_in[e[1]] - verts_in[e[0]]).length for e in edges] verts_out = verts edges_out = [] ed_inter = [[] for e in edges] for e, d, i in zip(edges, ed_lengths, range(len(edges))): # if there is no intersections this will create a normal edge ed_inter[i].append([0.0, e[0]]) ed_inter[i].append([d, e[1]]) v1 = verts_in[e[0]] v2 = verts_in[e[1]] if d == 0: continue for e2, d2, j in zip(edges, ed_lengths, range(len(edges))): if i <= j or d2 == 0: continue if (e2[0] in e) or (e2[1] in e): continue v3 = verts_in[e2[0]] v4 = verts_in[e2[1]] vx = intersect_line_line_2d(v1, v2, v3, v4) if vx: d_to_1 = (vx - v1.to_2d()).length d_to_2 = (vx - v3.to_2d()).length new_id = len(verts_out) if ((vx.x, vx.y, v1.z)) in verts_out: new_id = verts_out.index((vx.x, vx.y, v1.z)) else: if d_to_1 < epsilon: new_id = e[0] elif d_to_1 > d - epsilon: new_id = e[1] elif d_to_2 < epsilon: new_id = e2[0] elif d_to_2 > d2 - epsilon: new_id = e2[1] if new_id == len(verts_out): verts_out.append((vx.x, vx.y, v1.z)) # first item stores distance to origin, second the vertex id ed_inter[i].append([d_to_1, new_id]) ed_inter[j].append([d_to_2, new_id]) edges_out = edges_from_ed_inter(ed_inter) return verts_out, edges_out
def texture_intersects_others(texture_data, texture_slot, atlas_data): tex_fits_width = texture_slot.x + ( texture_data.width + atlas_data.margin) < atlas_data.width tex_fits_height = texture_slot.y + ( texture_data.height + atlas_data.margin) < atlas_data.height if tex_fits_width == False or tex_fits_height == False: return True for slot in atlas_data.texture_slots: if slot.texture_data != None and slot != texture_slot: r1 = [] r1.append(texture_slot.x) r1.append(texture_slot.y) r1.append(texture_slot.x + texture_data.width) r1.append(texture_slot.y + texture_data.height) r2 = [] r2.append(slot.x) r2.append(slot.y) r2.append(slot.x + slot.texture_data.width) r2.append(slot.y + slot.texture_data.height) points_a = [] points_a.append(Vector((r1[0], r1[1]))) points_a.append(Vector((r1[2], r1[1]))) points_a.append(Vector((r1[2], r1[3]))) points_a.append(Vector((r1[0], r1[3]))) lines_a = [[points_a[0], points_a[1]], [points_a[1], points_a[2]], [points_a[2], points_a[3]], [points_a[3], points_a[0]]] points_b = [] points_b.append(Vector((r2[0], r2[1]))) points_b.append(Vector((r2[2], r2[1]))) points_b.append(Vector((r2[2], r2[3]))) points_b.append(Vector((r2[0], r2[3]))) lines_b = [[points_b[0], points_b[1]], [points_b[1], points_b[2]], [points_b[2], points_b[3]], [points_b[3], points_b[0]]] for line_a in lines_a: for line_b in lines_b: i = intersect_line_line_2d(line_a[0], line_a[1], line_b[0], line_b[1]) if i != None: return True return False
def get_intersections(p1, p2, shape_ob): ''' > p1, p2: line segment as pair of 2D vectors > shape_ob: bezier shape object < list of intersection points ''' il = [] for spl in linear_spline_list(shape_ob): for i in range(0, len(spl)-1): point = intersect_line_line_2d(p2, p1, spl[i], spl[i+1]) if point != None and (point-p1.to_2d()).length > PRECISION: il.append(point) return il
def point_over_shape(point, verts, loops, outside_point=(-1, -1)): out = Vector(outside_point) pt = Vector(point) intersections = 0 for loop in loops: for i, p in enumerate(loop): a = Vector(verts[loop[i - 1]]) b = Vector(verts[p]) if intersect_line_line_2d(pt, out, a, b): intersections += 1 if intersections % 2 == 1: #chek if the nb of intersection is odd return True
def segment_intersection(self, p1, p2): if not isinstance(p1, Vector): p1 = Vector(p1) if not isinstance(p2, Vector): p2 = Vector(p2) min_r = BIG_FLOAT nearest = None for v_i, v_j in self.edges: intersection = intersect_line_line_2d(p1, p2, v_i, v_j) if intersection is not None: r = (p1 - intersection).length if r < min_r: nearest = intersection min_r = r return nearest
def point_inside_loop(verts, outside_point,mouse, scale, offset): nverts= len(verts) out = Vector(outside_point) pt = Vector(mouse) intersections = 0 for i in range(0,nverts): a = scale*Vector(verts[i-1]) + Vector(offset) b = scale*Vector(verts[i]) + Vector(offset) if intersect_line_line_2d(pt,out,a,b): intersections += 1 inside = False if intersections%2 == 1 : #chek if the nb of intersection is odd inside = True return inside
def border_over_shape(border, verts, loops): for loop in loops: for i, p in enumerate(loop): a = Vector(verts[loop[i - 1]]) b = Vector(verts[p]) for j in range(0, 4): c = border[j - 1] d = border[j] if intersect_line_line_2d(a, b, c, d): return True for point in verts: if point_inside_rectangle(point, border): return True for point in border: if point_over_shape(point, verts, loops): return True
def point_inside_loop(loop, point, scale, offset): nverts = len(loop) #vectorize our two item tuple out = Vector(outside_loop(loop, scale, offset)) pt = Vector(point) intersections = 0 for i in range(0, nverts): a = scale * Vector(loop[i - 1]) + Vector(offset) b = scale * Vector(loop[i]) + Vector(offset) if intersect_line_line_2d(pt, out, a, b): intersections += 1 inside = False if fmod(intersections, 2): inside = True return inside
def point_inside_loop(loop, point, scale, offset): nverts = len(loop) #vectorize our two item tuple out = Vector(outside_loop(loop, scale, offset)) pt = Vector(point) intersections = 0 for i in range(0,nverts): a = scale*Vector(loop[i-1]) + Vector(offset) b = scale*Vector(loop[i]) + Vector(offset) if intersect_line_line_2d(pt,out,a,b): intersections += 1 inside = False if fmod(intersections,2): inside = True return inside
def point_inside_loop2d(loop, point): ''' args: loop: list of vertices representing loop type-tuple or type-Vector point: location of point to be tested type-tuple or type-Vector return: True if point is inside loop ''' #test arguments type if any(not v for v in loop): return False ptype = str(type(point)) ltype = str(type(loop[0])) nverts = len(loop) if 'Vector' not in ptype: point = Vector(point) if 'Vector' not in ltype: for i in range(0, nverts): loop[i] = Vector(loop[i]) #find a point outside the loop and count intersections out = Vector(outside_loop_2d(loop)) intersections = 0 for i in range(0, nverts): a = Vector(loop[i - 1]) b = Vector(loop[i]) if intersect_line_line_2d(point, out, a, b): intersections += 1 inside = False if math.fmod(intersections, 2): inside = True return inside
def point_inside_loop2d(loop, point): ''' args: loop: list of vertices representing loop type-tuple or type-Vector point: location of point to be tested type-tuple or type-Vector return: True if point is inside loop ''' #test arguments type if any(not v for v in loop): return False ptype = str(type(point)) ltype = str(type(loop[0])) nverts = len(loop) if 'Vector' not in ptype: point = Vector(point) if 'Vector' not in ltype: for i in range(0,nverts): loop[i] = Vector(loop[i]) #find a point outside the loop and count intersections out = Vector(outside_loop_2d(loop)) intersections = 0 for i in range(0,nverts): a = Vector(loop[i-1]) b = Vector(loop[i]) if intersect_line_line_2d(point,out,a,b): intersections += 1 inside = False if math.fmod(intersections,2): inside = True return inside
def cut(self): v3 = ( 0, 0, 0 ) v4 = ( 0, self.size * 2 , 0 ) self.verts.append( v3 ) self.verts.append( v4 ) vl = len( self.verts ) # self.edges.append( (vl-2,vl-1)) everts = [] # print(v3,v4) for edge in self.edges: # print(edge) v1 = self.verts[edge[0]] v2 = self.verts[edge[1]] print('-' * 20) print( v1, v2 ) ins = intersect_line_line_2d(v1,v2,v3,v4) if ins != None: everts.append( ( ins.x, ins.y , 0) ) for x in everts: self.verts.append(x) vs = len( self.verts ) - 1 print(self.verts[vs]) print(self.verts[vl-2]) print(x) self.edges.append( ( vl-2, vs ) )
def process_stroke_get_next(stroke, from_edge, edges2D): # returns the next chunk of stroke to be processed # stops at... # - discontinuity # - intersection with self # - intersection with edges (ignoring from_edge) # - "strong" corners cstroke = [] to_edge = None curve_distance, curve_threshold = 25.0, math.cos(60.0 * math.pi / 180.0) discontinuity_distance = 10.0 def compute_cosangle_at_index(idx): nonlocal stroke if idx >= len(stroke): return 1.0 p0 = stroke[idx] for iprev in range(idx - 1, -1, -1): pprev = stroke[iprev] if (p0 - pprev).length < curve_distance: continue break else: return 1.0 for inext in range(idx + 1, len(stroke)): pnext = stroke[inext] if (p0 - pnext).length < curve_distance: continue break else: return 1.0 dprev = (p0 - pprev).normalized() dnext = (pnext - p0).normalized() cosangle = dprev.dot(dnext) return cosangle for i0 in range(1, len(stroke) - 1): i1 = i0 + 1 p0, p1 = stroke[i0], stroke[i1] # check for discontinuity if (p0 - p1).length > discontinuity_distance: # dprint('frag: %d %d %d' % (i0, len(stroke), len(stroke)-i1)) pass return (from_edge, stroke[:i1], None, False, stroke[i1:]) # check for self-intersection for j0 in range(i0 + 3, len(stroke) - 1): q0, q1 = stroke[j0], stroke[j0 + 1] p = intersect_line_line_2d(p0, p1, q0, q1) if not p: continue # dprint('self: %d %d %d' % (i0, len(stroke), len(stroke)-i1)) pass return (from_edge, stroke[:i1], None, False, stroke[i1:]) # check for intersections with edges for bme, (q0, q1) in edges2D: if bme is from_edge: continue p = intersect_line_line_2d(p0, p1, q0, q1) if not p: continue # dprint('edge: %d %d %d' % (i0, len(stroke), len(stroke)-i1)) pass return (from_edge, stroke[:i1], bme, True, stroke[i1:]) # check for strong angles cosangle = compute_cosangle_at_index(i0) if cosangle > curve_threshold: continue # found a strong angle, but there may be a stronger angle coming up... minangle = cosangle for i0_plus in range(i0 + 1, len(stroke)): p0_plus = stroke[i0_plus] if (p0 - p0_plus).length > curve_distance: break minangle = min(compute_cosangle_at_index(i0_plus), minangle) if minangle < cosangle: break if minangle < cosangle: continue # dprint('bend: %d %d %d' % (i0, len(stroke), len(stroke)-i1)) pass return (from_edge, stroke[:i1], None, False, stroke[i1:]) # dprint('full: %d %d' % (len(stroke), len(stroke))) pass return (from_edge, stroke, None, False, [])
def in_frame(cam, obj, container): ''' Filter objs and return just those viewed from cam ''' #print('Check visibility for', obj.name) linked = False if container.instance_collection: for inner_obj in container.instance_collection.all_objects: if obj == inner_obj: linked = True break if linked: print(obj.name, 'is linked') matrix = container.matrix_world #print('matrix', matrix) #print('obj matrix', obj.matrix_world) ref_offset = container.instance_collection.instance_offset else: matrix = Matrix(((1.0, 0.0, 0.0, 0.0), (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (0.0, 0.0, 0.0, 1.0))) ref_offset = Vector((0.0, 0.0, 0.0)) box = [ matrix @ ((obj.matrix_world @ Vector(v)) - ref_offset) for v in obj.bound_box ] #print('ref_offset', ref_offset) #print('box', box) frontal = False behind = False framed = False bound_box_verts_from_cam = [] ## If a vertex of bounding box is in camera_view then object is in for v in box: x, y, z = world_to_camera_view(bpy.context.scene, cam, v) bound_box_verts_from_cam.append((x, y, z)) if z >= cam.data.clip_start: frontal = True else: behind = True if 1 >= x >= 0 and 1 >= y >= 0: #print(obj.name, "is FRAMED! (in vertex", v, ")") framed = True if not framed: ## Check if obect is bigger than frame for face in BOUNDING_BOX_FACES: face_verts = [bound_box_verts_from_cam[v] for v in face] intersect = mathutils.geometry.intersect_point_quad_2d( Vector((0.5, 0.5, 0.0)), face_verts[0], face_verts[1], face_verts[2], face_verts[3]) if intersect: framed = True break if framed: return {'framed': framed, 'frontal': frontal, 'behind': behind} ## If an edge intersects camera frame then obj is in for e in BOUNDING_BOX_EDGES: box_edge = [ Vector( world_to_camera_view(bpy.context.scene, cam, box[e[0]])[:2]), Vector( world_to_camera_view(bpy.context.scene, cam, box[e[1]])[:2]) ] for i in range(4): intersect = geometry.intersect_line_line_2d( box_edge[0], box_edge[1], FRAME_EDGES[i], FRAME_EDGES[(i + 1) % 4]) #print('intersect in', intersect) if intersect: #print(obj.name, "is FRAMED! (intersects in", intersect, ")") framed = True return {'framed': framed, 'frontal': frontal, 'behind': behind} #print(obj.name, "is NOT FRAMED!") frontal = False behind = False return {'framed': framed, 'frontal': frontal, 'behind': behind}
def execute(self, context): start = time.time() # Load the bmesh from current edit mode object obj = bpy.context.object bm = bmesh.from_edit_mesh(obj.data) face = bm.faces.active # TODO: Allow selecting multiple faces # Compute target length of each edge poly = Polygon([v.co for v in face.verts]) targetlen = self.edge_length * poly.average_edge_length() / 100.0 # Check if there are any overly long edges that we should subdivide for edge in list(face.edges): length = (edge.verts[0].co - edge.verts[1].co).magnitude cuts = round(length / targetlen) - 1 if cuts >= 1: bmesh.ops.subdivide_edges(bm, edges=[edge], cuts=cuts) # Cast the face into 2D coordinate space poly = Polygon([v.co for v in face.verts]) # Build a mapping from resulting 2D points back to the original vertices. # We need this when building 3D faces later. vertidx = dict((id(poly.points[i]), face.verts[i]) for i in range(len(face.verts))) print("Loading: %6.3f s" % (time.time() - start)) start = time.time() # Generate vertices at each triangle grid intersection inside the polygon newpoints = [] locationidx = {} xstepsize = targetlen ystepsize = math.sqrt(3) / 2 * xstepsize ysteps = math.ceil((poly.max_y - poly.min_y) / ystepsize) xsteps = math.ceil((poly.max_x - poly.min_x) / xstepsize) offsety = ((poly.max_y - poly.min_y) - (ysteps * ystepsize)) / 2 offsetx = ((poly.max_x - poly.min_x) - (xsteps * xstepsize)) / 2 for yidx in range(ysteps): y = poly.min_y + offsety + (yidx + 0.5) * ystepsize for xidx in range(xsteps): mxidx = xidx * 2 + (1 - yidx % 2) x = poly.min_x + offsetx + mxidx * xstepsize / 2 p = Vector([x, y]) if poly.contains( p) and poly.edge_distance(p)[1] > xstepsize / 4: locationidx[(mxidx, yidx)] = p newpoints.append(p) # Index from point id() to point instance. # To make it possible to ignore duplicate edges, the items must be hashable, so # the code above uses id() of points. # Perhaps freezing the vectors could work also. allpoints = newpoints + poly.points pointidx = dict((id(p), p) for p in allpoints) orig_edges = { sortedge((id(p1), id(p2))) for p1, p2 in iter_tuples(poly.points, 2) } # Generate faces and edges for the regularly spaced inner area. regular_edges = set() regular_faces = set() for xidx, yidx in locationidx.keys(): # Check the triangles starting in +Y and -Y directions from here # p1--p2 # \ / # c # / \ # p3--p4 p1 = locationidx.get((xidx - 1, yidx - 1)) p2 = locationidx.get((xidx + 1, yidx - 1)) c = locationidx.get((xidx, yidx)) p3 = locationidx.get((xidx - 1, yidx + 1)) p4 = locationidx.get((xidx + 1, yidx + 1)) if p1 and p2 and c: regular_edges |= { sortedge((id(p1), id(p2))), sortedge((id(p2), id(c))), sortedge((id(c), id(p1))) } regular_faces |= {sortface(pointidx, (id(p1), id(p2), id(c)))} if p3 and p4 and c: regular_edges |= { sortedge((id(p3), id(p4))), sortedge((id(p4), id(c))), sortedge((id(c), id(p3))) } regular_faces |= {sortface(pointidx, (id(p3), id(p4), id(c)))} # Figure out which vertices lie outside the regular inner area edgecounts = dict((id(p), 0) for p in newpoints) for p1, p2 in regular_edges: edgecounts[p1] += 1 edgecounts[p2] += 1 borderpoints = {p for p, c in edgecounts.items() if c < 6} regular_edges_on_border = { e for e in regular_edges if e[0] in borderpoints and e[1] in borderpoints } print("Regulars: %6.3f s" % (time.time() - start)) start = time.time() # Connecting the border area vertices: # Generate edges between vertices that are close enough to each other border_edge_candidates = set() for p1 in [id(p) for p in poly.points]: for p2 in borderpoints: newedge = sortedge((p1, p2)) if newedge not in regular_edges and newedge not in orig_edges: if (pointidx[p1] - pointidx[p2]).magnitude <= targetlen * 2: border_edge_candidates.add(newedge) # When edges intersect each other, keep only the shortest edge. discardededges = set() border_edge_candidates = list(border_edge_candidates) for i, e1 in enumerate(border_edge_candidates): for e2 in regular_edges_on_border: if e1[0] not in e2 and e1[1] not in e2: points = [ pointidx[e1[0]], pointidx[e1[1]], pointidx[e2[0]], pointidx[e2[1]] ] if geometry.intersect_line_line_2d(*points): # Always favor the regular edges discardededges.add(e1) break if e1 not in discardededges: for e2 in border_edge_candidates[i + 1:]: if (e1[0] not in e2) and (e1[1] not in e2) and ( e2 not in discardededges): points = [ pointidx[e1[0]], pointidx[e1[1]], pointidx[e2[0]], pointidx[e2[1]] ] if geometry.intersect_line_line_2d(*points): # Keep shorter one d1 = (pointidx[e1[0]] - pointidx[e1[1]]).magnitude d2 = (pointidx[e2[0]] - pointidx[e2[1]]).magnitude if d1 < d2: discardededges.add(e2) else: discardededges.add(e1) border_edges = { e for e in border_edge_candidates if e not in discardededges } print("Border edges: %6.3f s" % (time.time() - start)) start = time.time() # Build a lookup from point id() to edge tuples # It allows us to find all edges that originate from a given point. alledges = regular_edges | border_edges | orig_edges edgelookup = dict((id(p), set()) for p in allpoints) for e in alledges: for p in e: edgelookup[p].add(e) # Take one edge at a time, and then try to find two other edges that # share points. Once such a triplet is found, make a triangular face # out of it. border_faces = set() for e1 in border_edges: e2_candidates = [e for e in edgelookup[e1[0]] if e1 != e] e3_candidates = [e for e in edgelookup[e1[1]] if e1 != e] for e2 in e2_candidates: p3 = e2[0] if e2[0] != e1[0] else e2[1] e3 = [e for e in e3_candidates if p3 in e] if e3: border_faces.add(sortface(pointidx, (e1[0], e1[1], p3))) print("Border faces: %6.3f s" % (time.time() - start)) start = time.time() # Add all new points to the bmesh as 3D vertices. # Update the vertidx mapping also. for p in newpoints: vertidx[id(p)] = bm.verts.new(poly.plane.to_3d(p)) if regular_faces or border_faces: # Remove the original selected face bmesh.ops.delete(bm, geom=[bm.faces.active], context=3) if self.only_edges: # For debugging: show the edges instead of faces for p1, p2 in border_edges | regular_edges: print("Adding " + str((p1, p2))) bm.edges.new((vertidx[p1], vertidx[p2])) else: # Add all the new faces by looking up 3d vertices using the map. for p1, p2, p3 in (regular_faces | border_faces): f = bm.faces.new((vertidx[p1], vertidx[p2], vertidx[p3])) f.normal_update() bmesh.update_edit_mesh(obj.data) print("Storage: %6.3f s" % (time.time() - start)) start = time.time() return {'FINISHED'}
def execute(self, context): start = time.time() # Load the bmesh from current edit mode object obj = bpy.context.object bm = bmesh.from_edit_mesh(obj.data) face = bm.faces.active # TODO: Allow selecting multiple faces # Compute target length of each edge poly = Polygon([v.co for v in face.verts]) targetlen = self.edge_length * poly.average_edge_length() / 100.0 # Check if there are any overly long edges that we should subdivide for edge in list(face.edges): length = (edge.verts[0].co - edge.verts[1].co).magnitude cuts = round(length / targetlen) - 1 if cuts >= 1: bmesh.ops.subdivide_edges(bm, edges=[edge], cuts=cuts) # Cast the face into 2D coordinate space poly = Polygon([v.co for v in face.verts]) # Build a mapping from resulting 2D points back to the original vertices. # We need this when building 3D faces later. vertidx = dict((id(poly.points[i]), face.verts[i]) for i in range(len(face.verts))) print("Loading: %6.3f s" % (time.time() - start)) start = time.time() # Generate vertices at each triangle grid intersection inside the polygon newpoints = [] locationidx = {} xstepsize = targetlen ystepsize = math.sqrt(3) / 2 * xstepsize ysteps = math.ceil((poly.max_y - poly.min_y) / ystepsize) xsteps = math.ceil((poly.max_x - poly.min_x) / xstepsize) offsety = ((poly.max_y - poly.min_y) - (ysteps * ystepsize)) / 2 offsetx = ((poly.max_x - poly.min_x) - (xsteps * xstepsize)) / 2 for yidx in range(ysteps): y = poly.min_y + offsety + (yidx + 0.5) * ystepsize for xidx in range(xsteps): mxidx = xidx * 2 + (1 - yidx % 2) x = poly.min_x + offsetx + mxidx * xstepsize / 2 p = Vector([x, y]) if poly.contains(p) and poly.edge_distance(p)[1] > xstepsize / 4: locationidx[(mxidx, yidx)] = p newpoints.append(p) # Index from point id() to point instance. # To make it possible to ignore duplicate edges, the items must be hashable, so # the code above uses id() of points. # Perhaps freezing the vectors could work also. allpoints = newpoints + poly.points pointidx = dict((id(p), p) for p in allpoints) orig_edges = {sortedge((id(p1), id(p2))) for p1, p2 in iter_tuples(poly.points, 2)} # Generate faces and edges for the regularly spaced inner area. regular_edges = set() regular_faces = set() for xidx, yidx in locationidx.keys(): # Check the triangles starting in +Y and -Y directions from here # p1--p2 # \ / # c # / \ # p3--p4 p1 = locationidx.get((xidx - 1, yidx - 1)) p2 = locationidx.get((xidx + 1, yidx - 1)) c = locationidx.get((xidx, yidx)) p3 = locationidx.get((xidx - 1, yidx + 1)) p4 = locationidx.get((xidx + 1, yidx + 1)) if p1 and p2 and c: regular_edges |= {sortedge((id(p1), id(p2))), sortedge((id(p2), id(c))), sortedge((id(c), id(p1)))} regular_faces |= {sortface(pointidx, (id(p1), id(p2), id(c)))} if p3 and p4 and c: regular_edges |= {sortedge((id(p3), id(p4))), sortedge((id(p4), id(c))), sortedge((id(c), id(p3)))} regular_faces |= {sortface(pointidx, (id(p3), id(p4), id(c)))} # Figure out which vertices lie outside the regular inner area edgecounts = dict((id(p), 0) for p in newpoints) for p1, p2 in regular_edges: edgecounts[p1] += 1 edgecounts[p2] += 1 borderpoints = {p for p, c in edgecounts.items() if c < 6} regular_edges_on_border = {e for e in regular_edges if e[0] in borderpoints and e[1] in borderpoints} print("Regulars: %6.3f s" % (time.time() - start)) start = time.time() # Connecting the border area vertices: # Generate edges between vertices that are close enough to each other border_edge_candidates = set() for p1 in [id(p) for p in poly.points]: for p2 in borderpoints: newedge = sortedge((p1, p2)) if newedge not in regular_edges and newedge not in orig_edges: if (pointidx[p1] - pointidx[p2]).magnitude <= targetlen * 2: border_edge_candidates.add(newedge) # When edges intersect each other, keep only the shortest edge. discardededges = set() border_edge_candidates = list(border_edge_candidates) for i, e1 in enumerate(border_edge_candidates): for e2 in regular_edges_on_border: if e1[0] not in e2 and e1[1] not in e2: points = [pointidx[e1[0]], pointidx[e1[1]], pointidx[e2[0]], pointidx[e2[1]]] if geometry.intersect_line_line_2d(*points): # Always favor the regular edges discardededges.add(e1) break if e1 not in discardededges: for e2 in border_edge_candidates[i + 1:]: if (e1[0] not in e2) and (e1[1] not in e2) and (e2 not in discardededges): points = [pointidx[e1[0]], pointidx[e1[1]], pointidx[e2[0]], pointidx[e2[1]]] if geometry.intersect_line_line_2d(*points): # Keep shorter one d1 = (pointidx[e1[0]] - pointidx[e1[1]]).magnitude d2 = (pointidx[e2[0]] - pointidx[e2[1]]).magnitude if d1 < d2: discardededges.add(e2) else: discardededges.add(e1) border_edges = {e for e in border_edge_candidates if e not in discardededges} print("Border edges: %6.3f s" % (time.time() - start)) start = time.time() # Build a lookup from point id() to edge tuples # It allows us to find all edges that originate from a given point. alledges = regular_edges | border_edges | orig_edges edgelookup = dict((id(p), set()) for p in allpoints) for e in alledges: for p in e: edgelookup[p].add(e) # Take one edge at a time, and then try to find two other edges that # share points. Once such a triplet is found, make a triangular face # out of it. border_faces = set() for e1 in border_edges: e2_candidates = [e for e in edgelookup[e1[0]] if e1 != e] e3_candidates = [e for e in edgelookup[e1[1]] if e1 != e] for e2 in e2_candidates: p3 = e2[0] if e2[0] != e1[0] else e2[1] e3 = [e for e in e3_candidates if p3 in e] if e3: border_faces.add(sortface(pointidx, (e1[0], e1[1], p3))) print("Border faces: %6.3f s" % (time.time() - start)) start = time.time() # Add all new points to the bmesh as 3D vertices. # Update the vertidx mapping also. for p in newpoints: vertidx[id(p)] = bm.verts.new(poly.plane.to_3d(p)) if regular_faces or border_faces: # Remove the original selected face bmesh.ops.delete(bm, geom=[bm.faces.active], context=3) if self.only_edges: # For debugging: show the edges instead of faces for p1, p2 in border_edges | regular_edges: print("Adding " + str((p1, p2))) bm.edges.new((vertidx[p1], vertidx[p2])) else: # Add all the new faces by looking up 3d vertices using the map. for p1, p2, p3 in (regular_faces | border_faces): f = bm.faces.new((vertidx[p1], vertidx[p2], vertidx[p3])) f.normal_update() bmesh.update_edit_mesh(obj.data) print("Storage: %6.3f s" % (time.time() - start)) start = time.time() return {'FINISHED'}
def intersect_line_tri_2d(v1, v2, tv1, tv2, tv3): """ 三角形と線分の交点を求める。 返り値のリストは0~2の長さ。 """ vec1 = geom.intersect_line_line_2d(v1, v2, tv1, tv2) vec2 = geom.intersect_line_line_2d(v1, v2, tv2, tv3) vec3 = geom.intersect_line_line_2d(v1, v2, tv3, tv1) return [v for v in (vec1, vec2, vec3) if v is not None]
def _modal(self, context, event): dv = self.delta_vector() if self.x_key: dv.y = 0 elif self.y_key: dv.x = 0 global running_modals if running_modals: x_factor = 2 * pi / self.canvas_width y_factor = pi / self.canvas_height else: x_factor = .0025 #2*pi / 500 y_factor = .0025 #pi / 250 if self.z_key: self.light_handle.location.z = max( self.base_object_distance + dv.x * 0.05, 0) import bpy_extras start_pos = self.light_handle.matrix_world.to_translation( ) - self.profile_handle.location start_pos = start_pos.normalized( ) * context.space_data.clip_end + self.profile_handle.location self.z_start_position = bpy_extras.view3d_utils.location_3d_to_region_2d( context.region, context.space_data.region_3d, start_pos) if self.z_start_position is None: self.z_start_position = bpy_extras.view3d_utils.location_3d_to_region_2d( context.region, context.space_data.region_3d, self.light_handle.matrix_world.to_translation()) self.z_end_position = bpy_extras.view3d_utils.location_3d_to_region_2d( context.region, context.space_data.region_3d, self.profile_handle.location) # self.z_start_position = bpy_extras.view3d_utils.location_3d_to_region_2d(context.region, context.space_data.region_3d, self.light_handle.matrix_world.to_translation().normalized() * context.space_data.clip_end) # self.z_end_position = bpy_extras.view3d_utils.location_3d_to_region_2d(context.region, context.space_data.region_3d, Vector((0,0,0))) if self.z_start_position is None or self.z_end_position is None: self.z_start_position = Vector((0, 0)) self.z_end_position = Vector((0, 0)) if running_modals: global panel_global v1 = panel_global.point_lt v2 = Vector((panel_global.point_rb.x, panel_global.point_lt.y)) v3 = panel_global.point_rb v4 = Vector((panel_global.point_lt.x, panel_global.point_rb.y)) lines = [(v1, v2), (v2, v3), (v3, v4), (v1, v4)] shortest = None for v1, v2 in lines: intersection = intersect_line_line_2d( self.z_start_position, self.z_end_position, v1, v2) if intersection: length = (self.z_start_position - intersection).length if not shortest or length < shortest: shortest = length self.z_end_position = intersection else: self.light_actuator.rotation_euler = self.base_object_rotation.copy( ) self.light_actuator.rotation_euler.x += dv.x * x_factor self.light_actuator.rotation_euler.y += dv.y * y_factor self.light_actuator.rotation_euler.y = clamp( -pi / 2 + 0.000001, self.light_actuator.rotation_euler.y, pi / 2 - 0.000001) bpy.context.workspace.status_text_set( f"Move Dx: {dv.x * x_factor:.3f} Dy: {dv.y * y_factor:.3f} [X/Y] Axis [Z] Distance [Shift] Precision Mode" ) #context.area.header_text_set(text=f"Move Dx: {dv.x * x_factor:.3f} Dy: {dv.y * y_factor:.3f} [X/Y] Axis | [Shift] Precision Mode") if event.value == "PRESS" and not event.type in { "MOUSEMOVE", "INBETWEEN_MOUSEMOVE" }: return {"RUNNING_MODAL"} return {"PASS_THROUGH"}
def get_intersection_point(v_a1, v_a2, v_b1, v_b2): #return vector or None return intersect_line_line_2d(v_a1, v_a2, v_b1, v_b2)
def process_stroke_get_next(stroke, from_edge, edges2D): # returns the next chunk of stroke to be processed # stops at... # - discontinuity # - intersection with self # - intersection with edges (ignoring from_edge) # - "strong" corners cstroke = [] to_edge = None curve_distance, curve_threshold = 25.0, math.cos(60.0 * math.pi/180.0) discontinuity_distance = 10.0 def compute_cosangle_at_index(idx): nonlocal stroke if idx >= len(stroke): return 1.0 p0 = stroke[idx] for iprev in range(idx-1, -1, -1): pprev = stroke[iprev] if (p0-pprev).length < curve_distance: continue break else: return 1.0 for inext in range(idx+1, len(stroke)): pnext = stroke[inext] if (p0-pnext).length < curve_distance: continue break else: return 1.0 dprev = (p0 - pprev).normalized() dnext = (pnext - p0).normalized() cosangle = dprev.dot(dnext) return cosangle for i0 in range(1, len(stroke)-1): i1 = i0 + 1 p0,p1 = stroke[i0],stroke[i1] # check for discontinuity if (p0-p1).length > discontinuity_distance: dprint('frag: %d %d %d' % (i0, len(stroke), len(stroke)-i1)) return (from_edge, stroke[:i1], None, False, stroke[i1:]) # check for self-intersection for j0 in range(i0+3, len(stroke)-1): q0,q1 = stroke[j0],stroke[j0+1] p = intersect_line_line_2d(p0,p1, q0,q1) if not p: continue dprint('self: %d %d %d' % (i0, len(stroke), len(stroke)-i1)) return (from_edge, stroke[:i1], None, False, stroke[i1:]) # check for intersections with edges for bme,(q0,q1) in edges2D: if bme is from_edge: continue p = intersect_line_line_2d(p0,p1, q0,q1) if not p: continue dprint('edge: %d %d %d' % (i0, len(stroke), len(stroke)-i1)) return (from_edge, stroke[:i1], bme, True, stroke[i1:]) # check for strong angles cosangle = compute_cosangle_at_index(i0) if cosangle > curve_threshold: continue # found a strong angle, but there may be a stronger angle coming up... minangle = cosangle for i0_plus in range(i0+1, len(stroke)): p0_plus = stroke[i0_plus] if (p0-p0_plus).length > curve_distance: break minangle = min(compute_cosangle_at_index(i0_plus), minangle) if minangle < cosangle: break if minangle < cosangle: continue dprint('bend: %d %d %d' % (i0, len(stroke), len(stroke)-i1)) return (from_edge, stroke[:i1], None, False, stroke[i1:]) dprint('full: %d %d' % (len(stroke), len(stroke))) return (from_edge, stroke, None, False, [])