blendercam/scripts/addons/cam/curvecamcreate.py

710 wiersze
37 KiB
Python

# blender CAM ops.py (c) 2022 Alain Pelletier
#
# ***** 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 3
# 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 bpy
from bpy.props import *
from bpy.types import Operator
from bpy_extras.io_utils import ImportHelper
from cam import utils, pack, polygon_utils_cam, simple, gcodepath, bridges, parametric, joinery, \
curvecamtools, puzzle_joinery
import shapely
from shapely.geometry import Point, LineString, Polygon, MultiLineString
import mathutils
import math
from Equation import Expression
import numpy as np
class CamCurveHatch(bpy.types.Operator):
"""perform hatch operation on single or multiple curves""" # by Alain Pelletier September 2021
bl_idname = "object.curve_hatch"
bl_label = "CrossHatch curve"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
angle: bpy.props.FloatProperty(name="angle", default=0, min=-math.pi/2, max=math.pi/2, precision=4, subtype="ANGLE")
distance: bpy.props.FloatProperty(name="spacing", default=0.015, min=0, max=3.0, precision=4, unit="LENGTH")
offset: bpy.props.FloatProperty(name="Margin", default=0.001, min=0, max=3.0, precision=4, unit="LENGTH")
pocket_type: EnumProperty(name='Type pocket',
items=(('BOUNDS', 'makes a bounds rectangle', 'makes a bounding square'),
('POCKET', 'Pocket', 'makes a pocket inside a closed loop')),
description='Type of pocket',
default='BOUNDS')
@classmethod
def poll(cls, context):
return context.active_object is not None and context.active_object.type in ['CURVE', 'FONT']
def execute(self, context):
from shapely import affinity
coords = []
shapes = utils.curveToShapely(bpy.context.active_object)
for s in shapes:
minx, miny, maxx, maxy = s.bounds
minx -= self.offset
miny -= self.offset
maxx += self.offset
maxy += self.offset
centery = (miny + maxy) / 2
height = maxy - miny
width = maxx - minx
centerx = (minx+maxx) / 2
diagonal = math.hypot(width, height)
simple.addBoundRectangle(minx, miny, maxx, maxy, 'crosshatch_bound')
# simple.addBoundRectangle(-width/2, -height/2, width/2, height/2, '_shape')
amount = int(2*diagonal/self.distance) + 1
for x in range(amount):
distance = x * self.distance - diagonal
coords.append(((distance, diagonal + 0.5), (distance, -diagonal - 0.5)))
lines = MultiLineString(coords)
rotated = affinity.rotate(lines, self.angle, use_radians=True) # rotate using shapely
translated = affinity.translate(rotated, xoff=centerx, yoff=centery) # move using shapely
simple.makeActive('crosshatch_bound')
bounds = simple.activeToShapelyPoly()
if self.pocket_type == 'BOUNDS':
xing = translated.intersection(bounds) # Shapely detects intersections with the square bounds
else:
xing = translated.intersection(s.buffer(self.offset)) # Shapely detects intersections with the square bounds
utils.shapelyToCurve('crosshatch_lines', xing, 0)
simple.removeMultiple('_')
simple.selectMultiple('crosshatch')
return {'FINISHED'}
class CamCurvePlate(bpy.types.Operator):
"""perform generates rounded plate with mounting holes""" # by Alain Pelletier Sept 2021
bl_idname = "object.curve_plate"
bl_label = "Sign plate"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
radius: bpy.props.FloatProperty(name="Corner Radius", default=.025, min=0, max=0.1, precision=4, unit="LENGTH")
width: bpy.props.FloatProperty(name="Width of plate", default=0.3048, min=0, max=3.0, precision=4, unit="LENGTH")
height: bpy.props.FloatProperty(name="Height of plate", default=0.457, min=0, max=3.0, precision=4, unit="LENGTH")
hole_diameter: bpy.props.FloatProperty(name="Hole diameter", default=0.01, min=0, max=3.0, precision=4,
unit="LENGTH")
hole_tolerance: bpy.props.FloatProperty(name="Hole V Tolerance", default=0.005, min=0, max=3.0, precision=4,
unit="LENGTH")
hole_vdist: bpy.props.FloatProperty(name="Hole Vert distance", default=0.400, min=0, max=3.0, precision=4,
unit="LENGTH")
hole_hdist: bpy.props.FloatProperty(name="Hole horiz distance", default=0, min=0, max=3.0, precision=4,
unit="LENGTH")
hole_hamount: bpy.props.IntProperty(name="Hole horiz amount", default=1, min=0, max=50)
resolution: bpy.props.IntProperty(name="Spline resolution", default=50, min=3, max=150)
def execute(self, context):
left = -self.width / 2 + self.radius
bottom = -self.height / 2 + self.radius
right = -left
top = -bottom
# create base
bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD',
location=(left, bottom, 0), scale=(1, 1, 1))
simple.activeName("_circ_LB")
bpy.context.object.data.resolution_u = self.resolution
bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD',
location=(right, bottom, 0), scale=(1, 1, 1))
simple.activeName("_circ_RB")
bpy.context.object.data.resolution_u = self.resolution
bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD',
location=(left, top, 0), scale=(1, 1, 1))
simple.activeName("_circ_LT")
bpy.context.object.data.resolution_u = self.resolution
bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD',
location=(right, top, 0), scale=(1, 1, 1))
simple.activeName("_circ_RT")
bpy.context.object.data.resolution_u = self.resolution
simple.selectMultiple("_circ") # select the circles for the four corners
utils.polygonConvexHull(context) # perform hull operation on the four corner circles
simple.activeName("plate_base")
simple.removeMultiple("_circ") # remove corner circles
if self.hole_diameter > 0 or self.hole_hamount > 0:
bpy.ops.curve.primitive_bezier_circle_add(radius=self.hole_diameter / 2, enter_editmode=False,
align='WORLD', location=(0, self.hole_tolerance / 2, 0),
scale=(1, 1, 1))
simple.activeName("_hole_Top")
bpy.context.object.data.resolution_u = self.resolution / 4
if self.hole_tolerance > 0:
bpy.ops.curve.primitive_bezier_circle_add(radius=self.hole_diameter / 2, enter_editmode=False,
align='WORLD', location=(0, -self.hole_tolerance / 2, 0),
scale=(1, 1, 1))
simple.activeName("_hole_Bottom")
bpy.context.object.data.resolution_u = self.resolution / 4
simple.selectMultiple("_hole") # select everything starting with _hole and perform a convex hull on them
utils.polygonConvexHull(context)
simple.activeName("plate_hole")
simple.move(y=-self.hole_vdist / 2)
simple.duplicate(y=self.hole_vdist)
simple.removeMultiple("_hole") # remove temporary holes
simple.joinMultiple("plate_hole") # join the holes together
# horizontal holes
if self.hole_hamount > 1:
if self.hole_hamount % 2 != 0:
for x in range(int((self.hole_hamount - 1) / 2)):
dist = self.hole_hdist * (x + 1) # calculate the distance from the middle
simple.duplicate()
bpy.context.object.location[0] = dist
simple.duplicate()
bpy.context.object.location[0] = -dist
else:
for x in range(int(self.hole_hamount / 2)):
dist = self.hole_hdist * x + self.hole_hdist / 2 # calculate the distance from the middle
if x == 0: # special case where the original hole only needs to move and not duplicate
bpy.context.object.location[0] = dist
simple.duplicate()
bpy.context.object.location[0] = -dist
else:
simple.duplicate()
bpy.context.object.location[0] = dist
simple.duplicate()
bpy.context.object.location[0] = -dist
simple.joinMultiple("plate_hole") # join the holes together
simple.selectMultiple("plate_") # select everything starting with plate_
bpy.context.view_layer.objects.active = bpy.data.objects['plate_base'] # Make the plate base active
utils.polygonBoolean(context, "DIFFERENCE") # Remove holes from the base
simple.removeMultiple("plate_") # Remove temporary base and holes
simple.activeName("plate")
bpy.context.active_object.select_set(True)
bpy.ops.object.curve_remove_doubles()
return {'FINISHED'}
class CamCurveMortise(bpy.types.Operator):
"""Generates mortise along a curve""" # by Alain Pelletier December 2021
bl_idname = "object.curve_mortise"
bl_label = "Mortise"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
finger_size: bpy.props.FloatProperty(name="Maximum Finger Size", default=0.015, min=0.005, max=3.0, precision=4,
unit="LENGTH")
min_finger_size: bpy.props.FloatProperty(name="Minimum Finger Size", default=0.0025, min=0.001, max=3.0,
precision=4,
unit="LENGTH")
finger_tolerance: bpy.props.FloatProperty(name="Finger play room", default=0.000045, min=0, max=0.003, precision=4,
unit="LENGTH")
plate_thickness: bpy.props.FloatProperty(name="Drawer plate thickness", default=0.00477, min=0.001, max=3.0,
unit="LENGTH")
side_height: bpy.props.FloatProperty(name="side height", default=0.05, min=0.001, max=3.0, unit="LENGTH")
flex_pocket: bpy.props.FloatProperty(name="Flex pocket", default=0.004, min=0.000, max=1.0, unit="LENGTH")
top_bottom: bpy.props.BoolProperty(name="Side Top & bottom fingers", default=True)
opencurve: bpy.props.BoolProperty(name="OpenCurve", default=False)
adaptive: bpy.props.FloatProperty(name="Adaptive angle threshold", default=0.0, min=0.000, max=2, subtype="ANGLE",
unit="ROTATION")
double_adaptive: bpy.props.BoolProperty(name="Double adaptive Pockets", default=False)
@classmethod
def poll(cls, context):
return context.active_object is not None and (context.active_object.type in ['CURVE', 'FONT'])
def execute(self, context):
o1 = bpy.context.active_object
bpy.context.object.data.resolution_u = 60
bpy.ops.object.duplicate()
obj = context.active_object
bpy.ops.object.convert(target='MESH')
simple.activeName("_temp_mesh")
if self.opencurve:
coords = []
for v in obj.data.vertices: # extract X,Y coordinates from the vertices data
coords.append((v.co.x, v.co.y))
line = LineString(coords) # convert coordinates to shapely LineString datastructure
simple.removeMultiple("-converted")
utils.shapelyToCurve('-converted_curve', line, 0.0)
shapes = utils.curveToShapely(o1)
for s in shapes:
if s.boundary.type == 'LineString':
loops = [s.boundary]
else:
loops = s.boundary
for ci, c in enumerate(loops):
if self.opencurve:
length = line.length
else:
length = c.length
print("loop Length:", length)
if self.opencurve:
loop_length = line.length
else:
loop_length = c.length
print("line Length:", loop_length)
if self.adaptive > 0.0:
joinery.variable_finger(c, length, self.min_finger_size, self.finger_size, self.plate_thickness,
self.finger_tolerance, self.adaptive)
locations = joinery.variable_finger(c, length, self.min_finger_size, self.finger_size,
self.plate_thickness, self.finger_tolerance, self.adaptive,
True, self.double_adaptive)
joinery.create_flex_side(loop_length, self.side_height, self.plate_thickness, self.top_bottom)
if self.flex_pocket > 0:
joinery.make_variable_flex_pocket(self.side_height, self.plate_thickness, self.flex_pocket,
locations)
else:
joinery.fixed_finger(c, length, self.finger_size, self.plate_thickness, self.finger_tolerance)
joinery.fixed_finger(c, length, self.finger_size, self.plate_thickness, self.finger_tolerance, True)
joinery.create_flex_side(loop_length, self.side_height, self.plate_thickness, self.top_bottom)
if self.flex_pocket > 0:
joinery.make_flex_pocket(length, self.side_height, self.plate_thickness, self.finger_size,
self.flex_pocket)
simple.removeMultiple('_')
return {'FINISHED'}
class CamCurveInterlock(bpy.types.Operator):
"""Generates interlock along a curve""" # by Alain Pelletier December 2021
bl_idname = "object.curve_interlock"
bl_label = "Interlock"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
finger_size: bpy.props.FloatProperty(name="Finger Size", default=0.015, min=0.005, max=3.0, precision=4,
unit="LENGTH")
finger_tolerance: bpy.props.FloatProperty(name="Finger play room", default=0.000045, min=0, max=0.003, precision=4,
unit="LENGTH")
plate_thickness: bpy.props.FloatProperty(name="Plate thickness", default=0.00477, min=0.001, max=3.0,
unit="LENGTH")
opencurve: bpy.props.BoolProperty(name="OpenCurve", default=False)
interlock_type: EnumProperty(name='Type of interlock',
items=(('TWIST', 'Twist', 'Iterlock requires 1/4 turn twist'),
('GROOVE', 'Groove', 'Simple sliding groove'),
('PUZZLE', 'Puzzle interlock', 'puzzle good for flat joints')),
description='Type of interlock',
default='GROOVE')
finger_amount: bpy.props.IntProperty(name="Finger Amount", default=2, min=1, max=100)
tangent_angle: bpy.props.FloatProperty(name="Tangent deviation", default=0.0, min=0.000, max=2, subtype="ANGLE",
unit="ROTATION")
fixed_angle: bpy.props.FloatProperty(name="fixed angle", default=0.0, min=0.000, max=2, subtype="ANGLE",
unit="ROTATION")
def execute(self, context):
print(len(context.selected_objects), "selected object", context.selected_objects)
if len(context.selected_objects) > 0 and (context.active_object.type in ['CURVE', 'FONT']):
o1 = bpy.context.active_object
bpy.context.object.data.resolution_u = 60
simple.duplicate()
obj = context.active_object
bpy.ops.object.convert(target='MESH')
simple.activeName("_temp_mesh")
if self.opencurve:
coords = []
for v in obj.data.vertices: # extract X,Y coordinates from the vertices data
coords.append((v.co.x, v.co.y))
line = LineString(coords) # convert coordinates to shapely LineString datastructure
simple.removeMultiple("-converted")
utils.shapelyToCurve('-converted_curve', line, 0.0)
shapes = utils.curveToShapely(o1)
for s in shapes:
if s.boundary.type == 'LineString':
loops = [s.boundary]
else:
loops = s.boundary
for ci, c in enumerate(loops):
if self.opencurve:
length = line.length
else:
length = c.length
print("loop Length:", length)
if self.opencurve:
loop_length = line.length
else:
loop_length = c.length
print("line Length:", loop_length)
joinery.distributed_interlock(c, length, self.finger_size, self.plate_thickness,
self.finger_tolerance, self.finger_amount,
fixed_angle=self.fixed_angle, tangent=self.tangent_angle,
closed=not self.opencurve, type=self.interlock_type)
else:
location = bpy.context.scene.cursor.location
joinery.single_interlock(self.finger_size, self.plate_thickness, self.finger_tolerance, location[0],
location[1], self.fixed_angle, self.interlock_type, self.finger_amount)
bpy.context.scene.cursor.location = location
return {'FINISHED'}
class CamCurveDrawer(bpy.types.Operator):
"""Generates drawers""" # by Alain Pelletier December 2021 inspired by The Drawinator
bl_idname = "object.curve_drawer"
bl_label = "Drawer"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
depth: bpy.props.FloatProperty(name="Drawer Depth", default=0.2, min=0, max=1.0, precision=4, unit="LENGTH")
width: bpy.props.FloatProperty(name="Width of Drawer", default=0.125, min=0, max=3.0, precision=4, unit="LENGTH")
height: bpy.props.FloatProperty(name="Height of drawer", default=0.07, min=0, max=3.0, precision=4, unit="LENGTH")
finger_size: bpy.props.FloatProperty(name="Maximum Finger Size", default=0.015, min=0.005, max=3.0, precision=4,
unit="LENGTH")
finger_tolerance: bpy.props.FloatProperty(name="Finger play room", default=0.000045, min=0, max=0.003, precision=4,
unit="LENGTH")
finger_inset: bpy.props.FloatProperty(name="Finger inset", default=0.0, min=0.0, max=0.01, precision=4,
unit="LENGTH")
drawer_plate_thickness: bpy.props.FloatProperty(name="Drawer plate thickness", default=0.00477, min=0.001, max=3.0,
precision=4, unit="LENGTH")
drawer_hole_diameter: bpy.props.FloatProperty(name="Drawer hole diameter", default=0.02, min=0.00001, max=0.5,
precision=4, unit="LENGTH")
drawer_hole_offset: bpy.props.FloatProperty(name="Drawer hole offset", default=0.0, min=-0.5, max=0.5, precision=4,
unit="LENGTH")
overcut: bpy.props.BoolProperty(name="Add overcut", default=False)
overcut_diameter: bpy.props.FloatProperty(name="Overcut toool Diameter", default=0.003175, min=-0.001, max=0.5,
precision=4, unit="LENGTH")
def draw(self, context):
layout = self.layout
layout.prop(self, 'depth')
layout.prop(self, 'width')
layout.prop(self, 'height')
layout.prop(self, 'finger_size')
layout.prop(self, 'finger_tolerance')
layout.prop(self, 'finger_inset')
layout.prop(self, 'drawer_plate_thickness')
layout.prop(self, 'drawer_hole_diameter')
layout.prop(self, 'drawer_hole_offset')
layout.prop(self, 'overcut')
if self.overcut:
layout.prop(self, 'overcut_diameter')
def execute(self, context):
height_finger_amt = int(joinery.finger_amount(self.height, self.finger_size))
height_finger = (self.height + 0.0004) / height_finger_amt
width_finger_amt = int(joinery.finger_amount(self.width, self.finger_size))
width_finger = (self.width - self.finger_size) / width_finger_amt
# create base
joinery.create_base_plate(self.height, self.width, self.depth)
bpy.context.object.data.resolution_u = 64
bpy.context.scene.cursor.location = (0, 0, 0)
joinery.vertical_finger(height_finger, self.drawer_plate_thickness, self.finger_tolerance, height_finger_amt)
joinery.horizontal_finger(width_finger, self.drawer_plate_thickness, self.finger_tolerance,
width_finger_amt * 2)
simple.makeActive('_wfb')
bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN')
# make drawer back
finger_pair = joinery.finger_pair("_vfa", self.width - self.drawer_plate_thickness - self.finger_inset * 2, 0)
simple.makeActive('_wfa')
fronth = bpy.context.active_object
simple.makeActive('_back')
finger_pair.select_set(True)
fronth.select_set(True)
bpy.ops.object.curve_boolean(boolean_type='DIFFERENCE')
simple.removeMultiple("_finger_pair")
simple.activeName("drawer_back")
simple.removeDoubles()
simple.addOvercut(self.overcut_diameter, self.overcut)
# make drawer front
bpy.ops.curve.primitive_bezier_circle_add(radius=self.drawer_hole_diameter / 2, enter_editmode=False,
align='WORLD', location=(0, self.height + self.drawer_hole_offset, 0),
scale=(1, 1, 1))
simple.activeName("_circ")
front_hole = bpy.context.active_object
simple.makeActive('drawer_back')
front_hole.select_set(True)
bpy.ops.object.curve_boolean(boolean_type='DIFFERENCE')
simple.activeName("drawer_front")
simple.removeDoubles()
simple.addOvercut(self.overcut_diameter, self.overcut)
# place back and front side by side
simple.makeActive('drawer_front')
bpy.ops.transform.transform(mode='TRANSLATION', value=(0.0, 2 * self.height, 0.0, 0.0))
simple.makeActive('drawer_back')
bpy.ops.transform.transform(mode='TRANSLATION', value=(self.width + 0.01, 2 * self.height, 0.0, 0.0))
# make side
finger_pair = joinery.finger_pair("_vfb", self.depth - self.drawer_plate_thickness, 0)
simple.makeActive('_side')
finger_pair.select_set(True)
fronth.select_set(True)
bpy.ops.object.curve_boolean(boolean_type='DIFFERENCE')
simple.activeName("drawer_side")
simple.removeDoubles()
simple.addOvercut(self.overcut_diameter, self.overcut)
simple.removeMultiple('_finger_pair')
# make bottom
simple.makeActive("_wfb")
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked": False, "mode": 'TRANSLATION'},
TRANSFORM_OT_translate={"value": (0, -self.drawer_plate_thickness / 2, 0.0)})
simple.activeName("_wfb0")
joinery.finger_pair("_wfb0", 0, self.depth - self.drawer_plate_thickness)
simple.activeName('_bot_fingers')
simple.difference('_bot', '_bottom')
simple.rotate(math.pi/2)
joinery.finger_pair("_wfb0", 0, self.width - self.drawer_plate_thickness - self.finger_inset * 2)
simple.activeName('_bot_fingers')
simple.difference('_bot', '_bottom')
simple.activeName("drawer_bottom")
simple.removeDoubles()
simple.addOvercut(self.overcut_diameter, self.overcut)
# cleanup all temp polygons
simple.removeMultiple("_")
# move side and bottom to location
simple.makeActive("drawer_side")
bpy.ops.transform.transform(mode='TRANSLATION',
value=(self.depth / 2 + 3 * self.width / 2 + 0.02, 2 * self.height, 0.0, 0.0))
simple.makeActive("drawer_bottom")
bpy.ops.transform.transform(mode='TRANSLATION',
value=(self.depth / 2 + 3 * self.width / 2 + 0.02, self.width / 2, 0.0, 0.0))
simple.selectMultiple('drawer')
return {'FINISHED'}
class CamCurvePuzzle(bpy.types.Operator):
"""Generates Puzzle joints and interlocks""" # by Alain Pelletier December 2021
bl_idname = "object.curve_puzzle"
bl_label = "Puzzle joints"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
diameter: bpy.props.FloatProperty(name="tool diameter", default=0.003175, min=0.001, max=3.0, precision=4,
unit="LENGTH")
finger_tolerance: bpy.props.FloatProperty(name="Finger play room", default=0.00005, min=0, max=0.003, precision=4,
unit="LENGTH")
finger_amount: bpy.props.IntProperty(name="Finger Amount", default=1, min=0, max=100)
stem_size: bpy.props.IntProperty(name="size of the stem", default=2, min=1, max=200)
width: bpy.props.FloatProperty(name="Width", default=0.100, min=0.005, max=3.0, precision=4,
unit="LENGTH")
height: bpy.props.FloatProperty(name="height or thickness", default=0.025, min=0.005, max=3.0, precision=4,
unit="LENGTH")
angle: bpy.props.FloatProperty(name="angle A", default=math.pi/4, min=-10, max=10, subtype="ANGLE",
unit="ROTATION")
angleb: bpy.props.FloatProperty(name="angle B", default=math.pi/4, min=-10, max=10, subtype="ANGLE",
unit="ROTATION")
radius: bpy.props.FloatProperty(name="Arc Radius", default=0.025, min=0.005, max=5, precision=4,
unit="LENGTH")
interlock_type: EnumProperty(name='Type of shape',
items=(('JOINT', 'Joint', 'Puzzle Joint interlock'),
('BAR', 'Bar', 'Bar interlock'),
('ARC', 'Arc', 'Arc interlock'),
('MULTIANGLE', 'Multi angle', 'Multi angle joint'),
('CURVEBAR', 'Arc Bar', 'Arc Bar interlock'),
('CURVEBARCURVE', 'Arc Bar Arc', 'Arc Bar Arc interlock'),
('CURVET', 'T curve', 'T curve interlock'),
('T', 'T Bar', 'T Bar interlock'),
('CORNER', 'Corner Bar', 'Corner Bar interlock'),
('OPENCURVE', 'Open Curve', 'Corner Bar interlock')),
description='Type of interlock',
default='CURVET')
gender: EnumProperty(name='Type gender',
items=(('MF', 'Male-Receptacle', 'Male and receptacle'),
('F', 'Receptacle only', 'Receptacle'),
('M', 'Male only', 'Male')),
description='Type of interlock',
default='MF')
base_gender: EnumProperty(name='Base gender',
items=(('MF', 'Male - Receptacle', 'Male - Receptacle'),
('F', 'Receptacle', 'Receptacle'),
('M', 'Male', 'Male')),
description='Type of interlock',
default='M')
multiangle_gender: EnumProperty(name='Multiangle gender',
items=(('MMF', 'Male Male Receptacle', 'M M F'),
('MFF', 'Male Receptacle Receptacle', 'M F F')),
description='Type of interlock',
default='MFF')
mitre: bpy.props.BoolProperty(name="Add Mitres", default=False)
twist_lock: bpy.props.BoolProperty(name="Add TwistLock", default=False)
twist_thick: bpy.props.FloatProperty(name="Twist Thickness", default=0.0047, min=0.001, max=3.0, precision=4,
unit="LENGTH")
twist_percent: bpy.props.FloatProperty(name="Twist neck", default=0.3, min=0.1, max=0.9, precision=4)
interlock_amount: bpy.props.IntProperty(name="Interlock amount on curve", default=2, min=0, max=200)
overcut: bpy.props.BoolProperty(name="Add overcut", default=False)
overcut_diameter: bpy.props.FloatProperty(name="Overcut toool Diameter", default=0.003175, min=-0.001, max=0.5,
precision=4, unit="LENGTH")
def draw(self, context):
layout = self.layout
layout.prop(self, 'interlock_type')
layout.label(text='Puzzle Joint Definition')
layout.prop(self, 'stem_size')
layout.prop(self, 'diameter')
layout.prop(self, 'finger_tolerance')
layout.prop(self, 'finger_amount')
if self.interlock_type != 'JOINT':
layout.prop(self, 'twist_lock')
if self.twist_lock:
layout.prop(self, 'twist_thick')
layout.prop(self, 'twist_percent')
if self.interlock_type == 'OPENCURVE':
layout.prop(self, 'interlock_amount')
layout.separator()
layout.prop(self, 'height')
if self.interlock_type == 'BAR':
layout.prop(self, 'mitre')
if self.interlock_type in ["ARC", "CURVEBARCURVE", "CURVEBAR", "MULTIANGLE", 'CURVET'] \
or (self.interlock_type == 'BAR' and self.mitre):
if self.interlock_type == 'MULTIANGLE':
layout.prop(self, 'multiangle_gender')
elif self.interlock_type != 'CURVET':
layout.prop(self, 'gender')
if not self.mitre:
layout.prop(self, 'radius')
layout.prop(self, 'angle')
if self.interlock_type == 'CURVEBARCURVE' or self.mitre:
layout.prop(self, 'angleb')
if self.interlock_type in ['BAR', 'CURVEBARCURVE', 'CURVEBAR', "T", 'CORNER', 'CURVET']:
layout.prop(self, 'gender')
if self.interlock_type in ['T', 'CURVET']:
layout.prop(self, 'base_gender')
if self.interlock_type == 'CURVEBARCURVE':
layout.label(text="Width includes 2 radius and thickness")
layout.prop(self, 'width')
layout.prop(self, 'overcut')
if self.overcut:
layout.prop(self, 'overcut_diameter')
def execute(self, context):
curve_detected = False
print(len(context.selected_objects), "selected object", context.selected_objects)
if len(context.selected_objects) > 0 and context.active_object.type == 'CURVE':
curve_detected = True
# bpy.context.object.data.resolution_u = 60
simple.duplicate()
bpy.ops.object.transform_apply(location=True)
obj = context.active_object
bpy.ops.object.convert(target='MESH')
bpy.context.active_object.name = "_tempmesh"
coords = []
for v in obj.data.vertices: # extract X,Y coordinates from the vertices data
coords.append((v.co.x, v.co.y))
simple.removeMultiple('_tmp')
line = LineString(coords) # convert coordinates to shapely LineString datastructure
simple.removeMultiple("_")
if self.interlock_type == 'FINGER':
puzzle_joinery.finger(self.diameter, self.finger_tolerance, stem=self.stem_size)
simple.rename('_puzzle', 'receptacle')
puzzle_joinery.finger(self.diameter, 0, stem=self.stem_size)
simple.rename('_puzzle', 'finger')
if self.interlock_type == 'JOINT':
if self.finger_amount == 0: # cannot be 0 in joints
self.finger_amount = 1
puzzle_joinery.fingers(self.diameter, self.finger_tolerance, self.finger_amount, stem=self.stem_size)
if self.interlock_type == 'BAR':
if not self.mitre:
puzzle_joinery.bar(self.width, self.height, self.diameter, self.finger_tolerance, self.finger_amount,
stem=self.stem_size, twist=self.twist_lock, tneck=self.twist_percent,
tthick=self.twist_thick, which=self.gender)
else:
puzzle_joinery.mitre(self.width, self.height, self.angle, self.angleb, self.diameter,
self.finger_tolerance, self.finger_amount, stem=self.stem_size,
twist=self.twist_lock, tneck=self.twist_percent,
tthick=self.twist_thick, which=self.gender)
elif self.interlock_type == 'ARC':
puzzle_joinery.arc(self.radius, self.height, self.angle, self.diameter,
self.finger_tolerance, self.finger_amount,
stem=self.stem_size, twist=self.twist_lock, tneck=self.twist_percent,
tthick=self.twist_thick, which=self.gender)
elif self.interlock_type == 'CURVEBARCURVE':
puzzle_joinery.arcbararc(self.width, self.radius, self.height, self.angle, self.angleb, self.diameter,
self.finger_tolerance, self.finger_amount,
stem=self.stem_size, twist=self.twist_lock, tneck=self.twist_percent,
tthick=self.twist_thick, which=self.gender)
elif self.interlock_type == 'CURVEBAR':
puzzle_joinery.arcbar(self.width, self.radius, self.height, self.angle, self.diameter,
self.finger_tolerance, self.finger_amount,
stem=self.stem_size, twist=self.twist_lock, tneck=self.twist_percent,
tthick=self.twist_thick, which=self.gender)
elif self.interlock_type == 'MULTIANGLE':
puzzle_joinery.multiangle(self.radius, self.height, math.pi/3, self.diameter, self.finger_tolerance,
self.finger_amount,
stem=self.stem_size, twist=self.twist_lock, tneck=self.twist_percent,
tthick=self.twist_thick, combination=self.multiangle_gender)
elif self.interlock_type == 'T':
puzzle_joinery.t(self.width, self.height, self.diameter, self.finger_tolerance, self.finger_amount,
stem=self.stem_size, twist=self.twist_lock, tneck=self.twist_percent,
tthick=self.twist_thick, combination=self.gender, base_gender=self.base_gender)
elif self.interlock_type == 'CURVET':
puzzle_joinery.curved_t(self.width, self.height, self.radius, self.diameter, self.finger_tolerance,
self.finger_amount,
stem=self.stem_size, twist=self.twist_lock, tneck=self.twist_percent,
tthick=self.twist_thick, combination=self.gender, base_gender=self.base_gender)
elif self.interlock_type == 'CORNER':
puzzle_joinery.t(self.width, self.height, self.diameter, self.finger_tolerance, self.finger_amount,
stem=self.stem_size, twist=self.twist_lock, tneck=self.twist_percent,
tthick=self.twist_thick, combination=self.gender, base_gender=self.base_gender,
corner=True)
elif self.interlock_type == 'OPENCURVE' and curve_detected:
puzzle_joinery.openCurve(line, self.height, self.diameter, self.finger_tolerance, self.finger_amount,
stem=self.stem_size, twist=self.twist_lock, t_neck=self.twist_percent,
t_thick=self.twist_thick, which=self.gender, twist_amount=self.interlock_amount)
simple.removeDoubles()
simple.addOvercut(self.overcut_diameter, self.overcut)
return {'FINISHED'}