149 lines
5.7 KiB
Python
149 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Extension Inkscape : génère une boîte à créneaux à découper au laser.
|
|
|
|
Chaque pièce est un objet indépendant, placé directement dans le calque courant,
|
|
pour pouvoir être sélectionnée et déplacée seule.
|
|
"""
|
|
|
|
import inkex
|
|
from inkex import Group, PathElement, TextElement
|
|
from inkex.localization import localize
|
|
|
|
import box_core
|
|
|
|
# Installe _() dans les builtins : catalogue « box_maker » de la langue de
|
|
# l'interface (Inkscape transmet domaine et dossier par l'environnement),
|
|
# textes anglais d'origine sinon.
|
|
localize()
|
|
|
|
|
|
def panel_names():
|
|
"""Noms affiches des pieces, indexes par leur nom dans box_core."""
|
|
return {
|
|
"Avant": _("Front"),
|
|
"Arrière": _("Back"),
|
|
"Gauche": _("Left"),
|
|
"Droite": _("Right"),
|
|
"Fond": _("Bottom"),
|
|
"Couvercle": _("Lid"),
|
|
}
|
|
|
|
|
|
class BoxMaker(inkex.EffectExtension):
|
|
def add_arguments(self, pars):
|
|
pars.add_argument("--tab")
|
|
pars.add_argument("--length", type=float, default=100.0)
|
|
pars.add_argument("--width", type=float, default=80.0)
|
|
pars.add_argument("--height", type=float, default=50.0)
|
|
pars.add_argument("--dim_type", default="exterior")
|
|
pars.add_argument("--thickness", type=float, default=3.0)
|
|
pars.add_argument("--finger", type=float, default=0.0)
|
|
pars.add_argument("--kerf", type=float, default=0.1)
|
|
pars.add_argument("--lid", type=inkex.Boolean, default=True)
|
|
pars.add_argument("--gap", type=float, default=5.0)
|
|
pars.add_argument("--stroke_color", type=inkex.Color, default=inkex.Color("red"))
|
|
pars.add_argument("--stroke_width", type=float, default=0.1)
|
|
pars.add_argument("--labels", type=inkex.Boolean, default=False)
|
|
|
|
def effect(self):
|
|
opt = self.options
|
|
try:
|
|
panels = box_core.make_box(
|
|
opt.length, opt.width, opt.height, opt.thickness,
|
|
finger=opt.finger, kerf=opt.kerf,
|
|
interior=opt.dim_type == "interior", lid=opt.lid, gap=opt.gap,
|
|
)
|
|
except box_core.DimensionTooSmall as err:
|
|
dimension = {
|
|
"length": _("length"),
|
|
"width": _("width"),
|
|
"height": _("height"),
|
|
}[err.dimension]
|
|
inkex.errormsg(_(
|
|
"The {dimension} ({size:.1f} mm exterior) is too small for the "
|
|
"thickness ({thickness:g} mm): it must be at least {minimum:g} mm."
|
|
).format(dimension=dimension, size=err.size, thickness=err.thickness,
|
|
minimum=err.minimum))
|
|
return
|
|
|
|
mm = self._uu_per_mm()
|
|
origin_x, origin_y = self._origin(panels, mm)
|
|
layer = self.svg.get_current_layer()
|
|
# Les coordonnées sont calculées dans le repère du document : on annule
|
|
# la transformation éventuelle du calque (et de ses parents).
|
|
to_layer = -layer.composed_transform()
|
|
style = {
|
|
"fill": "none",
|
|
"stroke": str(inkex.Color(opt.stroke_color).to_rgb()),
|
|
"stroke-width": opt.stroke_width * mm,
|
|
}
|
|
|
|
names = panel_names()
|
|
for panel in panels:
|
|
name = names[panel.name]
|
|
path = PathElement()
|
|
path.set_id(self.svg.get_unique_id("boite-"))
|
|
path.label = name
|
|
path.style = style
|
|
path.path = "M " + " L ".join(
|
|
f"{origin_x + x * mm:.4f},{origin_y + y * mm:.4f}" for x, y in panel.points
|
|
) + " Z"
|
|
|
|
if opt.labels:
|
|
group = Group()
|
|
group.set_id(self.svg.get_unique_id("boite-"))
|
|
group.label = name
|
|
group.transform = to_layer
|
|
group.add(path)
|
|
group.add(self._label(panel, name, origin_x, origin_y, mm))
|
|
layer.add(group)
|
|
else:
|
|
path.transform = to_layer
|
|
layer.add(path)
|
|
|
|
def _uu_per_mm(self):
|
|
"""Unités utilisateur par mm, comme Inkscape les affiche.
|
|
|
|
svg.unittouu() se trompe quand le viewBox n'a pas les proportions de la
|
|
page : Inkscape applique alors une échelle uniforme (preserveAspectRatio
|
|
« meet »), soit la plus petite des deux échelles page / viewBox.
|
|
"""
|
|
svg = self.svg
|
|
vb_w, vb_h = svg.viewbox_width, svg.viewbox_height
|
|
page_w = svg.to_dimensional(svg.viewport_width, "mm")
|
|
page_h = svg.to_dimensional(svg.viewport_height, "mm")
|
|
if not (vb_w and vb_h and page_w and page_h):
|
|
return svg.unittouu("1mm")
|
|
return 1 / min(page_w / vb_w, page_h / vb_h)
|
|
|
|
def _origin(self, panels, mm):
|
|
"""Centre l'ensemble des pièces sur la vue (ou la page à défaut)."""
|
|
max_x = max(p.bbox()[2] for p in panels) * mm
|
|
max_y = max(p.bbox()[3] for p in panels) * mm
|
|
namedview = self.svg.namedview
|
|
if namedview is not None and namedview.get("inkscape:cx") is not None:
|
|
cx, cy = namedview.center
|
|
else:
|
|
cx, cy = self.svg.get_page_bbox().center
|
|
return cx - max_x / 2, cy - max_y / 2
|
|
|
|
def _label(self, panel, name, origin_x, origin_y, mm):
|
|
min_x, min_y, max_x, max_y = panel.bbox()
|
|
size = min(5.0, (max_y - min_y) / 4) * mm
|
|
text = TextElement()
|
|
text.set_id(self.svg.get_unique_id("boite-"))
|
|
text.set("x", str(origin_x + (min_x + max_x) / 2 * mm))
|
|
text.set("y", str(origin_y + (min_y + max_y) / 2 * mm + size / 3))
|
|
text.style = {
|
|
"font-size": size,
|
|
"text-anchor": "middle",
|
|
"fill": "#0000ff",
|
|
"stroke": "none",
|
|
}
|
|
text.text = name
|
|
return text
|
|
|
|
|
|
if __name__ == "__main__":
|
|
BoxMaker().run()
|