@things-factory/figure-ui 10.1.25 → 10.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/client/graphql/index.ts +4 -1
  2. package/client/modeller/figure-animations.ts +229 -29
  3. package/client/modeller/figure-history-panel.ts +2 -2
  4. package/client/modeller/figure-inspector.ts +151 -5
  5. package/client/modeller/figure-preview.ts +5 -5
  6. package/client/modeller/figure-source.ts +33 -45
  7. package/client/modeller/figure-view.ts +20 -0
  8. package/client/modeller/joint-edits.ts +201 -0
  9. package/client/modeller/proposal-session.ts +8 -2
  10. package/client/pages/figure-modeller-page.ts +50 -15
  11. package/dist-client/graphql/index.js +4 -1
  12. package/dist-client/graphql/index.js.map +1 -1
  13. package/dist-client/modeller/figure-animations.d.ts +19 -0
  14. package/dist-client/modeller/figure-animations.js +206 -22
  15. package/dist-client/modeller/figure-animations.js.map +1 -1
  16. package/dist-client/modeller/figure-history-panel.js +2 -2
  17. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  18. package/dist-client/modeller/figure-inspector.d.ts +11 -0
  19. package/dist-client/modeller/figure-inspector.js +124 -4
  20. package/dist-client/modeller/figure-inspector.js.map +1 -1
  21. package/dist-client/modeller/figure-preview.js +5 -5
  22. package/dist-client/modeller/figure-preview.js.map +1 -1
  23. package/dist-client/modeller/figure-source.d.ts +12 -1
  24. package/dist-client/modeller/figure-source.js +13 -40
  25. package/dist-client/modeller/figure-source.js.map +1 -1
  26. package/dist-client/modeller/figure-view.d.ts +16 -0
  27. package/dist-client/modeller/figure-view.js +15 -0
  28. package/dist-client/modeller/figure-view.js.map +1 -1
  29. package/dist-client/modeller/joint-edits.d.ts +29 -0
  30. package/dist-client/modeller/joint-edits.js +175 -0
  31. package/dist-client/modeller/joint-edits.js.map +1 -0
  32. package/dist-client/modeller/proposal-session.d.ts +5 -0
  33. package/dist-client/modeller/proposal-session.js +3 -2
  34. package/dist-client/modeller/proposal-session.js.map +1 -1
  35. package/dist-client/pages/figure-modeller-page.d.ts +2 -22
  36. package/dist-client/pages/figure-modeller-page.js +46 -15
  37. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  38. package/dist-client/tsconfig.tsbuildinfo +1 -0
  39. package/dist-server/tsconfig.tsbuildinfo +1 -0
  40. package/package.json +3 -3
  41. package/test/ai-proposal-contract.test.ts +3 -2
  42. package/test/figure-source.test.ts +43 -57
  43. package/test/i18n-prefix-guard.test.ts +2 -3
  44. package/test/joint-edits.test.ts +130 -0
  45. package/test/preview-board-depth.test.ts +27 -0
  46. package/translations/en.json +119 -80
  47. package/translations/ja.json +208 -58
  48. package/translations/ko.json +139 -100
  49. package/translations/ms.json +206 -56
  50. package/translations/zh.json +207 -57
@@ -3,6 +3,7 @@
3
3
  "figure.button.add-clip": "add clip",
4
4
  "figure.button.add-key": "add key",
5
5
  "figure.button.add-parameter": "add parameter",
6
+ "figure.button.apply-fix": "apply fix · {part} {axis} face: {from} → {to}",
6
7
  "figure.button.ask": "ask",
7
8
  "figure.button.cancel": "cancel",
8
9
  "figure.button.center-x": "centre X",
@@ -18,6 +19,7 @@
18
19
  "figure.button.new-figure": "new figure",
19
20
  "figure.button.pivot-clear": "part centre",
20
21
  "figure.button.pivot-set": "set",
22
+ "figure.button.preview-fix": "preview fix · {part} {axis} face: {from} → {to}",
21
23
  "figure.button.redo": "redo",
22
24
  "figure.button.release-figure": "release",
23
25
  "figure.button.remove": "remove",
@@ -31,7 +33,7 @@
31
33
  "figure.button.to-floor": "to floor",
32
34
  "figure.button.undo": "undo",
33
35
  "figure.label.accepts": "accepts",
34
- "figure.label.anchor": "holds which face",
36
+ "figure.label.anchor": "anchored face",
35
37
  "figure.label.anchor-advanced": "advanced proportional sizing",
