Source code for cam.operators.pack_op

"""Fabex 'ops.py' © 2012 Vilem Novak

Blender Operator definitions are in this file.
They mostly call the functions from 'utils.py'
"""

from math import pi
import os
import random
import time

import shapely
from shapely import geometry as sgeometry
from shapely import affinity, prepared, speedups

import bpy
from bpy.props import (
    BoolProperty,
    EnumProperty,
    StringProperty,
    FloatProperty,
)
from bpy.types import Operator


from ..cam_chunk import curve_to_chunks
from ..constants import PRECISION
from ..pack import pack_curves

from ..utilities.chunk_utils import chunks_to_shapely
from ..utilities.shapely_utils import shapely_to_curve
from ..utilities.simple_utils import activate


[docs] class CamPackObjects(Operator): """Calculate All CAM Paths"""
[docs] bl_idname = "object.cam_pack_objects"
[docs] bl_label = "Pack Curves on Sheet"
[docs] bl_options = {"REGISTER", "UNDO"}
[docs] 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", )
[docs] sheet_x: FloatProperty( name="X Size", description="Sheet size", min=0.001, max=10, default=0.5, precision=PRECISION, unit="LENGTH", )
[docs] sheet_y: FloatProperty( name="Y Size", description="Sheet size", min=0.001, max=10, default=0.5, precision=PRECISION, unit="LENGTH", )
[docs] 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=PRECISION, unit="LENGTH", )
[docs] tolerance: FloatProperty( name="Placement Tolerance", description="Tolerance for placement: smaller value slower placemant", min=0.001, max=0.02, default=0.005, precision=PRECISION, unit="LENGTH", )
[docs] rotate: BoolProperty( name="Enable Rotation", description="Enable rotation of elements", default=True, )
[docs] 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, step=500, subtype="ANGLE", unit="ROTATION", )
[docs] def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self)
[docs] def execute(self, context): """Execute the operation in the given context. This function sets the Blender object mode to 'OBJECT', retrieves the currently selected objects, and calls the `pack_curves` function from the `pack` module. It is typically used to finalize operations on selected objects in Blender. Args: context: The context in which the operation is executed. Returns: dict: A dictionary indicating the completion status of the operation. """ bpy.ops.object.mode_set(mode="OBJECT") # force object mode obs = bpy.context.selected_objects if speedups.available: speedups.enable() t = time.time() sheetsizex = self.sheet_x sheetsizey = self.sheet_y direction = self.sheet_fill_direction distance = self.distance tolerance = self.tolerance rotate = self.rotate rotate_angle = self.rotate_angle # in this, position, rotation, and actual poly will be stored. polyfield = [] for ob in bpy.context.selected_objects: 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 = curve_to_chunks(ob) npolys = chunks_to_shapely(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 shapely_to_curve("test", sgeometry.MultiPolygon(placedpolys), 0) print(t) # layout. return {"FINISHED"}
[docs] def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False box = layout.box() col = box.column(align=True) col.label(text="Sheet Size") col.prop(self, "sheet_x", text="X") col.prop(self, "sheet_y", text="Y") col.prop(self, "sheet_fill_direction") col = layout.column(align=True) col.prop(self, "distance") col.prop(self, "tolerance") header, panel = col.panel_prop(self, "rotate") header.label(text="Rotation") if panel: col = panel.column(align=True) col.prop(self, "rotate_angle", text="Placement Angle Step")