#!/usr/bin/env python3 # coding=utf-8 """ Genere le schema explicatif des parametres de l'extension. python docs/schema_parametres.py # les deux versions python docs/schema_parametres.py readme # docs/parametres.svg + .png (README) python docs/schema_parametres.py dialogue # images/parametres.png (onglet # « Schéma » de la boite de dialogue) Les formes sont calculees par box_core (memes fonctions que l'extension), le schema reste donc fidele au resultat reel. L'export PNG passe par inkscape.com, qui attend la fin de l'export (contrairement au raccourci Chocolatey « inkscape »). """ import math import os import subprocess import sys HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.dirname(HERE) sys.path.insert(0, ROOT) import box_core # noqa: E402 W, H = 1400, 1080 # 4 panneaux de 700 x 540 INK = "#343a40" # element principal GHOST = "#adb5bd" # elements secondaires / voisins FILL = "#f1f3f5" # remplissage des pieces DIM = "#d9480f" # cotes DIM2 = "#1971c2" # cotes secondaires EDGE = "#212529" # contour des formes LASER = "#e03131" # trait de decoupe FONT = "font-family:Arial,Helvetica,sans-serif" INKSCAPE = [r"C:\Program Files\Inkscape\bin\inkscape.com", "/usr/bin/inkscape", "/Applications/Inkscape.app/Contents/MacOS/inkscape"] # L'extension n'existe qu'en francais : deux versions du meme schema. # La version « dialogue » est affichee reduite (900 px de large) dans la boite de # dialogue : textes x 1.25 pour rester lisibles. VERSIONS = { "readme": { "svg": os.path.join(HERE, "parametres.svg"), "png": os.path.join(HERE, "parametres.png"), "png_width": 1400, "font_scale": 1.0, }, "dialogue": { "svg": os.path.join(HERE, "parametres_dialogue.svg"), "png": os.path.join(ROOT, "images", "parametres.png"), "png_width": 900, "font_scale": 1.25, }, } out = [] FS = 1.0 def fmt(v): return "{:.2f}".format(v).rstrip("0").rstrip(".") def pts(points): return " ".join("{},{}".format(fmt(x), fmt(y)) for x, y in points) def polyline(points, color, width, extra=""): out.append(''.format(pts(points), color, fmt(width), extra)) def polygon(points, stroke, width, fill="none", extra=""): out.append(''.format(pts(points), fill, stroke, fmt(width), extra)) def text(x, y, s, size=17, color=INK, anchor="start", weight="normal", halo=True): style = "{};font-size:{}px;font-weight:{};fill:{}".format( FONT, fmt(size * FS), weight, color) if halo: style += ";paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round" out.append('{}'.format( fmt(x), fmt(y), anchor, style, s)) def note(x, y, lines): """Legende de bas de panneau, derniere ligne sur la ligne de base `y`.""" step = 22 * FS for k, line in enumerate(lines): text(x, y - (len(lines) - 1 - k) * step, line, size=16, halo=False) def arrow_head(tip, direction, color, size=9): ux, uy = direction nx, ny = -uy, ux base = (tip[0] - size * ux, tip[1] - size * uy) out.append(''.format(pts([ tip, (base[0] + 0.45 * size * nx, base[1] + 0.45 * size * ny), (base[0] - 0.45 * size * nx, base[1] - 0.45 * size * ny)]), color)) def dimension(p, q, offset, label, color=DIM, label_pos=0.5, label_shift=18, size=17): """Cote entre p et q, decalee de `offset` perpendiculairement.""" dx, dy = q[0] - p[0], q[1] - p[1] length = math.hypot(dx, dy) ux, uy = dx / length, dy / length nx, ny = -uy, ux a = (p[0] + offset * nx, p[1] + offset * ny) b = (q[0] + offset * nx, q[1] + offset * ny) sign = 1 if offset >= 0 else -1 for base, end in ((p, a), (q, b)): polyline([(base[0] + sign * 4 * nx, base[1] + sign * 4 * ny), (end[0] + sign * 6 * nx, end[1] + sign * 6 * ny)], color, 1, 'stroke-dasharray="3,3"') polyline([a, b], color, 1.6) arrow_head(a, (-ux, -uy), color) arrow_head(b, (ux, uy), color) lx = a[0] + label_pos * (b[0] - a[0]) + sign * label_shift * FS * nx ly = a[1] + label_pos * (b[1] - a[1]) + sign * label_shift * FS * ny + 6 * FS text(lx, ly, label, size=size, color=color, anchor="middle", weight="bold") def panel_title(x, y, number, title): text(x, y, "{} {}".format(number, title), size=22, weight="bold", halo=False) def place(points, x0, y0, scale): return [(x0 + x * scale, y0 + y * scale) for x, y in points] # -------------------------------------------------------------------------- # Panneaux # -------------------------------------------------------------------------- def panel_1(): """Longueur, largeur, hauteur : boite en perspective cavaliere.""" panel_title(30, 50, 1, "Dimensions de la boîte") s = 3.2 # px par mm length, width, height = 100, 80, 50 dx, dy = 0.5 * math.cos(math.radians(35)), -0.5 * math.sin(math.radians(35)) x0, y0 = 150, 365 # coin avant bas gauche def p(x, y, z): # x longueur, y profondeur, z hauteur return (x0 + (x + y * dx) * s, y0 + (y * dy - z) * s) L, P, Z = length, width, height # Faces visibles : avant, dessus (couvercle), droite polygon([p(0, 0, 0), p(L, 0, 0), p(L, 0, Z), p(0, 0, Z)], EDGE, 2, FILL) polygon([p(0, 0, Z), p(L, 0, Z), p(L, P, Z), p(0, P, Z)], EDGE, 2, "#e9ecef", 'stroke-dasharray="7,5"') polygon([p(L, 0, 0), p(L, P, 0), p(L, P, Z), p(L, 0, Z)], EDGE, 2, "#dee2e6") text(*p(L / 2, 0, Z / 2 - 3), "Avant", size=16, color=GHOST, anchor="middle", halo=False) text(*p(L / 2, P / 2, Z + 2), "Couvercle", size=16, color=GHOST, anchor="middle", halo=False) dimension(p(0, 0, 0), p(L, 0, 0), 30, "Longueur") dimension(p(L, 0, 0), p(L, P, 0), 34, "Largeur", label_shift=44) dimension(p(0, 0, 0), p(0, 0, Z), -30, "Hauteur", label_shift=48) note(30, 500, ["Couvercle (pointillés) : « Boîte fermée ». Décochée : 5 pièces,", "bord haut des côtés droit. Minimum : 5 × l'épaisseur."]) def panel_2(): """Epaisseur et dimensions interieures / exterieures : coupe verticale.""" ox = 700 panel_title(ox + 30, 50, 2, "Épaisseur et type de dimensions") s = 5.0 t, width, height = 6, 80, 44 # epaisseur exageree pour la lisibilite x0, y0 = ox + 150, 150 hatch = 'fill="url(#hachures)"' def rect(x, y, w, h): polygon(place([(x, y), (x + w, y), (x + w, y + h), (x, y + h)], x0, y0, s), EDGE, 1.6, "url(#hachures)") rect(0, 0, t, height) # cote gauche rect(width - t, 0, t, height) # cote droit rect(0, height - t, width, t) # fond del hatch text(x0 + width / 2 * s, y0 + (height - t / 2) * s + 6, "Fond", size=15, color=INK, anchor="middle") top = y0 dimension((x0, top), (x0 + width * s, top), -58, "Extérieure", label_shift=18) dimension((x0 + t * s, top + 60), (x0 + (width - t) * s, top + 60), 0, "Intérieure", color=DIM2) dimension((x0 + (width - t) * s, y0 + 12 * s), (x0 + width * s, y0 + 12 * s), 0, "", color=DIM) text(x0 + width * s + 16, y0 + 12 * s + 6, "Épaisseur", size=17, color=DIM, weight="bold") note(ox + 30, 500, ["Extérieures : cotes hors tout. Intérieures : l'extension ajoute", "2 épaisseurs (1 seule en hauteur si la boîte est ouverte)."]) def panel_3(): """Creneaux : bord male de l'avant et bord femelle du couvercle.""" oy = 540 panel_title(30, oy + 50, 3, "Largeur des créneaux") length, t = 100, 4 finger = 3 * t # valeur par defaut (parametre a 0) s = 5.6 x0 = 70 def edge(kind, y_top, depth): """Bord haut d'une piece de longueur `length`, corps de hauteur `depth` px.""" corner = 0 if kind == box_core.MALE else t pts_ = [(x0, y_top + depth), (x0, y_top + corner * s), (x0 + t * s, y_top + corner * s)] for a, b, level in box_core.edge_segments(length, t, finger, kind): pts_.append((x0 + a * s, y_top + level * s)) pts_.append((x0 + b * s, y_top + level * s)) pts_ += [(x0 + (length - t) * s, y_top + corner * s), (x0 + length * s, y_top + corner * s), (x0 + length * s, y_top + depth)] return pts_ # Couvercle (femelle) retourne au-dessus, l'avant (male) dessous lid_y, front_y = oy + 150, oy + 200 lid = [(x, 2 * lid_y - y) for x, y in edge(box_core.FEMALE, lid_y, 50)] polygon(lid, GHOST, 1.4, "#f8f9fa") polygon(edge(box_core.MALE, front_y, 150), EDGE, 2, FILL) text(x0 + length * s + 12, lid_y - 20, "Couvercle", size=15, color=GHOST) text(x0 + length * s + 12, front_y + 60, "Avant", size=15, color=GHOST) segs = box_core.edge_segments(length, t, finger, box_core.MALE) a, b, _ = segs[4] dimension((x0 + a * s, front_y), (x0 + b * s, front_y), 50, "Créneau", label_shift=20) c, d, _ = segs[1] mid = x0 + (c + d) / 2 * s dimension((mid, front_y), (mid, front_y + t * s), 0, "", color=DIM2) text(mid, front_y + t * s + 28, "Profondeur = épaisseur", size=15, color=DIM2, weight="bold", anchor="middle") note(30, oy + 500, ["0 = 3 × l'épaisseur. Largeur ajustée pour un nombre impair", "de créneaux par bord (ici {} sur l'avant).".format(len(segs))]) def panel_4(): """Kerf : contour dessine decale de kerf / 2 vers l'exterieur.""" ox, oy = 700, 540 panel_title(ox + 30, oy + 50, 4, "Compensation du kerf") t, finger, kerf = 4, 8, 1.2 # kerf exagere pour la lisibilite s = 18 x0, y0 = ox + 150, oy + 160 nominal = [(0, t), (6, t), (6, 0), (6 + finger, 0), (6 + finger, t), (20 + 2, t), (22, 13), (0, 13)] drawn = box_core.offset_orthogonal(nominal, kerf / 2) polygon(place(nominal, x0, y0, s), INK, 1.6, FILL, 'stroke-dasharray="6,4"') polygon(place(drawn, x0, y0, s), LASER, 2.2) k = kerf / 2 dimension((x0 + 6 * s, y0 + 0), (x0 + (6 + finger) * s, y0), 0, "") text(x0 + (6 + finger / 2) * s, y0 + 36, "Créneau voulu", size=15, color=DIM, anchor="middle", weight="bold") dimension((x0 + (6 - k) * s, y0 - k * s), (x0 + (6 + finger + k) * s, y0 - k * s), -40, "Dessiné : créneau + kerf", color=LASER, size=16) dimension((x0 + 22 * s, y0 + 6 * s), (x0 + (22 + k) * s, y0 + 6 * s), 0, "", color=DIM2) text(x0 + (22 + k) * s + 14, y0 + 6 * s + 6, "kerf / 2", size=16, color=DIM2, weight="bold") lx, ly = x0 + 10, y0 + 13 * s + 36 polyline([(lx, ly), (lx + 40, ly)], LASER, 2.2) text(lx + 50, ly + 6, "trait dessiné", size=15, halo=False) polyline([(lx + 190, ly), (lx + 230, ly)], INK, 1.6, 'stroke-dasharray="6,4"') text(lx + 240, ly + 6, "cote après découpe", size=15, halo=False) note(ox + 30, oy + 500, ["Le laser enlève kerf / 2 de chaque côté du trait : le contour", "est élargi d'autant. Défaut 0,1 mm ; 0 = pas de compensation."]) def export_png(svg_path, png_path, width): exe = next((path for path in INKSCAPE if os.path.exists(path)), None) if exe is None: print("Inkscape introuvable : exporter {} a la main".format(svg_path)) return os.makedirs(os.path.dirname(png_path), exist_ok=True) subprocess.run([exe, svg_path, "--export-type=png", "--export-width={}".format(width), "--export-filename={}".format(png_path)], check=True, stderr=subprocess.DEVNULL) print(png_path) def build(name): global FS version = VERSIONS[name] out.clear() FS = version["font_scale"] out.append('' '' '' ''.format(FILL, GHOST)) out.append(''.format(W, H)) for x1, y1, x2, y2 in ((700, 20, 700, H - 20), (20, 540, W - 20, 540)): out.append(''.format(x1, y1, x2, y2)) panel_1() panel_2() panel_3() panel_4() svg = ('\n' '\n{2}\n\n').format(W, H, "\n".join(out)) with open(version["svg"], "w", encoding="utf-8", newline="\n") as handle: handle.write(svg) print(version["svg"]) export_png(version["svg"], version["png"], version["png_width"]) def main(argv): for name in argv or sorted(VERSIONS): build(name) if __name__ == "__main__": main(sys.argv[1:])