36
38
  "figure.label.anchor-aspect-x": "grows only as much as x does (keeps ratio)",
37
39
  "figure.label.anchor-aspect-y": "grows only as much as y does (keeps ratio)",
@@ -70,6 +72,7 @@
70
72
  "figure.label.axis-detail-short": "fidelity",
71
73
  "figure.label.axis-height": "height",
72
74
  "figure.label.axis-labels": "axis labels",
75
+ "figure.label.axis-name": "{axis} axis",
73
76
  "figure.label.axis-reactive": "state display",
74
77
  "figure.label.axis-reactive-short": "state",
75
78
  "figure.label.axis-readable": "recognisability",
@@ -85,7 +88,7 @@
85
88
  "figure.label.budget-surface": "surfaces outside the palette",
86
89
  "figure.label.budget-transparent": "transparent materials",
87
90
  "figure.label.budgets": "budgets",
88
- "figure.label.by-axis": "maturity by axis",
91
+ "figure.label.by-axis": "maturity by item",
89
92
  "figure.label.candidate-review": "candidate review",
90
93
  "figure.label.cast-shadow": "cast shadow",
91
94
  "figure.label.category": "category",
@@ -93,9 +96,11 @@
93
96
  "figure.label.channel-path": "moves",
94
97
  "figure.label.channel-pivot": "pivot",
95
98
  "figure.label.channel-target": "part",
99
+ "figure.label.channel-target-joints": "joints",
100
+ "figure.label.channel-target-parts": "parts",
96
101
  "figure.label.channels": "channels",
97
102
  "figure.label.clip-name": "clip name",
98
- "figure.label.clips": "animation · time runs",
103
+ "figure.label.clips": "animation (plays over time)",
99
104
  "figure.label.coordinate-ground": "coordinate ground",
100
105
  "figure.label.corner-round": "corner round",
101
106
  "figure.label.detail-L": "detailed",
@@ -134,8 +139,24 @@
134
139
  "figure.label.intensity": "intensity",
135
140
  "figure.label.interpolation-linear": "smooth",
136
141
  "figure.label.interpolation-step": "stepped",
142
+ "figure.label.joint": "joint",
143
+ "figure.label.joint-axis": "axis",
144
+ "figure.label.joint-limit-max": "max",
145
+ "figure.label.joint-limit-min": "min",
146
+ "figure.label.joint-limits-deg": "limits (degrees)",
147
+ "figure.label.joint-limits-mm": "limits (mm)",
148
+ "figure.label.joint-origin": "origin",
149
+ "figure.label.joint-parent": "attached to",
150
+ "figure.label.joint-parent-none": "none (figure frame)",
151
+ "figure.label.joint-type": "movement",
152
+ "figure.label.joint-type-continuous": "turn without limits",
153
+ "figure.label.joint-type-none": "fixed",
154
+ "figure.label.joint-type-prismatic": "slide",
155
+ "figure.label.joint-type-revolute": "turn within limits",
156
+ "figure.label.joint-value": "joint value",
137
157
  "figure.label.keep-round": "section",
138
158
  "figure.label.key-at": "at (s)",
159
+ "figure.label.key-at-value": "at (0–1)",
139
160
  "figure.label.key-light": "key light",
140
161
  "figure.label.maturity": "maturity",
141
162
  "figure.label.mode-edit": "edit",
@@ -160,9 +181,10 @@
160
181
  "figure.label.palette-role": "color role",
161
182
  "figure.label.palette-token": "palette token",
162
183
  "figure.label.parameter-default": "default",
184
+ "figure.label.parameter-duration": "full travel (s)",
163
185
  "figure.label.parameter-name": "parameter name",
164
186
  "figure.label.parameter-range": "range",
165
- "figure.label.parameters": "parameters · a value makes the pose",
187
+ "figure.label.parameters": "parameters (pose set by value)",
166
188
  "figure.label.part": "part",
167
189
  "figure.label.part-material": "material",
168
190
  "figure.label.part-position": "position (centre)",
@@ -176,6 +198,7 @@
176
198
  "figure.label.placement-center": "center-based (center)",
177
199
  "figure.label.placement-floor": "floor-based (floor)",
178
200
  "figure.label.placement-limit": "placement limit",
201
+ "figure.label.playback-speed": "speed",
179
202
  "figure.label.preset": "preset",
180
203
  "figure.label.preset-ceramic": "ceramic",
181
204
  "figure.label.preset-default": "default",
