def curve(o): print('operation: curve') pathSamples = [] utils.getOperationSources(o) if not o.onlycurves: o.warnings += 'at least one of assigned objects is not a curve\n' for ob in o.objects: pathSamples.extend( curveToChunks(ob)) # make the chunks from curve here pathSamples = utils.sortChunks(pathSamples, o) # sort before sampling pathSamples = chunksRefine(pathSamples, o) # simplify # layers here if o.use_layers: layers = getLayers( o, o.maxz, round(checkminz(o), 6) ) # layers is a list of lists [[0.00,l1],[l1,l2],[l2,l3]] containg the start and end of each layer extendorder = [] chunks = [] for layer in layers: for ch in pathSamples: extendorder.append( [ch.copy(), layer]) # include layer information to chunk list for chl in extendorder: # Set offset Z for all chunks according to the layer information, chunk = chl[0] layer = chl[1] print('layer: ' + str(layer[1])) chunk.offsetZ(o.maxz * 2 - o.minz + layer[1]) chunk.clampZ(o.minz) # safety to not cut lower than minz chunk.clampmaxZ(o.free_movement_height ) # safety, not higher than free movement height for chl in extendorder: # strip layer information from extendorder and transfer them to chunks chunks.append(chl[0]) chunksToMesh(chunks, o) # finish by converting to mesh else: # no layers, old curve for ch in pathSamples: ch.clampZ(o.minz) # safety to not cut lower than minz ch.clampmaxZ(o.free_movement_height ) # safety, not higher than free movement height chunksToMesh(pathSamples, o)
def getPath3axis(context, operation): s = bpy.context.scene o = operation utils.getBounds(o) if o.strategy == 'CUTOUT': strategy.cutout(o) elif o.strategy == 'CURVE': strategy.curve(o) elif o.strategy == 'PROJECTED_CURVE': strategy.proj_curve(s, o) elif o.strategy == 'POCKET': strategy.pocket(o) elif o.strategy in ['PARALLEL', 'CROSS', 'BLOCK', 'SPIRAL', 'CIRCLES', 'OUTLINEFILL', 'CARVE', 'PENCIL', 'CRAZY']: if o.strategy == 'CARVE': pathSamples = [] # for ob in o.objects: ob = bpy.data.objects[o.curve_object] pathSamples.extend(curveToChunks(ob)) pathSamples = utils.sortChunks(pathSamples, o) # sort before sampling pathSamples = chunksRefine(pathSamples, o) elif o.strategy == 'PENCIL': prepareArea(o) utils.getAmbient(o) pathSamples = getOffsetImageCavities(o, o.offset_image) # for ch in pathSamples: # for i,p in enumerate(ch.points): # ch.points[i]=(p[0],p[1],0) pathSamples = limitChunks(pathSamples, o) pathSamples = utils.sortChunks(pathSamples, o) # sort before sampling elif o.strategy == 'CRAZY': prepareArea(o) # pathSamples = crazyStrokeImage(o) #####this kind of worked and should work: millarea = o.zbuffer_image < o.minz + 0.000001 avoidarea = o.offset_image > o.minz + 0.000001 pathSamples = crazyStrokeImageBinary(o, millarea, avoidarea) ##### pathSamples = utils.sortChunks(pathSamples, o) pathSamples = chunksRefine(pathSamples, o) else: print("PARALLEL") if o.strategy == 'OUTLINEFILL': utils.getOperationSilhouete(o) pathSamples = getPathPattern(o) if o.strategy == 'OUTLINEFILL': pathSamples = utils.sortChunks(pathSamples, o) # have to be sorted once before, because of the parenting inside of samplechunks # chunksToMesh(pathSamples,o)#for testing pattern script # return if o.strategy in ['BLOCK', 'SPIRAL', 'CIRCLES']: pathSamples = utils.connectChunksLow(pathSamples, o) # print (minz) chunks = [] layers = strategy.getLayers(o, o.maxz, o.min.z) print("SAMPLE", o.name) chunks.extend(utils.sampleChunks(o, pathSamples, layers)) print("SAMPLE OK") if o.strategy == 'PENCIL': # and bpy.app.debug_value==-3: chunks = chunksCoherency(chunks) print('coherency check') if o.strategy in ['PARALLEL', 'CROSS', 'PENCIL', 'OUTLINEFILL']: # and not o.parallel_step_back: print('sorting') chunks = utils.sortChunks(chunks, o) if o.strategy == 'OUTLINEFILL': chunks = utils.connectChunksLow(chunks, o) if o.ramp: for ch in chunks: ch.rampZigZag(ch.zstart, ch.points[0][2], o) # print(chunks) if o.strategy == 'CARVE': for ch in chunks: for vi in range(0, len(ch.points)): ch.points[vi] = (ch.points[vi][0], ch.points[vi][1], ch.points[vi][2] - o.carve_depth) if o.use_bridges: for bridge_chunk in chunks: useBridges(bridge_chunk, o) strategy.chunksToMesh(chunks, o) elif o.strategy == 'WATERLINE' and o.use_opencamlib: utils.getAmbient(o) chunks = [] oclGetWaterline(o, chunks) chunks = limitChunks(chunks, o) if (o.movement_type == 'CLIMB' and o.spindle_rotation_direction == 'CW') or ( o.movement_type == 'CONVENTIONAL' and o.spindle_rotation_direction == 'CCW'): for ch in chunks: ch.points.reverse() strategy.chunksToMesh(chunks, o) elif o.strategy == 'WATERLINE' and not o.use_opencamlib: topdown = True tw = time.time() chunks = [] progress('retrieving object slices') prepareArea(o) layerstep = 1000000000 if o.use_layers: layerstep = math.floor(o.stepdown / o.slice_detail) if layerstep == 0: layerstep = 1 # for projection of filled areas layerstart = o.max.z # layerend = o.min.z # layers = [[layerstart, layerend]] ####################### nslices = ceil(abs(o.minz / o.slice_detail)) lastislice = numpy.array([]) lastslice = spolygon.Polygon() # polyversion layerstepinc = 0 slicesfilled = 0 utils.getAmbient(o) # polyToMesh(o.ambient,0) for h in range(0, nslices): layerstepinc += 1 slicechunks = [] z = o.minz + h * o.slice_detail if h == 0: z += 0.0000001 # if people do mill flat areas, this helps to reach those... otherwise first layer would actually be one slicelevel above min z. # print(z) # sliceimage=o.offset_image>z islice = o.offset_image > z slicepolys = imageToShapely(o, islice, with_border=True) # for pviz in slicepolys: # polyToMesh('slice',pviz,z) poly = spolygon.Polygon() # polygversion lastchunks = [] # imagechunks=imageToChunks(o,islice) # for ch in imagechunks: # slicechunks.append(camPathChunk([])) # for s in ch.points: # slicechunks[-1].points.append((s[0],s[1],z)) # print('found polys',layerstepinc,len(slicepolys)) for p in slicepolys: # print('polypoints',p.nPoints(0)) poly = poly.union(p) # polygversion TODO: why is this added? # print() # polyToMesh(p,z) nchunks = shapelyToChunks(p, z) nchunks = limitChunks(nchunks, o, force=True) # print('chunksnum',len(nchunks)) # if len(nchunks)>0: # print('chunkpoints',len(nchunks[0].points)) # print() lastchunks.extend(nchunks) slicechunks.extend(nchunks) # print('totchunks',len(slicechunks)) if len(slicepolys) > 0: slicesfilled += 1 # chunks.extend(polyToChunks(slicepolys[1],z)) # print(len(p),'slicelen') # # print(len(lastslice)) # """ if o.waterline_fill: layerstart = min(o.maxz, z + o.slice_detail) # layerend = max(o.min.z, z - o.slice_detail) # layers = [[layerstart, layerend]] ##################################### # fill top slice for normal and first for inverse, fill between polys if not lastslice.is_empty or (o.inverse and not poly.is_empty and slicesfilled == 1): # offs=False restpoly = None if not lastslice.is_empty: # between polys if o.inverse: restpoly = poly.difference(lastslice) else: restpoly = lastslice.difference(poly) # print('filling between') if (not o.inverse and poly.is_empty and slicesfilled > 0) or ( o.inverse and not poly.is_empty and slicesfilled == 1): # first slice fill restpoly = lastslice # print('filling first') # print(len(restpoly)) # polyToMesh('fillrest',restpoly,z) restpoly = restpoly.buffer(-o.dist_between_paths, resolution=o.circle_detail) fillz = z i = 0 while not restpoly.is_empty: nchunks = shapelyToChunks(restpoly, fillz) # project paths TODO: path projection during waterline is not working if o.waterline_project: nchunks = chunksRefine(nchunks, o) nchunks = utils.sampleChunks(o, nchunks, layers) nchunks = limitChunks(nchunks, o, force=True) ######################### slicechunks.extend(nchunks) parentChildDist(lastchunks, nchunks, o) lastchunks = nchunks # slicechunks.extend(polyToChunks(restpoly,z)) restpoly = restpoly.buffer(-o.dist_between_paths, resolution=o.circle_detail) i += 1 # print(i) i = 0 # """ ##################################### # fill layers and last slice, last slice with inverse is not working yet - inverse millings end now always on 0 so filling ambient does have no sense. if (slicesfilled > 0 and layerstepinc == layerstep) or ( not o.inverse and not poly.is_empty and slicesfilled == 1) or ( o.inverse and poly.is_empty and slicesfilled > 0): fillz = z layerstepinc = 0 # ilim=1000#TODO:this should be replaced... no limit, just check if the shape grows over limits. # offs=False boundrect = o.ambient restpoly = boundrect.difference(poly) if (o.inverse and poly.is_empty and slicesfilled > 0): restpoly = boundrect.difference(lastslice) restpoly = restpoly.buffer(-o.dist_between_paths, resolution=o.circle_detail) i = 0 while not restpoly.is_empty: # 'GeometryCollection':#len(restpoly.boundary.coords)>0: # print(i) nchunks = shapelyToChunks(restpoly, fillz) ######################### nchunks = limitChunks(nchunks, o, force=True) slicechunks.extend(nchunks) parentChildDist(lastchunks, nchunks, o) lastchunks = nchunks # slicechunks.extend(polyToChunks(restpoly,z)) restpoly = restpoly.buffer(-o.dist_between_paths, resolution=o.circle_detail) i += 1 # """ percent = int(h / nslices * 100) progress('waterline layers ', percent) lastslice = poly # print(poly) # print(len(lastslice)) # if len(lastislice)>0: # i=numpy.logical_xor(lastislice , islice) # # n=0 # while i.sum()>0 and n<10000: # i=outlineImageBinary(o,o.dist_between_paths,i,False) # polys=imageToShapely(o,i) # for poly in polys: # chunks.extend(polyToChunks(poly,z)) # n+=1 # # # #restpoly=outlinePoly(restpoly,o.dist_between_paths,oo.circle_detail,o.optimize,o.optimize_threshold,,False) # #chunks.extend(polyToChunks(restpoly,z)) # # lastislice=islice # if bpy.app.debug_value==1: if (o.movement_type == 'CONVENTIONAL' and o.spindle_rotation_direction == 'CCW') or ( o.movement_type == 'CLIMB' and o.spindle_rotation_direction == 'CW'): for chunk in slicechunks: chunk.points.reverse() slicechunks = utils.sortChunks(slicechunks, o) if topdown: slicechunks.reverse() # project chunks in between chunks.extend(slicechunks) # chunks=sortChunks(chunks,o) if topdown: chunks.reverse() # chi=0 # if len(chunks)>2: # while chi<len(chunks)-2: # d=dist2d((chunks[chi][-1][0],chunks[chi][-1][1]),(chunks[chi+1][0][0],chunks[chi+1][0][1])) # if chunks[chi][0][2]>=chunks[chi+1][0][2] and d<o.dist_between_paths*2: # chunks[chi].extend(chunks[chi+1]) # chunks.remove(chunks[chi+1]) # chi=chi-1 # chi+=1 print(time.time() - tw) strategy.chunksToMesh(chunks, o) elif o.strategy == 'DRILL': strategy.drill(o) elif o.strategy == 'MEDIAL_AXIS': strateg.medial_axis(o)
def medial_axis(o): print('operation: Medial Axis') print('doing highly experimental stuff') from cam.voronoi import Site, computeVoronoiDiagram chunks = [] gpoly = spolygon.Polygon() angle = o.cutter_tip_angle slope = math.tan(math.pi * (90 - angle / 2) / 180) if o.cutter_type == 'VCARVE': angle = o.cutter_tip_angle # start the max depth calc from the "start depth" of the operation. maxdepth = o.maxz - math.tan( math.pi * (90 - angle / 2) / 180) * o.cutter_diameter / 2 # don't cut any deeper than the "end depth" of the operation. if maxdepth < o.minz: maxdepth = o.minz # the effective cutter diameter can be reduced from it's max since we will be cutting shallower than the original maxdepth # without this, the curve is calculated as if the diameter was at the original maxdepth and we get the bit # pulling away from the desired cut surface o.cutter_diameter = (maxdepth - o.maxz) / ( -math.tan(math.pi * (90 - angle / 2) / 180)) * 2 elif o.cutter_type == 'BALLNOSE' or o.cutter_type == 'BALL': # angle = o.cutter_tip_angle maxdepth = o.cutter_diameter / 2 else: o.warnings += 'Only Ballnose, Ball and V-carve cutters\n are supported' return # remember resolutions of curves, to refine them, # otherwise medial axis computation yields too many branches in curved parts resolutions_before = [] for ob in o.objects: if ob.type == 'CURVE' or ob.type == 'FONT': resolutions_before.append(ob.data.resolution_u) if ob.data.resolution_u < 64: ob.data.resolution_u = 64 polys = utils.getOperationSilhouete(o) mpoly = sgeometry.asMultiPolygon(polys) mpoly_boundary = mpoly.boundary for poly in polys: schunks = shapelyToChunks(poly, -1) schunks = chunksRefineThreshold( schunks, o.medial_axis_subdivision, o.medial_axis_threshold) # chunksRefine(schunks,o) verts = [] for ch in schunks: for pt in ch.points: # pvoro = Site(pt[0], pt[1]) verts.append(pt) # (pt[0], pt[1]), pt[2]) # verts= points#[[vert.x, vert.y, vert.z] for vert in vertsPts] nDupli, nZcolinear = unique(verts) nVerts = len(verts) print(str(nDupli) + " duplicates points ignored") print(str(nZcolinear) + " z colinear points excluded") if nVerts < 3: print("Not enough points") return {'FINISHED'} # Check colinear xValues = [pt[0] for pt in verts] yValues = [pt[1] for pt in verts] if checkEqual(xValues) or checkEqual(yValues): print("Points are colinear") return {'FINISHED'} # Create diagram print("Tesselation... (" + str(nVerts) + " points)") xbuff, ybuff = 5, 5 # % zPosition = 0 vertsPts = [Point(vert[0], vert[1], vert[2]) for vert in verts] # vertsPts= [Point(vert[0], vert[1]) for vert in verts] pts, edgesIdx = computeVoronoiDiagram(vertsPts, xbuff, ybuff, polygonsOutput=False, formatOutput=True) # # pts=[[pt[0], pt[1], zPosition] for pt in pts] newIdx = 0 vertr = [] filteredPts = [] print('filter points') for p in pts: if not poly.contains(sgeometry.Point(p)): vertr.append((True, -1)) else: vertr.append((False, newIdx)) if o.cutter_type == 'VCARVE': # start the z depth calc from the "start depth" of the operation. z = o.maxz - mpoly.boundary.distance( sgeometry.Point(p)) * slope if z < maxdepth: z = maxdepth elif o.cutter_type == 'BALL' or o.cutter_type == 'BALLNOSE': d = mpoly_boundary.distance(sgeometry.Point(p)) r = o.cutter_diameter / 2.0 if d >= r: z = -r else: # print(r, d) z = -r + sqrt(r * r - d * d) else: z = 0 # # print(mpoly.distance(sgeometry.Point(0,0))) # if(z!=0):print(z) filteredPts.append((p[0], p[1], z)) newIdx += 1 print('filter edges') filteredEdgs = [] ledges = [] for e in edgesIdx: do = True p1 = pts[e[0]] p2 = pts[e[1]] # print(p1,p2,len(vertr)) if vertr[e[0]][0]: # exclude edges with allready excluded points do = False elif vertr[e[1]][0]: do = False if do: filteredEdgs.append(((vertr[e[0]][1], vertr[e[1]][1]))) ledges.append( sgeometry.LineString((filteredPts[vertr[e[0]][1]], filteredPts[vertr[e[1]][1]]))) # print(ledges[-1].has_z) bufpoly = poly.buffer(-o.cutter_diameter / 2, resolution=64) lines = shapely.ops.linemerge(ledges) # print(lines.type) if bufpoly.type == 'Polygon' or bufpoly.type == 'MultiPolygon': lines = lines.difference(bufpoly) chunks.extend(shapelyToChunks(bufpoly, maxdepth)) chunks.extend(shapelyToChunks(lines, 0)) # segments=[] # processEdges=filteredEdgs.copy() # chunk=camPathChunk([]) # chunk.points.append(filteredEdgs.pop()) # while len(filteredEdgs)>0: # Create new mesh structure # print("Create mesh...") # voronoiDiagram = bpy.data.meshes.new("VoronoiDiagram") #create a new mesh # # # # voronoiDiagram.from_pydata(filteredPts, filteredEdgs, []) #Fill the mesh with triangles # # voronoiDiagram.update(calc_edges=True) #Update mesh with new data # #create an object with that mesh # voronoiObj = bpy.data.objects.new("VoronoiDiagram", voronoiDiagram) # #place object # #bpy.ops.view3d.snap_cursor_to_selected()#move 3d-cursor # # #update scene # bpy.context.scene.objects.link(voronoiObj) #Link object to scene # bpy.context.scene.objects.active = voronoiObj # voronoiObj.select = True # bpy.ops.object.convert(target='CURVE') oi = 0 for ob in o.objects: if ob.type == 'CURVE' or ob.type == 'FONT': ob.data.resolution_u = resolutions_before[oi] oi += 1 # bpy.ops.object.join() chunks = utils.sortChunks(chunks, o) layers = getLayers(o, o.maxz, o.min.z) chunklayers = [] for layer in layers: for chunk in chunks: if chunk.isbelowZ(layer[0]): newchunk = chunk.copy() newchunk.clampZ(layer[1]) chunklayers.append(newchunk) if o.first_down: chunklayers = utils.sortChunks(chunklayers, o) chunksToMesh(chunklayers, o)
def cutout(o): if o.straight: join = 2 else: join = 1 print('operation: cutout') offset = True if o.cut_type == 'ONLINE' and o.onlycurves == True: # is separate to allow open curves :) print('separate') chunksFromCurve = [] for ob in o.objects: chunksFromCurve.extend(curveToChunks(ob, o.use_modifiers)) for ch in chunksFromCurve: # print(ch.points) if len(ch.points) > 2: ch.poly = chunkToShapely(ch) # p.addContour(ch.poly) else: chunksFromCurve = [] if o.cut_type == 'ONLINE': p = utils.getObjectOutline(0, o, True) else: offset = True if o.cut_type == 'INSIDE': offset = False p = utils.getObjectOutline(o.cutter_diameter / 2, o, offset) if o.outlines_count > 1: for i in range(1, o.outlines_count): chunksFromCurve.extend(shapelyToChunks(p, -1)) p = p.buffer(distance=o.dist_between_paths * offset, resolution=o.circle_detail, join_style=join, mitre_limit=2) chunksFromCurve.extend(shapelyToChunks(p, -1)) if o.outlines_count > 1 and o.movement_insideout == 'OUTSIDEIN': chunksFromCurve.reverse() # parentChildPoly(chunksFromCurve,chunksFromCurve,o) chunksFromCurve = limitChunks(chunksFromCurve, o) if not o.dont_merge: parentChildPoly(chunksFromCurve, chunksFromCurve, o) if o.outlines_count == 1: chunksFromCurve = utils.sortChunks(chunksFromCurve, o) # if o.outlines_count>0 and o.cut_type!='ONLINE' and o.movement_insideout=='OUTSIDEIN':#reversing just with more outlines # chunksFromCurve.reverse() if (o.movement_type == 'CLIMB' and o.spindle_rotation_direction == 'CCW') or (o.movement_type == 'CONVENTIONAL' and o.spindle_rotation_direction == 'CW'): for ch in chunksFromCurve: ch.points.reverse() if o.cut_type == 'INSIDE': # there would bee too many conditions above, so for now it gets reversed once again when inside cutting. for ch in chunksFromCurve: ch.points.reverse() layers = getLayers(o, o.maxz, checkminz(o)) extendorder = [] if o.first_down: # each shape gets either cut all the way to bottom, or every shape gets cut 1 layer, then all again. has to create copies, because same chunks are worked with on more layers usually for chunk in chunksFromCurve: dir_switch = False # needed to avoid unnecessary lifting of cutter with open chunks and movement set to "MEANDER" for layer in layers: chunk_copy = chunk.copy() if dir_switch: chunk_copy.points.reverse() extendorder.append([chunk_copy, layer]) if (not chunk.closed) and o.movement_type == "MEANDER": dir_switch = not dir_switch else: for layer in layers: for chunk in chunksFromCurve: extendorder.append([chunk.copy(), layer]) for chl in extendorder: # Set Z for all chunks chunk = chl[0] layer = chl[1] print(layer[1]) chunk.setZ(layer[1]) chunks = [] if o.use_bridges: # add bridges to chunks # bridges=getBridges(p,o) print('using bridges') bridgeheight = min(o.max.z, o.min.z + abs(o.bridges_height)) for chl in extendorder: chunk = chl[0] layer = chl[1] if layer[1] < bridgeheight: bridges.useBridges(chunk, o) if o.profile_start > 0: print("cutout change profile start") for chl in extendorder: chunk = chl[0] if chunk.closed: chunk.changePathStart(o) ## Lead in if o.lead_in > 0.0 or o.lead_out > 0: print("cutout leadin") for chl in extendorder: chunk = chl[0] if chunk.closed: chunk.breakPathForLeadinLeadout(o) chunk.leadContour(o) if o.ramp: # add ramps or simply add chunks for chl in extendorder: chunk = chl[0] layer = chl[1] if chunk.closed: chunk.rampContour(layer[0], layer[1], o) chunks.append(chunk) else: chunk.rampZigZag(layer[0], layer[1], o) chunks.append(chunk) else: for chl in extendorder: chunks.append(chl[0]) chunksToMesh(chunks, o)
def drill(o): print('operation: Drill') chunks = [] for ob in o.objects: activate(ob) bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={ "linked": False, "mode": 'TRANSLATION' }, TRANSFORM_OT_translate={ "value": (0, 0, 0), "constraint_axis": (False, False, False), "orient_type": 'GLOBAL', "mirror": False, "use_proportional_edit": False, "proportional_edit_falloff": 'SMOOTH', "proportional_size": 1, "snap": False, "snap_target": 'CLOSEST', "snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0), "texture_space": False, "release_confirm": False }) # bpy.ops.collection.objects_remove_all() bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') ob = bpy.context.active_object if ob.type == 'CURVE': ob.data.dimensions = '3D' try: bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) except: pass l = ob.location if ob.type == 'CURVE': for c in ob.data.splines: maxx, minx, maxy, miny, maxz, minz = -10000, 10000, -10000, 10000, -10000, 10000 for p in c.points: if o.drill_type == 'ALL_POINTS': chunks.append( camPathChunk([(p.co.x + l.x, p.co.y + l.y, p.co.z + l.z)])) minx = min(p.co.x, minx) maxx = max(p.co.x, maxx) miny = min(p.co.y, miny) maxy = max(p.co.y, maxy) minz = min(p.co.z, minz) maxz = max(p.co.z, maxz) for p in c.bezier_points: if o.drill_type == 'ALL_POINTS': chunks.append( camPathChunk([(p.co.x + l.x, p.co.y + l.y, p.co.z + l.z)])) minx = min(p.co.x, minx) maxx = max(p.co.x, maxx) miny = min(p.co.y, miny) maxy = max(p.co.y, maxy) minz = min(p.co.z, minz) maxz = max(p.co.z, maxz) cx = (maxx + minx) / 2 cy = (maxy + miny) / 2 cz = (maxz + minz) / 2 center = (cx, cy) aspect = (maxx - minx) / (maxy - miny) if (1.3 > aspect > 0.7 and o.drill_type == 'MIDDLE_SYMETRIC') or o.drill_type == 'MIDDLE_ALL': chunks.append( camPathChunk([(center[0] + l.x, center[1] + l.y, cz + l.z)])) elif ob.type == 'MESH': for v in ob.data.vertices: chunks.append( camPathChunk([(v.co.x + l.x, v.co.y + l.y, v.co.z + l.z)])) delob(ob) # delete temporary object with applied transforms layers = getLayers(o, o.maxz, checkminz(o)) chunklayers = [] for layer in layers: for chunk in chunks: # If using object for minz then use z from points in object if o.minz_from_ob: z = chunk.points[0][2] else: # using operation minz z = o.minz # only add a chunk layer if the chunk z point is in or lower than the layer if z <= layer[0]: if z <= layer[1]: z = layer[1] # perform peck drill newchunk = chunk.copy() newchunk.setZ(z) chunklayers.append(newchunk) # retract tool to maxz (operation depth start in ui) newchunk = chunk.copy() newchunk.setZ(o.maxz) chunklayers.append(newchunk) chunklayers = utils.sortChunks(chunklayers, o) chunksToMesh(chunklayers, o)
def pocket(o): print('operation: pocket') p = utils.getObjectOutline(o.cutter_diameter / 2, o, False) approxn = (min(o.max.x - o.min.x, o.max.y - o.min.y) / o.dist_between_paths) / 2 print("approximative:" + str(approxn)) i = 0 chunks = [] chunksFromCurve = [] lastchunks = [] centers = None firstoutline = p # for testing in the end. prest = p.buffer(-o.cutter_diameter / 2, o.circle_detail) while not p.is_empty: nchunks = shapelyToChunks(p, o.min.z) #print("nchunks") pnew = p.buffer(-o.dist_between_paths, o.circle_detail) #print("pnew") # caused a bad slow down # if o.dist_between_paths > o.cutter_diameter / 2.0: # prest = prest.difference(pnew.boundary.buffer(o.cutter_diameter / 2, o.circle_detail)) # if not (pnew.contains(prest)): # prest = shapelyToMultipolygon(prest) # fine = [] # go = [] # for p1 in prest: # if pnew.contains(p1): # fine.append(p1) # else: # go.append(p1) # if len(go) > 0: # for p1 in go: # nchunks1 = shapelyToChunks(p1, o.min.z) # nchunks.extend(nchunks1) # prest = sgeometry.MultiPolygon(fine) nchunks = limitChunks(nchunks, o) chunksFromCurve.extend(nchunks) parentChildDist(lastchunks, nchunks, o) lastchunks = nchunks percent = int(i / approxn * 100) progress('outlining polygons ', percent) p = pnew i += 1 # if (o.poc)#TODO inside outside! if (o.movement_type == 'CLIMB' and o.spindle_rotation_direction == 'CW') or (o.movement_type == 'CONVENTIONAL' and o.spindle_rotation_direction == 'CCW'): for ch in chunksFromCurve: ch.points.reverse() # if bpy.app.debug_value==1: chunksFromCurve = utils.sortChunks(chunksFromCurve, o) chunks = [] layers = getLayers(o, o.maxz, checkminz(o)) # print(layers) # print(chunksFromCurve) # print(len(chunksFromCurve)) for l in layers: lchunks = setChunksZ(chunksFromCurve, l[1]) if o.ramp: for ch in lchunks: ch.zstart = l[0] ch.zend = l[1] ###########helix_enter first try here TODO: check if helix radius is not out of operation area. if o.helix_enter: helix_radius = o.cutter_diameter * 0.5 * o.helix_diameter * 0.01 # 90 percent of cutter radius helix_circumference = helix_radius * pi * 2 revheight = helix_circumference * tan(o.ramp_in_angle) for chi, ch in enumerate(lchunks): if chunksFromCurve[chi].children == []: p = ch.points[ 0] # TODO:intercept closest next point when it should stay low # first thing to do is to check if helix enter can really enter. checkc = Circle(helix_radius + o.cutter_diameter / 2, o.circle_detail) checkc = affinity.translate(checkc, p[0], p[1]) covers = False for poly in o.silhouete: if poly.contains(checkc): covers = True break if covers: revolutions = (l[0] - p[2]) / revheight # print(revolutions) h = Helix(helix_radius, o.circle_detail, l[0], p, revolutions) # invert helix if not the typical direction if (o.movement_type == 'CONVENTIONAL' and o.spindle_rotation_direction == 'CW') or ( o.movement_type == 'CLIMB' and o.spindle_rotation_direction == 'CCW'): nhelix = [] for v in h: nhelix.append((2 * p[0] - v[0], v[1], v[2])) h = nhelix ch.points = h + ch.points else: o.warnings = o.warnings + 'Helix entry did not fit! \n ' ch.closed = True ch.rampZigZag(l[0], l[1], o) # Arc retract here first try: if o.retract_tangential: # TODO: check for entry and exit point before actual computing... will be much better. # TODO: fix this for CW and CCW! for chi, ch in enumerate(lchunks): # print(chunksFromCurve[chi]) # print(chunksFromCurve[chi].parents) if chunksFromCurve[chi].parents == [] or len( chunksFromCurve[chi].parents) == 1: revolutions = 0.25 v1 = Vector(ch.points[-1]) i = -2 v2 = Vector(ch.points[i]) v = v1 - v2 while v.length == 0: i = i - 1 v2 = Vector(ch.points[i]) v = v1 - v2 v.normalize() rotangle = Vector((v.x, v.y)).angle_signed(Vector((1, 0))) e = Euler((0, 0, pi / 2.0)) # TODO:#CW CLIMB! v.rotate(e) p = v1 + v * o.retract_radius center = p p = (p.x, p.y, p.z) # progress(str((v1,v,p))) h = Helix(o.retract_radius, o.circle_detail, p[2] + o.retract_height, p, revolutions) e = Euler( (0, 0, rotangle + pi)) # angle to rotate whole retract move rothelix = [] c = [] # polygon for outlining and checking collisions. for p in h: # rotate helix to go from tangent of vector v1 = Vector(p) v = v1 - center v.x = -v.x # flip it here first... v.rotate(e) p = center + v rothelix.append(p) c.append((p[0], p[1])) c = sgeometry.Polygon(c) # print('çoutline') # print(c) coutline = c.buffer(o.cutter_diameter / 2, o.circle_detail) # print(h) # print('çoutline') # print(coutline) # polyToMesh(coutline,0) rothelix.reverse() covers = False for poly in o.silhouete: if poly.contains(coutline): covers = True break if covers: ch.points.extend(rothelix) chunks.extend(lchunks) if o.ramp: for ch in chunks: ch.rampZigZag(ch.zstart, ch.points[0][2], o) if o.first_down: chunks = utils.sortChunks(chunks, o) chunksToMesh(chunks, o)
def medial_axis(o): print('operation: Medial Axis') simple.remove_multiple("medialMesh") from cam.voronoi import Site, computeVoronoiDiagram chunks = [] gpoly = spolygon.Polygon() angle = o.cutter_tip_angle slope = math.tan(math.pi * (90 - angle / 2) / 180) # angle in degrees # slope = math.tan((math.pi-angle)/2) #angle in radian new_cutter_diameter = o.cutter_diameter m_o_name = o.object_name if o.cutter_type == 'VCARVE': angle = o.cutter_tip_angle # start the max depth calc from the "start depth" of the operation. maxdepth = o.maxz - slope * o.cutter_diameter / 2 # don't cut any deeper than the "end depth" of the operation. if maxdepth < o.minz: maxdepth = o.minz # the effective cutter diameter can be reduced from it's max # since we will be cutting shallower than the original maxdepth # without this, the curve is calculated as if the diameter was at the original maxdepth and we get the bit # pulling away from the desired cut surface new_cutter_diameter = (maxdepth - o.maxz) / (-slope) * 2 elif o.cutter_type == 'BALLNOSE': maxdepth = -new_cutter_diameter / 2 else: o.warnings += 'Only Ballnose, Ball and V-carve cutters\n are supported' return # remember resolutions of curves, to refine them, # otherwise medial axis computation yields too many branches in curved parts resolutions_before = [] for ob in o.objects: if ob.type == 'CURVE' or ob.type == 'FONT': resolutions_before.append(ob.data.resolution_u) if ob.data.resolution_u < 64: ob.data.resolution_u = 64 polys = utils.getOperationSilhouete(o) mpoly = sgeometry.shape(polys) mpoly_boundary = mpoly.boundary ipol = 0 for poly in polys.geoms: ipol = ipol + 1 print("polygon:", ipol) schunks = shapelyToChunks(poly, -1) schunks = chunksRefineThreshold( schunks, o.medial_axis_subdivision, o.medial_axis_threshold) # chunksRefine(schunks,o) verts = [] for ch in schunks: for pt in ch.points: # pvoro = Site(pt[0], pt[1]) verts.append(pt) # (pt[0], pt[1]), pt[2]) # verts= points#[[vert.x, vert.y, vert.z] for vert in vertsPts] nDupli, nZcolinear = unique(verts) nVerts = len(verts) print(str(nDupli) + " duplicates points ignored") print(str(nZcolinear) + " z colinear points excluded") if nVerts < 3: print("Not enough points") return {'FINISHED'} # Check colinear xValues = [pt[0] for pt in verts] yValues = [pt[1] for pt in verts] if checkEqual(xValues) or checkEqual(yValues): print("Points are colinear") return {'FINISHED'} # Create diagram print("Tesselation... (" + str(nVerts) + " points)") xbuff, ybuff = 5, 5 # % zPosition = 0 vertsPts = [Point(vert[0], vert[1], vert[2]) for vert in verts] # vertsPts= [Point(vert[0], vert[1]) for vert in verts] pts, edgesIdx = computeVoronoiDiagram(vertsPts, xbuff, ybuff, polygonsOutput=False, formatOutput=True) # pts=[[pt[0], pt[1], zPosition] for pt in pts] newIdx = 0 vertr = [] filteredPts = [] print('filter points') ipts = 0 for p in pts: ipts = ipts + 1 if ipts % 500 == 0: sys.stdout.write('\r') # the exact output you're looking for: prog_message = "points: " + str(ipts) + " / " + str( len(pts)) + " " + str(round(100 * ipts / len(pts))) + "%" sys.stdout.write(prog_message) sys.stdout.flush() if not poly.contains(sgeometry.Point(p)): vertr.append((True, -1)) else: vertr.append((False, newIdx)) if o.cutter_type == 'VCARVE': # start the z depth calc from the "start depth" of the operation. z = o.maxz - mpoly.boundary.distance( sgeometry.Point(p)) * slope if z < maxdepth: z = maxdepth elif o.cutter_type == 'BALL' or o.cutter_type == 'BALLNOSE': d = mpoly_boundary.distance(sgeometry.Point(p)) r = new_cutter_diameter / 2.0 if d >= r: z = -r else: # print(r, d) z = -r + sqrt(r * r - d * d) else: z = 0 # # print(mpoly.distance(sgeometry.Point(0,0))) # if(z!=0):print(z) filteredPts.append((p[0], p[1], z)) newIdx += 1 print('filter edges') filteredEdgs = [] ledges = [] for e in edgesIdx: do = True # p1 = pts[e[0]] # p2 = pts[e[1]] # print(p1,p2,len(vertr)) if vertr[e[0]][0]: # exclude edges with allready excluded points do = False elif vertr[e[1]][0]: do = False if do: filteredEdgs.append((vertr[e[0]][1], vertr[e[1]][1])) ledges.append( sgeometry.LineString((filteredPts[vertr[e[0]][1]], filteredPts[vertr[e[1]][1]]))) # print(ledges[-1].has_z) bufpoly = poly.buffer(-new_cutter_diameter / 2, resolution=64) lines = shapely.ops.linemerge(ledges) # print(lines.type) if bufpoly.type == 'Polygon' or bufpoly.type == 'MultiPolygon': lines = lines.difference(bufpoly) chunks.extend(shapelyToChunks(bufpoly, maxdepth)) chunks.extend(shapelyToChunks(lines, 0)) # generate a mesh from the medial calculations if o.add_mesh_for_medial: polygon_utils_cam.shapelyToCurve('medialMesh', lines, 0.0) bpy.ops.object.convert(target='MESH') oi = 0 for ob in o.objects: if ob.type == 'CURVE' or ob.type == 'FONT': ob.data.resolution_u = resolutions_before[oi] oi += 1 # bpy.ops.object.join() chunks = utils.sortChunks(chunks, o) layers = getLayers(o, o.maxz, o.min.z) chunklayers = [] for layer in layers: for chunk in chunks: if chunk.isbelowZ(layer[0]): newchunk = chunk.copy() newchunk.clampZ(layer[1]) chunklayers.append(newchunk) if o.first_down: chunklayers = utils.sortChunks(chunklayers, o) if o.add_mesh_for_medial: # make curve instead of a path simple.joinMultiple("medialMesh") chunksToMesh(chunklayers, o) # add pocket operation for medial if add pocket checked if o.add_pocket_for_medial: # o.add_pocket_for_medial = False # export medial axis parameter to pocket op ops.Add_Pocket(None, maxdepth, m_o_name, new_cutter_diameter)