blendercam/scripts/addons/cam/pattern.py

553 wiersze
20 KiB
Python

# blender CAM pattern.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 *****
import time
import mathutils
from mathutils import *
from cam import simple, chunk, utils
from cam.simple import *
from cam.chunk import *
from cam import polygon_utils_cam
from cam.polygon_utils_cam import *
import shapely
from shapely import geometry as sgeometry
import numpy
def getPathPatternParallel(o, 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)
v += vm # shifting for the rotation, so pattern rotates around middle...
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, -math.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
def getPathPattern(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...
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 - math.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 = math.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 = math.sqrt(rx * rx + ry * ry)
# progress(x,y,midx,midy)
e = Euler((0, 0, 0))
pi = math.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 = operation.silhouete
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
def getPathPattern4axis(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