def getBridgesPoly(o): if not hasattr(o, 'bridgespolyorig'): bridgecollectionname = o.bridges_collection_name bridgecollection = bpy.data.collections[bridgecollectionname] shapes = [] bpy.ops.object.select_all(action='DESELECT') for ob in bridgecollection.objects: if ob.type == 'CURVE': ob.select_set(state=True) bpy.context.view_layer.objects.active = ob bpy.ops.object.duplicate() bpy.ops.object.join() ob = bpy.context.active_object shapes.extend(utils.curveToShapely(ob, o.use_bridge_modifiers)) ob.select_set(state=True) bpy.ops.object.delete(use_global=False) bridgespoly = sops.unary_union(shapes) # buffer the poly, so the bridges are not actually milled... o.bridgespolyorig = bridgespoly.buffer(distance=o.cutter_diameter / 2.0) o.bridgespoly_boundary = o.bridgespolyorig.boundary o.bridgespoly_boundary_prep = prepared.prep(o.bridgespolyorig.boundary) o.bridgespoly = prepared.prep(o.bridgespolyorig)
def addAutoBridges(o): """attempt to add auto bridges as set of curves""" utils.getOperationSources(o) # if not o.onlycurves: # o.warnings+=('not curves') # return; bridgecollectionname = o.bridges_collection_name if bridgecollectionname == '' or bpy.data.collections.get( bridgecollectionname) == None: bridgecollectionname = 'bridges_' + o.name bpy.data.collections.new(bridgecollectionname) bpy.context.collection.children.link( bpy.data.collections[bridgecollectionname]) g = bpy.data.collections[bridgecollectionname] o.bridges_collection_name = bridgecollectionname for ob in o.objects: if ob.type == 'CURVE' or ob.type == 'TEXT': curve = utils.curveToShapely(ob) if ob.type == 'MESH': curve = utils.getObjectSilhouete('OBJECTS', [ob]) # curve = shapelyToMultipolygon(curve) for c in curve: c = c.exterior minx, miny, maxx, maxy = c.bounds d1 = c.project(sgeometry.Point(maxx + 1000, (maxy + miny) / 2.0)) p = c.interpolate(d1) bo = addBridge(p.x, p.y, -math.pi / 2, o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point(minx - 1000, (maxy + miny) / 2.0)) p = c.interpolate(d1) bo = addBridge(p.x, p.y, math.pi / 2, o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point((minx + maxx) / 2.0, maxy + 1000)) p = c.interpolate(d1) bo = addBridge(p.x, p.y, 0, o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point((minx + maxx) / 2.0, miny - 1000)) p = c.interpolate(d1) bo = addBridge(p.x, p.y, math.pi, o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo)
def execute(self, context): o1 = bpy.context.active_object shapes = utils.curveToShapely(o1) negative_overcuts = [] positive_overcuts = [] # count all the corners including inside and out cornerCnt = 0 # a list of tuples for defining the inside corner # tuple is: (pos, v1, v2, angle, allCorners list index) insideCorners = [] diameter = self.diameter * 1.001 radius = diameter / 2 anglethreshold = math.pi - self.threshold centerv = Vector((0,0)) extendedv = Vector((0,0)) pos = Vector((0,0)) sign = -1 if self.do_invert else 1 isTBone = self.style == 'TBONE' # indexes in insideCorner tuple POS, V1, V2, A, IDX = range(5) def addOvercut(a): nonlocal pos, centerv, radius, extendedv, sign, negative_overcuts, positive_overcuts # move the overcut shape center position 1 radius in direction v pos -= centerv * radius if abs(a) < math.pi/2: shape = utils.Circle(radius, 64) shape = shapely.affinity.translate(shape, pos.x, pos.y) else: # elongate overcut circle to make sure tool bit can fit into slot p1 = pos + (extendedv * radius) l = shapely.geometry.LineString((pos, p1)) shape = l.buffer(radius, resolution = 64) if sign>0: negative_overcuts.append(shape) else: positive_overcuts.append(shape) def setOtherEdge(v1, v2, a): nonlocal centerv, extendedv if self.otherEdge: centerv = v1 extendedv = v2 else: centerv = -v2 extendedv = -v1 addOvercut(a) def setCenterOffset(a): nonlocal centerv, extendedv, sign centerv = v1 - v2 centerv.normalize() extendedv = centerv * math.tan(a/2) * -sign addOvercut(a) def getCorner(idx, offset): nonlocal insideCorners idx += offset if idx >= len(insideCorners): idx -= len(insideCorners) return insideCorners[idx] def getCornerDelta(curidx, nextidx): nonlocal cornerCnt delta = nextidx - curidx if delta < 0: delta += cornerCnt return delta for s in shapes: s = shapely.geometry.polygon.orient(s,1) loops = [s.boundary] if s.boundary.type == 'LineString' else s.boundary outercurve = self.do_outer or len(loops)==1 for ci,c in enumerate(loops): if ci>0 or outercurve: if isTBone: cornerCnt = 0 insideCorners = [] for i,co in enumerate(c.coords): i1 = i-1 if i1==-1: i1 = -2 i2 = i+1 if i2 == len(c.coords): i2 = 0 v1 = Vector(co).xy - Vector(c.coords[i1]).xy v2 = Vector(c.coords[i2]).xy - Vector(co).xy if not v1.length==0 and not v2.length == 0: a = v1.angle_signed(v2) insideCornerFound = False outsideCornerFound = False if a<-anglethreshold: if sign<0: insideCornerFound = True else: outsideCornerFound = True elif a>anglethreshold: if sign>0: insideCornerFound = True else: outsideCornerFound = True if insideCornerFound: # an inside corner with an overcut has been found # which means a new side has been found pos = Vector((co[0],co[1])) v1.normalize() v2.normalize() # figure out which direction vector to use # v is the main direction vector to move the overcut shape along # ev is the direction vector used to elongate the overcut shape if self.style != 'DOGBONE': # t-bone and opposite edge styles get treated nearly the same if isTBone: cornerCnt += 1 #insideCorner tuplet: (pos, v1, v2, angle, corner index) insideCorners.append((pos, v1, v2, a, cornerCnt-1)) #processing of corners for T-Bone are done after all points are processed continue setOtherEdge(v1, v2, a) else: # DOGBONE style setCenterOffset(a) elif isTBone and outsideCornerFound: # add an outside corner to the list cornerCnt += 1 # check if t-bone processing required # if no inside corners then nothing to do if isTBone and len(insideCorners) > 0: #print(cornerCnt, len(insideCorners)) # process all of the inside corners for i, corner in enumerate(insideCorners): pos, v1, v2, a, idx = corner # figure out which side of the corner to do overcut # if prev corner is outside corner # calc index distance between current corner and prev prevCorner = getCorner(i, -1) #print('first:', i, idx, prevCorner[IDX]) if getCornerDelta(prevCorner[IDX], idx) == 1: # make sure there is an outside corner #print(getCornerDelta(getCorner(i, -2)[IDX], idx)) if getCornerDelta(getCorner(i, -2)[IDX], idx) > 2: setOtherEdge(v1, v2, a) #print('first won') continue nextCorner = getCorner(i, 1) #print('second:', i, idx, nextCorner[IDX]) if getCornerDelta(idx, nextCorner[IDX]) == 1: # make sure there is an outside corner #print(getCornerDelta(idx, getCorner(i, 2)[IDX])) if getCornerDelta(idx, getCorner(i, 2)[IDX]) > 2: #print('second won') setOtherEdge(-v2, -v1, a) continue #print('third') if getCornerDelta(prevCorner[IDX], idx) == 3: # check if they share the same edge a1 = v1.angle_signed(prevCorner[V2])*180.0/math.pi #print('third won', a1) if a1 < -135 or a1 > 135: setOtherEdge(-v2, -v1, a) continue #print('fourth') if getCornerDelta(idx, nextCorner[IDX]) == 3: # check if they share the same edge a1 = v2.angle_signed(nextCorner[V1])*180.0/math.pi #print('fourth won', a1) if a1 < -135 or a1 > 135: setOtherEdge(v1, v2, a) continue #print('***No Win***') # the default if no other rules pass setCenterOffset(a) negative_overcuts = shapely.ops.unary_union(negative_overcuts) positive_overcuts = shapely.ops.unary_union(positive_overcuts) fs = shapely.ops.unary_union(shapes) fs = fs.union(positive_overcuts) fs = fs.difference(negative_overcuts) o=utils.shapelyToCurve(o1.name+'_overcuts',fs,o1.location.z) return {'FINISHED'}
def execute(self, context): #utils.silhoueteOffset(context,-self.diameter) o1=bpy.context.active_object shapes=utils.curveToShapely(o1) negative_overcuts=[] positive_overcuts=[] diameter = self.diameter*1.001 for s in shapes: s=shapely.geometry.polygon.orient(s,1) if s.boundary.type == 'LineString': loops = [s.boundary]#s=shapely.geometry.asMultiLineString(s) else: loops = s.boundary for ci,c in enumerate(loops): if ci>0 or self.do_outer: #c=s.boundary for i,co in enumerate(c.coords): i1=i-1 if i1==-1: i1=-2 i2=i+1 if i2 == len(c.coords): i2=0 v1 = Vector(co) - Vector(c.coords[i1]) v1 = v1.xy#Vector((v1.x,v1.y,0)) v2 = Vector(c.coords[i2]) - Vector(co) v2 = v2.xy#v2 = Vector((v2.x,v2.y,0)) if not v1.length==0 and not v2.length == 0: a=v1.angle_signed(v2) sign=1 #if ci==0: # sign=-1 #else: # sign=1 if self.invert:# and ci>0: sign*=-1 if (sign<0 and a<-self.threshold) or (sign>0 and a>self.threshold): p=Vector((co[0],co[1])) v1.normalize() v2.normalize() v=v1-v2 v.normalize() p=p-v*diameter/2 if abs(a)<math.pi/2: shape=utils.Circle(diameter/2,64) shape= shapely.affinity.translate(shape,p.x,p.y) else: l=math.tan(a/2)*diameter/2 p1=p-sign*v*l l=shapely.geometry.LineString((p,p1)) shape=l.buffer(diameter/2, resolution = 64) if sign>0: negative_overcuts.append(shape) else: positive_overcuts.append(shape) print(a) #for c in s.boundary: negative_overcuts = shapely.ops.unary_union(negative_overcuts) positive_overcuts = shapely.ops.unary_union(positive_overcuts) #shapes.extend(overcuts) fs=shapely.ops.unary_union(shapes) fs = fs.union(positive_overcuts) fs = fs.difference(negative_overcuts) o=utils.shapelyToCurve(o1.name+'_overcuts',fs,o1.location.z) #o=utils.shapelyToCurve('overcuts',overcuts,0) return {'FINISHED'}
def execute(self, context): #utils.silhoueteOffset(context,-self.diameter) o1 = bpy.context.active_object shapes = utils.curveToShapely(o1) negative_overcuts = [] positive_overcuts = [] diameter = self.diameter * 1.001 for s in shapes: s = shapely.geometry.polygon.orient(s, 1) if s.boundary.type == 'LineString': loops = [s.boundary] #s=shapely.geometry.asMultiLineString(s) else: loops = s.boundary for ci, c in enumerate(loops): if ci > 0 or self.do_outer: #c=s.boundary for i, co in enumerate(c.coords): i1 = i - 1 if i1 == -1: i1 = -2 i2 = i + 1 if i2 == len(c.coords): i2 = 0 v1 = Vector(co) - Vector(c.coords[i1]) v1 = v1.xy #Vector((v1.x,v1.y,0)) v2 = Vector(c.coords[i2]) - Vector(co) v2 = v2.xy #v2 = Vector((v2.x,v2.y,0)) if not v1.length == 0 and not v2.length == 0: a = v1.angle_signed(v2) sign = 1 #if ci==0: # sign=-1 #else: # sign=1 if self.invert: # and ci>0: sign *= -1 if (sign < 0 and a < -self.threshold) or ( sign > 0 and a > self.threshold): p = Vector((co[0], co[1])) v1.normalize() v2.normalize() v = v1 - v2 v.normalize() p = p - v * diameter / 2 if abs(a) < math.pi / 2: shape = utils.Circle(diameter / 2, 64) shape = shapely.affinity.translate( shape, p.x, p.y) else: l = math.tan(a / 2) * diameter / 2 p1 = p - sign * v * l l = shapely.geometry.LineString((p, p1)) shape = l.buffer(diameter / 2, resolution=64) if sign > 0: negative_overcuts.append(shape) else: positive_overcuts.append(shape) print(a) #for c in s.boundary: negative_overcuts = shapely.ops.unary_union(negative_overcuts) positive_overcuts = shapely.ops.unary_union(positive_overcuts) #shapes.extend(overcuts) fs = shapely.ops.unary_union(shapes) fs = fs.union(positive_overcuts) fs = fs.difference(negative_overcuts) o = utils.shapelyToCurve(o1.name + '_overcuts', fs, o1.location.z) #o=utils.shapelyToCurve('overcuts',overcuts,0) return {'FINISHED'}
def execute(self, context): o1 = bpy.context.active_object shapes = utils.curveToShapely(o1) negative_overcuts = [] positive_overcuts = [] # count all the corners including inside and out cornerCnt = 0 # a list of tuples for defining the inside corner # tuple is: (pos, v1, v2, angle, allCorners list index) insideCorners = [] diameter = self.diameter * 1.001 radius = diameter / 2 anglethreshold = math.pi - self.threshold centerv = mathutils.Vector((0, 0)) extendedv = mathutils.Vector((0, 0)) pos = mathutils.Vector((0, 0)) sign = -1 if self.do_invert else 1 isTBone = self.style == 'TBONE' # indexes in insideCorner tuple POS, V1, V2, A, IDX = range(5) def addOvercut(a): nonlocal pos, centerv, radius, extendedv, sign, negative_overcuts, positive_overcuts # move the overcut shape center position 1 radius in direction v pos -= centerv * radius if abs(a) < math.pi / 2: shape = utils.Circle(radius, 64) shape = shapely.affinity.translate(shape, pos.x, pos.y) else: # elongate overcut circle to make sure tool bit can fit into slot p1 = pos + (extendedv * radius) l = shapely.geometry.LineString((pos, p1)) shape = l.buffer(radius, resolution=64) if sign > 0: negative_overcuts.append(shape) else: positive_overcuts.append(shape) def setOtherEdge(v1, v2, a): nonlocal centerv, extendedv if self.otherEdge: centerv = v1 extendedv = v2 else: centerv = -v2 extendedv = -v1 addOvercut(a) def setCenterOffset(a): nonlocal centerv, extendedv, sign centerv = v1 - v2 centerv.normalize() extendedv = centerv * math.tan(a / 2) * -sign addOvercut(a) def getCorner(idx, offset): nonlocal insideCorners idx += offset if idx >= len(insideCorners): idx -= len(insideCorners) return insideCorners[idx] def getCornerDelta(curidx, nextidx): nonlocal cornerCnt delta = nextidx - curidx if delta < 0: delta += cornerCnt return delta for s in shapes: s = shapely.geometry.polygon.orient(s, 1) loops = [s.boundary ] if s.boundary.type == 'LineString' else s.boundary outercurve = self.do_outer or len(loops) == 1 for ci, c in enumerate(loops): if ci > 0 or outercurve: if isTBone: cornerCnt = 0 insideCorners = [] for i, co in enumerate(c.coords): i1 = i - 1 if i1 == -1: i1 = -2 i2 = i + 1 if i2 == len(c.coords): i2 = 0 v1 = mathutils.Vector(co).xy - mathutils.Vector( c.coords[i1]).xy v2 = mathutils.Vector( c.coords[i2]).xy - mathutils.Vector(co).xy if not v1.length == 0 and not v2.length == 0: a = v1.angle_signed(v2) insideCornerFound = False outsideCornerFound = False if a < -anglethreshold: if sign < 0: insideCornerFound = True else: outsideCornerFound = True elif a > anglethreshold: if sign > 0: insideCornerFound = True else: outsideCornerFound = True if insideCornerFound: # an inside corner with an overcut has been found # which means a new side has been found pos = mathutils.Vector((co[0], co[1])) v1.normalize() v2.normalize() # figure out which direction vector to use # v is the main direction vector to move the overcut shape along # ev is the direction vector used to elongate the overcut shape if self.style != 'DOGBONE': # t-bone and opposite edge styles get treated nearly the same if isTBone: cornerCnt += 1 # insideCorner tuplet: (pos, v1, v2, angle, corner index) insideCorners.append( (pos, v1, v2, a, cornerCnt - 1)) # processing of corners for T-Bone are done after all points are processed continue setOtherEdge(v1, v2, a) else: # DOGBONE style setCenterOffset(a) elif isTBone and outsideCornerFound: # add an outside corner to the list cornerCnt += 1 # check if t-bone processing required # if no inside corners then nothing to do if isTBone and len(insideCorners) > 0: # print(cornerCnt, len(insideCorners)) # process all of the inside corners for i, corner in enumerate(insideCorners): pos, v1, v2, a, idx = corner # figure out which side of the corner to do overcut # if prev corner is outside corner # calc index distance between current corner and prev prevCorner = getCorner(i, -1) # print('first:', i, idx, prevCorner[IDX]) if getCornerDelta(prevCorner[IDX], idx) == 1: # make sure there is an outside corner # print(getCornerDelta(getCorner(i, -2)[IDX], idx)) if getCornerDelta(getCorner(i, -2)[IDX], idx) > 2: setOtherEdge(v1, v2, a) # print('first won') continue nextCorner = getCorner(i, 1) # print('second:', i, idx, nextCorner[IDX]) if getCornerDelta(idx, nextCorner[IDX]) == 1: # make sure there is an outside corner # print(getCornerDelta(idx, getCorner(i, 2)[IDX])) if getCornerDelta(idx, getCorner(i, 2)[IDX]) > 2: # print('second won') setOtherEdge(-v2, -v1, a) continue # print('third') if getCornerDelta(prevCorner[IDX], idx) == 3: # check if they share the same edge a1 = v1.angle_signed( prevCorner[V2]) * 180.0 / math.pi # print('third won', a1) if a1 < -135 or a1 > 135: setOtherEdge(-v2, -v1, a) continue # print('fourth') if getCornerDelta(idx, nextCorner[IDX]) == 3: # check if they share the same edge a1 = v2.angle_signed( nextCorner[V1]) * 180.0 / math.pi # print('fourth won', a1) if a1 < -135 or a1 > 135: setOtherEdge(v1, v2, a) continue # print('***No Win***') # the default if no other rules pass setCenterOffset(a) negative_overcuts = shapely.ops.unary_union(negative_overcuts) positive_overcuts = shapely.ops.unary_union(positive_overcuts) fs = shapely.ops.unary_union(shapes) fs = fs.union(positive_overcuts) fs = fs.difference(negative_overcuts) o = utils.shapelyToCurve(o1.name + '_overcuts', fs, o1.location.z) return {'FINISHED'}