@@ -184,6 +207,7 @@
184
207
  "figure.label.preset-plastic": "plastic",
185
208
  "figure.label.preset-rubber": "rubber",
186
209
  "figure.label.preset-wood": "wood",
210
+ "figure.label.preview-base": "base",
187
211
  "figure.label.primitive-cube": "box",
188
212
  "figure.label.primitive-cylinder": "cylinder",
189
213
  "figure.label.primitive-polygon": "polygon",
@@ -241,30 +265,29 @@
241
265
  "figure.label.weight": "drawing cost",
242
266
  "figure.label.what-this-does": "what this figure does",
243
267
  "figure.text.accepts-anything": "empty means anything",
244
- "figure.text.accepts-explained": "only these types can be placed here. empty means no restriction. separate several with commas",
268
+ "figure.text.accepts-explained": "types allowed in this slot. separate with commas; leave empty for no limit.",
245
269
  "figure.text.add-a-shape": "click to add a part",
246
- "figure.text.all-lights-off": "both lights are off. only self-lit surfaces show; everything else goes black — this is how you check whether a lamp is lit.",
270
+ "figure.text.all-lights-off": "both lights are off; only emissive surfaces show.",
247
271
  "figure.text.already-released-edit-to-release-again": "already released. edit and save to go back to draft, then release again.",
248
- "figure.text.anchor-explained": "where this part stays as the box grows. Two touching parts on different faces drift apart as it grows.",
249
- "figure.text.anchors-are-not-drawn": "a slot is where things are placed, a port is where things enter and leave. neither is drawn on a drawing; the editor shows their positions",
250
- "figure.text.animated-parts-cost": "A moving part is drawn on its own — one more draw call each, and a matrix update every frame.",
251
- "figure.text.animated-parts-over": "Over the limit. Nothing is blocked, but many of these on one drawing will cost frames.",
252
- "figure.text.animation-empty-examples": "A turning roller · a sliding door leaf · a rising lift · a blinking beacon.",
253
- "figure.text.animation-empty-what-it-is": "This figure does not move yet. A clip is one set of motion across several parts — a door opening is the two leaves sliding apart, not two separate things.",
254
- "figure.text.animation-is-driven-by-a-fact": "Motion comes in two kinds: animation, where time runs, and parameters, where the value an instance gives is the pose.",
255
- "figure.text.animation-needs-a-named-part": "Add a part first. A channel names the part it moves.",
256
- "figure.text.assistant-proposed-a-figure": "here is a proposal the result is on the right.",
257
- "figure.text.axes-not-measurable-yet": "axes that cannot be measured yet are left off the chart {axes}. the reason is listed below.",
258
- "figure.text.bound-by-drawCalls": "draw calls reach the limit first {per} per instance, against {budget} for a drawing.",
259
- "figure.text.bound-by-triangles": "triangles reach the limit first — {per} per instance, against {budget} for a drawing.",
272
+ "figure.text.anchor-explained": "where this part stays as the box grows. two touching parts on different faces drift apart as it grows.",
273
+ "figure.text.anchors-are-not-drawn": "slots and ports are not drawn on a drawing; only the editor shows them.",
274
+ "figure.text.animated-parts-cost": "each moving part adds one draw call.",
275
+ "figure.text.animated-parts-over": "over the limit. many of these on one drawing will slow it down.",
276
+ "figure.text.animation-empty-examples": "a turning roller · a sliding door leaf · a rising lift · a blinking beacon.",
277
+ "figure.text.animation-empty-what-it-is": "no motion yet. add a clip to move parts.",
278
+ "figure.text.animation-needs-a-named-part": "add a part first.",
279
+ "figure.text.assistant-proposed-a-figure": "the proposal is shown on the right.",
280
+ "figure.text.axes-not-measurable-yet": "left off the chart because they cannot be measured: {axes}. the reasons are below.",
281
+ "figure.text.bound-by-drawCalls": "draw calls reach the limit first ({per} per instance, {budget} per drawing).",
282
+ "figure.text.bound-by-triangles": "triangles reach the limit first ({per} per instance, {budget} per drawing).",
260
283
  "figure.text.budget-not-measured-yet": "this target has not been measured yet.",
261
284
  "figure.text.cannot-release-now": "cannot be released yet",
