#!/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 import box_core 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 ValueError as err: raise inkex.AbortExtension(str(err)) 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, } for panel in panels: path = PathElement() path.set_id(self.svg.get_unique_id("boite-")) path.label = panel.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 = panel.name group.transform = to_layer group.add(path) group.add(self._label(panel, 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, 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 = panel.name return text if __name__ == "__main__": BoxMaker().run()