609 lines
26 KiB
Python
609 lines
26 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
deps_dir = Path(__file__).parent / "deps"
|
|
if deps_dir.exists() and str(deps_dir) not in sys.path:
|
|
sys.path.insert(0, str(deps_dir))
|
|
|
|
import inkex
|
|
from inkex import PathElement, Group, TextElement
|
|
from offset import offset_path, boolean_lpe
|
|
from placements import pattern_along_path, calculate_path_length
|
|
from livinghinge import create_living_hinge_pattern, detect_straight_segments
|
|
|
|
|
|
class Boxbot(inkex.EffectExtension):
|
|
CUT_OUTER_STYLE = {
|
|
"stroke": "#cc0000",
|
|
"stroke-width": "1mm",
|
|
"-inkscape-stroke": "hairline",
|
|
"fill": "none",
|
|
}
|
|
|
|
CUT_INNER_STYLE = {
|
|
"stroke": "#ff66cc",
|
|
"stroke-width": "1mm",
|
|
"-inkscape-stroke": "hairline",
|
|
"fill": "none",
|
|
}
|
|
|
|
META_STYLE = {
|
|
"stroke": "#ffff00",
|
|
"stroke-width": "1mm",
|
|
"-inkscape-stroke": "hairline",
|
|
"fill": "none",
|
|
}
|
|
|
|
LABEL_STYLE = {
|
|
"font-size": "6px",
|
|
"fill": "#ffff00",
|
|
"font-family": "sans-serif",
|
|
"text-anchor": "middle",
|
|
"dominant-baseline": "middle",
|
|
}
|
|
|
|
def add_arguments(self, pars):
|
|
pars.add_argument("--units", default="mm", help="Document units")
|
|
pars.add_argument("--notebook", default="box", help="Active notebook tab")
|
|
pars.add_argument("--generate_lid", type=inkex.Boolean, default=True, help="Generate lid")
|
|
pars.add_argument("--material_thickness", type=float, default=3.0, help="Material thickness")
|
|
pars.add_argument("--box_height", type=float, default=50.0, help="Box height")
|
|
pars.add_argument("--tab_inset", type=float, default=5.0, help="Tab inset distance")
|
|
pars.add_argument("--top_hole_inset", type=float, default=10.0, help="Top hole inset")
|
|
pars.add_argument("--kerf", type=float, default=0.1, help="Kerf compensation")
|
|
pars.add_argument("--tab_width", type=float, default=6.0, help="Tab width")
|
|
pars.add_argument("--tab_start_offset", type=float, default=0.0, help="Tab start offset")
|
|
pars.add_argument("--tab_border_radius", type=float, default=0.5, help="Tab border radius")
|
|
pars.add_argument("--num_tabs", type=int, default=8, help="Number of tabs per side")
|
|
pars.add_argument("--generate_living_hinge", type=inkex.Boolean, default=False, help="Generate living hinge pattern")
|
|
pars.add_argument("--hinge_length_percent", type=int, default=25, help="Hinge cut length as percentage of side height")
|
|
pars.add_argument("--hinge_gap", type=float, default=1.5, help="Hinge gap")
|
|
pars.add_argument("--hinge_spacing", type=float, default=5.0, help="Hinge spacing")
|
|
pars.add_argument("--magnet_type", default="none", help="Magnet type")
|
|
pars.add_argument("--rectangle_magnet_width", type=float, default=6.0, help="Rectangle magnet width")
|
|
pars.add_argument("--rectangle_magnet_height", type=float, default=2.0, help="Rectangle magnet height")
|
|
pars.add_argument("--circle_magnet_diameter", type=float, default=6.0, help="Circle magnet diameter")
|
|
pars.add_argument("--num_magnets", type=int, default=4, help="Number of magnets")
|
|
pars.add_argument("--magnet_placement_offset", type=float, default=0.0, help="Magnet placement offset")
|
|
pars.add_argument("--hide_magnets", type=inkex.Boolean, default=True, help="Hide magnets")
|
|
|
|
def create_label(self, text, bbox, label_id):
|
|
label = TextElement()
|
|
label.set_id(self.svg.get_unique_id(label_id))
|
|
label.set('x', str(bbox.center_x))
|
|
label.set('y', str(bbox.center_y))
|
|
label.style = self.LABEL_STYLE
|
|
label.text = text
|
|
return label
|
|
|
|
def create_bottom_tabs_piece(self, inset_path_d):
|
|
|
|
def create_tab(index):
|
|
x = -tab_width / 2
|
|
y = -tab_height / 2
|
|
path_data = f"M {x},{y} L {x + tab_width},{y} L {x + tab_width},{y + tab_height} L {x},{y + tab_height} Z"
|
|
|
|
tab = PathElement()
|
|
tab.set_id(self.svg.get_unique_id(f"tab_{index}"))
|
|
tab.set('d', path_data)
|
|
return tab
|
|
|
|
group = Group(id=self.svg.get_unique_id("boxbot"))
|
|
self.svg.get_current_layer().add(group)
|
|
|
|
self.inset_path = PathElement()
|
|
self.inset_path.set_id(self.svg.get_unique_id("inset_path"))
|
|
self.inset_path.set('d', inset_path_d)
|
|
self.inset_path.style = self.META_STYLE
|
|
group.append(self.inset_path)
|
|
|
|
inset_csp = inkex.Path(inset_path_d).to_superpath()
|
|
self.inset_length = sum(
|
|
inkex.bezier.bezierlength((seg[1], seg[2], next_seg[0], next_seg[1]))
|
|
for subpath in inset_csp
|
|
for i, seg in enumerate(subpath[:-1])
|
|
for next_seg in [subpath[i + 1]]
|
|
)
|
|
|
|
kerf = self.svg.unittouu(f"{self.options.kerf}{self.options.units}")
|
|
tab_width = self.svg.unittouu(f"{self.options.tab_width}{self.options.units}") - kerf
|
|
tab_height = self.svg.unittouu(f"{self.options.material_thickness}{self.options.units}") - kerf
|
|
tab_start_offset = self.svg.unittouu(f"{self.options.tab_start_offset}{self.options.units}")
|
|
|
|
self.tabs = pattern_along_path(
|
|
inset_path_d,
|
|
self.options.num_tabs,
|
|
tab_width,
|
|
tab_start_offset,
|
|
"simple",
|
|
create_tab
|
|
)
|
|
for tab in self.tabs:
|
|
tab.style = self.CUT_INNER_STYLE
|
|
group.append(tab)
|
|
|
|
bottom_tabs_label = self.create_label("bottom tabs", group.bounding_box(), "bottom_tabs_label")
|
|
group.append(bottom_tabs_label)
|
|
self.bottom_inset = self.inset_path
|
|
self.bottom_tab_holes = self.tabs
|
|
|
|
def create_bottom_piece(self, offset_x):
|
|
bottom_group = Group(id=self.svg.get_unique_id("bottom"))
|
|
self.svg.get_current_layer().add(bottom_group)
|
|
|
|
bottom_path = PathElement()
|
|
bottom_path.set_id(self.svg.get_unique_id("bottom_path"))
|
|
bottom_path.set('d', str(self.original_path))
|
|
bottom_path.style = self.CUT_OUTER_STYLE
|
|
bottom_group.append(bottom_path)
|
|
|
|
bottom_inset = self.bottom_inset.copy()
|
|
bottom_inset.set_id(self.svg.get_unique_id("bottom_inset"))
|
|
bottom_group.append(bottom_inset)
|
|
|
|
for i, tab in enumerate(self.bottom_tab_holes):
|
|
bottom_tab = tab.copy()
|
|
bottom_tab.set_id(self.svg.get_unique_id(f"bottom_tab_{i}"))
|
|
bottom_tab.style = self.META_STYLE
|
|
bottom_group.append(bottom_tab)
|
|
|
|
bottom_bbox = bottom_group.bounding_box()
|
|
bottom_label = self.create_label("bottom", bottom_bbox, "bottom_label")
|
|
bottom_group.append(bottom_label)
|
|
|
|
bottom_group.transform = inkex.Transform(translate=(offset_x, 0))
|
|
|
|
def create_top_tabs_piece(self, offset_x):
|
|
top_tabs_group = Group(id=self.svg.get_unique_id("top_tabs"))
|
|
self.svg.get_current_layer().add(top_tabs_group)
|
|
|
|
top_tabs_original = PathElement()
|
|
top_tabs_original.set_id(self.svg.get_unique_id("top_tabs_original"))
|
|
top_tabs_original.set('d', str(self.original_path))
|
|
top_tabs_original.style = self.CUT_OUTER_STYLE
|
|
top_tabs_group.append(top_tabs_original)
|
|
|
|
top_tabs_inset = self.inset_path.copy()
|
|
top_tabs_inset.set_id(self.svg.get_unique_id("top_tabs_inset"))
|
|
top_tabs_group.append(top_tabs_inset)
|
|
|
|
for tab in self.tabs:
|
|
top_tab = tab.copy()
|
|
top_tab.set_id(self.svg.get_unique_id("top_tab"))
|
|
top_tabs_group.append(top_tab)
|
|
|
|
from offset import offset_path as create_offset_path
|
|
top_hole_inset_dist = -self.svg.unittouu(f"{self.options.top_hole_inset}{self.options.units}")
|
|
self.top_hole_inset = None
|
|
try:
|
|
top_hole_inset_d = create_offset_path(self.original_path, top_hole_inset_dist)
|
|
self.top_hole_inset = PathElement()
|
|
self.top_hole_inset.set_id(self.svg.get_unique_id("top_hole_inset"))
|
|
self.top_hole_inset.set('d', top_hole_inset_d)
|
|
self.top_hole_inset.style = self.CUT_OUTER_STYLE
|
|
top_tabs_group.append(self.top_hole_inset)
|
|
except ValueError:
|
|
pass
|
|
|
|
self.magnets = []
|
|
if self.options.magnet_type != "none":
|
|
num_magnets = self.options.num_magnets
|
|
magnet_placement_offset = self.svg.unittouu(f"{self.options.magnet_placement_offset}{self.options.units}")
|
|
inset_path_d = self.inset_path.get('d')
|
|
|
|
def create_magnet(index):
|
|
if self.options.magnet_type == "rectangle":
|
|
magnet_width = self.svg.unittouu(f"{self.options.rectangle_magnet_width}{self.options.units}")
|
|
magnet_height = self.svg.unittouu(f"{self.options.rectangle_magnet_height}{self.options.units}")
|
|
|
|
rect_x = -magnet_width / 2
|
|
rect_y = -magnet_height / 2
|
|
path_data = (
|
|
f"M {rect_x},{rect_y} "
|
|
f"L {rect_x + magnet_width},{rect_y} "
|
|
f"L {rect_x + magnet_width},{rect_y + magnet_height} "
|
|
f"L {rect_x},{rect_y + magnet_height} Z"
|
|
)
|
|
else:
|
|
magnet_diameter = self.svg.unittouu(f"{self.options.circle_magnet_diameter}{self.options.units}")
|
|
radius = magnet_diameter / 2
|
|
path_data = (
|
|
f"M {radius},0 "
|
|
f"A {radius},{radius} 0 0 1 0,{radius} "
|
|
f"A {radius},{radius} 0 0 1 {-radius},0 "
|
|
f"A {radius},{radius} 0 0 1 0,{-radius} "
|
|
f"A {radius},{radius} 0 0 1 {radius},0 Z"
|
|
)
|
|
|
|
magnet = PathElement()
|
|
magnet.set_id(self.svg.get_unique_id(f"magnet_{index}"))
|
|
magnet.set('d', path_data)
|
|
magnet.style = self.CUT_OUTER_STYLE if self.options.hide_magnets else self.META_STYLE
|
|
return magnet
|
|
|
|
if self.options.magnet_type == "rectangle":
|
|
item_width = self.svg.unittouu(f"{self.options.rectangle_magnet_width}{self.options.units}")
|
|
else:
|
|
item_width = self.svg.unittouu(f"{self.options.circle_magnet_diameter}{self.options.units}")
|
|
|
|
self.magnets = pattern_along_path(
|
|
inset_path_d,
|
|
num_magnets,
|
|
item_width,
|
|
magnet_placement_offset,
|
|
"even",
|
|
create_magnet
|
|
)
|
|
|
|
for magnet in self.magnets:
|
|
top_tabs_group.append(magnet)
|
|
|
|
top_tabs_bbox = top_tabs_group.bounding_box()
|
|
top_tabs_label = self.create_label("top tabs", top_tabs_bbox, "top_tabs_label")
|
|
top_tabs_group.append(top_tabs_label)
|
|
top_tabs_group.transform = inkex.Transform(translate=(offset_x, 0))
|
|
|
|
def create_top_piece(self, offset_x):
|
|
top_group = Group(id=self.svg.get_unique_id("top"))
|
|
self.svg.get_current_layer().add(top_group)
|
|
|
|
top_path = PathElement()
|
|
top_path.set_id(self.svg.get_unique_id("top_path"))
|
|
top_path.set('d', str(self.original_path))
|
|
top_path.style = self.CUT_OUTER_STYLE
|
|
top_group.append(top_path)
|
|
|
|
top_inset = self.inset_path.copy()
|
|
top_inset.set_id(self.svg.get_unique_id("top_inset"))
|
|
top_group.append(top_inset)
|
|
|
|
for tab in self.tabs:
|
|
top_tab = tab.copy()
|
|
top_tab.set_id(self.svg.get_unique_id("top_tab"))
|
|
top_tab.style = self.META_STYLE
|
|
top_group.append(top_tab)
|
|
|
|
if self.top_hole_inset is not None:
|
|
top_hole_inset_copy = self.top_hole_inset.copy()
|
|
top_hole_inset_copy.set_id(self.svg.get_unique_id("top_hole_inset"))
|
|
top_group.append(top_hole_inset_copy)
|
|
|
|
if len(self.magnets) > 0:
|
|
for i, magnet in enumerate(self.magnets):
|
|
magnet_copy = magnet.copy()
|
|
magnet_copy.set_id(self.svg.get_unique_id(f"top_magnet_{i}"))
|
|
magnet_copy.style = self.META_STYLE if self.options.hide_magnets else self.CUT_OUTER_STYLE
|
|
top_group.append(magnet_copy)
|
|
|
|
top_bbox = top_group.bounding_box()
|
|
top_label = self.create_label("top", top_bbox, "top_label")
|
|
top_group.append(top_label)
|
|
top_group.transform = inkex.Transform(translate=(offset_x, 0))
|
|
|
|
def create_side_piece(self, offset_x, offset_y, inset_path_d):
|
|
|
|
side_group = Group(id=self.svg.get_unique_id("side"))
|
|
self.svg.get_current_layer().add(side_group)
|
|
|
|
tab_width = self.svg.unittouu(f"{self.options.tab_width}{self.options.units}")
|
|
tab_height = self.svg.unittouu(f"{self.options.material_thickness}{self.options.units}")
|
|
tab_start_offset = self.svg.unittouu(f"{self.options.tab_start_offset}{self.options.units}")
|
|
num_tabs = self.options.num_tabs
|
|
|
|
rect_width = self.inset_length
|
|
rect_height = (self.svg.unittouu(f"{self.options.box_height}{self.options.units}") -
|
|
4 * self.svg.unittouu(f"{self.options.material_thickness}{self.options.units}"))
|
|
|
|
rect_path_data = f"M 0,0 L {rect_width},0 L {rect_width},{rect_height} L 0,{rect_height} Z"
|
|
|
|
side_rect = PathElement()
|
|
side_rect.set_id(self.svg.get_unique_id("side_rect"))
|
|
side_rect.set('d', rect_path_data)
|
|
side_rect.style = self.CUT_OUTER_STYLE
|
|
side_rect.transform = inkex.Transform(translate=(offset_x, offset_y))
|
|
side_group.append(side_rect)
|
|
|
|
corner_radius = self.svg.unittouu(f"{self.options.tab_border_radius}{self.options.units}")
|
|
total_tabs = num_tabs + 1
|
|
half_tab_width = tab_width / 2
|
|
|
|
full_tab_height = rect_height + 2 * tab_height
|
|
tab_y = -tab_height
|
|
|
|
tab_elements = []
|
|
|
|
for i in range(total_tabs):
|
|
if i == 0:
|
|
current_tab_width = half_tab_width
|
|
tab_x = 0
|
|
elif i == total_tabs - 1:
|
|
current_tab_width = half_tab_width
|
|
tab_x = rect_width - half_tab_width
|
|
else:
|
|
current_tab_width = tab_width
|
|
spacing = rect_width / num_tabs
|
|
tab_x = i * spacing - current_tab_width / 2
|
|
|
|
r = min(corner_radius, current_tab_width / 2, full_tab_height / 2)
|
|
tab_path = (
|
|
f"M {tab_x + r},{tab_y} "
|
|
f"L {tab_x + current_tab_width - r},{tab_y} "
|
|
f"A {r},{r} 0 0 1 {tab_x + current_tab_width},{tab_y + r} "
|
|
f"L {tab_x + current_tab_width},{tab_y + full_tab_height - r} "
|
|
f"A {r},{r} 0 0 1 {tab_x + current_tab_width - r},{tab_y + full_tab_height} "
|
|
f"L {tab_x + r},{tab_y + full_tab_height} "
|
|
f"A {r},{r} 0 0 1 {tab_x},{tab_y + full_tab_height - r} "
|
|
f"L {tab_x},{tab_y + r} "
|
|
f"A {r},{r} 0 0 1 {tab_x + r},{tab_y} "
|
|
f"Z"
|
|
)
|
|
|
|
tab_elem = PathElement()
|
|
tab_elem.set_id(self.svg.get_unique_id("side_tab"))
|
|
tab_elem.set('d', tab_path)
|
|
tab_elem.style = self.META_STYLE
|
|
tab_elem.transform = inkex.Transform(translate=(offset_x, offset_y))
|
|
side_group.append(tab_elem)
|
|
tab_elements.append(tab_elem)
|
|
|
|
boolean_lpe(self.svg, side_rect, tab_elements, operation="union")
|
|
|
|
path = inkex.Path(inset_path_d)
|
|
total_length = calculate_path_length(path)
|
|
|
|
gap = (total_length - num_tabs * tab_width) / num_tabs
|
|
first_tab_start = tab_start_offset % total_length
|
|
first_tab_center = (first_tab_start + tab_width / 2) % total_length
|
|
side_start_offset = first_tab_center
|
|
|
|
straight_segments = detect_straight_segments(inset_path_d, 20.0, self.svg, self.options.units)
|
|
|
|
adjusted_straight_segments = []
|
|
for seg_start, seg_end in straight_segments:
|
|
start_pos = (seg_start - side_start_offset) % total_length
|
|
end_pos = (seg_end - side_start_offset) % total_length
|
|
|
|
if start_pos < end_pos:
|
|
adjusted_straight_segments.append((start_pos, end_pos))
|
|
else:
|
|
adjusted_straight_segments.append((start_pos, total_length))
|
|
adjusted_straight_segments.append((0, end_pos))
|
|
|
|
hinge_regions = []
|
|
if not adjusted_straight_segments:
|
|
hinge_regions = [(0, rect_width)]
|
|
else:
|
|
adjusted_straight_segments = sorted(adjusted_straight_segments, key=lambda x: x[0])
|
|
|
|
current_pos = 0
|
|
for seg_start, seg_end in adjusted_straight_segments:
|
|
if current_pos < seg_start:
|
|
hinge_regions.append((current_pos, seg_start))
|
|
current_pos = seg_end
|
|
|
|
if current_pos < rect_width:
|
|
hinge_regions.append((current_pos, rect_width))
|
|
|
|
for i, (hinge_start, hinge_end) in enumerate(hinge_regions):
|
|
hinge_rect_data = f"M {hinge_start},0 L {hinge_end},0 L {hinge_end},{rect_height} L {hinge_start},{rect_height} Z"
|
|
|
|
hinge_rect = PathElement()
|
|
hinge_rect.set_id(self.svg.get_unique_id(f"hinge_rect_{i}"))
|
|
hinge_rect.set('d', hinge_rect_data)
|
|
hinge_rect.style = self.META_STYLE
|
|
hinge_rect.transform = inkex.Transform(translate=(offset_x, offset_y))
|
|
side_group.append(hinge_rect)
|
|
|
|
if self.options.generate_living_hinge:
|
|
hinge_width = hinge_end - hinge_start
|
|
hinge_length_param = rect_height * (self.options.hinge_length_percent / 100.0)
|
|
hinge_gap = self.svg.unittouu(f"{self.options.hinge_gap}{self.options.units}")
|
|
hinge_spacing = self.svg.unittouu(f"{self.options.hinge_spacing}{self.options.units}")
|
|
|
|
hinge_cuts = create_living_hinge_pattern(
|
|
self.svg,
|
|
hinge_length_param,
|
|
hinge_gap,
|
|
hinge_spacing,
|
|
hinge_width,
|
|
rect_height,
|
|
offset_x + hinge_start,
|
|
offset_y,
|
|
self.CUT_INNER_STYLE,
|
|
tab_positions=None,
|
|
segment_start=0
|
|
)
|
|
|
|
for hinge_cut in hinge_cuts:
|
|
side_group.append(hinge_cut)
|
|
|
|
side_bbox = side_group.bounding_box()
|
|
side_label = self.create_label("side", side_bbox, "side_label")
|
|
side_group.append(side_label)
|
|
|
|
self.side_group = side_group
|
|
|
|
def create_lid_top_piece(self, offset_x, offset_y):
|
|
lid_top_group = Group(id=self.svg.get_unique_id("lid_top"))
|
|
self.svg.get_current_layer().add(lid_top_group)
|
|
|
|
lid_top_path = PathElement()
|
|
lid_top_path.set_id(self.svg.get_unique_id("lid_top_path"))
|
|
lid_top_path.set('d', str(self.original_path))
|
|
lid_top_path.style = self.CUT_OUTER_STYLE
|
|
lid_top_group.append(lid_top_path)
|
|
|
|
lid_top_inset = self.inset_path.copy()
|
|
lid_top_inset.set_id(self.svg.get_unique_id("lid_top_inset"))
|
|
lid_top_group.append(lid_top_inset)
|
|
|
|
for i, magnet in enumerate(self.magnets):
|
|
lid_top_magnet = magnet.copy()
|
|
lid_top_magnet.set_id(self.svg.get_unique_id(f"lid_top_magnet_{i}"))
|
|
lid_top_magnet.style = self.META_STYLE
|
|
lid_top_group.append(lid_top_magnet)
|
|
|
|
lid_top_bbox_local = lid_top_group.bounding_box()
|
|
lid_top_label = self.create_label("lid top", lid_top_bbox_local, "lid_top_label")
|
|
lid_top_group.append(lid_top_label)
|
|
|
|
lid_top_group.transform = inkex.Transform(translate=(offset_x, offset_y))
|
|
|
|
return lid_top_group.bounding_box()
|
|
|
|
def create_lid_middle_piece(self, offset_x, offset_y):
|
|
lid_middle_group = Group(id=self.svg.get_unique_id("lid_middle"))
|
|
self.svg.get_current_layer().add(lid_middle_group)
|
|
|
|
lid_middle_path = PathElement()
|
|
lid_middle_path.set_id(self.svg.get_unique_id("lid_middle_path"))
|
|
lid_middle_path.set('d', str(self.original_path))
|
|
lid_middle_path.style = self.CUT_OUTER_STYLE
|
|
lid_middle_group.append(lid_middle_path)
|
|
|
|
lid_middle_inset = self.inset_path.copy()
|
|
lid_middle_inset.set_id(self.svg.get_unique_id("lid_middle_inset"))
|
|
lid_middle_group.append(lid_middle_inset)
|
|
|
|
for i, magnet in enumerate(self.magnets):
|
|
lid_middle_magnet = magnet.copy()
|
|
lid_middle_magnet.set_id(self.svg.get_unique_id(f"lid_middle_magnet_{i}"))
|
|
lid_middle_magnet.style = self.CUT_OUTER_STYLE if self.options.hide_magnets else self.META_STYLE
|
|
lid_middle_group.append(lid_middle_magnet)
|
|
|
|
lid_middle_bbox_local = lid_middle_group.bounding_box()
|
|
lid_middle_label = self.create_label("lid middle", lid_middle_bbox_local, "lid_middle_label")
|
|
lid_middle_group.append(lid_middle_label)
|
|
|
|
lid_middle_group.transform = inkex.Transform(translate=(offset_x, offset_y))
|
|
|
|
return lid_middle_group.bounding_box()
|
|
|
|
def create_lid_bottom_piece(self, offset_x, offset_y):
|
|
lid_bottom_group = Group(id=self.svg.get_unique_id("lid_bottom"))
|
|
self.svg.get_current_layer().add(lid_bottom_group)
|
|
|
|
lid_bottom_path = PathElement()
|
|
lid_bottom_path.set_id(self.svg.get_unique_id("lid_bottom_path"))
|
|
lid_bottom_path.set('d', str(self.original_path))
|
|
lid_bottom_path.style = self.CUT_OUTER_STYLE
|
|
lid_bottom_group.append(lid_bottom_path)
|
|
|
|
lid_bottom_inset = self.inset_path.copy()
|
|
lid_bottom_inset.set_id(self.svg.get_unique_id("lid_bottom_inset"))
|
|
lid_bottom_group.append(lid_bottom_inset)
|
|
|
|
for i, magnet in enumerate(self.magnets):
|
|
lid_bottom_magnet = magnet.copy()
|
|
lid_bottom_magnet.set_id(self.svg.get_unique_id(f"lid_bottom_magnet_{i}"))
|
|
lid_bottom_magnet.style = self.CUT_OUTER_STYLE if not self.options.hide_magnets else self.META_STYLE
|
|
lid_bottom_group.append(lid_bottom_magnet)
|
|
|
|
lid_bottom_bbox_local = lid_bottom_group.bounding_box()
|
|
lid_bottom_label = self.create_label("lid bottom", lid_bottom_bbox_local, "lid_bottom_label")
|
|
lid_bottom_group.append(lid_bottom_label)
|
|
|
|
lid_bottom_group.transform = inkex.Transform(translate=(offset_x, offset_y))
|
|
|
|
return lid_bottom_group.bounding_box()
|
|
|
|
def create_lid_fitting_piece(self, offset_x, offset_y):
|
|
lid_fitting_group = Group(id=self.svg.get_unique_id("lid_fitting"))
|
|
self.svg.get_current_layer().add(lid_fitting_group)
|
|
|
|
top_hole_inset = self.svg.unittouu(f"{self.options.top_hole_inset}{self.options.units}")
|
|
extra_inset = self.svg.unittouu("1mm")
|
|
lid_offset_dist = -(top_hole_inset + extra_inset)
|
|
|
|
try:
|
|
from offset import offset_path
|
|
lid_fitting_path_d = offset_path(self.original_path, lid_offset_dist)
|
|
lid_fitting_path = PathElement()
|
|
lid_fitting_path.set_id(self.svg.get_unique_id("lid_fitting_path"))
|
|
lid_fitting_path.set('d', lid_fitting_path_d)
|
|
lid_fitting_path.style = self.CUT_OUTER_STYLE
|
|
lid_fitting_group.append(lid_fitting_path)
|
|
|
|
lid_fitting_bbox_local = lid_fitting_group.bounding_box()
|
|
lid_fitting_label = self.create_label("lid fitting", lid_fitting_bbox_local, "lid_fitting_label")
|
|
lid_fitting_group.append(lid_fitting_label)
|
|
|
|
lid_fitting_group.transform = inkex.Transform(translate=(offset_x, offset_y))
|
|
|
|
return lid_fitting_group.bounding_box()
|
|
except ValueError:
|
|
return None
|
|
|
|
def effect(self):
|
|
if not self.svg.selection:
|
|
raise inkex.AbortExtension("Select a single path.")
|
|
|
|
selected_element = list(self.svg.selection.values())[0]
|
|
node = selected_element
|
|
|
|
if not isinstance(node, PathElement):
|
|
if hasattr(node, 'path') and node.path is not None:
|
|
path_element = PathElement()
|
|
path_element.path = node.path
|
|
path_element.style = node.style
|
|
path_element.transform = node.transform
|
|
node = path_element
|
|
else:
|
|
try:
|
|
node = node.to_path_element()
|
|
except Exception:
|
|
raise inkex.AbortExtension("Selection must be a path or convertible to a path.")
|
|
|
|
current_layer = self.svg.get_current_layer()
|
|
layer_transform = current_layer.composed_transform()
|
|
layer_transform_inv = -layer_transform
|
|
|
|
doc_path = node.path.to_absolute().transform(selected_element.composed_transform())
|
|
self.original_path = doc_path.transform(layer_transform_inv)
|
|
self.original_path_bbox = self.original_path.bounding_box()
|
|
|
|
selected_element.style = self.CUT_OUTER_STYLE
|
|
|
|
tab_height = self.svg.unittouu(f"{self.options.material_thickness}{self.options.units}")
|
|
|
|
try:
|
|
offset_dist = -self.svg.unittouu(f"{self.options.tab_inset}{self.options.units}")
|
|
inset_path_d = offset_path(self.original_path, offset_dist)
|
|
except ValueError as e:
|
|
raise inkex.AbortExtension(str(e))
|
|
|
|
self.create_bottom_tabs_piece(inset_path_d)
|
|
offset_x = self.original_path_bbox.width + self.svg.unittouu("2mm")
|
|
self.create_bottom_piece(offset_x)
|
|
self.create_top_tabs_piece(2 * offset_x)
|
|
self.create_top_piece(3 * offset_x)
|
|
|
|
offset_x = self.original_path_bbox.left
|
|
offset_y = self.original_path_bbox.bottom + self.svg.unittouu("2mm") + tab_height
|
|
self.create_side_piece(offset_x, offset_y, inset_path_d)
|
|
|
|
if self.options.generate_lid:
|
|
side_bbox_with_tabs = self.side_group.bounding_box()
|
|
lid_target_x = self.original_path_bbox.left
|
|
lid_target_y = side_bbox_with_tabs.bottom + self.svg.unittouu("2mm")
|
|
|
|
translate_x = lid_target_x - self.original_path_bbox.left
|
|
translate_y = lid_target_y - self.original_path_bbox.top
|
|
|
|
lid_top_bbox = self.create_lid_top_piece(translate_x, translate_y)
|
|
|
|
lid_middle_offset_x = lid_top_bbox.right + self.svg.unittouu("2mm") - self.original_path_bbox.left
|
|
lid_middle_bbox = self.create_lid_middle_piece(lid_middle_offset_x, translate_y)
|
|
|
|
lid_bottom_offset_x = lid_middle_bbox.right + self.svg.unittouu("2mm") - self.original_path_bbox.left
|
|
lid_bottom_bbox = self.create_lid_bottom_piece(lid_bottom_offset_x, translate_y)
|
|
|
|
lid_fitting_offset_x = lid_bottom_bbox.right + self.svg.unittouu("2mm") - self.original_path_bbox.left
|
|
self.create_lid_fitting_piece(lid_fitting_offset_x, translate_y)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
Boxbot().run()
|