262
- "figure.text.canvas-appears-once-a-part-exists": "the editing area where you place and move parts. pick one from shapes on the left to start.",
263
- "figure.text.capability-hand-written": "these capabilities were set by hand and match no rung. the names below are what gets stored",
285
+ "figure.text.canvas-appears-once-a-part-exists": "pick a shape on the left to add the first part.",
286
+ "figure.text.capability-hand-written": "set by hand, so it matches none of the levels above. it is saved under the names below.",
264
287
  "figure.text.capacity-from-repeat": "capacity comes from the repeat setting and the instance size",
265
- "figure.text.category-explained": "how the palette groups figures. one per figure — a figure in two groups meets the author twice.",
288
+ "figure.text.category-explained": "how the palette groups figures. one per figure.",
266
289
  "figure.text.category-placeholder": "e.g. conveyor",
267
- "figure.text.ceiling-height-explained": "where a ceiling figure hangs from in the preview. A figure does not know how tall the room it goes in is.",
290
+ "figure.text.ceiling-height-explained": "the height a ceiling figure hangs from in the preview.",
268
291
  "figure.text.center-on-x": "move to the centre of the base box on X.",
269
292
  "figure.text.center-on-z": "move to the centre of the base box on Z.",
270
293
  "figure.text.change-added": "a new part",
@@ -276,54 +299,64 @@
276
299
  "figure.text.change-placement": "placement",
277
300
  "figure.text.change-removed": "a part being removed",
278
301
  "figure.text.change-styleKit": "the style kit",
279
- "figure.text.channel-target-is-gone": "No such part. If you renamed the part, pick it again here.",
280
- "figure.text.clip-is-time-running": "Time runs. The value an instance gives is a speed — a turning roller works this way.",
281
- "figure.text.clip-name-is-how-it-is-driven": "An instance gives a speed by this name.",
282
- "figure.text.clip-name-taken": "Another clip already has this name. Instances drive by name, so two cannot share one.",
283
- "figure.text.clip-needs-a-channel": "With no channel there is nothing to move.",
284
- "figure.text.clip-needs-a-name": "Without a name nothing can drive it.",
302
+ "figure.text.channel-target-is-gone": "no such part. if you renamed the part, pick it again here.",
303
+ "figure.text.clip-is-time-running": "plays over time. the value an instance gives is the playback speed.",
304
+ "figure.text.clip-name-is-how-it-is-driven": "an instance gives a speed by this name.",
305
+ "figure.text.clip-name-taken": "another clip already has this name. enter a different name.",
306
+ "figure.text.clip-needs-a-channel": "add at least one channel.",
307
+ "figure.text.clip-needs-a-name": "enter a name.",
285
308
  "figure.text.collapse-parts-pane": "collapse the part list",
286
309
  "figure.text.collapse-properties-pane": "collapse the property pane",
287
310
  "figure.text.coordinate-ground-explained": "draws the origin and a grid on the floor",
288
311
  "figure.text.delete-figure-confirm": "delete {name}? drawings already using this type will no longer find it.",
289
- "figure.text.designate-in-properties": "nothing designated yet. select a part and set its role in the properties tab",
290
- "figure.text.detail-level-explained": "declares how detailed this figure is meant to be. S simple suits signs and posts, M normal suits most equipment, L detailed suits the large equipment a view is built around. the level sets the part limit, and maturity checks whether the figure lives up to it.",
312
+ "figure.text.designate-in-properties": "nothing designated yet. select a part and set its role in the properties tab.",
313
+ "figure.text.detail-level-explained": "S for signs and posts, M for most equipment, L for the large equipment a view centres on. the level sets the part limit.",
291
314
  "figure.text.discard-changes-and-start-new": "there are unsaved changes. discard them and start a new figure?",
292
315
  "figure.text.does-not-block": "does not block",
293
- "figure.text.emissive-explained": "the surface lights itself. 0 means it is not a lamp. it does not spill onto its surroundings.",
294
- "figure.text.environment-explained": "sets what metal surfaces reflect. it does not change the shape.",
316
+ "figure.text.emissive-explained": "how bright the surface itself is. 0 is unlit, and it does not light its surroundings.",
317
+ "figure.text.enter-a-type-name": "enter a type name.",
318
+ "figure.text.environment-explained": "sets what metal surfaces reflect. the shape does not change.",
295
319
  "figure.text.expand-parts-pane": "expand the part list",
296
320
  "figure.text.expand-properties-pane": "expand the property pane",
297
321
  "figure.text.figure-display-name": "display name",
322
+ "figure.text.figure-name-example": "e.g. overhead hoist transport",
323
+ "figure.text.figure-type-example": "e.g. OHT, AGV_LIFTER",
298
324
  "figure.text.figure-type-placeholder": "Figure type identifier (e.g. OHT)",
