Source code for cam.pattern

"""BlenderCAM 'pattern.py' © 2012 Vilem Novak

Functions to read CAM path patterns and return CAM path chunks.
"""

from math import (
    ceil,
    floor,
    pi,
    sqrt
)
import time

import numpy

import bpy
from mathutils import (
    Euler,
    Vector
)

from .cam_chunk import (
    camPathChunk,
    camPathChunkBuilder,
    chunksRefine,
    parentChildDist,
    shapelyToChunks,
)
from .simple import progress


[docs] def getPathPatternParallel(o, angle): """Generate path chunks for parallel movement based on object dimensions and angle. This function calculates a series of path chunks for a given object, taking into account its dimensions and the specified angle. It utilizes both a traditional method and an alternative algorithm (currently disabled) to generate these paths. The paths are constructed by iterating over calculated vectors and applying transformations based on the object's properties. The resulting path chunks can be used for various movement types, including conventional and climb movements. Args: o (object): An object containing properties such as dimensions and movement type. angle (float): The angle to rotate the path generation. Returns: list: A list of path chunks generated based on the object's dimensions and angle. """ zlevel = 1 pathd = o.dist_between_paths pathstep = o.dist_along_paths pathchunks = [] xm = (o.max.x + o.min.x) / 2 ym = (o.max.y + o.min.y) / 2 vm = Vector((xm, ym, 0)) xdim = o.max.x - o.min.x ydim = o.max.y - o.min.y dim = (xdim + ydim) / 2.0 e = Euler((0, 0, angle)) reverse = False if bpy.app.debug_value == 0: # by default off # this is the original pattern method, slower, but well tested: dirvect = Vector((0, 1, 0)) dirvect.rotate(e) dirvect.normalize() dirvect *= pathstep for a in range(int(-dim / pathd), int(dim / pathd)): # this is highly ineffective, computes path2x the area needed... chunk = camPathChunkBuilder([]) v = Vector((a * pathd, int(-dim / pathstep) * pathstep, 0)) v.rotate(e) # shifting for the rotation, so pattern rotates around middle... v += vm for b in range(int(-dim / pathstep), int(dim / pathstep)): v += dirvect if v.x > o.min.x and v.x < o.max.x and v.y > o.min.y and v.y < o.max.y: chunk.points.append((v.x, v.y, zlevel)) if (reverse and o.movement.type == 'MEANDER') or ( o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CW') or ( o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CCW'): chunk.points.reverse() if len(chunk.points) > 0: pathchunks.append(chunk.to_chunk()) if len(pathchunks) > 1 and reverse and o.movement.parallel_step_back and not o.use_layers: # parallel step back - for finishing, best with climb movement, saves cutter life by going into # material with climb, while using move back on the surface to improve finish # (which would otherwise be a conventional move in the material) if o.movement.type == 'CONVENTIONAL' or o.movement.type == 'CLIMB': pathchunks[-2].reverse() changechunk = pathchunks[-1] pathchunks[-1] = pathchunks[-2] pathchunks[-2] = changechunk reverse = not reverse # print (chunk.points) else: # alternative algorithm with numpy, didn't work as should so blocked now... v = Vector((0, 1, 0)) v.rotate(e) e1 = Euler((0, 0, -pi / 2)) v1 = v.copy() v1.rotate(e1) axis_across_paths = numpy.array((numpy.arange(int(-dim / pathd), int(dim / pathd)) * pathd * v1.x + xm, numpy.arange(int(-dim / pathd), int(dim / pathd)) * pathd * v1.y + ym, numpy.arange(int(-dim / pathd), int(dim / pathd)) * 0)) axis_along_paths = numpy.array((numpy.arange(int(-dim / pathstep), int(dim / pathstep)) * pathstep * v.x, numpy.arange(int(-dim / pathstep), int(dim / pathstep)) * pathstep * v.y, numpy.arange(int(-dim / pathstep), int(dim / pathstep)) * 0 + zlevel)) # rotate this first progress(axis_along_paths) chunks = [] for a in range(0, len(axis_across_paths[0])): # progress(chunks[a,...,...].shape) # progress(axis_along_paths.shape) nax = axis_along_paths.copy() # progress(nax.shape) nax[0] += axis_across_paths[0][a] nax[1] += axis_across_paths[1][a] # progress(a) # progress(nax.shape) # progress(chunks.shape) # progress(chunks[...,a,...].shape) xfitmin = nax[0] > o.min.x xfitmax = nax[0] < o.max.x xfit = xfitmin & xfitmax # print(xfit,nax) nax = numpy.array([nax[0][xfit], nax[1][xfit], nax[2][xfit]]) yfitmin = nax[1] > o.min.y yfitmax = nax[1] < o.max.y yfit = yfitmin & yfitmax nax = numpy.array([nax[0][yfit], nax[1][yfit], nax[2][yfit]]) chunks.append(nax.swapaxes(0, 1)) # chunks pathchunks = [] print("WOO") for ch in chunks: ch = ch.tolist() pathchunks.append(camPathChunk(ch)) # print (ch) return pathchunks
[docs] def getPathPattern(operation): """Generate a path pattern based on the specified operation strategy. This function constructs a path pattern for a given operation by analyzing its parameters and applying different strategies such as 'PARALLEL', 'CROSS', 'BLOCK', 'SPIRAL', 'CIRCLES', and 'OUTLINEFILL'. Each strategy dictates how the path is built, utilizing various geometric calculations and conditions to ensure the path adheres to the specified operational constraints. The function also handles the orientation and direction of the path based on the movement settings provided in the operation. Args: operation (object): An object containing parameters for path generation, including strategy, movement type, and geometric bounds. Returns: list: A list of path chunks representing the generated path pattern. """ o = operation t = time.time() progress('Building Path Pattern') minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z pathchunks = [] zlevel = 1 # minz#this should do layers... if o.strategy == 'PARALLEL': pathchunks = getPathPatternParallel(o, o.parallel_angle) elif o.strategy == 'CROSS': pathchunks.extend(getPathPatternParallel(o, o.parallel_angle)) pathchunks.extend(getPathPatternParallel(o, o.parallel_angle - pi / 2.0)) elif o.strategy == 'BLOCK': pathd = o.dist_between_paths pathstep = o.dist_along_paths maxxp = maxx maxyp = maxy minxp = minx minyp = miny x = 0.0 y = 0.0 incx = 1 incy = 0 chunk = camPathChunkBuilder([]) i = 0 while maxxp - minxp > 0 and maxyp - minyp > 0: y = minyp for a in range(ceil(minxp / pathstep), ceil(maxxp / pathstep), 1): x = a * pathstep chunk.points.append((x, y, zlevel)) if i > 0: minxp += pathd chunk.points.append((maxxp, minyp, zlevel)) x = maxxp for a in range(ceil(minyp / pathstep), ceil(maxyp / pathstep), 1): y = a * pathstep chunk.points.append((x, y, zlevel)) minyp += pathd chunk.points.append((maxxp, maxyp, zlevel)) y = maxyp for a in range(floor(maxxp / pathstep), ceil(minxp / pathstep), -1): x = a * pathstep chunk.points.append((x, y, zlevel)) maxxp -= pathd chunk.points.append((minxp, maxyp, zlevel)) x = minxp for a in range(floor(maxyp / pathstep), ceil(minyp / pathstep), -1): y = a * pathstep chunk.points.append((x, y, zlevel)) chunk.points.append((minxp, minyp, zlevel)) maxyp -= pathd i += 1 if o.movement.insideout == 'INSIDEOUT': chunk.points.reverse() if (o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CW') or ( o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CCW'): for si in range(0, len(chunk.points)): s = chunk.points[si] chunk.points[si] = (o.max.x + o.min.x - s[0], s[1], s[2]) pathchunks = [chunk.to_chunk()] elif o.strategy == 'SPIRAL': chunk = camPathChunkBuilder([]) pathd = o.dist_between_paths pathstep = o.dist_along_paths midx = (o.max.x + o.min.x) / 2 midy = (o.max.y + o.min.y) / 2 x = pathd / 4 y = pathd / 4 v = Vector((pathd / 4, 0, 0)) # progress(x,y,midx,midy) e = Euler((0, 0, 0)) # pi = pi chunk.points.append((midx + v.x, midy + v.y, zlevel)) while midx + v.x > o.min.x or midy + v.y > o.min.y: # v.x=x-midx # v.y=y-midy offset = 2 * v.length * pi e.z = 2 * pi * (pathstep / offset) v.rotate(e) v.length = (v.length + pathd / (offset / pathstep)) # progress(v.x,v.y) if o.max.x > midx + v.x > o.min.x and o.max.y > midy + v.y > o.min.y: chunk.points.append((midx + v.x, midy + v.y, zlevel)) else: pathchunks.append(chunk.to_chunk()) chunk = camPathChunkBuilder([]) if len(chunk.points) > 0: pathchunks.append(chunk.to_chunk()) if o.movement.insideout == 'OUTSIDEIN': pathchunks.reverse() for chunk in pathchunks: if o.movement.insideout == 'OUTSIDEIN': chunk.reverse() if (o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CW') or ( o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CCW'): # TODO chunk.flipX(o.max.x+o.min.x) # for si in range(0, len(chunk.points)): # s = chunk.points[si] # chunk.points[si] = (o.max.x + o.min.x - s[0], s[1], s[2]) elif o.strategy == 'CIRCLES': pathd = o.dist_between_paths pathstep = o.dist_along_paths midx = (o.max.x + o.min.x) / 2 midy = (o.max.y + o.min.y) / 2 rx = o.max.x - o.min.x ry = o.max.y - o.min.y maxr = sqrt(rx * rx + ry * ry) # progress(x,y,midx,midy) e = Euler((0, 0, 0)) # pi = pi chunk = camPathChunkBuilder([]) chunk.points.append((midx, midy, zlevel)) pathchunks.append(chunk.to_chunk()) r = 0 while r < maxr: r += pathd chunk = camPathChunkBuilder([]) firstchunk = chunk v = Vector((-r, 0, 0)) steps = 2 * pi * r / pathstep e.z = 2 * pi / steps laststepchunks = [] currentstepchunks = [] for a in range(0, int(steps)): laststepchunks = currentstepchunks currentstepchunks = [] if o.max.x > midx + v.x > o.min.x and o.max.y > midy + v.y > o.min.y: chunk.points.append((midx + v.x, midy + v.y, zlevel)) else: if len(chunk.points) > 0: chunk.closed = False chunk = chunk.to_chunk() pathchunks.append(chunk) currentstepchunks.append(chunk) chunk = camPathChunkBuilder([]) v.rotate(e) if len(chunk.points) > 0: chunk.points.append(firstchunk.points[0]) if chunk == firstchunk: chunk.closed = True chunk = chunk.to_chunk() pathchunks.append(chunk) currentstepchunks.append(chunk) chunk = camPathChunkBuilder([]) for ch in laststepchunks: for p in currentstepchunks: parentChildDist(p, ch, o) if o.movement.insideout == 'OUTSIDEIN': pathchunks.reverse() for chunk in pathchunks: if o.movement.insideout == 'OUTSIDEIN': chunk.reverse() if (o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CW') or ( o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CCW'): chunk.reverse() # pathchunks=sortChunks(pathchunks,o)not until they get hierarchy parents! elif o.strategy == 'OUTLINEFILL': polys = o.silhouete.geoms pathchunks = [] chunks = [] for p in polys: p = p.buffer(-o.dist_between_paths / 10, o.optimisation.circle_detail) # first, move a bit inside, because otherwise the border samples go crazy very often changin between # hit/non hit and making too many jumps in the path. chunks.extend(shapelyToChunks(p, 0)) pathchunks.extend(chunks) lastchunks = chunks firstchunks = chunks approxn = (min(maxx - minx, maxy - miny) / o.dist_between_paths) / 2 i = 0 for porig in polys: p = porig while not p.is_empty: p = p.buffer(-o.dist_between_paths, o.optimisation.circle_detail) if not p.is_empty: nchunks = shapelyToChunks(p, zlevel) if o.movement.insideout == 'INSIDEOUT': parentChildDist(lastchunks, nchunks, o) else: parentChildDist(nchunks, lastchunks, o) pathchunks.extend(nchunks) lastchunks = nchunks percent = int(i / approxn * 100) progress('outlining polygons ', percent) i += 1 pathchunks.reverse() if not o.inverse: # dont do ambient for inverse milling lastchunks = firstchunks for p in polys: d = o.dist_between_paths steps = o.ambient_radius / o.dist_between_paths for a in range(0, int(steps)): dist = d if a == int(o.cutter_diameter / 2 / o.dist_between_paths): if o.optimisation.use_exact: dist += o.optimisation.pixsize * 0.85 # this is here only because silhouette is still done with zbuffer method, # even if we use bullet collisions. else: dist += o.optimisation.pixsize * 2.5 p = p.buffer(dist, o.optimisation.circle_detail) if not p.is_empty: nchunks = shapelyToChunks(p, zlevel) if o.movement.insideout == 'INSIDEOUT': parentChildDist(nchunks, lastchunks, o) else: parentChildDist(lastchunks, nchunks, o) pathchunks.extend(nchunks) lastchunks = nchunks if o.movement.insideout == 'OUTSIDEIN': pathchunks.reverse() for chunk in pathchunks: if o.movement.insideout == 'OUTSIDEIN': chunk.reverse() if (o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CW') or ( o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CCW'): chunk.reverse() chunksRefine(pathchunks, o) progress(time.time() - t) return pathchunks
[docs] def getPathPattern4axis(operation): """Generate path patterns for a specified operation along a rotary axis. This function constructs a series of path chunks based on the provided operation's parameters, including the rotary axis, strategy, and dimensions. It calculates the necessary angles and positions for the cutter based on the specified strategy (PARALLELR, PARALLEL, or HELIX) and generates the corresponding path chunks for machining operations. Args: operation (object): An object containing parameters for the machining operation, including min and max coordinates, rotary axis configuration, distance settings, and movement strategy. Returns: list: A list of path chunks generated for the specified operation. """ o = operation t = time.time() progress('Building Path Pattern') minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z pathchunks = [] zlevel = 1 # minz#this should do layers... # set axes for various options, Z option is obvious nonsense now. if o.rotary_axis_1 == 'X': a1 = 0 a2 = 1 a3 = 2 if o.rotary_axis_1 == 'Y': a1 = 1 a2 = 0 a3 = 2 if o.rotary_axis_1 == 'Z': a1 = 2 a2 = 0 a3 = 1 o.max.z = o.maxz # set radius for all types of operation radius = max(o.max.z, 0.0001) radiusend = o.min.z mradius = max(radius, radiusend) circlesteps = (mradius * pi * 2) / o.dist_along_paths circlesteps = max(4, circlesteps) anglestep = 2 * pi / circlesteps # generalized rotation e = Euler((0, 0, 0)) e[a1] = anglestep # generalized length of the operation maxl = o.max[a1] minl = o.min[a1] steps = (maxl - minl) / o.dist_between_paths # set starting positions for cutter e.t.c. cutterstart = Vector((0, 0, 0)) cutterend = Vector((0, 0, 0)) # end point for casting if o.strategy4axis == 'PARALLELR': for a in range(0, floor(steps) + 1): chunk = camPathChunkBuilder([]) cutterstart[a1] = o.min[a1] + a * o.dist_between_paths cutterend[a1] = cutterstart[a1] cutterstart[a2] = 0 # radius cutterend[a2] = 0 # radiusend cutterstart[a3] = radius cutterend[a3] = radiusend for b in range(0, floor(circlesteps) + 1): # print(cutterstart,cutterend) chunk.startpoints.append(cutterstart.to_tuple()) chunk.endpoints.append(cutterend.to_tuple()) rot = [0, 0, 0] rot[a1] = a * 2 * pi + b * anglestep chunk.rotations.append(rot) cutterstart.rotate(e) cutterend.rotate(e) chunk.depth = radiusend - radius # last point = first chunk.startpoints.append(chunk.startpoints[0]) chunk.endpoints.append(chunk.endpoints[0]) chunk.rotations.append(chunk.rotations[0]) pathchunks.append(chunk.to_chunk()) if o.strategy4axis == 'PARALLEL': circlesteps = (mradius * pi * 2) / o.dist_between_paths steps = (maxl - minl) / o.dist_along_paths anglestep = 2 * pi / circlesteps # generalized rotation e = Euler((0, 0, 0)) e[a1] = anglestep reverse = False for b in range(0, floor(circlesteps) + 1): chunk = camPathChunkBuilder([]) cutterstart[a2] = 0 cutterstart[a3] = radius cutterend[a2] = 0 cutterend[a3] = radiusend e[a1] = anglestep * b cutterstart.rotate(e) cutterend.rotate(e) for a in range(0, floor(steps) + 1): cutterstart[a1] = o.min[a1] + a * o.dist_along_paths cutterend[a1] = cutterstart[a1] chunk.startpoints.append(cutterstart.to_tuple()) chunk.endpoints.append(cutterend.to_tuple()) rot = [0, 0, 0] rot[a1] = b * anglestep chunk.rotations.append(rot) chunk = chunk.to_chunk() chunk.depth = radiusend - radius pathchunks.append(chunk) if (reverse and o.movement.type == 'MEANDER') or ( o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CW') or ( o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CCW'): chunk.reverse() reverse = not reverse if o.strategy4axis == 'HELIX': print('helix') a1step = o.dist_between_paths / circlesteps chunk = camPathChunkBuilder([]) # only one chunk, init here for a in range(0, floor(steps) + 1): cutterstart[a1] = o.min[a1] + a * o.dist_between_paths cutterend[a1] = cutterstart[a1] cutterstart[a2] = 0 cutterstart[a3] = radius cutterend[a3] = radiusend for b in range(0, floor(circlesteps) + 1): # print(cutterstart,cutterend) cutterstart[a1] += a1step cutterend[a1] += a1step chunk.startpoints.append(cutterstart.to_tuple()) chunk.endpoints.append(cutterend.to_tuple()) rot = [0, 0, 0] rot[a1] = a * 2 * pi + b * anglestep chunk.rotations.append(rot) cutterstart.rotate(e) cutterend.rotate(e) chunk = chunk.to_chunk() chunk.depth = radiusend - radius pathchunks.append(chunk) # print(chunk.startpoints) # print(pathchunks) # sprint(len(pathchunks)) # print(o.strategy4axis) return pathchunks