blendercam/scripts/addons/cam/pack.py

261 wiersze
7.6 KiB
Python

"""BlenderCAM 'pack.py' © 2012 Vilem Novak
Takes all selected curves, converts them to polygons, offsets them by the pre-set margin
then chooses a starting location possibly inside the already occupied area and moves and rotates the
polygon out of the occupied area if one or more positions are found where the poly doesn't overlap,
it is placed and added to the occupied area - allpoly
Very slow and STUPID, a collision algorithm would be much much faster...
"""
from math import pi
import random
import time
import shapely
from shapely import geometry as sgeometry
from shapely import (
affinity,
prepared,
speedups
)
import bpy
from bpy.types import PropertyGroup
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
)
from mathutils import (
Euler,
Vector
)
from . import (
constants,
polygon_utils_cam,
simple,
utils,
)
def srotate(s, r, x, y):
ncoords = []
e = Euler((0, 0, r))
for p in s.exterior.coords:
v1 = Vector((p[0], p[1], 0))
v2 = Vector((x, y, 0))
v = v1 - v2
v.rotate(e)
ncoords.append((v[0], v[1]))
return sgeometry.Polygon(ncoords)
def packCurves():
if speedups.available:
speedups.enable()
t = time.time()
packsettings = bpy.context.scene.cam_pack
sheetsizex = packsettings.sheet_x
sheetsizey = packsettings.sheet_y
direction = packsettings.sheet_fill_direction
distance = packsettings.distance
tolerance = packsettings.tolerance
rotate = packsettings.rotate
rotate_angle = packsettings.rotate_angle
# in this, position, rotation, and actual poly will be stored.
polyfield = []
for ob in bpy.context.selected_objects:
simple.activate(ob)
bpy.ops.object.make_single_user(type='SELECTED_OBJECTS')
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
z = ob.location.z
bpy.ops.object.location_clear()
bpy.ops.object.rotation_clear()
chunks = utils.curveToChunks(ob)
npolys = utils.chunksToShapely(chunks)
# add all polys in silh to one poly
poly = shapely.ops.unary_union(npolys)
poly = poly.buffer(distance / 1.5, 8)
poly = poly.simplify(0.0003)
polyfield.append([[0, 0], 0.0, poly, ob, z])
random.shuffle(polyfield)
# primitive layout here:
allpoly = prepared.prep(sgeometry.Polygon()) # main collision poly.
shift = tolerance # one milimeter by now.
rotchange = rotate_angle # in radians
xmin, ymin, xmax, ymax = polyfield[0][2].bounds
if direction == 'X':
mindist = -xmin
else:
mindist = -ymin
i = 0
p = polyfield[0][2]
placedpolys = []
rotcenter = sgeometry.Point(0, 0)
for pf in polyfield:
print(i)
rot = 0
porig = pf[2]
placed = False
xmin, ymin, xmax, ymax = p.bounds
if direction == 'X':
x = mindist
y = -ymin
if direction == 'Y':
x = -xmin
y = mindist
itera = 0
best = None
hits = 0
besthit = None
while not placed:
# swap x and y, and add to x
# print(x,y)
p = porig
if rotate:
ptrans = affinity.rotate(p, rot, origin=rotcenter, use_radians=True)
ptrans = affinity.translate(ptrans, x, y)
else:
ptrans = affinity.translate(p, x, y)
xmin, ymin, xmax, ymax = ptrans.bounds
# print(iter,p.bounds)
if xmin > 0 and ymin > 0 and (
(direction == 'Y' and xmax < sheetsizex) or (direction == 'X' and ymax < sheetsizey)):
if not allpoly.intersects(ptrans):
# we do more good solutions, choose best out of them:
hits += 1
if best is None:
best = [x, y, rot, xmax, ymax]
besthit = hits
if direction == 'X':
if xmax < best[3]:
best = [x, y, rot, xmax, ymax]
besthit = hits
elif ymax < best[4]:
best = [x, y, rot, xmax, ymax]
besthit = hits
if hits >= 15 or (
itera > 20000 and hits > 0): # here was originally more, but 90% of best solutions are still 1
placed = True
pf[3].location.x = best[0]
pf[3].location.y = best[1]
pf[3].location.z = pf[4]
pf[3].rotation_euler.z = best[2]
pf[3].select_set(state=True)
# print(mindist)
mindist = mindist - 0.5 * (xmax - xmin)
# print(mindist)
# print(iter)
# reset polygon to best position here:
ptrans = affinity.rotate(porig, best[2], rotcenter, use_radians=True)
ptrans = affinity.translate(ptrans, best[0], best[1])
print(best[0], best[1], itera)
placedpolys.append(ptrans)
allpoly = prepared.prep(sgeometry.MultiPolygon(placedpolys))
# cleanup allpoly
print(itera, hits, besthit)
if not placed:
if direction == 'Y':
x += shift
mindist = y
if xmax + shift > sheetsizex:
x = x - xmin
y += shift
if direction == 'X':
y += shift
mindist = x
if ymax + shift > sheetsizey:
y = y - ymin
x += shift
if rotate:
rot += rotchange
itera += 1
i += 1
t = time.time() - t
polygon_utils_cam.shapelyToCurve('test', sgeometry.MultiPolygon(placedpolys), 0)
print(t)
class PackObjectsSettings(PropertyGroup):
"""stores all data for machines"""
sheet_fill_direction: EnumProperty(
name="Fill Direction",
items=(
("X", "X", "Fills sheet in X axis direction"),
("Y", "Y", "Fills sheet in Y axis direction"),
),
description="Fill direction of the packer algorithm",
default="Y",
)
sheet_x: FloatProperty(
name="X Size",
description="Sheet size",
min=0.001,
max=10,
default=0.5,
precision=constants.PRECISION,
unit="LENGTH",
)
sheet_y: FloatProperty(
name="Y Size",
description="Sheet size",
min=0.001,
max=10,
default=0.5,
precision=constants.PRECISION,
unit="LENGTH",
)
distance: FloatProperty(
name="Minimum Distance",
description="Minimum distance between objects(should be "
"at least cutter diameter!)",
min=0.001,
max=10,
default=0.01,
precision=constants.PRECISION,
unit="LENGTH",
)
tolerance: FloatProperty(
name="Placement Tolerance",
description="Tolerance for placement: smaller value slower placemant",
min=0.001,
max=0.02,
default=0.005,
precision=constants.PRECISION,
unit="LENGTH",
)
rotate: BoolProperty(
name="Enable Rotation",
description="Enable rotation of elements",
default=True,
)
rotate_angle: FloatProperty(
name="Placement Angle Rotation Step",
description="Bigger rotation angle, faster placemant",
default=0.19635 * 4,
min=pi / 180,
max=pi,
precision=5,
subtype="ANGLE",
unit="ROTATION",
)