299
325
  "figure.text.find-by-this-tag": "find by this tag",
300
326
  "figure.text.fits-n-instances": "up to {n}",
301
- "figure.text.floor-explained": "overlaps the coordinate ground, so usually off",
327
+ "figure.text.floor-explained": "overlaps the coordinate ground, so it is usually off",
302
328
  "figure.text.follow-camera-explained": "turn off to fix the shadow direction",
303
329
  "figure.text.grade-decided-by": "what decided the grade",
304
- "figure.text.grade-explained": "the grade comes from how much of a declared limit is used, taking the one closest to its cap. averaging would hide a full limit behind the loose ones.",
330
+ "figure.text.grade-explained": "the grade is set by the usage of the item closest to its limit.",
305
331
  "figure.text.grade-scale-lead": "grade scale (limit used)",
306
332
  "figure.text.headroom-percent": "{percent}% headroom",
307
- "figure.text.hemisphere-light-explained": "even light from sky and ground. it lifts the shaded faces.",
333
+ "figure.text.hemisphere-light-explained": "even light from sky and ground. it brightens shaded faces.",
308
334
  "figure.text.hide-all-parts": "hide all",
309
335
  "figure.text.hide-this-part": "hide this part while you work. not saved",
310
- "figure.text.how-many-fit": "how many fit on one drawing before it slows down",
336
+ "figure.text.how-many-fit": "how many fit on one drawing without slowing it down",
311
337
  "figure.text.instances-unit": "instances",
312
- "figure.text.interpolation-explained": "How the gap between keys is filled.",
338
+ "figure.text.interpolation-explained": "how the gap between keys is filled.",
339
+ "figure.text.joint-channel-value-deg": "key values are joint angles in degrees.",
340
+ "figure.text.joint-channel-value-mm": "key values are joint travel in mm.",
341
+ "figure.text.joint-driven-by": "parameter \"{name}\" moves this joint. check it in the preview.",
342
+ "figure.text.joint-explained": "when the part it is attached to moves, this part moves with it. a joint sets the axis this part turns about or slides along relative to that part, and its limits. a parameter created with the joint sets its value.",
343
+ "figure.text.joint-limits-must-include-zero": "the limits must include 0. the pose as drawn is 0.",
344
+ "figure.text.joint-not-driven": "no parameter moves this joint. add one in the animation tab.",
345
+ "figure.text.joint-value-beyond-limits": "outside the joint limits.",
313
346
  "figure.text.keep-round-cost": "1 draw call",
314
347
  "figure.text.keep-round-merged": "merged into a group",
315
348
  "figure.text.keep-round-off": "off",
316
349
  "figure.text.keep-round-on": "keep round",
317
- "figure.text.keys-must-rise": "Each key must come after the one before it.",
350
+ "figure.text.keys-must-rise": "each key must come after the one before it.",
318
351
  "figure.text.limit-comes-from-level": "the detail level sets this limit.",
319
- "figure.text.maturity-blocked-no-binding": "the format has no way to connect a state value to a part yet, and the renderer does not read it.",
352
+ "figure.text.maturity-blocked-no-binding": "state display is not measured yet.",
320
353
  "figure.text.maturity-blocked-no-parts": "nothing to measure — there are no parts.",
321
- "figure.text.maturity-consistency-because": "checks textures, segment presets and material count what it takes to sit well with other assets and stay crisp when zoomed.",
354
+ "figure.text.maturity-consistency-because": "checks texture use, recommended segment counts and the number of materials.",
322
355
  "figure.text.maturity-consistency-clean": "no issues",
323
356
  "figure.text.maturity-consistency-faults": "has issues",
324
- "figure.text.maturity-detail-because": "how much of the declared level you actually used.",
325
- "figure.text.maturity-readable-because": "how many distinct colour regions are visible from directly above, and how evenly. Viewpoint entropy (Vazquez et al.) computed over colour regions rather than faces — counting faces would reward raising the segment count alone.",
326
- "figure.text.maturity-reuse-because": "shapes saved because several parts share one. a low number is not a fault.",
357
+ "figure.text.maturity-detail-because": "the share of the declared level's part limit that is used.",
358
+ "figure.text.maturity-readable-because": "how many distinct colour regions show from above, and how evenly.",
359
+ "figure.text.maturity-reuse-because": "shapes saved because several parts share one.",
327
360
  "figure.text.maturity-scale-because": "measured against a drawing holding 500 assets.",
