# blender CAM utils.py (c) 2012 Vilem Novak # # ***** BEGIN GPL LICENSE BLOCK ***** # # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** # here is the bridges functionality of Blender CAM. The functions here are called with operators defined from ops.py. import bpy from bpy.props import * from bpy_extras.object_utils import AddObjectHelper, object_data_add from cam import utils from cam import simple import mathutils import math from shapely import ops as sops from shapely import geometry as sgeometry from shapely import affinity, prepared def addBridge(x, y, rot, sizex, sizey): bpy.ops.mesh.primitive_plane_add(size=sizey*2, calc_uvs=True, enter_editmode=False, align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0)) b = bpy.context.active_object b.name = 'bridge' # b.show_name=True b.dimensions.x = sizex bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) bpy.ops.object.editmode_toggle() bpy.ops.transform.translate(value=(0, sizey / 2, 0), constraint_axis=(False, True, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1) bpy.ops.object.editmode_toggle() bpy.ops.object.convert(target='CURVE') b.location = x, y, 0 b.rotation_euler.z = rot return b def addAutoBridges(o): """attempt to add auto bridges as set of curves""" utils.getOperationSources(o) bridgecollectionname = o.bridges_collection_name if bridgecollectionname == '' or bpy.data.collections.get(bridgecollectionname) is 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]) for c in curve.geoms: 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 getBridgesPoly(o): if not hasattr(o, 'bridgespolyorig'): bridgecollectionname = o.bridges_collection_name bridgecollection = bpy.data.collections[bridgecollectionname] 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 = 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 useBridges(ch, o): """this adds bridges to chunks, takes the bridge-objects collection and uses the curves inside it as bridges.""" bridgecollectionname = o.bridges_collection_name bridgecollection = bpy.data.collections[bridgecollectionname] if len(bridgecollection.objects) > 0: # get bridgepoly getBridgesPoly(o) #### bridgeheight = min(o.max.z, o.min.z + abs(o.bridges_height)) vi = 0 newpoints = [] p1 = sgeometry.Point(ch.points[0]) startinside = o.bridgespoly.contains(p1) interrupted = False verts = [] edges = [] faces = [] while vi < len(ch.points): i1 = vi i2 = vi chp1 = ch.points[i1] chp2 = ch.points[i1] # Vector(v1)#this is for case of last point and not closed chunk.. if vi + 1 < len(ch.points): i2 = vi + 1 chp2 = ch.points[vi + 1] # Vector(ch.points[vi+1]) v1 = mathutils.Vector(chp1) v2 = mathutils.Vector(chp2) if v1.z < bridgeheight or v2.z < bridgeheight: v = v2 - v1 p2 = sgeometry.Point(chp2) if interrupted: p1 = sgeometry.Point(chp1) startinside = o.bridgespoly.contains(p1) interrupted = False endinside = o.bridgespoly.contains(p2) l = sgeometry.LineString([chp1, chp2]) if o.bridgespoly_boundary_prep.intersects(l): intersections = o.bridgespoly_boundary.intersection(l) else: intersections = sgeometry.GeometryCollection() itpoint = intersections.geom_type == 'Point' itmpoint = intersections.geom_type == 'MultiPoint' if not startinside: newpoints.append(chp1) elif startinside: newpoints.append((chp1[0], chp1[1], max(chp1[2], bridgeheight))) cpoints = [] if itpoint: pt = mathutils.Vector((intersections.x, intersections.y, intersections.z)) cpoints = [pt] elif itmpoint: cpoints = [] for p in intersections.geoms: pt = mathutils.Vector((p.x, p.y, p.z)) cpoints.append(pt) # ####sort collisions here :( ncpoints = [] while len(cpoints) > 0: mind = 10000000 mini = -1 for i, p in enumerate(cpoints): if min(mind, (p - v1).length) < mind: mini = i mind = (p - v1).length ncpoints.append(cpoints.pop(mini)) cpoints = ncpoints # endsorting if startinside: isinside = True else: isinside = False for cp in cpoints: v3 = cp # print(v3) if v.length == 0: ratio = 1 else: fractvect = v3 - v1 ratio = fractvect.length / v.length collisionz = v1.z + v.z * ratio np1 = (v3.x, v3.y, collisionz) np2 = (v3.x, v3.y, max(collisionz, bridgeheight)) if not isinside: newpoints.extend((np1, np2)) else: newpoints.extend((np2, np1)) isinside = not isinside startinside = endinside vi += 1 else: newpoints.append(chp1) vi += 1 interrupted = True ch.points = newpoints # create bridge cut curve here count = 0 isedge = 0 x2, y2 = 0, 0 for pt in newpoints: x = pt[0] y = pt[1] z = pt[2] if z == bridgeheight: # find all points with z = bridge height count += 1 if isedge == 1: # This is to subdivide edges which are longer than the width of the bridge edgelength = math.hypot(x - x2, y - y2) if edgelength > o.bridges_width: verts.append(((x + x2)/2, (y + y2)/2, o.minz)) # make new vertex isedge += 1 edge = [count - 2, count - 1] edges.append(edge) count += 1 else: x2 = x y2 = y verts.append((x, y, o.minz)) # make new vertex isedge += 1 if isedge > 1: # Two points make an edge edge = [count - 2, count - 1] edges.append(edge) else: isedge = 0 # verify if vertices have been generated and generate a mesh if verts: mesh = bpy.data.meshes.new(name=o.name + "_cut_bridges") # generate new mesh mesh.from_pydata(verts, edges, faces) # integrate coordinates and edges object_data_add(bpy.context, mesh) # create object bpy.ops.object.convert(target='CURVE') # convert mesh to curve simple.join_multiple(o.name + '_cut_bridges') # join all the new cut bridges curves simple.remove_doubles() # remove overlapping vertices def auto_cut_bridge(o): bridgecollectionname = o.bridges_collection_name bridgecollection = bpy.data.collections[bridgecollectionname] if len(bridgecollection.objects) > 0: print("bridges")