i18n
This commit is contained in:
parent
5dc977f9dd
commit
41987f8ea8
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Cache Python et outils
|
||||||
|
__pycache__/*
|
||||||
|
*.pyc
|
||||||
|
.pytest_cache/
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# Archives de distribution
|
||||||
|
*.zip
|
||||||
|
|
||||||
|
# SVG intermediaires du schema (regeneres par docs/schema_parametres.py)
|
||||||
|
docs/*.svg
|
||||||
50
README.md
50
README.md
@ -39,6 +39,7 @@ Extension Inkscape 1.x qui génère le patron d'une boîte à découper au laser
|
|||||||
- Couleur et épaisseur du trait réglables (défaut : rouge, 0,1 mm)
|
- Couleur et épaisseur du trait réglables (défaut : rouge, 0,1 mm)
|
||||||
- Étiquettes optionnelles (texte bleu). Désactivées par défaut : le laser graverait le texte
|
- Étiquettes optionnelles (texte bleu). Désactivées par défaut : le laser graverait le texte
|
||||||
- Message d'erreur si une dimension est trop petite (minimum 5 × l'épaisseur en dimension extérieure)
|
- Message d'erreur si une dimension est trop petite (minimum 5 × l'épaisseur en dimension extérieure)
|
||||||
|
- Interface **en français et en anglais** selon la langue d'Inkscape : libellés, onglet **Aide**, noms des pièces et messages d'erreur. Le schéma de l'onglet **Schéma** / **Diagram** est en anglais (Inkscape ne traduit pas les images)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -49,14 +50,14 @@ Inkscape fermé, depuis le dossier du projet (PowerShell) :
|
|||||||
.\deploy.ps1 -Uninstall # désinstalle
|
.\deploy.ps1 -Uninstall # désinstalle
|
||||||
```
|
```
|
||||||
|
|
||||||
Le script copie `box_maker.inx`, `box_maker.py`, `box_core.py` et le dossier `images\` (schéma de l'onglet « Schéma »). Options : `-ExtensionsDir`, `-FolderName`, `-Force` (déploie même si Inkscape est ouvert). Redémarrer ensuite Inkscape.
|
Le script copie `box_maker.inx`, `box_maker.py`, `box_core.py`, `images\parameters_en.png` (schéma de la boîte de dialogue) et le dossier `locale\` (traductions compilées). Options : `-ExtensionsDir`, `-FolderName`, `-Force` (déploie même si Inkscape est ouvert). Redémarrer ensuite Inkscape.
|
||||||
|
|
||||||
Installation manuelle : copier ces mêmes fichiers et le dossier `images\` dans `%APPDATA%\inkscape\extensions\boxMaker\`.
|
Installation manuelle : copier ces mêmes fichiers et dossiers, en gardant l'arborescence, dans `%APPDATA%\inkscape\extensions\boxMaker\`.
|
||||||
|
|
||||||
## Utilisation
|
## Utilisation
|
||||||
|
|
||||||
1. Menu **Extensions > AlexDesign > Boîte à créneaux…**
|
1. Menu **Extensions > AlexDesign > Boîte à créneaux…** (en anglais : _Finger-Joint Box…_)
|
||||||
2. Régler les paramètres (illustrés dans l'onglet **Schéma**), cocher **Aperçu en direct** si besoin
|
2. Régler les paramètres (illustrés dans l'onglet **Schéma**, résumés dans l'onglet **Aide**), cocher **Aperçu en direct** si besoin
|
||||||
3. **Appliquer**
|
3. **Appliquer**
|
||||||
|
|
||||||
### En ligne de commande
|
### En ligne de commande
|
||||||
@ -133,39 +134,58 @@ Le kerf dépend de la matière : refaire la mesure en changeant de matériau ou
|
|||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── box_maker.inx # Interface de l'extension (paramètres, menu)
|
├── box_maker.inx # Interface de l'extension (textes source en anglais)
|
||||||
├── box_maker.py # Extension inkex : conversion en SVG, placement dans le calque
|
├── box_maker.py # Extension inkex : conversion en SVG, placement, textes traduits
|
||||||
├── box_core.py # Géométrie pure Python (mm) : créneaux, kerf, mise en page
|
├── box_core.py # Géométrie pure Python (mm) : créneaux, kerf, mise en page
|
||||||
|
├── i18n.py # Chaîne de traduction (extract / update / compile), Python pur
|
||||||
|
├── po/ # box_maker.pot, en.po, fr.po (seul fr.po se traduit à la main)
|
||||||
|
├── locale/ # Catalogues .mo compilés (générés, déployés)
|
||||||
├── deploy.ps1 # Installation / désinstallation dans Inkscape
|
├── deploy.ps1 # Installation / désinstallation dans Inkscape
|
||||||
├── images/
|
├── images/
|
||||||
│ └── parametres.png # Schéma de l'onglet « Schéma » (déployé)
|
│ └── parameters_en.png # Schéma de l'onglet « Diagram » (déployé)
|
||||||
├── docs/ # Illustrations du README (non déployées)
|
├── docs/ # Illustrations du README (non déployées)
|
||||||
│ ├── apercu-boite.png # Patron d'exemple
|
│ ├── apercu-boite.png # Patron d'exemple
|
||||||
│ ├── parametres.png # Schéma des paramètres (+ sources .svg)
|
│ ├── parametres.png # Schéma des paramètres en français
|
||||||
│ └── schema_parametres.py # Générateur des deux versions du schéma
|
│ └── schema_parametres.py # Générateur du schéma fr et en
|
||||||
└── tests/
|
└── tests/
|
||||||
└── test_box_core.py # Tests unitaires de la géométrie
|
├── test_box_core.py # Tests unitaires de la géométrie
|
||||||
|
└── test_extension.py # Traductions, cohérence .inx, bout en bout avec inkex
|
||||||
```
|
```
|
||||||
|
|
||||||
## Développement
|
## Développement
|
||||||
|
|
||||||
Environnement et tests (13 tests, géométrie pure, sans `inkex`) :
|
Environnement et tests :
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
.\.venv\Scripts\Activate.ps1
|
.\.venv\Scripts\Activate.ps1
|
||||||
pip install pytest
|
pip install pytest
|
||||||
python -m pytest -q
|
python -m pytest -q # 18 réussis, 3 ignorés (sans inkex)
|
||||||
|
|
||||||
|
$env:PYTHONPATH = "C:\Program Files\Inkscape\share\inkscape\extensions"
|
||||||
|
python -m pytest -q # 21 réussis (avec inkex)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Les tests couvrent la géométrie (`box_core`), les catalogues de traduction (complets, `.mo` à jour, champs de format conservés), la cohérence `.inx` ↔ `add_argument`, la présence des images de la boîte de dialogue et l'extension de bout en bout (6 pièces nommées, message d'erreur).
|
||||||
|
|
||||||
|
### Traductions
|
||||||
|
|
||||||
|
Les textes source (`.inx` et appels `_("...")` de `box_maker.py`) sont en anglais ; Inkscape les affiche traduits grâce à l'attribut `translationdomain="box_maker"` du `.inx`.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python i18n.py # extract + update + compile ; liste les chaînes à traduire
|
||||||
|
```
|
||||||
|
|
||||||
|
Après modification d'un texte : lancer `python i18n.py`, compléter les `msgstr` vides de `po/fr.po`, relancer `python i18n.py`, puis `.\deploy.ps1`. `po/en.po` se remplit tout seul ; ne jamais modifier les `.mo`. Pour une nouvelle langue : l'ajouter à `LANGUAGES` dans `i18n.py`, puis même démarche avec `po/<langue>.po`.
|
||||||
|
|
||||||
### Schéma des paramètres
|
### Schéma des paramètres
|
||||||
|
|
||||||
`docs/schema_parametres.py` dessine le schéma avec les fonctions de `box_core.py` (il reste fidèle au rendu réel) et l'exporte en PNG via `C:\Program Files\Inkscape\bin\inkscape.com` :
|
`docs/schema_parametres.py` dessine le schéma avec les fonctions de `box_core.py` (il reste fidèle au rendu réel) et l'exporte en PNG via `C:\Program Files\Inkscape\bin\inkscape.com` :
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
python docs/schema_parametres.py # les deux versions
|
python docs/schema_parametres.py # les deux versions
|
||||||
python docs/schema_parametres.py readme # docs/parametres.png, 1400 px
|
python docs/schema_parametres.py fr # docs/parametres.png, 1400 px (README)
|
||||||
python docs/schema_parametres.py dialogue # images/parametres.png, 900 px, textes agrandis
|
python docs/schema_parametres.py en # images/parameters_en.png, 900 px, textes agrandis (boîte de dialogue)
|
||||||
```
|
```
|
||||||
|
|
||||||
Relancer après toute modification de la géométrie ou des paramètres, puis `.\deploy.ps1`.
|
Relancer après toute modification de la géométrie ou des paramètres, puis `.\deploy.ps1`.
|
||||||
|
|||||||
28
box_core.py
28
box_core.py
@ -33,6 +33,27 @@ class Panel:
|
|||||||
return Panel(self.name, [(x + dx, y + dy) for x, y in self.points])
|
return Panel(self.name, [(x + dx, y + dy) for x, y in self.points])
|
||||||
|
|
||||||
|
|
||||||
|
_DIMENSION_NAMES = {"length": "longueur", "width": "largeur", "height": "hauteur"}
|
||||||
|
|
||||||
|
|
||||||
|
class DimensionTooSmall(ValueError):
|
||||||
|
"""Dimension exterieure trop petite pour l'epaisseur.
|
||||||
|
|
||||||
|
Les champs (dimension = "length" | "width" | "height", size, thickness,
|
||||||
|
minimum) permettent a l'extension de traduire le message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, dimension, size, thickness):
|
||||||
|
self.dimension = dimension
|
||||||
|
self.size = size
|
||||||
|
self.thickness = thickness
|
||||||
|
self.minimum = 5 * thickness
|
||||||
|
super().__init__(
|
||||||
|
f"La {_DIMENSION_NAMES[dimension]} ({size:.1f} mm extérieure) est trop petite "
|
||||||
|
f"pour l'épaisseur ({thickness:g} mm) : il faut au moins {self.minimum:g} mm."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def finger_count(middle, finger):
|
def finger_count(middle, finger):
|
||||||
"""Nombre impair (≥ 3) de créneaux sur une longueur utile `middle`."""
|
"""Nombre impair (≥ 3) de créneaux sur une longueur utile `middle`."""
|
||||||
n = max(1, round(middle / finger))
|
n = max(1, round(middle / finger))
|
||||||
@ -119,13 +140,10 @@ def box_panels(length, width, height, thickness, finger=0.0, interior=False, lid
|
|||||||
width += 2 * t
|
width += 2 * t
|
||||||
height += 2 * t if lid else t
|
height += 2 * t if lid else t
|
||||||
|
|
||||||
for label, size in (("longueur", length), ("largeur", width), ("hauteur", height)):
|
for dimension, size in (("length", length), ("width", width), ("height", height)):
|
||||||
middle = size - 2 * t
|
middle = size - 2 * t
|
||||||
if middle <= 0 or middle / finger_count(middle, finger) < t:
|
if middle <= 0 or middle / finger_count(middle, finger) < t:
|
||||||
raise ValueError(
|
raise DimensionTooSmall(dimension, size, t)
|
||||||
f"La {label} ({size:.1f} mm extérieure) est trop petite pour "
|
|
||||||
f"l'épaisseur ({t:g} mm) : il faut au moins {5 * t:g} mm."
|
|
||||||
)
|
|
||||||
|
|
||||||
top = MALE if lid else PLAIN
|
top = MALE if lid else PLAIN
|
||||||
front = (top, MALE, MALE, MALE)
|
front = (top, MALE, MALE, MALE)
|
||||||
|
|||||||
@ -1,39 +1,70 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
<!--
|
||||||
<name>Boîte à créneaux</name>
|
Textes source en anglais, traduits par le catalogue « box_maker »
|
||||||
|
(locale/<langue>/LC_MESSAGES/box_maker.mo). Après toute modification
|
||||||
|
d'un texte : python i18n.py, puis compléter po/fr.po et relancer.
|
||||||
|
-->
|
||||||
|
<inkscape-extension translationdomain="box_maker"
|
||||||
|
xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Finger-Joint Box</name>
|
||||||
<id>fr.alex.box_maker</id>
|
<id>fr.alex.box_maker</id>
|
||||||
|
|
||||||
<param name="tab" type="notebook">
|
<param name="tab" type="notebook">
|
||||||
<page name="dimensions" gui-text="Dimensions">
|
<page name="dimensions" gui-text="Dimensions">
|
||||||
<param name="length" type="float" min="5" max="5000" precision="1" gui-text="Longueur (mm) :">100</param>
|
<param name="length" type="float" min="5" max="5000" precision="1" gui-text="Length (mm):">100</param>
|
||||||
<param name="width" type="float" min="5" max="5000" precision="1" gui-text="Largeur (mm) :">80</param>
|
<param name="width" type="float" min="5" max="5000" precision="1" gui-text="Width (mm):">80</param>
|
||||||
<param name="height" type="float" min="5" max="5000" precision="1" gui-text="Hauteur (mm) :">50</param>
|
<param name="height" type="float" min="5" max="5000" precision="1" gui-text="Height (mm):">50</param>
|
||||||
<param name="dim_type" type="optiongroup" appearance="combo" gui-text="Les dimensions sont :">
|
<param name="dim_type" type="optiongroup" appearance="combo" gui-text="Dimensions are:">
|
||||||
<option value="exterior">Extérieures</option>
|
<option value="exterior">Exterior</option>
|
||||||
<option value="interior">Intérieures</option>
|
<option value="interior">Interior</option>
|
||||||
</param>
|
</param>
|
||||||
<param name="lid" type="bool" gui-text="Boîte fermée (avec couvercle)">true</param>
|
<param name="lid" type="bool" gui-text="Closed box (with lid)">true</param>
|
||||||
</page>
|
</page>
|
||||||
|
|
||||||
<page name="material" gui-text="Matière et créneaux">
|
<page name="material" gui-text="Material and fingers">
|
||||||
<param name="thickness" type="float" min="0.5" max="50" precision="2" gui-text="Épaisseur de la matière (mm) :">3</param>
|
<param name="thickness" type="float" min="0.5" max="50" precision="2" gui-text="Material thickness (mm):">3</param>
|
||||||
<param name="finger" type="float" min="0" max="500" precision="2" gui-text="Largeur des créneaux (mm, 0 = 3 × épaisseur) :">0</param>
|
<param name="finger" type="float" min="0" max="500" precision="2" gui-text="Finger width (mm, 0 = 3 × thickness):">0</param>
|
||||||
<param name="kerf" type="float" min="0" max="2" precision="3" gui-text="Compensation du trait laser, kerf (mm) :">0.1</param>
|
<param name="kerf" type="float" min="0" max="2" precision="3" gui-text="Laser beam compensation, kerf (mm):">0.1</param>
|
||||||
<label appearance="header">Remarques</label>
|
<label appearance="header">Notes</label>
|
||||||
<label>La largeur des créneaux est ajustée pour obtenir un nombre impair de créneaux sur chaque bord.</label>
|
<label>The finger width is adjusted to get an odd number of fingers on each edge.</label>
|
||||||
<label>Le kerf élargit les créneaux et rétrécit les encoches pour un assemblage serré.</label>
|
<label>The kerf widens the fingers and narrows the slots for a tight fit.</label>
|
||||||
</page>
|
</page>
|
||||||
|
|
||||||
<page name="drawing" gui-text="Tracé">
|
<page name="drawing" gui-text="Drawing">
|
||||||
<param name="gap" type="float" min="0" max="100" precision="1" gui-text="Espacement entre les pièces (mm) :">5</param>
|
<param name="gap" type="float" min="0" max="100" precision="1" gui-text="Spacing between parts (mm):">5</param>
|
||||||
<param name="stroke_width" type="float" min="0.001" max="5" precision="3" gui-text="Épaisseur du trait (mm) :">0.1</param>
|
<param name="stroke_width" type="float" min="0.001" max="5" precision="3" gui-text="Stroke width (mm):">0.1</param>
|
||||||
<param name="stroke_color" type="color" gui-text="Couleur du trait :">0xff0000ff</param>
|
<param name="stroke_color" type="color" gui-text="Stroke color:">0xff0000ff</param>
|
||||||
<param name="labels" type="bool" gui-text="Nommer les pièces (texte gravé !)">false</param>
|
<param name="labels" type="bool" gui-text="Name the parts (engraved text!)">false</param>
|
||||||
</page>
|
</page>
|
||||||
|
|
||||||
<!-- Image générée par docs/schema_parametres.py -->
|
<!-- Schéma généré par docs/schema_parametres.py, en anglais :
|
||||||
<page name="diagram" gui-text="Schéma">
|
Inkscape ne traduit pas le chemin d'une image. -->
|
||||||
<image width="900" height="694">images/parametres.png</image>
|
<page name="diagram" gui-text="Diagram">
|
||||||
|
<image width="900" height="694">images/parameters_en.png</image>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page name="help" gui-text="Help">
|
||||||
|
<label xml:space="preserve">Draws the pattern of a laser-cut box whose
|
||||||
|
panels interlock with straight finger joints.
|
||||||
|
No selection is needed: the parts are added
|
||||||
|
to the current layer, centered on the view.
|
||||||
|
|
||||||
|
Dimensions: exterior sizes, or interior sizes
|
||||||
|
(the thickness is then added). Unchecking
|
||||||
|
"Closed box" gives 5 parts, with a straight
|
||||||
|
top edge on the sides.
|
||||||
|
|
||||||
|
Fingers: 0 = 3 × the thickness. The width is
|
||||||
|
adjusted to get an odd number per edge.
|
||||||
|
|
||||||
|
Kerf: the outline is moved outwards by
|
||||||
|
kerf / 2 so that parts keep their nominal size
|
||||||
|
after cutting. Measure it: cut a 20 mm square,
|
||||||
|
kerf = 20 − measured size. Larger = tighter.
|
||||||
|
|
||||||
|
Each part is a separate closed path, named
|
||||||
|
(Front, Bottom…), that can be moved alone.
|
||||||
|
Labels are text: the laser would engrave them.</label>
|
||||||
</page>
|
</page>
|
||||||
</param>
|
</param>
|
||||||
|
|
||||||
|
|||||||
44
box_maker.py
44
box_maker.py
@ -7,9 +7,27 @@ pour pouvoir être sélectionnée et déplacée seule.
|
|||||||
|
|
||||||
import inkex
|
import inkex
|
||||||
from inkex import Group, PathElement, TextElement
|
from inkex import Group, PathElement, TextElement
|
||||||
|
from inkex.localization import localize
|
||||||
|
|
||||||
import box_core
|
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):
|
class BoxMaker(inkex.EffectExtension):
|
||||||
def add_arguments(self, pars):
|
def add_arguments(self, pars):
|
||||||
@ -35,8 +53,18 @@ class BoxMaker(inkex.EffectExtension):
|
|||||||
finger=opt.finger, kerf=opt.kerf,
|
finger=opt.finger, kerf=opt.kerf,
|
||||||
interior=opt.dim_type == "interior", lid=opt.lid, gap=opt.gap,
|
interior=opt.dim_type == "interior", lid=opt.lid, gap=opt.gap,
|
||||||
)
|
)
|
||||||
except ValueError as err:
|
except box_core.DimensionTooSmall as err:
|
||||||
raise inkex.AbortExtension(str(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()
|
mm = self._uu_per_mm()
|
||||||
origin_x, origin_y = self._origin(panels, mm)
|
origin_x, origin_y = self._origin(panels, mm)
|
||||||
@ -50,10 +78,12 @@ class BoxMaker(inkex.EffectExtension):
|
|||||||
"stroke-width": opt.stroke_width * mm,
|
"stroke-width": opt.stroke_width * mm,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
names = panel_names()
|
||||||
for panel in panels:
|
for panel in panels:
|
||||||
|
name = names[panel.name]
|
||||||
path = PathElement()
|
path = PathElement()
|
||||||
path.set_id(self.svg.get_unique_id("boite-"))
|
path.set_id(self.svg.get_unique_id("boite-"))
|
||||||
path.label = panel.name
|
path.label = name
|
||||||
path.style = style
|
path.style = style
|
||||||
path.path = "M " + " L ".join(
|
path.path = "M " + " L ".join(
|
||||||
f"{origin_x + x * mm:.4f},{origin_y + y * mm:.4f}" for x, y in panel.points
|
f"{origin_x + x * mm:.4f},{origin_y + y * mm:.4f}" for x, y in panel.points
|
||||||
@ -62,10 +92,10 @@ class BoxMaker(inkex.EffectExtension):
|
|||||||
if opt.labels:
|
if opt.labels:
|
||||||
group = Group()
|
group = Group()
|
||||||
group.set_id(self.svg.get_unique_id("boite-"))
|
group.set_id(self.svg.get_unique_id("boite-"))
|
||||||
group.label = panel.name
|
group.label = name
|
||||||
group.transform = to_layer
|
group.transform = to_layer
|
||||||
group.add(path)
|
group.add(path)
|
||||||
group.add(self._label(panel, origin_x, origin_y, mm))
|
group.add(self._label(panel, name, origin_x, origin_y, mm))
|
||||||
layer.add(group)
|
layer.add(group)
|
||||||
else:
|
else:
|
||||||
path.transform = to_layer
|
path.transform = to_layer
|
||||||
@ -97,7 +127,7 @@ class BoxMaker(inkex.EffectExtension):
|
|||||||
cx, cy = self.svg.get_page_bbox().center
|
cx, cy = self.svg.get_page_bbox().center
|
||||||
return cx - max_x / 2, cy - max_y / 2
|
return cx - max_x / 2, cy - max_y / 2
|
||||||
|
|
||||||
def _label(self, panel, origin_x, origin_y, mm):
|
def _label(self, panel, name, origin_x, origin_y, mm):
|
||||||
min_x, min_y, max_x, max_y = panel.bbox()
|
min_x, min_y, max_x, max_y = panel.bbox()
|
||||||
size = min(5.0, (max_y - min_y) / 4) * mm
|
size = min(5.0, (max_y - min_y) / 4) * mm
|
||||||
text = TextElement()
|
text = TextElement()
|
||||||
@ -110,7 +140,7 @@ class BoxMaker(inkex.EffectExtension):
|
|||||||
"fill": "#0000ff",
|
"fill": "#0000ff",
|
||||||
"stroke": "none",
|
"stroke": "none",
|
||||||
}
|
}
|
||||||
text.text = panel.name
|
text.text = name
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
32
deploy.ps1
32
deploy.ps1
@ -1,10 +1,11 @@
|
|||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Déploie l'extension « Boîte à créneaux » dans Inkscape.
|
Déploie l'extension « Boîte à créneaux » dans Inkscape.
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Copie box_maker.inx, box_maker.py, box_core.py et images dans un
|
Copie box_maker.inx, box_maker.py, box_core.py, le schéma de l'onglet
|
||||||
sous-dossier dédié du répertoire des extensions utilisateur d'Inkscape
|
« Diagram » (images\parameters_en.png) et les traductions compilées
|
||||||
|
(locale\<langue>\LC_MESSAGES\box_maker.mo) dans un sous-dossier dédié du répertoire des extensions utilisateur d'Inkscape
|
||||||
(Inkscape explore récursivement ce répertoire). Refuse de s'exécuter si
|
(Inkscape explore récursivement ce répertoire). Refuse de s'exécuter si
|
||||||
Inkscape est ouvert : les extensions ne sont chargées qu'au démarrage.
|
Inkscape est ouvert : les extensions ne sont chargées qu'au démarrage.
|
||||||
|
|
||||||
@ -36,7 +37,8 @@ param(
|
|||||||
|
|
||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
$files = @('box_maker.inx', 'box_maker.py', 'box_core.py')
|
$files = @('box_maker.inx', 'box_maker.py', 'box_core.py', 'images\parameters_en.png')
|
||||||
|
$localeDir = 'locale'
|
||||||
$source = $PSScriptRoot
|
$source = $PSScriptRoot
|
||||||
$destination = Join-Path $ExtensionsDir $FolderName
|
$destination = Join-Path $ExtensionsDir $FolderName
|
||||||
|
|
||||||
@ -69,6 +71,9 @@ if ($Uninstall) {
|
|||||||
|
|
||||||
# --- Vérification des sources ------------------------------------------------
|
# --- Vérification des sources ------------------------------------------------
|
||||||
$missing = $files | Where-Object { -not (Test-Path -LiteralPath (Join-Path $source $_)) }
|
$missing = $files | Where-Object { -not (Test-Path -LiteralPath (Join-Path $source $_)) }
|
||||||
|
if (-not (Test-Path -LiteralPath (Join-Path $source $localeDir))) {
|
||||||
|
$missing = @($missing) + "$localeDir\ (lancer : python i18n.py)"
|
||||||
|
}
|
||||||
if ($missing) {
|
if ($missing) {
|
||||||
Write-Error "Fichier(s) introuvable(s) dans $source : $($missing -join ', ')"
|
Write-Error "Fichier(s) introuvable(s) dans $source : $($missing -join ', ')"
|
||||||
exit 1
|
exit 1
|
||||||
@ -89,20 +94,29 @@ if (-not (Test-Path -LiteralPath $destination)) {
|
|||||||
Write-Host "Dossier créé : $destination"
|
Write-Host "Dossier créé : $destination"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Images : le dossier est recréé (une image renommée ne reste pas en double)
|
||||||
|
$imagesTo = Join-Path $destination 'images'
|
||||||
|
if (Test-Path -LiteralPath $imagesTo) {
|
||||||
|
Remove-Item -LiteralPath $imagesTo -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($file in $files) {
|
foreach ($file in $files) {
|
||||||
$from = Join-Path $source $file
|
$from = Join-Path $source $file
|
||||||
$to = Join-Path $destination $file
|
$to = Join-Path $destination $file
|
||||||
$action = if (Test-Path -LiteralPath $to) { 'remplacé' } else { 'copié ' }
|
$action = if (Test-Path -LiteralPath $to) { 'remplacé' } else { 'copié ' }
|
||||||
|
New-Item -ItemType Directory -Path (Split-Path $to) -Force | Out-Null
|
||||||
Copy-Item -LiteralPath $from -Destination $to -Force
|
Copy-Item -LiteralPath $from -Destination $to -Force
|
||||||
Write-Host " $action $file"
|
Write-Host " $action $file"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Schéma affiché dans l'onglet « Schéma » de la boîte de dialogue
|
# Traductions : le dossier est remplacé en entier (langue retirée => supprimée)
|
||||||
$images = Join-Path $source 'images'
|
$localeTo = Join-Path $destination $localeDir
|
||||||
if (Test-Path -LiteralPath $images) {
|
if (Test-Path -LiteralPath $localeTo) {
|
||||||
Copy-Item -LiteralPath $images -Destination $destination -Recurse -Force
|
Remove-Item -LiteralPath $localeTo -Recurse -Force
|
||||||
Write-Host " copié images\"
|
|
||||||
}
|
}
|
||||||
|
Copy-Item -LiteralPath (Join-Path $source $localeDir) -Destination $localeTo -Recurse
|
||||||
|
$languages = Get-ChildItem -LiteralPath $localeTo -Directory | ForEach-Object Name
|
||||||
|
Write-Host " copié $localeDir\ ($($languages -join ', '))"
|
||||||
|
|
||||||
# Le cache Python d'une version précédente peut masquer les fichiers copiés
|
# Le cache Python d'une version précédente peut masquer les fichiers copiés
|
||||||
$pycache = Join-Path $destination '__pycache__'
|
$pycache = Join-Path $destination '__pycache__'
|
||||||
|
|||||||
@ -1,108 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1400" height="1080" viewBox="0 0 1400 1080">
|
|
||||||
<defs><pattern id="hachures" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="8" height="8" fill="#f1f3f5"/><line x1="0" y1="0" x2="0" y2="8" stroke="#adb5bd" stroke-width="1.5"/></pattern></defs>
|
|
||||||
<rect width="1400" height="1080" fill="#ffffff"/>
|
|
||||||
<line x1="700" y1="20" x2="700" y2="1060" stroke="#dee2e6" stroke-width="1.5"/>
|
|
||||||
<line x1="20" y1="540" x2="1380" y2="540" stroke="#dee2e6" stroke-width="1.5"/>
|
|
||||||
<text x="30" y="50" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:27.5px;font-weight:bold;fill:#343a40">1 Dimensions de la boîte</text>
|
|
||||||
<polygon points="150,365 470,365 470,205 150,205" fill="#f1f3f5" stroke="#212529" stroke-width="2" stroke-linejoin="miter" />
|
|
||||||
<polygon points="150,205 470,205 574.85,131.58 254.85,131.58" fill="#e9ecef" stroke="#212529" stroke-width="2" stroke-linejoin="miter" stroke-dasharray="7,5"/>
|
|
||||||
<polygon points="470,365 574.85,291.58 574.85,131.58 470,205" fill="#dee2e6" stroke="#212529" stroke-width="2" stroke-linejoin="miter" />
|
|
||||||
<text x="310" y="294.6" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#adb5bd">Avant</text>
|
|
||||||
<text x="362.43" y="161.89" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#adb5bd">Couvercle</text>
|
|
||||||
<polyline points="150,369 150,401" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="470,369 470,401" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="150,395 470,395" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="150,395 159,390.95 159,399.05" fill="#d9480f"/>
|
|
||||||
<polygon points="470,395 461,399.05 461,390.95" fill="#d9480f"/>
|
|
||||||
<text x="310" y="425" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Longueur</text>
|
|
||||||
<polyline points="472.29,368.28 492.94,397.77" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="577.15,294.86 597.79,324.35" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="489.5,392.85 594.35,319.43" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="489.5,392.85 494.55,384.37 499.2,391.01" fill="#d9480f"/>
|
|
||||||
<polygon points="594.35,319.43 589.3,327.91 584.66,321.28" fill="#d9480f"/>
|
|
||||||
<text x="573.47" y="408.7" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Largeur</text>
|
|
||||||
<polyline points="146,365 114,365" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="146,205 114,205" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="120,365 120,205" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="120,365 115.95,356 124.05,356" fill="#d9480f"/>
|
|
||||||
<polygon points="120,205 124.05,214 115.95,214" fill="#d9480f"/>
|
|
||||||
<text x="60" y="292.5" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Hauteur</text>
|
|
||||||
<text x="30" y="472.5" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">Couvercle (pointillés) : « Boîte fermée ». Décochée : 5 pièces,</text>
|
|
||||||
<text x="30" y="500" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">bord haut des côtés droit. Minimum : 5 × l'épaisseur.</text>
|
|
||||||
<text x="730" y="50" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:27.5px;font-weight:bold;fill:#343a40">2 Épaisseur et type de dimensions</text>
|
|
||||||
<polygon points="850,150 880,150 880,370 850,370" fill="url(#hachures)" stroke="#212529" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="1220,150 1250,150 1250,370 1220,370" fill="url(#hachures)" stroke="#212529" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="850,340 1250,340 1250,370 850,370" fill="url(#hachures)" stroke="#212529" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<text x="1050" y="361" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:normal;fill:#343a40;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Fond</text>
|
|
||||||
<polyline points="850,146 850,86" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="1250,146 1250,86" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="850,92 1250,92" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="850,92 859,87.95 859,96.05" fill="#d9480f"/>
|
|
||||||
<polygon points="1250,92 1241,96.05 1241,87.95" fill="#d9480f"/>
|
|
||||||
<text x="1050" y="77" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Extérieure</text>
|
|
||||||
<polyline points="880,214 880,216" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="1220,214 1220,216" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="880,210 1220,210" fill="none" stroke="#1971c2" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="880,210 889,205.95 889,214.05" fill="#1971c2"/>
|
|
||||||
<polygon points="1220,210 1211,214.05 1211,205.95" fill="#1971c2"/>
|
|
||||||
<text x="1050" y="240" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#1971c2;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Intérieure</text>
|
|
||||||
<polyline points="1220,214 1220,216" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="1250,214 1250,216" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="1220,210 1250,210" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="1220,210 1229,205.95 1229,214.05" fill="#d9480f"/>
|
|
||||||
<polygon points="1250,210 1241,214.05 1241,205.95" fill="#d9480f"/>
|
|
||||||
<text x="1235" y="240" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round"></text>
|
|
||||||
<text x="1266" y="216" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Épaisseur</text>
|
|
||||||
<text x="730" y="472.5" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">Extérieures : cotes hors tout. Intérieures : l'extension ajoute</text>
|
|
||||||
<text x="730" y="500" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">2 épaisseurs (1 seule en hauteur si la boîte est ouverte).</text>
|
|
||||||
<text x="30" y="590" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:27.5px;font-weight:bold;fill:#343a40">3 Largeur des créneaux</text>
|
|
||||||
<polygon points="70,640 70,667.6 92.4,667.6 92.4,667.6 166,667.6 166,690 239.6,690 239.6,667.6 313.2,667.6 313.2,690 386.8,690 386.8,667.6 460.4,667.6 460.4,690 534,690 534,667.6 607.6,667.6 607.6,667.6 630,667.6 630,640" fill="#f8f9fa" stroke="#adb5bd" stroke-width="1.4" stroke-linejoin="miter" />
|
|
||||||
<polygon points="70,890 70,740 92.4,740 92.4,740 166,740 166,762.4 239.6,762.4 239.6,740 313.2,740 313.2,762.4 386.8,762.4 386.8,740 460.4,740 460.4,762.4 534,762.4 534,740 607.6,740 607.6,740 630,740 630,890" fill="#f1f3f5" stroke="#212529" stroke-width="2" stroke-linejoin="miter" />
|
|
||||||
<text x="642" y="670" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:normal;fill:#adb5bd;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Couvercle</text>
|
|
||||||
<text x="642" y="800" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:normal;fill:#adb5bd;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Avant</text>
|
|
||||||
<polyline points="386.8,744 386.8,796" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="460.4,744 460.4,796" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="386.8,790 460.4,790" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="386.8,790 395.8,785.95 395.8,794.05" fill="#d9480f"/>
|
|
||||||
<polygon points="460.4,790 451.4,794.05 451.4,785.95" fill="#d9480f"/>
|
|
||||||
<text x="423.6" y="822.5" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Créneau</text>
|
|
||||||
<polyline points="198.8,740 196.8,740" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="198.8,762.4 196.8,762.4" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="202.8,740 202.8,762.4" fill="none" stroke="#1971c2" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="202.8,740 206.85,749 198.75,749" fill="#1971c2"/>
|
|
||||||
<polygon points="202.8,762.4 198.75,753.4 206.85,753.4" fill="#1971c2"/>
|
|
||||||
<text x="180.3" y="758.7" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#1971c2;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round"></text>
|
|
||||||
<text x="202.8" y="790.4" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:bold;fill:#1971c2;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Profondeur = épaisseur</text>
|
|
||||||
<text x="30" y="1012.5" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">0 = 3 × l'épaisseur. Largeur ajustée pour un nombre impair</text>
|
|
||||||
<text x="30" y="1040" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">de créneaux par bord (ici 7 sur l'avant).</text>
|
|
||||||
<text x="730" y="590" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:27.5px;font-weight:bold;fill:#343a40">4 Compensation du kerf</text>
|
|
||||||
<polygon points="850,772 958,772 958,700 1102,700 1102,772 1246,772 1246,934 850,934" fill="#f1f3f5" stroke="#343a40" stroke-width="1.6" stroke-linejoin="miter" stroke-dasharray="6,4"/>
|
|
||||||
<polygon points="839.2,761.2 947.2,761.2 947.2,689.2 1112.8,689.2 1112.8,761.2 1256.8,761.2 1256.8,944.8 839.2,944.8" fill="none" stroke="#e03131" stroke-width="2.2" stroke-linejoin="miter" />
|
|
||||||
<polyline points="958,704 958,706" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="1102,704 1102,706" fill="none" stroke="#d9480f" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="958,700 1102,700" fill="none" stroke="#d9480f" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="958,700 967,695.95 967,704.05" fill="#d9480f"/>
|
|
||||||
<polygon points="1102,700 1093,704.05 1093,695.95" fill="#d9480f"/>
|
|
||||||
<text x="1030" y="730" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round"></text>
|
|
||||||
<text x="1030" y="736" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:bold;fill:#d9480f;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Créneau voulu</text>
|
|
||||||
<polyline points="947.2,685.2 947.2,643.2" fill="none" stroke="#e03131" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="1112.8,685.2 1112.8,643.2" fill="none" stroke="#e03131" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="947.2,649.2 1112.8,649.2" fill="none" stroke="#e03131" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="947.2,649.2 956.2,645.15 956.2,653.25" fill="#e03131"/>
|
|
||||||
<polygon points="1112.8,649.2 1103.8,653.25 1103.8,645.15" fill="#e03131"/>
|
|
||||||
<text x="1030" y="634.2" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:bold;fill:#e03131;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">Dessiné : créneau + kerf</text>
|
|
||||||
<polyline points="1246,812 1246,814" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="1256.8,812 1256.8,814" fill="none" stroke="#1971c2" stroke-width="1" stroke-linejoin="miter" stroke-dasharray="3,3"/>
|
|
||||||
<polyline points="1246,808 1256.8,808" fill="none" stroke="#1971c2" stroke-width="1.6" stroke-linejoin="miter" />
|
|
||||||
<polygon points="1246,808 1255,803.95 1255,812.05" fill="#1971c2"/>
|
|
||||||
<polygon points="1256.8,808 1247.8,812.05 1247.8,803.95" fill="#1971c2"/>
|
|
||||||
<text x="1251.4" y="838" text-anchor="middle" style="font-family:Arial,Helvetica,sans-serif;font-size:21.25px;font-weight:bold;fill:#1971c2;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round"></text>
|
|
||||||
<text x="1270.8" y="814" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:bold;fill:#1971c2;paint-order:stroke;stroke:#ffffff;stroke-width:5px;stroke-linejoin:round">kerf / 2</text>
|
|
||||||
<polyline points="860,970 900,970" fill="none" stroke="#e03131" stroke-width="2.2" stroke-linejoin="miter" />
|
|
||||||
<text x="910" y="976" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:normal;fill:#343a40">trait dessiné</text>
|
|
||||||
<polyline points="1050,970 1090,970" fill="none" stroke="#343a40" stroke-width="1.6" stroke-linejoin="miter" stroke-dasharray="6,4"/>
|
|
||||||
<text x="1100" y="976" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:18.75px;font-weight:normal;fill:#343a40">cote après découpe</text>
|
|
||||||
<text x="730" y="1012.5" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">Le laser enlève kerf / 2 de chaque côté du trait : le contour</text>
|
|
||||||
<text x="730" y="1040" text-anchor="start" style="font-family:Arial,Helvetica,sans-serif;font-size:20px;font-weight:normal;fill:#343a40">est élargi d'autant. Défaut 0,1 mm ; 0 = pas de compensation.</text>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 15 KiB |
@ -3,10 +3,11 @@
|
|||||||
"""
|
"""
|
||||||
Genere le schema explicatif des parametres de l'extension.
|
Genere le schema explicatif des parametres de l'extension.
|
||||||
|
|
||||||
python docs/schema_parametres.py # les deux versions
|
python docs/schema_parametres.py # les deux versions
|
||||||
python docs/schema_parametres.py readme # docs/parametres.svg + .png (README)
|
python docs/schema_parametres.py fr # docs/parametres.svg + .png (README)
|
||||||
python docs/schema_parametres.py dialogue # images/parametres.png (onglet
|
python docs/schema_parametres.py en # docs/parameters_en.svg
|
||||||
# « Schéma » de la boite de dialogue)
|
# + images/parameters_en.png (onglet
|
||||||
|
# « Diagram » de la boite de dialogue)
|
||||||
|
|
||||||
Les formes sont calculees par box_core (memes fonctions que l'extension),
|
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
|
le schema reste donc fidele au resultat reel. L'export PNG passe par
|
||||||
@ -39,25 +40,85 @@ FONT = "font-family:Arial,Helvetica,sans-serif"
|
|||||||
INKSCAPE = [r"C:\Program Files\Inkscape\bin\inkscape.com", "/usr/bin/inkscape",
|
INKSCAPE = [r"C:\Program Files\Inkscape\bin\inkscape.com", "/usr/bin/inkscape",
|
||||||
"/Applications/Inkscape.app/Contents/MacOS/inkscape"]
|
"/Applications/Inkscape.app/Contents/MacOS/inkscape"]
|
||||||
|
|
||||||
# L'extension n'existe qu'en francais : deux versions du meme schema.
|
# Version par langue : textes, fichiers produits, largeur du PNG et agrandissement
|
||||||
# La version « dialogue » est affichee reduite (900 px de large) dans la boite de
|
# des textes (la version anglaise est affichee reduite dans la boite de dialogue :
|
||||||
# dialogue : textes x 1.25 pour rester lisibles.
|
# 900 px de large, textes x 1.25 pour rester lisibles). Inkscape ne traduit pas
|
||||||
|
# le chemin d'une image : la boite de dialogue montre la version anglaise.
|
||||||
VERSIONS = {
|
VERSIONS = {
|
||||||
"readme": {
|
"fr": {
|
||||||
"svg": os.path.join(HERE, "parametres.svg"),
|
"svg": os.path.join(HERE, "parametres.svg"),
|
||||||
"png": os.path.join(HERE, "parametres.png"),
|
"png": os.path.join(HERE, "parametres.png"),
|
||||||
"png_width": 1400,
|
"png_width": 1400,
|
||||||
"font_scale": 1.0,
|
"font_scale": 1.0,
|
||||||
|
"text": {
|
||||||
|
"title1": "Dimensions de la boîte",
|
||||||
|
"front": "Avant",
|
||||||
|
"lid": "Couvercle",
|
||||||
|
"length": "Longueur",
|
||||||
|
"width": "Largeur",
|
||||||
|
"height": "Hauteur",
|
||||||
|
"note1": ["Couvercle (pointillés) : « Boîte fermée ». Décochée : 5 pièces,",
|
||||||
|
"bord haut des côtés droit. Minimum : 5 × l'épaisseur."],
|
||||||
|
"title2": "Épaisseur et type de dimensions",
|
||||||
|
"bottom": "Fond",
|
||||||
|
"exterior": "Extérieure",
|
||||||
|
"interior": "Intérieure",
|
||||||
|
"thickness": "Épaisseur",
|
||||||
|
"note2": ["Extérieures : cotes hors tout. Intérieures : l'extension ajoute",
|
||||||
|
"2 épaisseurs (1 seule en hauteur si la boîte est ouverte)."],
|
||||||
|
"title3": "Largeur des créneaux",
|
||||||
|
"finger": "Créneau",
|
||||||
|
"depth": "Profondeur = épaisseur",
|
||||||
|
"note3": ["0 = 3 × l'épaisseur. Largeur ajustée pour un nombre impair",
|
||||||
|
"de créneaux par bord (ici {} sur l'avant)."],
|
||||||
|
"title4": "Compensation du kerf",
|
||||||
|
"wanted": "Créneau voulu",
|
||||||
|
"drawn": "Dessiné : créneau + kerf",
|
||||||
|
"legend_drawn": "trait dessiné",
|
||||||
|
"legend_cut": "cote après découpe",
|
||||||
|
"note4": ["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."],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"dialogue": {
|
"en": {
|
||||||
"svg": os.path.join(HERE, "parametres_dialogue.svg"),
|
"svg": os.path.join(HERE, "parameters_en.svg"),
|
||||||
"png": os.path.join(ROOT, "images", "parametres.png"),
|
"png": os.path.join(ROOT, "images", "parameters_en.png"),
|
||||||
"png_width": 900,
|
"png_width": 900,
|
||||||
"font_scale": 1.25,
|
"font_scale": 1.25,
|
||||||
|
"text": {
|
||||||
|
"title1": "Box dimensions",
|
||||||
|
"front": "Front",
|
||||||
|
"lid": "Lid",
|
||||||
|
"length": "Length",
|
||||||
|
"width": "Width",
|
||||||
|
"height": "Height",
|
||||||
|
"note1": ["Lid (dashed): \"Closed box\". Unchecked: 5 parts, straight",
|
||||||
|
"top edge on the sides. Minimum: 5 × the thickness."],
|
||||||
|
"title2": "Thickness and dimension type",
|
||||||
|
"bottom": "Bottom",
|
||||||
|
"exterior": "Exterior",
|
||||||
|
"interior": "Interior",
|
||||||
|
"thickness": "Thickness",
|
||||||
|
"note2": ["Exterior: overall sizes. Interior: the extension adds",
|
||||||
|
"2 thicknesses (only 1 in height for an open box)."],
|
||||||
|
"title3": "Finger width",
|
||||||
|
"finger": "Finger",
|
||||||
|
"depth": "Depth = thickness",
|
||||||
|
"note3": ["0 = 3 × the thickness. The width is adjusted to get an",
|
||||||
|
"odd number of fingers per edge ({} on the front here)."],
|
||||||
|
"title4": "Kerf compensation",
|
||||||
|
"wanted": "Wanted finger",
|
||||||
|
"drawn": "Drawn: finger + kerf",
|
||||||
|
"legend_drawn": "drawn stroke",
|
||||||
|
"legend_cut": "size after cutting",
|
||||||
|
"note4": ["The laser removes kerf / 2 on each side of the stroke: the",
|
||||||
|
"outline is widened accordingly. Default 0.1 mm; 0 = none."],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
|
T = {}
|
||||||
FS = 1.0
|
FS = 1.0
|
||||||
|
|
||||||
|
|
||||||
@ -139,7 +200,7 @@ def place(points, x0, y0, scale):
|
|||||||
|
|
||||||
def panel_1():
|
def panel_1():
|
||||||
"""Longueur, largeur, hauteur : boite en perspective cavaliere."""
|
"""Longueur, largeur, hauteur : boite en perspective cavaliere."""
|
||||||
panel_title(30, 50, 1, "Dimensions de la boîte")
|
panel_title(30, 50, 1, T["title1"])
|
||||||
s = 3.2 # px par mm
|
s = 3.2 # px par mm
|
||||||
length, width, height = 100, 80, 50
|
length, width, height = 100, 80, 50
|
||||||
dx, dy = 0.5 * math.cos(math.radians(35)), -0.5 * math.sin(math.radians(35))
|
dx, dy = 0.5 * math.cos(math.radians(35)), -0.5 * math.sin(math.radians(35))
|
||||||
@ -154,24 +215,22 @@ def panel_1():
|
|||||||
polygon([p(0, 0, Z), p(L, 0, Z), p(L, P, Z), p(0, P, Z)], EDGE, 2, "#e9ecef",
|
polygon([p(0, 0, Z), p(L, 0, Z), p(L, P, Z), p(0, P, Z)], EDGE, 2, "#e9ecef",
|
||||||
'stroke-dasharray="7,5"')
|
'stroke-dasharray="7,5"')
|
||||||
polygon([p(L, 0, 0), p(L, P, 0), p(L, P, Z), p(L, 0, Z)], EDGE, 2, "#dee2e6")
|
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, 0, Z / 2 - 3), T["front"], size=16, color=GHOST, anchor="middle", halo=False)
|
||||||
text(*p(L / 2, P / 2, Z + 2), "Couvercle", size=16, color=GHOST, anchor="middle",
|
text(*p(L / 2, P / 2, Z + 2), T["lid"], size=16, color=GHOST, anchor="middle",
|
||||||
halo=False)
|
halo=False)
|
||||||
dimension(p(0, 0, 0), p(L, 0, 0), 30, "Longueur")
|
dimension(p(0, 0, 0), p(L, 0, 0), 30, T["length"])
|
||||||
dimension(p(L, 0, 0), p(L, P, 0), 34, "Largeur", label_shift=44)
|
dimension(p(L, 0, 0), p(L, P, 0), 34, T["width"], label_shift=44)
|
||||||
dimension(p(0, 0, 0), p(0, 0, Z), -30, "Hauteur", label_shift=48)
|
dimension(p(0, 0, 0), p(0, 0, Z), -30, T["height"], label_shift=48)
|
||||||
note(30, 500, ["Couvercle (pointillés) : « Boîte fermée ». Décochée : 5 pièces,",
|
note(30, 500, T["note1"])
|
||||||
"bord haut des côtés droit. Minimum : 5 × l'épaisseur."])
|
|
||||||
|
|
||||||
|
|
||||||
def panel_2():
|
def panel_2():
|
||||||
"""Epaisseur et dimensions interieures / exterieures : coupe verticale."""
|
"""Epaisseur et dimensions interieures / exterieures : coupe verticale."""
|
||||||
ox = 700
|
ox = 700
|
||||||
panel_title(ox + 30, 50, 2, "Épaisseur et type de dimensions")
|
panel_title(ox + 30, 50, 2, T["title2"])
|
||||||
s = 5.0
|
s = 5.0
|
||||||
t, width, height = 6, 80, 44 # epaisseur exageree pour la lisibilite
|
t, width, height = 6, 80, 44 # epaisseur exageree pour la lisibilite
|
||||||
x0, y0 = ox + 150, 150
|
x0, y0 = ox + 150, 150
|
||||||
hatch = 'fill="url(#hachures)"'
|
|
||||||
|
|
||||||
def rect(x, y, w, h):
|
def rect(x, y, w, h):
|
||||||
polygon(place([(x, y), (x + w, y), (x + w, y + h), (x, y + h)], x0, y0, s),
|
polygon(place([(x, y), (x + w, y), (x + w, y + h), (x, y + h)], x0, y0, s),
|
||||||
@ -180,26 +239,24 @@ def panel_2():
|
|||||||
rect(0, 0, t, height) # cote gauche
|
rect(0, 0, t, height) # cote gauche
|
||||||
rect(width - t, 0, t, height) # cote droit
|
rect(width - t, 0, t, height) # cote droit
|
||||||
rect(0, height - t, width, t) # fond
|
rect(0, height - t, width, t) # fond
|
||||||
del hatch
|
text(x0 + width / 2 * s, y0 + (height - t / 2) * s + 6, T["bottom"], size=15,
|
||||||
text(x0 + width / 2 * s, y0 + (height - t / 2) * s + 6, "Fond", size=15,
|
|
||||||
color=INK, anchor="middle")
|
color=INK, anchor="middle")
|
||||||
|
|
||||||
top = y0
|
top = y0
|
||||||
dimension((x0, top), (x0 + width * s, top), -58, "Extérieure", label_shift=18)
|
dimension((x0, top), (x0 + width * s, top), -58, T["exterior"], label_shift=18)
|
||||||
dimension((x0 + t * s, top + 60), (x0 + (width - t) * s, top + 60), 0,
|
dimension((x0 + t * s, top + 60), (x0 + (width - t) * s, top + 60), 0,
|
||||||
"Intérieure", color=DIM2)
|
T["interior"], color=DIM2)
|
||||||
dimension((x0 + (width - t) * s, y0 + 12 * s), (x0 + width * s, y0 + 12 * s), 0,
|
dimension((x0 + (width - t) * s, y0 + 12 * s), (x0 + width * s, y0 + 12 * s), 0,
|
||||||
"", color=DIM)
|
"", color=DIM)
|
||||||
text(x0 + width * s + 16, y0 + 12 * s + 6, "Épaisseur", size=17, color=DIM,
|
text(x0 + width * s + 16, y0 + 12 * s + 6, T["thickness"], size=17, color=DIM,
|
||||||
weight="bold")
|
weight="bold")
|
||||||
note(ox + 30, 500, ["Extérieures : cotes hors tout. Intérieures : l'extension ajoute",
|
note(ox + 30, 500, T["note2"])
|
||||||
"2 épaisseurs (1 seule en hauteur si la boîte est ouverte)."])
|
|
||||||
|
|
||||||
|
|
||||||
def panel_3():
|
def panel_3():
|
||||||
"""Creneaux : bord male de l'avant et bord femelle du couvercle."""
|
"""Creneaux : bord male de l'avant et bord femelle du couvercle."""
|
||||||
oy = 540
|
oy = 540
|
||||||
panel_title(30, oy + 50, 3, "Largeur des créneaux")
|
panel_title(30, oy + 50, 3, T["title3"])
|
||||||
length, t = 100, 4
|
length, t = 100, 4
|
||||||
finger = 3 * t # valeur par defaut (parametre a 0)
|
finger = 3 * t # valeur par defaut (parametre a 0)
|
||||||
s = 5.6
|
s = 5.6
|
||||||
@ -221,25 +278,24 @@ def panel_3():
|
|||||||
lid = [(x, 2 * lid_y - y) for x, y in edge(box_core.FEMALE, lid_y, 50)]
|
lid = [(x, 2 * lid_y - y) for x, y in edge(box_core.FEMALE, lid_y, 50)]
|
||||||
polygon(lid, GHOST, 1.4, "#f8f9fa")
|
polygon(lid, GHOST, 1.4, "#f8f9fa")
|
||||||
polygon(edge(box_core.MALE, front_y, 150), EDGE, 2, FILL)
|
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, lid_y - 20, T["lid"], size=15, color=GHOST)
|
||||||
text(x0 + length * s + 12, front_y + 60, "Avant", size=15, color=GHOST)
|
text(x0 + length * s + 12, front_y + 60, T["front"], size=15, color=GHOST)
|
||||||
|
|
||||||
segs = box_core.edge_segments(length, t, finger, box_core.MALE)
|
segs = box_core.edge_segments(length, t, finger, box_core.MALE)
|
||||||
a, b, _ = segs[4]
|
a, b, _ = segs[4]
|
||||||
dimension((x0 + a * s, front_y), (x0 + b * s, front_y), 50, "Créneau", label_shift=20)
|
dimension((x0 + a * s, front_y), (x0 + b * s, front_y), 50, T["finger"], label_shift=20)
|
||||||
c, d, _ = segs[1]
|
c, d, _ = segs[1]
|
||||||
mid = x0 + (c + d) / 2 * s
|
mid = x0 + (c + d) / 2 * s
|
||||||
dimension((mid, front_y), (mid, front_y + t * s), 0, "", color=DIM2)
|
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,
|
text(mid, front_y + t * s + 28, T["depth"], size=15, color=DIM2,
|
||||||
weight="bold", anchor="middle")
|
weight="bold", anchor="middle")
|
||||||
note(30, oy + 500, ["0 = 3 × l'épaisseur. Largeur ajustée pour un nombre impair",
|
note(30, oy + 500, [line.format(len(segs)) for line in T["note3"]])
|
||||||
"de créneaux par bord (ici {} sur l'avant).".format(len(segs))])
|
|
||||||
|
|
||||||
|
|
||||||
def panel_4():
|
def panel_4():
|
||||||
"""Kerf : contour dessine decale de kerf / 2 vers l'exterieur."""
|
"""Kerf : contour dessine decale de kerf / 2 vers l'exterieur."""
|
||||||
ox, oy = 700, 540
|
ox, oy = 700, 540
|
||||||
panel_title(ox + 30, oy + 50, 4, "Compensation du kerf")
|
panel_title(ox + 30, oy + 50, 4, T["title4"])
|
||||||
t, finger, kerf = 4, 8, 1.2 # kerf exagere pour la lisibilite
|
t, finger, kerf = 4, 8, 1.2 # kerf exagere pour la lisibilite
|
||||||
s = 18
|
s = 18
|
||||||
x0, y0 = ox + 150, oy + 160
|
x0, y0 = ox + 150, oy + 160
|
||||||
@ -251,21 +307,20 @@ def panel_4():
|
|||||||
|
|
||||||
k = kerf / 2
|
k = kerf / 2
|
||||||
dimension((x0 + 6 * s, y0 + 0), (x0 + (6 + finger) * s, y0), 0, "")
|
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,
|
text(x0 + (6 + finger / 2) * s, y0 + 36, T["wanted"], size=15, color=DIM,
|
||||||
anchor="middle", weight="bold")
|
anchor="middle", weight="bold")
|
||||||
dimension((x0 + (6 - k) * s, y0 - k * s), (x0 + (6 + finger + k) * s, y0 - k * s), -40,
|
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)
|
T["drawn"], color=LASER, size=16)
|
||||||
dimension((x0 + 22 * s, y0 + 6 * s), (x0 + (22 + k) * s, y0 + 6 * s), 0, "", color=DIM2)
|
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,
|
text(x0 + (22 + k) * s + 14, y0 + 6 * s + 6, "kerf / 2", size=16, color=DIM2,
|
||||||
weight="bold")
|
weight="bold")
|
||||||
|
|
||||||
lx, ly = x0 + 10, y0 + 13 * s + 36
|
lx, ly = x0 + 10, y0 + 13 * s + 36
|
||||||
polyline([(lx, ly), (lx + 40, ly)], LASER, 2.2)
|
polyline([(lx, ly), (lx + 40, ly)], LASER, 2.2)
|
||||||
text(lx + 50, ly + 6, "trait dessiné", size=15, halo=False)
|
text(lx + 50, ly + 6, T["legend_drawn"], size=15, halo=False)
|
||||||
polyline([(lx + 190, ly), (lx + 230, ly)], INK, 1.6, 'stroke-dasharray="6,4"')
|
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)
|
text(lx + 240, ly + 6, T["legend_cut"], size=15, halo=False)
|
||||||
note(ox + 30, oy + 500, ["Le laser enlève kerf / 2 de chaque côté du trait : le contour",
|
note(ox + 30, oy + 500, T["note4"])
|
||||||
"est élargi d'autant. Défaut 0,1 mm ; 0 = pas de compensation."])
|
|
||||||
|
|
||||||
|
|
||||||
def export_png(svg_path, png_path, width):
|
def export_png(svg_path, png_path, width):
|
||||||
@ -285,6 +340,8 @@ def build(name):
|
|||||||
global FS
|
global FS
|
||||||
version = VERSIONS[name]
|
version = VERSIONS[name]
|
||||||
out.clear()
|
out.clear()
|
||||||
|
T.clear()
|
||||||
|
T.update(version["text"])
|
||||||
FS = version["font_scale"]
|
FS = version["font_scale"]
|
||||||
|
|
||||||
out.append('<defs><pattern id="hachures" width="8" height="8" '
|
out.append('<defs><pattern id="hachures" width="8" height="8" '
|
||||||
|
|||||||
304
i18n.py
Normal file
304
i18n.py
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
Chaine de traduction de l'extension, sans dependance a gettext.
|
||||||
|
|
||||||
|
python i18n.py # extract + update + compile
|
||||||
|
python i18n.py extract # po/<domaine>.pot depuis le .inx et les .py
|
||||||
|
python i18n.py update # reporte le modele dans po/<langue>.po
|
||||||
|
python i18n.py compile # po/<langue>.po -> locale/<langue>/LC_MESSAGES/*.mo
|
||||||
|
|
||||||
|
Le domaine est lu dans l'attribut translationdomain du .inx ; les sources sont
|
||||||
|
le .inx et tous les .py du dossier (hors i18n.py et tests).
|
||||||
|
|
||||||
|
Les textes source (.inx et appels _() des .py) sont en anglais : c'est ce
|
||||||
|
qu'Inkscape affiche pour une langue sans catalogue. Le catalogue anglais est
|
||||||
|
rempli automatiquement avec les textes source ; seul fr.po se traduit a la main.
|
||||||
|
|
||||||
|
Les chaines retenues dans le .inx suivent les regles d'Inkscape (inx.its) :
|
||||||
|
<name>, attributs gui-text et gui-description, contenu de <option>, <item> et
|
||||||
|
<label>, sauf translatable="no". Les espaces sont normalises, sauf sous
|
||||||
|
xml:space="preserve".
|
||||||
|
"""
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
LANGUAGES = ["en", "fr"]
|
||||||
|
SOURCE_LANGUAGE = "en"
|
||||||
|
PO_DIR = os.path.join(HERE, "po")
|
||||||
|
LOCALE_DIR = os.path.join(HERE, "locale")
|
||||||
|
|
||||||
|
XML_SPACE = "{http://www.w3.org/XML/1998/namespace}space"
|
||||||
|
INX_NS = "{http://www.inkscape.org/namespace/inkscape/extension}"
|
||||||
|
|
||||||
|
|
||||||
|
def _find_inx():
|
||||||
|
found = sorted(name for name in os.listdir(HERE) if name.endswith(".inx"))
|
||||||
|
if len(found) != 1:
|
||||||
|
sys.exit("i18n.py attend un seul fichier .inx dans {} (trouve : {})".format(
|
||||||
|
HERE, found))
|
||||||
|
return found[0]
|
||||||
|
|
||||||
|
|
||||||
|
INX = _find_inx()
|
||||||
|
_INX_ROOT = ET.parse(os.path.join(HERE, INX)).getroot()
|
||||||
|
DOMAIN = _INX_ROOT.get("translationdomain")
|
||||||
|
if not DOMAIN:
|
||||||
|
sys.exit("Attribut translationdomain absent de " + INX)
|
||||||
|
EXTENSION_NAME = (_INX_ROOT.findtext(INX_NS + "name") or DOMAIN).strip()
|
||||||
|
SOURCES = [INX] + sorted(
|
||||||
|
name for name in os.listdir(HERE)
|
||||||
|
if name.endswith(".py") and name != "i18n.py" and not name.startswith("test"))
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Extraction
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _local(tag):
|
||||||
|
return tag.rsplit("}", 1)[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize(text, preserve):
|
||||||
|
return text if preserve else " ".join(text.split())
|
||||||
|
|
||||||
|
|
||||||
|
def extract_inx(path):
|
||||||
|
"""Chaines traduisibles d'un fichier .inx, dans l'ordre du document."""
|
||||||
|
messages = []
|
||||||
|
|
||||||
|
def walk(elem, preserve):
|
||||||
|
preserve = elem.get(XML_SPACE, "preserve" if preserve else "default") == "preserve"
|
||||||
|
if elem.get("translatable") != "no":
|
||||||
|
for attr in ("gui-text", "gui-description"):
|
||||||
|
if elem.get(attr):
|
||||||
|
messages.append(_normalize(elem.get(attr), preserve))
|
||||||
|
if _local(elem.tag) in ("name", "option", "item", "label") and elem.text:
|
||||||
|
messages.append(_normalize(elem.text, preserve))
|
||||||
|
for child in elem:
|
||||||
|
walk(child, preserve)
|
||||||
|
|
||||||
|
walk(ET.parse(path).getroot(), False)
|
||||||
|
return [m for m in messages if m]
|
||||||
|
|
||||||
|
|
||||||
|
def extract_py(path):
|
||||||
|
"""Arguments litteraux des appels _("...") d'un script Python."""
|
||||||
|
with open(path, encoding="utf-8") as handle:
|
||||||
|
tree = ast.parse(handle.read(), path)
|
||||||
|
calls = [node for node in ast.walk(tree)
|
||||||
|
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)
|
||||||
|
and node.func.id == "_" and len(node.args) == 1
|
||||||
|
and isinstance(node.args[0], ast.Constant)
|
||||||
|
and isinstance(node.args[0].value, str)]
|
||||||
|
calls.sort(key=lambda node: (node.lineno, node.col_offset))
|
||||||
|
return [node.args[0].value for node in calls]
|
||||||
|
|
||||||
|
|
||||||
|
def extract():
|
||||||
|
"""Liste ordonnee et sans doublon de (msgid, [fichiers sources])."""
|
||||||
|
found = {}
|
||||||
|
for name in SOURCES:
|
||||||
|
path = os.path.join(HERE, name)
|
||||||
|
strings = extract_inx(path) if name.endswith(".inx") else extract_py(path)
|
||||||
|
for msgid in strings:
|
||||||
|
found.setdefault(msgid, [])
|
||||||
|
if name not in found[msgid]:
|
||||||
|
found[msgid].append(name)
|
||||||
|
return list(found.items())
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Lecture / ecriture des fichiers .po
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_ESCAPES = {"n": "\n", "t": "\t", '"': '"', "\\": "\\"}
|
||||||
|
|
||||||
|
|
||||||
|
def _unquote(line):
|
||||||
|
body = line.strip()[1:-1]
|
||||||
|
return re.sub(r'\\(.)', lambda m: _ESCAPES.get(m.group(1), m.group(1)), body)
|
||||||
|
|
||||||
|
|
||||||
|
def _quote(text):
|
||||||
|
text = text.replace("\\", "\\\\").replace('"', '\\"').replace("\t", "\\t")
|
||||||
|
lines = text.split("\n")
|
||||||
|
parts = [line + "\\n" for line in lines[:-1]]
|
||||||
|
if lines[-1]:
|
||||||
|
parts.append(lines[-1])
|
||||||
|
if len(parts) <= 1:
|
||||||
|
return '"{}"'.format(parts[0] if parts else "")
|
||||||
|
return '""\n' + "\n".join('"{}"'.format(part) for part in parts)
|
||||||
|
|
||||||
|
|
||||||
|
def read_po(path):
|
||||||
|
"""Dictionnaire msgid -> (msgstr, fuzzy). L'en-tete a pour cle ""."""
|
||||||
|
entries = {}
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return entries
|
||||||
|
msgid = msgstr = None
|
||||||
|
field = None
|
||||||
|
fuzzy = False
|
||||||
|
|
||||||
|
def flush():
|
||||||
|
if msgid is not None:
|
||||||
|
entries[msgid] = (msgstr or "", fuzzy)
|
||||||
|
|
||||||
|
with open(path, encoding="utf-8") as handle:
|
||||||
|
for raw in handle:
|
||||||
|
line = raw.strip()
|
||||||
|
if line.startswith("#,") and "fuzzy" in line:
|
||||||
|
flush()
|
||||||
|
msgid = msgstr = field = None
|
||||||
|
fuzzy = True
|
||||||
|
elif line.startswith("msgid "):
|
||||||
|
if field == "msgstr":
|
||||||
|
flush()
|
||||||
|
fuzzy = False
|
||||||
|
msgid, msgstr, field = _unquote(line[6:]), None, "msgid"
|
||||||
|
elif line.startswith("msgstr "):
|
||||||
|
msgstr, field = _unquote(line[7:]), "msgstr"
|
||||||
|
elif line.startswith('"') and field == "msgid":
|
||||||
|
msgid += _unquote(line)
|
||||||
|
elif line.startswith('"') and field == "msgstr":
|
||||||
|
msgstr += _unquote(line)
|
||||||
|
elif not line and field == "msgstr":
|
||||||
|
flush()
|
||||||
|
msgid = msgstr = field = None
|
||||||
|
fuzzy = False
|
||||||
|
flush()
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def _header(language):
|
||||||
|
fields = [
|
||||||
|
("Project-Id-Version", DOMAIN),
|
||||||
|
("Language", language or ""),
|
||||||
|
("MIME-Version", "1.0"),
|
||||||
|
("Content-Type", "text/plain; charset=UTF-8"),
|
||||||
|
("Content-Transfer-Encoding", "8bit"),
|
||||||
|
("Plural-Forms", {"fr": "nplurals=2; plural=(n > 1);",
|
||||||
|
"en": "nplurals=2; plural=(n != 1);"}.get(language, "")),
|
||||||
|
]
|
||||||
|
return "".join("{}: {}\n".format(key, value) for key, value in fields if value)
|
||||||
|
|
||||||
|
|
||||||
|
def write_po(path, language, messages, translations):
|
||||||
|
title = ("Modele de traduction" if language is None
|
||||||
|
else "Traduction ({})".format(language))
|
||||||
|
out = ["# {} de l'extension Inkscape « {} ».".format(title, EXTENSION_NAME),
|
||||||
|
"# Genere par i18n.py ; ne modifier que les msgstr.",
|
||||||
|
"msgid \"\"",
|
||||||
|
"msgstr " + _quote(_header(language)),
|
||||||
|
""]
|
||||||
|
for msgid, refs in messages:
|
||||||
|
msgstr, fuzzy = translations.get(msgid, ("", False))
|
||||||
|
out.append("#: " + " ".join(refs))
|
||||||
|
if fuzzy:
|
||||||
|
out.append("#, fuzzy")
|
||||||
|
out.append("msgid " + _quote(msgid))
|
||||||
|
out.append("msgstr " + _quote(msgstr))
|
||||||
|
out.append("")
|
||||||
|
with open(path, "w", encoding="utf-8", newline="\n") as handle:
|
||||||
|
handle.write("\n".join(out))
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Compilation .mo (format GNU gettext)
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def write_mo(path, catalog):
|
||||||
|
"""Ecrit un catalogue {msgid: msgstr} au format .mo, cles triees."""
|
||||||
|
keys = sorted(catalog)
|
||||||
|
ids = [key.encode("utf-8") for key in keys]
|
||||||
|
strs = [catalog[key].encode("utf-8") for key in keys]
|
||||||
|
count = len(keys)
|
||||||
|
ids_start = 7 * 4 + 16 * count
|
||||||
|
strs_start = ids_start + sum(len(b) + 1 for b in ids)
|
||||||
|
|
||||||
|
table_ids, table_strs = [], []
|
||||||
|
offset = ids_start
|
||||||
|
for data in ids:
|
||||||
|
table_ids += [len(data), offset]
|
||||||
|
offset += len(data) + 1
|
||||||
|
offset = strs_start
|
||||||
|
for data in strs:
|
||||||
|
table_strs += [len(data), offset]
|
||||||
|
offset += len(data) + 1
|
||||||
|
|
||||||
|
output = struct.pack("<7I", 0x950412DE, 0, count, 7 * 4, 7 * 4 + 8 * count, 0, 0)
|
||||||
|
output += struct.pack("<{}I".format(2 * count), *table_ids)
|
||||||
|
output += struct.pack("<{}I".format(2 * count), *table_strs)
|
||||||
|
output += b"".join(data + b"\0" for data in ids)
|
||||||
|
output += b"".join(data + b"\0" for data in strs)
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
|
with open(path, "wb") as handle:
|
||||||
|
handle.write(output)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Commandes
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def po_path(language):
|
||||||
|
return os.path.join(PO_DIR, "{}.po".format(language))
|
||||||
|
|
||||||
|
|
||||||
|
def mo_path(language):
|
||||||
|
return os.path.join(LOCALE_DIR, language, "LC_MESSAGES", DOMAIN + ".mo")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_extract():
|
||||||
|
os.makedirs(PO_DIR, exist_ok=True)
|
||||||
|
messages = extract()
|
||||||
|
write_po(os.path.join(PO_DIR, DOMAIN + ".pot"), None, messages, {})
|
||||||
|
print("{} chaines -> po/{}.pot".format(len(messages), DOMAIN))
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_update():
|
||||||
|
messages = extract()
|
||||||
|
for language in LANGUAGES:
|
||||||
|
translations = read_po(po_path(language))
|
||||||
|
if language == SOURCE_LANGUAGE:
|
||||||
|
translations = {msgid: (msgid, False) for msgid, _refs in messages}
|
||||||
|
write_po(po_path(language), language, messages, translations)
|
||||||
|
missing = [m for m, _r in messages if not translations.get(m, ("", False))[0]]
|
||||||
|
print("po/{}.po : {} a traduire".format(language, len(missing)))
|
||||||
|
for msgid in missing:
|
||||||
|
print(" " + msgid.splitlines()[0])
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_compile():
|
||||||
|
for language in LANGUAGES:
|
||||||
|
entries = read_po(po_path(language))
|
||||||
|
catalog = {msgid: msgstr for msgid, (msgstr, fuzzy) in entries.items()
|
||||||
|
if msgid and msgstr and not fuzzy}
|
||||||
|
catalog[""] = _header(language)
|
||||||
|
write_mo(mo_path(language), catalog)
|
||||||
|
print("{} -> {} ({} chaines)".format(
|
||||||
|
os.path.relpath(po_path(language), HERE),
|
||||||
|
os.path.relpath(mo_path(language), HERE), len(catalog) - 1))
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
commands = {"extract": [cmd_extract], "update": [cmd_update],
|
||||||
|
"compile": [cmd_compile],
|
||||||
|
"all": [cmd_extract, cmd_update, cmd_compile]}
|
||||||
|
name = argv[0] if argv else "all"
|
||||||
|
if name not in commands:
|
||||||
|
print(__doc__)
|
||||||
|
return 1
|
||||||
|
for command in commands[name]:
|
||||||
|
command()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
||||||
BIN
images/parameters_en.png
Normal file
BIN
images/parameters_en.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 116 KiB |
BIN
locale/en/LC_MESSAGES/box_maker.mo
Normal file
BIN
locale/en/LC_MESSAGES/box_maker.mo
Normal file
Binary file not shown.
BIN
locale/fr/LC_MESSAGES/box_maker.mo
Normal file
BIN
locale/fr/LC_MESSAGES/box_maker.mo
Normal file
Binary file not shown.
165
po/box_maker.pot
Normal file
165
po/box_maker.pot
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
# Modele de traduction de l'extension Inkscape « Finger-Joint Box ».
|
||||||
|
# Genere par i18n.py ; ne modifier que les msgstr.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: box_maker\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Finger-Joint Box"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Dimensions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Length (mm):"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Width (mm):"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Height (mm):"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Dimensions are:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Exterior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Interior"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Closed box (with lid)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Material and fingers"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Material thickness (mm):"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Finger width (mm, 0 = 3 × thickness):"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Laser beam compensation, kerf (mm):"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Notes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "The finger width is adjusted to get an odd number of fingers on each edge."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "The kerf widens the fingers and narrows the slots for a tight fit."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Drawing"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Spacing between parts (mm):"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Stroke width (mm):"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Stroke color:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Name the parts (engraved text!)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Diagram"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Help"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid ""
|
||||||
|
"Draws the pattern of a laser-cut box whose\n"
|
||||||
|
"panels interlock with straight finger joints.\n"
|
||||||
|
"No selection is needed: the parts are added\n"
|
||||||
|
"to the current layer, centered on the view.\n"
|
||||||
|
"\n"
|
||||||
|
"Dimensions: exterior sizes, or interior sizes\n"
|
||||||
|
"(the thickness is then added). Unchecking\n"
|
||||||
|
"\"Closed box\" gives 5 parts, with a straight\n"
|
||||||
|
"top edge on the sides.\n"
|
||||||
|
"\n"
|
||||||
|
"Fingers: 0 = 3 × the thickness. The width is\n"
|
||||||
|
"adjusted to get an odd number per edge.\n"
|
||||||
|
"\n"
|
||||||
|
"Kerf: the outline is moved outwards by\n"
|
||||||
|
"kerf / 2 so that parts keep their nominal size\n"
|
||||||
|
"after cutting. Measure it: cut a 20 mm square,\n"
|
||||||
|
"kerf = 20 − measured size. Larger = tighter.\n"
|
||||||
|
"\n"
|
||||||
|
"Each part is a separate closed path, named\n"
|
||||||
|
"(Front, Bottom…), that can be moved alone.\n"
|
||||||
|
"Labels are text: the laser would engrave them."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Front"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Left"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Right"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Bottom"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Lid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "length"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "width"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "height"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "The {dimension} ({size:.1f} mm exterior) is too small for the thickness ({thickness:g} mm): it must be at least {minimum:g} mm."
|
||||||
|
msgstr ""
|
||||||
188
po/en.po
Normal file
188
po/en.po
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
# Traduction (en) de l'extension Inkscape « Finger-Joint Box ».
|
||||||
|
# Genere par i18n.py ; ne modifier que les msgstr.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: box_maker\n"
|
||||||
|
"Language: en\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Finger-Joint Box"
|
||||||
|
msgstr "Finger-Joint Box"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Dimensions"
|
||||||
|
msgstr "Dimensions"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Length (mm):"
|
||||||
|
msgstr "Length (mm):"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Width (mm):"
|
||||||
|
msgstr "Width (mm):"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Height (mm):"
|
||||||
|
msgstr "Height (mm):"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Dimensions are:"
|
||||||
|
msgstr "Dimensions are:"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Exterior"
|
||||||
|
msgstr "Exterior"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Interior"
|
||||||
|
msgstr "Interior"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Closed box (with lid)"
|
||||||
|
msgstr "Closed box (with lid)"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Material and fingers"
|
||||||
|
msgstr "Material and fingers"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Material thickness (mm):"
|
||||||
|
msgstr "Material thickness (mm):"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Finger width (mm, 0 = 3 × thickness):"
|
||||||
|
msgstr "Finger width (mm, 0 = 3 × thickness):"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Laser beam compensation, kerf (mm):"
|
||||||
|
msgstr "Laser beam compensation, kerf (mm):"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Notes"
|
||||||
|
msgstr "Notes"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "The finger width is adjusted to get an odd number of fingers on each edge."
|
||||||
|
msgstr "The finger width is adjusted to get an odd number of fingers on each edge."
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "The kerf widens the fingers and narrows the slots for a tight fit."
|
||||||
|
msgstr "The kerf widens the fingers and narrows the slots for a tight fit."
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Drawing"
|
||||||
|
msgstr "Drawing"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Spacing between parts (mm):"
|
||||||
|
msgstr "Spacing between parts (mm):"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Stroke width (mm):"
|
||||||
|
msgstr "Stroke width (mm):"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Stroke color:"
|
||||||
|
msgstr "Stroke color:"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Name the parts (engraved text!)"
|
||||||
|
msgstr "Name the parts (engraved text!)"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Diagram"
|
||||||
|
msgstr "Diagram"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Help"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid ""
|
||||||
|
"Draws the pattern of a laser-cut box whose\n"
|
||||||
|
"panels interlock with straight finger joints.\n"
|
||||||
|
"No selection is needed: the parts are added\n"
|
||||||
|
"to the current layer, centered on the view.\n"
|
||||||
|
"\n"
|
||||||
|
"Dimensions: exterior sizes, or interior sizes\n"
|
||||||
|
"(the thickness is then added). Unchecking\n"
|
||||||
|
"\"Closed box\" gives 5 parts, with a straight\n"
|
||||||
|
"top edge on the sides.\n"
|
||||||
|
"\n"
|
||||||
|
"Fingers: 0 = 3 × the thickness. The width is\n"
|
||||||
|
"adjusted to get an odd number per edge.\n"
|
||||||
|
"\n"
|
||||||
|
"Kerf: the outline is moved outwards by\n"
|
||||||
|
"kerf / 2 so that parts keep their nominal size\n"
|
||||||
|
"after cutting. Measure it: cut a 20 mm square,\n"
|
||||||
|
"kerf = 20 − measured size. Larger = tighter.\n"
|
||||||
|
"\n"
|
||||||
|
"Each part is a separate closed path, named\n"
|
||||||
|
"(Front, Bottom…), that can be moved alone.\n"
|
||||||
|
"Labels are text: the laser would engrave them."
|
||||||
|
msgstr ""
|
||||||
|
"Draws the pattern of a laser-cut box whose\n"
|
||||||
|
"panels interlock with straight finger joints.\n"
|
||||||
|
"No selection is needed: the parts are added\n"
|
||||||
|
"to the current layer, centered on the view.\n"
|
||||||
|
"\n"
|
||||||
|
"Dimensions: exterior sizes, or interior sizes\n"
|
||||||
|
"(the thickness is then added). Unchecking\n"
|
||||||
|
"\"Closed box\" gives 5 parts, with a straight\n"
|
||||||
|
"top edge on the sides.\n"
|
||||||
|
"\n"
|
||||||
|
"Fingers: 0 = 3 × the thickness. The width is\n"
|
||||||
|
"adjusted to get an odd number per edge.\n"
|
||||||
|
"\n"
|
||||||
|
"Kerf: the outline is moved outwards by\n"
|
||||||
|
"kerf / 2 so that parts keep their nominal size\n"
|
||||||
|
"after cutting. Measure it: cut a 20 mm square,\n"
|
||||||
|
"kerf = 20 − measured size. Larger = tighter.\n"
|
||||||
|
"\n"
|
||||||
|
"Each part is a separate closed path, named\n"
|
||||||
|
"(Front, Bottom…), that can be moved alone.\n"
|
||||||
|
"Labels are text: the laser would engrave them."
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Front"
|
||||||
|
msgstr "Front"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Back"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Left"
|
||||||
|
msgstr "Left"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Right"
|
||||||
|
msgstr "Right"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Bottom"
|
||||||
|
msgstr "Bottom"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Lid"
|
||||||
|
msgstr "Lid"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "length"
|
||||||
|
msgstr "length"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "width"
|
||||||
|
msgstr "width"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "height"
|
||||||
|
msgstr "height"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "The {dimension} ({size:.1f} mm exterior) is too small for the thickness ({thickness:g} mm): it must be at least {minimum:g} mm."
|
||||||
|
msgstr "The {dimension} ({size:.1f} mm exterior) is too small for the thickness ({thickness:g} mm): it must be at least {minimum:g} mm."
|
||||||
190
po/fr.po
Normal file
190
po/fr.po
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
# Traduction (fr) de l'extension Inkscape « Finger-Joint Box ».
|
||||||
|
# Genere par i18n.py ; ne modifier que les msgstr.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: box_maker\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Finger-Joint Box"
|
||||||
|
msgstr "Boîte à créneaux"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Dimensions"
|
||||||
|
msgstr "Dimensions"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Length (mm):"
|
||||||
|
msgstr "Longueur (mm) :"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Width (mm):"
|
||||||
|
msgstr "Largeur (mm) :"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Height (mm):"
|
||||||
|
msgstr "Hauteur (mm) :"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Dimensions are:"
|
||||||
|
msgstr "Les dimensions sont :"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Exterior"
|
||||||
|
msgstr "Extérieures"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Interior"
|
||||||
|
msgstr "Intérieures"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Closed box (with lid)"
|
||||||
|
msgstr "Boîte fermée (avec couvercle)"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Material and fingers"
|
||||||
|
msgstr "Matière et créneaux"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Material thickness (mm):"
|
||||||
|
msgstr "Épaisseur de la matière (mm) :"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Finger width (mm, 0 = 3 × thickness):"
|
||||||
|
msgstr "Largeur des créneaux (mm, 0 = 3 × épaisseur) :"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Laser beam compensation, kerf (mm):"
|
||||||
|
msgstr "Compensation du trait laser, kerf (mm) :"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Notes"
|
||||||
|
msgstr "Remarques"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "The finger width is adjusted to get an odd number of fingers on each edge."
|
||||||
|
msgstr "La largeur des créneaux est ajustée pour obtenir un nombre impair de créneaux sur chaque bord."
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "The kerf widens the fingers and narrows the slots for a tight fit."
|
||||||
|
msgstr "Le kerf élargit les créneaux et rétrécit les encoches pour un assemblage serré."
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Drawing"
|
||||||
|
msgstr "Tracé"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Spacing between parts (mm):"
|
||||||
|
msgstr "Espacement entre les pièces (mm) :"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Stroke width (mm):"
|
||||||
|
msgstr "Épaisseur du trait (mm) :"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Stroke color:"
|
||||||
|
msgstr "Couleur du trait :"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Name the parts (engraved text!)"
|
||||||
|
msgstr "Nommer les pièces (texte gravé !)"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Diagram"
|
||||||
|
msgstr "Schéma"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Aide"
|
||||||
|
|
||||||
|
#: box_maker.inx
|
||||||
|
msgid ""
|
||||||
|
"Draws the pattern of a laser-cut box whose\n"
|
||||||
|
"panels interlock with straight finger joints.\n"
|
||||||
|
"No selection is needed: the parts are added\n"
|
||||||
|
"to the current layer, centered on the view.\n"
|
||||||
|
"\n"
|
||||||
|
"Dimensions: exterior sizes, or interior sizes\n"
|
||||||
|
"(the thickness is then added). Unchecking\n"
|
||||||
|
"\"Closed box\" gives 5 parts, with a straight\n"
|
||||||
|
"top edge on the sides.\n"
|
||||||
|
"\n"
|
||||||
|
"Fingers: 0 = 3 × the thickness. The width is\n"
|
||||||
|
"adjusted to get an odd number per edge.\n"
|
||||||
|
"\n"
|
||||||
|
"Kerf: the outline is moved outwards by\n"
|
||||||
|
"kerf / 2 so that parts keep their nominal size\n"
|
||||||
|
"after cutting. Measure it: cut a 20 mm square,\n"
|
||||||
|
"kerf = 20 − measured size. Larger = tighter.\n"
|
||||||
|
"\n"
|
||||||
|
"Each part is a separate closed path, named\n"
|
||||||
|
"(Front, Bottom…), that can be moved alone.\n"
|
||||||
|
"Labels are text: the laser would engrave them."
|
||||||
|
msgstr ""
|
||||||
|
"Dessine le patron d'une boîte à découper au laser,\n"
|
||||||
|
"dont les faces s'assemblent par créneaux droits.\n"
|
||||||
|
"Aucune sélection nécessaire : les pièces sont\n"
|
||||||
|
"ajoutées au calque courant, centrées sur la vue.\n"
|
||||||
|
"\n"
|
||||||
|
"Dimensions : extérieures, ou intérieures\n"
|
||||||
|
"(l'épaisseur est alors ajoutée). Décocher\n"
|
||||||
|
"« Boîte fermée » donne 5 pièces, bord haut\n"
|
||||||
|
"des côtés droit.\n"
|
||||||
|
"\n"
|
||||||
|
"Créneaux : 0 = 3 × l'épaisseur. La largeur est\n"
|
||||||
|
"ajustée pour un nombre impair par bord.\n"
|
||||||
|
"\n"
|
||||||
|
"Kerf : le contour est décalé vers l'extérieur\n"
|
||||||
|
"de kerf / 2 : les pièces gardent leurs cotes\n"
|
||||||
|
"après découpe. Mesure : découper un carré de\n"
|
||||||
|
"20 mm, kerf = 20 − cote mesurée. Plus grand =\n"
|
||||||
|
"plus serré.\n"
|
||||||
|
"\n"
|
||||||
|
"Chaque pièce est un chemin fermé indépendant,\n"
|
||||||
|
"nommé (Avant, Fond…), déplaçable seul.\n"
|
||||||
|
"Les étiquettes sont du texte : le laser les\n"
|
||||||
|
"graverait."
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Front"
|
||||||
|
msgstr "Avant"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Arrière"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Left"
|
||||||
|
msgstr "Gauche"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Right"
|
||||||
|
msgstr "Droite"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Bottom"
|
||||||
|
msgstr "Fond"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "Lid"
|
||||||
|
msgstr "Couvercle"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "length"
|
||||||
|
msgstr "longueur"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "width"
|
||||||
|
msgstr "largeur"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "height"
|
||||||
|
msgstr "hauteur"
|
||||||
|
|
||||||
|
#: box_maker.py
|
||||||
|
msgid "The {dimension} ({size:.1f} mm exterior) is too small for the thickness ({thickness:g} mm): it must be at least {minimum:g} mm."
|
||||||
|
msgstr "La {dimension} ({size:.1f} mm extérieure) est trop petite pour l'épaisseur ({thickness:g} mm) : il faut au moins {minimum:g} mm."
|
||||||
128
tests/test_extension.py
Normal file
128
tests/test_extension.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""Tests de la boite de dialogue, des traductions et de l'extension (pytest)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
|
||||||
|
sys.path.insert(0, ROOT)
|
||||||
|
|
||||||
|
INX = os.path.join(ROOT, "box_maker.inx")
|
||||||
|
|
||||||
|
import box_core # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Traductions
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_translations_up_to_date_and_complete():
|
||||||
|
"""Chaque texte du .inx et des .py a sa traduction dans chaque catalogue."""
|
||||||
|
import gettext
|
||||||
|
import i18n
|
||||||
|
|
||||||
|
msgids = [msgid for msgid, _refs in i18n.extract()]
|
||||||
|
assert "Finger-Joint Box" in msgids
|
||||||
|
assert "Front" in msgids # noms des pieces, marques dans box_maker.py
|
||||||
|
|
||||||
|
for language in i18n.LANGUAGES:
|
||||||
|
entries = i18n.read_po(i18n.po_path(language))
|
||||||
|
missing = [m for m in msgids if not entries.get(m, ("", False))[0]]
|
||||||
|
assert not missing, "{}.po incomplet : {}".format(language, missing)
|
||||||
|
|
||||||
|
catalog = gettext.translation(i18n.DOMAIN, i18n.LOCALE_DIR, [language])
|
||||||
|
for msgid in msgids:
|
||||||
|
assert catalog.gettext(msgid) == entries[msgid][0], \
|
||||||
|
"{} : .mo a recompiler (python i18n.py)".format(language)
|
||||||
|
|
||||||
|
|
||||||
|
def test_french_error_message_keeps_placeholders():
|
||||||
|
"""La traduction du message d'erreur garde les memes champs de format."""
|
||||||
|
import i18n
|
||||||
|
|
||||||
|
fields = re.compile(r"\{\w+(?::[^}]*)?\}")
|
||||||
|
for msgid, (msgstr, _fuzzy) in i18n.read_po(i18n.po_path("fr")).items():
|
||||||
|
assert sorted(fields.findall(msgid)) == sorted(fields.findall(msgstr)), msgid
|
||||||
|
|
||||||
|
|
||||||
|
def test_po_roundtrip(tmp_path):
|
||||||
|
import i18n
|
||||||
|
|
||||||
|
messages = [("Simple", ["a"]), ('Quote "x" and \\ back', ["a"]),
|
||||||
|
("Two\nlines", ["a"]), ("Tab\tend\n", ["a"])]
|
||||||
|
path = str(tmp_path / "xx.po")
|
||||||
|
i18n.write_po(path, "fr", messages,
|
||||||
|
{m: ("<" + m + ">", False) for m, _r in messages})
|
||||||
|
entries = i18n.read_po(path)
|
||||||
|
for msgid, _refs in messages:
|
||||||
|
assert entries[msgid] == ("<" + msgid + ">", False)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Boite de dialogue
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_inx_matches_arguments():
|
||||||
|
"""Chaque <param> du .inx a son add_argument, et inversement."""
|
||||||
|
root = ET.parse(INX).getroot()
|
||||||
|
params = {elem.get("name") for elem in root.iter()
|
||||||
|
if elem.tag.rsplit("}", 1)[-1] == "param"}
|
||||||
|
with open(os.path.join(ROOT, "box_maker.py"), encoding="utf-8") as handle:
|
||||||
|
arguments = set(re.findall(r'add_argument\("--(\w+)"', handle.read()))
|
||||||
|
assert params == arguments
|
||||||
|
|
||||||
|
|
||||||
|
def test_inx_images_exist():
|
||||||
|
"""Les images de la boite de dialogue sont presentes (chemin relatif au .inx)."""
|
||||||
|
root = ET.parse(INX).getroot()
|
||||||
|
images = [elem.text.strip() for elem in root.iter()
|
||||||
|
if elem.tag.rsplit("}", 1)[-1] == "image"]
|
||||||
|
assert images
|
||||||
|
for image in images:
|
||||||
|
assert os.path.isfile(os.path.join(ROOT, image)), image
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_core_panel_has_a_translated_name():
|
||||||
|
inkex = pytest.importorskip("inkex") # noqa: F841
|
||||||
|
import box_maker
|
||||||
|
|
||||||
|
names = {p.name for p in box_core.box_panels(100, 80, 50, 3)}
|
||||||
|
assert names == set(box_maker.panel_names())
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Bout en bout (avec inkex)
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
BLANK = ('<svg xmlns="http://www.w3.org/2000/svg" '
|
||||||
|
'xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" '
|
||||||
|
'width="300mm" height="300mm" viewBox="0 0 300 300">'
|
||||||
|
'<g inkscape:groupmode="layer" id="layer1"/></svg>')
|
||||||
|
|
||||||
|
|
||||||
|
def _run(tmp_path, *args):
|
||||||
|
pytest.importorskip("inkex")
|
||||||
|
import box_maker
|
||||||
|
|
||||||
|
source = tmp_path / "in.svg"
|
||||||
|
source.write_text(BLANK, encoding="utf-8")
|
||||||
|
output = tmp_path / "out.svg"
|
||||||
|
box_maker.BoxMaker().run(list(args) + ["--output={}".format(output), str(source)])
|
||||||
|
return output.read_text(encoding="utf-8") if output.exists() else ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_end_to_end_closed_box_with_labels(tmp_path):
|
||||||
|
svg = _run(tmp_path, "--labels=true")
|
||||||
|
assert svg.count("<path") == 6
|
||||||
|
for name in ("Front", "Back", "Left", "Right", "Bottom", "Lid"):
|
||||||
|
assert ">{}<".format(name) in svg
|
||||||
|
|
||||||
|
|
||||||
|
def test_end_to_end_too_small_reports_error(tmp_path, capsys):
|
||||||
|
_run(tmp_path, "--length=10")
|
||||||
|
err = capsys.readouterr().err
|
||||||
|
assert "length" in err and "at least 15 mm" in err
|
||||||
Loading…
Reference in New Issue
Block a user