328
361
  "figure.text.mirror-explained": "adds a mirrored copy on the other side — for parts that come in pairs, like legs or arms.",
329
362
  "figure.text.mirror-would-overlap": "already centred — a mirror would land in the same place and overlap.",
@@ -339,78 +372,84 @@
339
372
  "figure.text.no-findings": "nothing to report.",
340
373
  "figure.text.not-measurable-yet": "not measurable",
341
374
  "figure.text.not-measured-yet": "not measured",
342
- "figure.text.nothing-released-yet": "nothing released yet. releasing keeps the figure as it is now, as a version.",
343
- "figure.text.on-by-default-explained": "the starting state when placed on a drawing. an instance switching it wins.",
375
+ "figure.text.nothing-released-yet": "nothing released yet.",
376
+ "figure.text.on-by-default-explained": "the starting state when placed on a drawing. a value set on the instance takes priority.",
344
377
  "figure.text.other-segment-count": "enter a value",
345
- "figure.text.palette-purpose-explained": "Choose the visual role this part plays, not an implementation color name.",
346
- "figure.text.parameter-curve-is-the-value-axis": "The curve’s time axis is the value axis. Its start is the range minimum and its end the maximum, and that end time is how long a full move takes.",
347
- "figure.text.parameter-is-a-value-not-a-timeline": "Nothing is played back; it simply is this far “the hoist is 1200mm down”. An instance gives that number.",
348
- "figure.text.parameter-name-is-how-a-value-arrives": "An instance gives a value by this name.",
349
- "figure.text.parameter-needs-a-name": "Without a name a value has nowhere to land.",
378
+ "figure.text.palette-purpose-explained": "choose the color role this part plays on screen.",
379
+ "figure.text.parameter-curve-is-the-value-axis": "keys sit on the value (0 = minimum, 1 = maximum). full travel time is set separately.",
380
+ "figure.text.parameter-duration-empty-is-instant": "seconds to move from minimum to maximum. leave empty to apply a new value at once.",
381
+ "figure.text.parameter-is-a-value-not-a-timeline": "shows the pose for the value an instance gives (e.g. hoist 1200mm down).",
382
+ "figure.text.parameter-key-within-span": "enter a key between 0 (minimum) and 1 (maximum).",
383
+ "figure.text.parameter-name-is-how-a-value-arrives": "an instance gives a value by this name.",
384
+ "figure.text.parameter-needs-a-name": "enter a name.",
350
385
  "figure.text.part-limit-reached": "the detail level allows no more parts — raise the level or remove a part.",
351
- "figure.text.part-limit-reached-short": "level {level} allows {limit} parts. Raise the level or remove a part.",
386
+ "figure.text.part-limit-reached-short": "level {level} allows {limit} parts. raise the level or remove a part.",
352
387
  "figure.text.part-name-cannot-be-empty": "give the part a name.",
353
- "figure.text.path-rotation-unit": "Euler degrees the same convention as the part's own rotation.",
354
- "figure.text.path-scale-unit": "A ratio. 1 is full size; 0 makes the part vanish.",
355
- "figure.text.path-translation-unit": "Offset from where the part sits. 0 leaves it in place.",
388
+ "figure.text.path-rotation-unit": "euler angles in degrees, the same convention as the part's rotation.",
389
+ "figure.text.path-scale-unit": "a ratio. 1 is full size; 0 makes the part vanish.",
390
+ "figure.text.path-translation-unit": "offset from where the part sits. 0 leaves it in place.",
356
391
  "figure.text.pick-a-shape-above-to-start": "pick a shape above to add the first part.",
357
392
  "figure.text.picked-changes-do-not-validate": "the selected changes do not make a valid figure",
358
- "figure.text.pivot-defaults-to-the-part-centre": "Left unset, the part turns about its own centre. Coordinates are local to the part.",
393
+ "figure.text.pivot-defaults-to-the-part-centre": "left unset, the part turns about its own centre. coordinates are local to the part.",
359
394
  "figure.text.placement-explained": "sets the anchor plane in 3D scene (floor: AGV/conveyor, ceiling: OHT/hoist, center: sensor/drone).",
