#!/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 fr # docs/parametres.svg + .png (README)
python docs/schema_parametres.py en # docs/parameters_en.svg
# + images/parameters_en.png (onglet
# « Help » de la boite de dialogue)
Les formes sont calculees par hachures_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 hachures_core # noqa: E402,F401
W, H = 1400, 1280 # 4 panneaux de 700 x 640
INK = "#343a40" # element principal
GHOST = "#dde1e5" # elements secondaires / voisins
DIM = "#d9480f" # cotes
DIM2 = "#1971c2" # cotes secondaires
EDGE = "#212529" # contour des formes
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"]
# Version par langue : textes, fichiers produits, largeur du PNG et agrandissement
# des textes (la version anglaise est affichee reduite dans la boite de dialogue :
# 900 px de large, textes x 1.25 pour rester lisibles).
VERSIONS = {
"fr": {
"svg": os.path.join(HERE, "parametres.svg"),
"png": os.path.join(HERE, "parametres.png"),
"png_width": 1400,
"font_scale": 1.0,
"text": {
"title1": "1 Taux de remplissage",
"note1": ["Part de la surface qui devient noire : 100 % = traits jointifs.",
"Le pas entre les traits en est déduit."],
"title2": "2 Largeur du trait et pas",
"width": "largeur", "spacing": "pas",
"formula": "taux = largeur / pas",
"note2": ["Réglez la largeur sur celle du faisceau laser ;",
"plus le taux monte, plus le pas se resserre."],
"title3": "3 Angle des hachures",
"angle": "angle", "ref": "horizontale",
"note3": ["En degrés, sens anti-horaire (comme dans Inkscape) :",
"0 = horizontal, 90 = vertical."],
"title4": "4 Trajet optimisé de la tête laser",
"cut": "trait (laser allumé)", "travel": "déplacement à vide",
"note4": ["La fin d'un trait est proche du début du suivant :",
"la tête fait un serpentin, sans retour en arrière."],
},
},
"en": {
"svg": os.path.join(HERE, "parameters_en.svg"),
"png": os.path.join(ROOT, "images", "parameters_en.png"),
"png_width": 900,
"font_scale": 1.25,
"text": {
"title1": "1 Coverage",
"note1": ["Share of the area that turns black: 100 % = touching lines.",
"The spacing between lines follows from it."],
"title2": "2 Line width and spacing",
"width": "width", "spacing": "spacing",
"formula": "coverage = width / spacing",
"note2": ["Set the width to your laser beam width;",
"the higher the coverage, the tighter the spacing."],
"title3": "3 Hatch angle",
"angle": "angle", "ref": "horizontal",
"note3": ["In degrees, counter-clockwise (as in Inkscape):",
"0 = horizontal, 90 = vertical."],
"title4": "4 Optimized laser head path",
"cut": "line (laser on)", "travel": "idle move",
"note4": ["Each line ends close to where the next one starts:",
"the head zigzags, never travelling back."],
},
},
}
def _unit(angle):
"""Vecteur unitaire, angle en degres, sens anti-horaire a l'ecran (y vers le bas)."""
a = math.radians(angle)
return math.cos(a), -math.sin(a)
out = []
T = {}
FS = 1.0
def fmt(v):
return "{:.2f}".format(v).rstrip("0").rstrip(".")
def pts(polyline):
return " ".join("{},{}".format(fmt(x), fmt(y)) for x, y in polyline)
def polyline(points, color, width, extra=""):
out.append(''.format(pts(points), color, fmt(width), extra))
def motif(polylines, color, width):
for pl in polylines:
polyline(pl, color, width)
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 arc(center, radius, a0, a1, color, label=None, label_radius=None, size=16):
"""Arc de a0 a a1 degres (sens anti-horaire a l'ecran), fleche au bout."""
steps = 40
points = []
for k in range(steps + 1):
ux, uy = _unit(a0 + (a1 - a0) * k / steps)
points.append((center[0] + radius * ux, center[1] + radius * uy))
polyline(points[:-2], color, 1.6)
last, before = points[-1], points[-3]
d = math.hypot(last[0] - before[0], last[1] - before[1])
arrow_head(last, ((last[0] - before[0]) / d, (last[1] - before[1]) / d), color, 8)
if label:
ux, uy = _unit((a0 + a1) / 2)
r = label_radius or radius + 24
text(center[0] + r * ux, center[1] + r * uy + 6 * FS, label, size=size,
color=color, anchor="middle", weight="bold")
def circle_ring(cx, cy, r, n=180):
return [(cx + r * math.cos(2 * math.pi * k / n), cy + r * math.sin(2 * math.pi * k / n))
for k in range(n)]
def panel_title(x, y, _number, title):
text(x, y, title, size=22, weight="bold", halo=False)
# --------------------------------------------------------------------------
# Panneaux : un par parametre (ou groupe de parametres), numerotes 1 a 4.
# Dessiner avec les vraies fonctions de hachures_core, puis coter avec
# dimension() / arc() et legender avec note().
# --------------------------------------------------------------------------
PX = 3.0 # largeur de trait dessinee (px) pour les panneaux 1 et 3
def hatched(rings, density, angle, color=INK, width=PX):
spacing = hachures_core.spacing_from_density(density, width)
motif(hachures_core.hatch(rings, spacing, angle), color, width)
def outline(ring, color=EDGE):
polyline(ring + [ring[0]], color, 1.5)
def square(x, y, size):
return [(x, y), (x + size, y), (x + size, y + size), (x, y + size)]
def panel_1():
panel_title(30, 50, "", T["title1"])
for column, density in enumerate((10, 30, 60)):
x = 40 + column * 220
box = square(x, 100, 180)
hatched([box], density, 45)
outline(box)
disc = circle_ring(x + 90, 420, 85)
hatched([disc], density, 45)
outline(disc)
text(x + 90, 548, "{} %".format(density), size=22, color=DIM, anchor="middle",
weight="bold")
note(30, 610, T["note1"])
def panel_2():
panel_title(730, 50, "", T["title2"])
xs = [800 + 80 * i for i in range(6)]
for x in xs:
out.append(''.format(x - 8, INK))
out.append(''.format(xs[0] - 40, xs[-1] - xs[0] + 80, EDGE))
dimension((xs[1] - 8, 360), (xs[1] + 8, 360), 30, T["width"], DIM, label_shift=22)
dimension((xs[2], 360), (xs[3], 360), 70, T["spacing"], DIM2, label_shift=22)
text(1050, 520, T["formula"], size=24, color=INK, anchor="middle", weight="bold", halo=False)
note(730, 610, T["note2"])
def panel_3():
panel_title(30, 690, "", T["title3"])
box = square(70, 760, 340)
hatched([box], 30, 30)
outline(box)
c = (470, 1130)
ux, uy = _unit(30)
polyline([c, (c[0] + 170, c[1])], DIM2, 1.6, 'stroke-dasharray="6,5"')
polyline([c, (c[0] + 170 * ux, c[1] + 170 * uy)], DIM, 2.5)
arc(c, 110, 0, 30, DIM, T["angle"], 140)
text(c[0] + 170, c[1] + 24, T["ref"], size=16, color=DIM2, anchor="end", halo=False)
note(30, 1250, T["note3"])
def panel_4():
panel_title(730, 690, "", T["title4"])
ring = circle_ring(1030, 950, 150)
spacing = 25.0
lines = hachures_core.hatch([ring], spacing, 90)
polyline(ring + [ring[0]], GHOST, 1.5)
for (a0, a1), (b0, b1) in zip(lines, lines[1:]):
polyline([a1, b0], DIM, 1.8, 'stroke-dasharray="4,3"')
for first, last in lines:
polyline([first, last], INK, 3)
for number, (first, last) in enumerate(lines[:3], 1):
arrow_head(last, _norm(last, first), INK, 11)
text(first[0], first[1] + (-12 if first[1] < last[1] else 24),
str(number), size=18, color=INK, anchor="middle", weight="bold")
polyline([(1100, 1160), (1140, 1160)], INK, 3)
text(1150, 1166, T["cut"], size=16, color=INK, halo=False)
polyline([(1100, 1190), (1140, 1190)], DIM, 1.8, 'stroke-dasharray="4,3"')
text(1150, 1196, T["travel"], size=16, color=DIM, halo=False)
note(730, 1250, T["note4"])
def _norm(p, q):
"""Vecteur unitaire de q vers p."""
d = math.hypot(p[0] - q[0], p[1] - q[1]) or 1.0
return (p[0] - q[0]) / d, (p[1] - q[1]) / d
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(language):
global FS
version = VERSIONS[language]
out.clear()
T.clear()
T.update(version["text"])
FS = version["font_scale"]
out.append(''.format(W, H))
for x1, y1, x2, y2 in ((700, 20, 700, H - 20), (20, 640, W - 20, 640)):
out.append(''.format(x1, y1, x2, y2))
panel_1()
panel_2()
panel_3()
panel_4()
svg = ('\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):
languages = argv or sorted(VERSIONS)
for language in languages:
build(language)
if __name__ == "__main__":
main(sys.argv[1:])