360
- "figure.text.preview-blocked-by-errors": "cannot build the figure breaks the format. the maturity tab has the reason.",
361
- "figure.text.preview-needs-a-part": "shows how it looks placed on a drawing. appears once you make a part.",
362
- "figure.text.proposal-feedback-note": "Optional note for the next suggestion (this session only)",
395
+ "figure.text.preview-blocked-by-errors": "cannot show the preview because of format errors. see the maturity tab for the reason.",
396
+ "figure.text.preview-needs-a-part": "add a part to see how it looks placed on a drawing.",
397
+ "figure.text.proposal-feedback-note": "optional note for the next suggestion (this session only)",
363
398
  "figure.text.proposal-feedback-placeholder": "e.g. keep the silhouette, but make the port easier to read",
364
399
  "figure.text.put-on-floor": "drop it to the floor (Z=0).",
365
- "figure.text.range-needs-room": "The maximum must be above the minimum; equal leaves no room to move.",
400
+ "figure.text.range-needs-room": "the maximum must be above the minimum.",
366
401
  "figure.text.recipe-conveyor": "animation · an instance gives it a speed",
367
402
  "figure.text.recipe-gate": "parameter · an instance gives how far open, 0 to 100 %",
368
403
  "figure.text.recipe-lift": "parameter · an instance gives the height, 0 to 100 mm",
369
404
  "figure.text.recipe-pusher": "parameter · an instance gives the travel, 0 to 100 mm",
370
- "figure.text.release-needs-a-save-first": "there are unsaved changes. a release publishes what was saved, so save first.",
405
+ "figure.text.release-check-fix": "release check fix · {part} {axis} face: {from} {to}",
406
+ "figure.text.release-needs-a-save-first": "there are unsaved changes. save first.",
371
407
  "figure.text.releasing": "releasing…",
372
- "figure.text.renaming-a-part-breaks-bindings": "the name is a stored identifier — changing it breaks bindings on instances already placed.",
373
- "figure.text.repeat-count-capped": "stops at {limit} each copy is one draw call",
408
+ "figure.text.renaming-a-part-breaks-bindings": "renaming breaks bindings on instances already placed.",
409
+ "figure.text.repeat-count-capped": "up to {limit}. each copy adds one draw call.",
374
410
  "figure.text.repeat-count-now": "{count} at the authored size",
375
- "figure.text.repeat-edits-the-unit": "you author one unit even though several appear. the gizmo moves the whole run, while size and rotation apply to that one unit — the pitch is declared separately so the layout stays. a single copy cannot be edited on its own.",
411
+ "figure.text.repeat-edits-the-unit": "repeated copies are edited as one. to change a single copy, add it as a separate part.",
376
412
  "figure.text.repeat-grows-with-instance": "the count grows as the instance grows on this axis. the pitch stays",
377
- "figure.text.repeat-keeps-count": "the count stays and each copy grows, because this axis anchors to scale",
378
- "figure.text.repeat-pitch-explained": "centre to centre distance. it does not depend on the part size, so widening or turning the part leaves the layout alone.",
413
+ "figure.text.repeat-keeps-count": "the count stays and each copy grows.",
414
+ "figure.text.repeat-pitch-explained": "centre-to-centre distance. it stays when the part size changes.",
379
415
  "figure.text.report-appears-when-you-add-a-part": "shows triangles, draw calls, and how many fit on one drawing. measured once you make a part.",
380
416
  "figure.text.reuse-score-explained": "how much headroom is left in the declared limits. the one closest to its cap sets the score.",
381
- "figure.text.revert-to-version-confirm": "revert to v{version}? what you are working on will be replaced by that version.",
417
+ "figure.text.revert-to-version-confirm": "revert to v{version}? what you are editing will be replaced by that version.",
382
418
  "figure.text.role-none-means-drawn": "with none checked this is just a drawn part",
383
- "figure.text.rung-flow-note": "holds, and takes in and sends out through ports. conveyors, chutes, sorters",
384
- "figure.text.rung-hold-note": "has places to put things. racks, pallets, benches",
419
+ "figure.text.rung-flow-note": "holds things and takes them in and out through ports. conveyors, chutes, sorters",
420
+ "figure.text.rung-hold-note": "has slots to put things in. racks, pallets, benches",
385
421
  "figure.text.rung-none-note": "takes nothing. most figures belong here",
386
- "figure.text.save-new-figure-desc": "Set the unique type code and display name for this figure.",
422
+ "figure.text.save-new-figure-desc": "set the unique type code and display name for this figure.",
387
423
  "figure.text.save-the-figure-before-releasing": "this figure has not been saved yet. save it and you can release it.",
388
424
  "figure.text.saving-figure": "saving",
389
- "figure.text.scene-reference-explained": "which kind of drawing you are looking at this on. Not the same as the figure's own placement put a ceiling figure on a floor-based drawing and the drawing stays put while the figure hangs at ceiling height.",
425
+ "figure.text.scene-reference-explained": "the kind of drawing the preview places the figure on. the figure's own placement does not change.",
390
426
  "figure.text.search-figure": "search by name, type, description, category or tag",
391
427
  "figure.text.select-a-part-to-edit": "select a part to edit it here.",
428
+ "figure.text.server-returned-no-data": "the server response had no data. try again.",
392
429
  "figure.text.show-all-parts": "show all",
393
430
  "figure.text.show-this-part": "show this part again",
394
- "figure.text.sizing-costs-one-draw-call": "a part that does not scale proportionally cannot be merged with the others, so it costs one more draw call. that shows up in the group count under maturity.",
431
+ "figure.text.sizing-costs-one-draw-call": "a part that does not scale proportionally adds one draw call.",
395
432
  "figure.text.sizing-fixed-explained": "the size stays, only the position follows — like a motor or a gauge.",
396
- "figure.text.sizing-repeat-explained": "the count grows instead of the size. the shape is shared so vertices do not grow, but each copy costs one draw call.",
433
+ "figure.text.sizing-repeat-explained": "the count grows instead of the size. each copy adds one draw call.",
397
434
  "figure.text.sizing-scale-explained": "this part grows with the instance.",
398
435
  "figure.text.sizing-stretch-explained": "only one axis stretches — like a frame, a belt or a rail.",
399
- "figure.text.slots-need-capability": "select \"holds things\" or above before designating a slot",
436
+ "figure.text.slots-need-capability": "select \"holds things\" or above before designating a slot.",
400
437
  "figure.text.snap-explained": "rounds position and size to the grid — the same spacing you see on the canvas.",
401
- "figure.text.tags-explained": "for the finding a single category cannot do. a figure may carry several a conveyor that is also outbound. separate them with commas.",
438
+ "figure.text.source-changed-ask-again": "the source changed. discard the candidate and ask again in AI assist.",
439
+ "figure.text.tags-explained": "tags used for search. separate several with commas.",
402
440
  "figure.text.tags-placeholder": "e.g. outbound, automatic, level 2",
403
441
  "figure.text.thumbnail-not-made-yet": "no thumbnail yet",
404
- "figure.text.tools-explained": "Use alignment after setting position, size, and rotation. From left: centre X, centre Z, put on floor, mirror X, mirror Z, and snap to grid. Hover an icon to confirm its action.",
442
+ "figure.text.tools-explained": "from left: centre X, centre Z, to floor, mirror X, mirror Z, snap to grid.",
405
443
  "figure.text.triangle-count": "{count} triangles",
406
- "figure.text.two-keys-at-least": "A channel needs two keys. With one it is not motion, it is a single pose.",
444
+ "figure.text.two-keys-at-least": "add at least two keys.",
407
445
  "figure.text.type-name-already-taken": "the type name {type} is already in use. pick another one — it cannot be changed later.",
408
- "figure.text.type-name-cannot-change": "the type name is a stored identifier and cannot be changed after creation.",
409
- "figure.text.unit-is-a-word-not-a-rule": "A word the screen and the instance read mm, deg, %. The format does not interpret it.",
446
+ "figure.text.type-name-cannot-change": "the type name cannot be changed after creation.",
447
+ "figure.text.type-name-will-not-release": "this name cannot be released. use capitals, digits and single underscores, 3 to 32 characters (e.g. AGV_LIFTER).",
448
+ "figure.text.unit-is-a-word-not-a-rule": "a display unit (e.g. mm, deg, %). it is not used in calculation.",
410
449
  "figure.text.unsaved-changes": "unsaved",
411
450
  "figure.text.version-number": "v{version}",
412
- "figure.text.view-settings-are-not-saved": "these settings are not saved. they make the view comfortable to work in and do not travel with the asset.",
413
- "figure.text.what-a-figure-is": "a Figure is a 3D component you author. Compose it from simple shapes and it becomes a component you can place on a drawing, sharing shape and material across every instance.",
451
+ "figure.text.view-settings-are-not-saved": "these settings are not saved.",
452
+ "figure.text.what-a-figure-is": "a Figure is a 3D component you author. compose it from simple shapes and it becomes a component you can place on a drawing, sharing shape and material across every instance.",
414
453
  "figure.text.what-changed-in-this-release": "what changed in this release",
415
454
  "figure.title.figures": "figures",
416
455
  "figure.title.new-figure": "new figure",