@things-factory/figure-ui 10.1.76 → 10.1.77

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 (125) hide show
  1. package/dist-client/tsconfig.tsbuildinfo +1 -1
  2. package/package.json +2 -2
  3. package/dist-client/graphql/v3-store.d.ts +0 -3
  4. package/dist-client/graphql/v3-store.js +0 -39
  5. package/dist-client/graphql/v3-store.js.map +0 -1
  6. package/dist-client/modeller/figure-v3-capability.d.ts +0 -22
  7. package/dist-client/modeller/figure-v3-capability.js +0 -141
  8. package/dist-client/modeller/figure-v3-capability.js.map +0 -1
  9. package/dist-client/modeller/figure-v3-clips.d.ts +0 -31
  10. package/dist-client/modeller/figure-v3-clips.js +0 -268
  11. package/dist-client/modeller/figure-v3-clips.js.map +0 -1
  12. package/dist-client/modeller/figure-v3-controls.d.ts +0 -36
  13. package/dist-client/modeller/figure-v3-controls.js +0 -226
  14. package/dist-client/modeller/figure-v3-controls.js.map +0 -1
  15. package/dist-client/modeller/figure-v3-first-save.d.ts +0 -20
  16. package/dist-client/modeller/figure-v3-first-save.js +0 -118
  17. package/dist-client/modeller/figure-v3-first-save.js.map +0 -1
  18. package/dist-client/modeller/figure-v3-links.d.ts +0 -77
  19. package/dist-client/modeller/figure-v3-links.js +0 -649
  20. package/dist-client/modeller/figure-v3-links.js.map +0 -1
  21. package/dist-client/modeller/figure-v3-material.d.ts +0 -39
  22. package/dist-client/modeller/figure-v3-material.js +0 -323
  23. package/dist-client/modeller/figure-v3-material.js.map +0 -1
  24. package/dist-client/modeller/figure-v3-parts.d.ts +0 -69
  25. package/dist-client/modeller/figure-v3-parts.js +0 -559
  26. package/dist-client/modeller/figure-v3-parts.js.map +0 -1
  27. package/dist-client/modeller/figure-v3-preview.d.ts +0 -34
  28. package/dist-client/modeller/figure-v3-preview.js +0 -346
  29. package/dist-client/modeller/figure-v3-preview.js.map +0 -1
  30. package/dist-client/modeller/figure-v3-properties.d.ts +0 -45
  31. package/dist-client/modeller/figure-v3-properties.js +0 -239
  32. package/dist-client/modeller/figure-v3-properties.js.map +0 -1
  33. package/dist-client/modeller/figure-v3-recipe-catalog.d.ts +0 -38
  34. package/dist-client/modeller/figure-v3-recipe-catalog.js +0 -399
  35. package/dist-client/modeller/figure-v3-recipe-catalog.js.map +0 -1
  36. package/dist-client/modeller/figure-v3-verdict.d.ts +0 -64
  37. package/dist-client/modeller/figure-v3-verdict.js +0 -787
  38. package/dist-client/modeller/figure-v3-verdict.js.map +0 -1
  39. package/dist-client/modeller/v3-ai-proposal.d.ts +0 -51
  40. package/dist-client/modeller/v3-ai-proposal.js +0 -64
  41. package/dist-client/modeller/v3-ai-proposal.js.map +0 -1
  42. package/dist-client/modeller/v3-align.d.ts +0 -30
  43. package/dist-client/modeller/v3-align.js +0 -75
  44. package/dist-client/modeller/v3-align.js.map +0 -1
  45. package/dist-client/modeller/v3-canvas.d.ts +0 -72
  46. package/dist-client/modeller/v3-canvas.js +0 -379
  47. package/dist-client/modeller/v3-canvas.js.map +0 -1
  48. package/dist-client/modeller/v3-clips.d.ts +0 -39
  49. package/dist-client/modeller/v3-clips.js +0 -58
  50. package/dist-client/modeller/v3-clips.js.map +0 -1
  51. package/dist-client/modeller/v3-controls.d.ts +0 -28
  52. package/dist-client/modeller/v3-controls.js +0 -28
  53. package/dist-client/modeller/v3-controls.js.map +0 -1
  54. package/dist-client/modeller/v3-editor.d.ts +0 -203
  55. package/dist-client/modeller/v3-editor.js +0 -1526
  56. package/dist-client/modeller/v3-editor.js.map +0 -1
  57. package/dist-client/modeller/v3-fields.d.ts +0 -45
  58. package/dist-client/modeller/v3-fields.js +0 -83
  59. package/dist-client/modeller/v3-fields.js.map +0 -1
  60. package/dist-client/modeller/v3-figure-facts.d.ts +0 -35
  61. package/dist-client/modeller/v3-figure-facts.js +0 -55
  62. package/dist-client/modeller/v3-figure-facts.js.map +0 -1
  63. package/dist-client/modeller/v3-first-save.d.ts +0 -17
  64. package/dist-client/modeller/v3-first-save.js +0 -26
  65. package/dist-client/modeller/v3-first-save.js.map +0 -1
  66. package/dist-client/modeller/v3-gizmo.d.ts +0 -72
  67. package/dist-client/modeller/v3-gizmo.js +0 -111
  68. package/dist-client/modeller/v3-gizmo.js.map +0 -1
  69. package/dist-client/modeller/v3-help.d.ts +0 -18
  70. package/dist-client/modeller/v3-help.js +0 -37
  71. package/dist-client/modeller/v3-help.js.map +0 -1
  72. package/dist-client/modeller/v3-joint-drag.d.ts +0 -19
  73. package/dist-client/modeller/v3-joint-drag.js +0 -97
  74. package/dist-client/modeller/v3-joint-drag.js.map +0 -1
  75. package/dist-client/modeller/v3-joint-fan.d.ts +0 -9
  76. package/dist-client/modeller/v3-joint-fan.js +0 -276
  77. package/dist-client/modeller/v3-joint-fan.js.map +0 -1
  78. package/dist-client/modeller/v3-keys.d.ts +0 -22
  79. package/dist-client/modeller/v3-keys.js +0 -26
  80. package/dist-client/modeller/v3-keys.js.map +0 -1
  81. package/dist-client/modeller/v3-links.d.ts +0 -267
  82. package/dist-client/modeller/v3-links.js +0 -480
  83. package/dist-client/modeller/v3-links.js.map +0 -1
  84. package/dist-client/modeller/v3-live.d.ts +0 -37
  85. package/dist-client/modeller/v3-live.js +0 -68
  86. package/dist-client/modeller/v3-live.js.map +0 -1
  87. package/dist-client/modeller/v3-maturity.d.ts +0 -120
  88. package/dist-client/modeller/v3-maturity.js +0 -313
  89. package/dist-client/modeller/v3-maturity.js.map +0 -1
  90. package/dist-client/modeller/v3-occupancy.d.ts +0 -18
  91. package/dist-client/modeller/v3-occupancy.js +0 -24
  92. package/dist-client/modeller/v3-occupancy.js.map +0 -1
  93. package/dist-client/modeller/v3-palette.d.ts +0 -7
  94. package/dist-client/modeller/v3-palette.js +0 -46
  95. package/dist-client/modeller/v3-palette.js.map +0 -1
  96. package/dist-client/modeller/v3-preview-model.d.ts +0 -47
  97. package/dist-client/modeller/v3-preview-model.js +0 -68
  98. package/dist-client/modeller/v3-preview-model.js.map +0 -1
  99. package/dist-client/modeller/v3-product-session.d.ts +0 -510
  100. package/dist-client/modeller/v3-product-session.js +0 -157
  101. package/dist-client/modeller/v3-product-session.js.map +0 -1
  102. package/dist-client/modeller/v3-recipe-source.d.ts +0 -8
  103. package/dist-client/modeller/v3-recipe-source.js +0 -14
  104. package/dist-client/modeller/v3-recipe-source.js.map +0 -1
  105. package/dist-client/modeller/v3-recipes.d.ts +0 -37
  106. package/dist-client/modeller/v3-recipes.js +0 -103
  107. package/dist-client/modeller/v3-recipes.js.map +0 -1
  108. package/dist-client/modeller/v3-refusals.d.ts +0 -68
  109. package/dist-client/modeller/v3-refusals.js +0 -142
  110. package/dist-client/modeller/v3-refusals.js.map +0 -1
  111. package/dist-client/modeller/v3-renderer.d.ts +0 -40
  112. package/dist-client/modeller/v3-renderer.js +0 -73
  113. package/dist-client/modeller/v3-renderer.js.map +0 -1
  114. package/dist-client/modeller/v3-stage-model.d.ts +0 -39
  115. package/dist-client/modeller/v3-stage-model.js +0 -147
  116. package/dist-client/modeller/v3-stage-model.js.map +0 -1
  117. package/dist-client/modeller/v3-thumbnail.d.ts +0 -3
  118. package/dist-client/modeller/v3-thumbnail.js +0 -75
  119. package/dist-client/modeller/v3-thumbnail.js.map +0 -1
  120. package/dist-client/pages/figure-v3-modeller-page.d.ts +0 -102
  121. package/dist-client/pages/figure-v3-modeller-page.js +0 -548
  122. package/dist-client/pages/figure-v3-modeller-page.js.map +0 -1
  123. package/dist-client/tsconfig.chk.tsbuildinfo +0 -1
  124. package/dist-client/tsconfig.fui.tsbuildinfo +0 -1
  125. package/dist-client/tsconfig.scratch-fm.tsbuildinfo +0 -1
@@ -1,649 +0,0 @@
1
- import { __decorate, __metadata } from "tslib";
2
- import '@material/web/icon/icon.js';
3
- import { css, html, LitElement, nothing } from 'lit';
4
- import { customElement, property, state } from 'lit/decorators.js';
5
- import { i18next, localize } from '@operato/i18n';
6
- import { V3_FACES } from '@hatiolab/figure-model';
7
- import { acceptsOf, alignAxesOf, mountingPlaneOf, alignOf, AXES, canFasten, facesOf, canFollow, dimensionNow, followableAxes, positionIsShare, choiceOf, heldBy, followOf, hangsFrom, shownWhenOf, visibilityControlsOf, placeKeptOf, fasteningOf, hasInstanceSize, holdsThings, leadersFor, motionOf, motionFormOf, motionJoint, placeFacesOf, placeIdFor, placesOn, rowAxesOf, positionsWorkedOut } from './v3-links.js';
8
- import { SidePanelStyles } from './side-panel-styles.js';
9
- import { HelpToggles } from './v3-help.js';
10
- const FACES = Object.keys(V3_FACES);
11
- const freshFasten = () => ({
12
- face: 'bottom',
13
- toPlane: false,
14
- target: '',
15
- targetFace: 'top',
16
- facing: 'meet',
17
- gap: 0,
18
- twist: 0,
19
- align: {},
20
- alignMm: {}
21
- });
22
- const freshMotion = () => ({ kind: 'slide', axis: 'x', id: '', min: 0, max: 500, follow: '', multiplier: 1, offset: 0, travel: '', travelPlus: 0 });
23
- const freshRow = () => ({ axis: '', pitch: 200, fills: true, count: 5 });
24
- const freshPlace = () => ({ role: 'grip', face: 'top', label: '', accepts: '', capacity: 1 });
25
- /**
26
- * What a part is tied to — its size response, what it is fastened to, how it moves.
27
- *
28
- * 「Build Once, Fit Anywhere」 lives here: a dimension that follows the instance size or another part is what lets
29
- * a figure be placed at any size, and a part fastened to the face it rests on is what keeps it there when that
30
- * happens. The release gate refuses a figure that falls apart when resized, and tells the author to fasten the
31
- * parts that come away — this is where they do it. Motion is a joint: a slide or a turn driven by a control.
32
- *
33
- * The sections are dimensions, placement and joint, in that order. Every
34
- * change is one figure-model authoring command, sent as `v3-command` and run by the editor through the session — one
35
- * step of the undo history. Choices the command would refuse (a round body fastened, a size following an instance
36
- * size the figure does not declare) are not offered, so the author is not sent into a refusal.
37
- */
38
- let FigureV3Links = class FigureV3Links extends localize(i18next)(LitElement) {
39
- constructor() {
40
- super(...arguments);
41
- this.help = new HelpToggles(this);
42
- /** The selected part. */
43
- this.partId = '';
44
- this.locked = false;
45
- /** Which ties this shows: the part's shape (size and placement, in 속성), its places (자리·능력) or its motion (움직임). */
46
- this.section = 'shape';
47
- this.fasten = freshFasten();
48
- this.motion = freshMotion();
49
- this.place = freshPlace();
50
- this.row = freshRow();
51
- /** The motion form is open over the part's own motion, to change it where it is. */
52
- this.editingMotion = false;
53
- }
54
- static { this.styles = [SidePanelStyles, css `
55
- :host {
56
- display: block;
57
- }
58
- /* The measure's name, then its rule: X, or a cylinder's 반지름 · 길이, on one line. */
59
- div[row] > b {
60
- white-space: nowrap;
61
- }
62
- div[row] {
63
- display: grid;
64
- grid-template-columns: minmax(24px, max-content) minmax(0, 1fr);
65
- gap: 6px;
66
- align-items: center;
67
- }
68
- b[bound] {
69
- font-weight: 600;
70
- }
71
- /* The difference a following measure keeps: under its choice, on the same line as its words. */
72
- label[inline-plus] {
73
- display: flex;
74
- align-items: center;
75
- gap: 6px;
76
- }
77
- label[inline-plus] input {
78
- flex: 1;
79
- min-width: 0;
80
- }
81
- `]; }
82
- /*
83
- The forms describe the next command for the selected part, so another part starts from the defaults: carrying the
84
- last part's choices over fastened a shutter curtain level with a right edge nobody chose for it (2026-09-25).
85
- */
86
- willUpdate(changed) {
87
- if (changed.has('partId') && changed.get('partId') !== undefined) {
88
- /* A part's own fastening is not another part's choices: the form opens on it (walk 1, 2026-09-26). */
89
- const own = this.asset && this.partId ? fasteningOf(this.asset, this.partId) : null;
90
- this.fasten = own ? { ...freshFasten(), ...own } : freshFasten();
91
- this.motion = freshMotion();
92
- this.place = freshPlace();
93
- this.row = freshRow();
94
- this.editingMotion = false;
95
- }
96
- }
97
- render() {
98
- const asset = this.asset;
99
- if (!asset || !this.partId)
100
- return nothing;
101
- if (this.section === 'motion')
102
- return html `<section><h3>${i18next.t('figure.label.motion')}${this.help.button('figure.text.motion-explained')}</h3>${this.help.hint('figure.text.motion-explained')}
103
- ${this.renderMotion(asset)}${this.renderShown(asset)}</section>`;
104
- if (this.section === 'places')
105
- return html `<section><h3>${i18next.t('figure.label.places-on-part', { part: this.partId })}${this.help.button('figure.text.place-explained')}</h3>${this.help.hint('figure.text.place-explained')}
106
- ${placeFacesOf(asset, this.partId).length ? this.renderPlaces(asset) : html `<p hint>${i18next.t('figure.text.places-need-a-box')}</p>`}</section>`;
107
- return html `
108
- <section>
109
- <h3>${i18next.t('figure.label.dimensions')}</h3>
110
- ${canFollow(asset, this.partId) ? this.renderDimensions(asset) : html `<p hint>${i18next.t('figure.text.links-need-a-box')}</p>`}
111
- </section>
112
- <section>
113
- <h3>${i18next.t('figure.label.placement')}${this.help.button('figure.text.fastening-explained')}</h3>${this.help.hint('figure.text.fastening-explained')}
114
- ${this.renderPositions(asset)}
115
- ${canFasten(asset, this.partId) ? this.renderFastening(asset) : html `<p hint>${i18next.t('figure.text.links-need-a-box')}</p>`}
116
- </section>
117
- ${this.renderRow(asset)}
118
- `;
119
- }
120
- /** Where things are held on this part: a seat things rest on, or a grip things are held in. */
121
- renderPlaces(asset) {
122
- const faces = placeFacesOf(asset, this.partId);
123
- const p = this.place;
124
- const face = faces.includes(p.face) ? p.face : faces[0];
125
- const accepts = acceptsOf(p.accepts);
126
- const capacityOk = Number.isSafeInteger(p.capacity) && p.capacity >= 1;
127
- const roleName = (role) => i18next.t('figure.label.place-' + role);
128
- return html `
129
- ${placesOn(asset, this.partId).map(one => html `<div pair>
130
- <span>
131
- <b bound>${one.label}</b>
132
- <small>${roleName(one.role)} · ${one.face ? i18next.t('figure.label.face-' + one.face) : ''} · ${one.accepts.join(', ')} · ${i18next.t('figure.label.place-holds', { count: one.capacity })}</small>
133
- </span>
134
- <button act ?disabled=${this.locked} @click=${() => this.command({ kind: 'remove-binding', id: one.id })}>
135
- <md-icon>close</md-icon>${i18next.t('figure.button.remove-place')}
136
- </button>
137
- </div>`)}
138
- ${holdsThings(asset)
139
- ? nothing
140
- : html `<p hint>${i18next.t('figure.text.place-needs-holding')}</p>
141
- <button act ?disabled=${this.locked} @click=${() => this.declareHolding(asset)}>
142
- <md-icon>inventory_2</md-icon>${i18next.t('figure.button.declare-holding')}
143
- </button>`}
144
- <div pair>
145
- <select @change=${(e) => (this.place = { ...p, role: e.target.value })}>
146
- <option value="grip" ?selected=${p.role === 'grip'}>${roleName('grip')}</option>
147
- <option value="seat" ?selected=${p.role === 'seat'}>${roleName('seat')}</option>
148
- </select>
149
- <select @change=${(e) => (this.place = { ...p, face: e.target.value })}>
150
- ${faces.map(one => html `<option value=${one} ?selected=${one === face}>${i18next.t('figure.label.face-' + one)}</option>`)}
151
- </select>
152
- </div>
153
- <div form>
154
- <span>${i18next.t('figure.label.place-name')}</span>
155
- <input .value=${p.label} placeholder=${roleName(p.role)} @change=${(e) => (this.place = { ...p, label: e.target.value })} />
156
- <span>${i18next.t('figure.label.place-accepts')}</span>
157
- <input .value=${p.accepts} placeholder="BOX, TOTE" @input=${(e) => (this.place = { ...p, accepts: e.target.value })} />
158
- <span>${i18next.t('figure.label.place-capacity')}</span>
159
- <input type="number" min="1" step="1" .value=${String(p.capacity)} @change=${(e) => (this.place = { ...p, capacity: e.target.valueAsNumber })} />
160
- </div>
161
- ${accepts.length ? nothing : html `<small>${i18next.t('figure.text.place-accepts-needed')}</small>`}
162
- <button
163
- act
164
- ?disabled=${this.locked || !accepts.length || !capacityOk}
165
- @click=${() => this.command({
166
- kind: 'add-binding',
167
- id: placeIdFor(asset, this.partId, p.role),
168
- label: p.label.trim() || roleName(p.role),
169
- role: p.role,
170
- part: this.partId,
171
- face,
172
- accepts,
173
- capacity: p.capacity
174
- })}
175
- >
176
- <md-icon>back_hand</md-icon>${i18next.t('figure.button.add-place')}
177
- </button>
178
- `;
179
- }
180
- /** The figure holds things: `Capacity` and `Holdable`, the ladder's middle rung. One part edit, one undo step. */
181
- declareHolding(asset) {
182
- const names = [...new Set([...(asset.document.capabilities.names ?? []), 'Capacity', 'Holdable'])];
183
- this.dispatchEvent(new CustomEvent('v3-part-edit', { detail: { edit: { kind: 'set-capabilities', names } }, bubbles: true, composed: true }));
184
- }
185
- renderDimensions(asset) {
186
- const others = asset.appearance.map(look => look.target).filter(one => one !== this.partId && canFollow(asset, one));
187
- const round = followableAxes(asset, this.partId).length === 2;
188
- /* One step for the whole part (figure-model follow-size): the size is declared from the parts if there is none. */
189
- const followsAll = followableAxes(asset, this.partId).every(axis => followOf(asset, this.partId, axis).of === 'instance');
190
- const whole = followsAll
191
- ? nothing
192
- : html `<button act follow-size ?disabled=${this.locked} @click=${() => this.command({ kind: 'follow-size', part: this.partId })}>
193
- <md-icon>open_in_full</md-icon>${i18next.t('figure.button.follow-size')}
194
- </button>`;
195
- return html `${whole}${followableAxes(asset, this.partId).map(axis => {
196
- const now = followOf(asset, this.partId, axis);
197
- const value = choiceOf(now);
198
- /* A cylinder's two measures are its radius and its length; its X and Y name them. */
199
- const name = round ? i18next.t(axis === 'x' ? 'figure.label.field-radius' : 'figure.label.field-length') : axis.toUpperCase();
200
- return html `<div row>
201
- <b>${name}</b>
202
- <select ?disabled=${this.locked} @change=${(e) => this.follow(asset, axis, e.target.value, now)}>
203
- <option value="own" ?selected=${value === 'own'}>${i18next.t('figure.label.follows-nothing')}</option>
204
- ${AXES.filter(one => hasInstanceSize(asset, one)).map(one => html `<option value=${`share:${one}`} ?selected=${value === `share:${one}`}>
205
- ${i18next.t('figure.label.follows-instance-share')} · ${one.toUpperCase()}
206
- </option>
207
- <option value=${`instance:${one}`} ?selected=${value === `instance:${one}`}>
208
- ${i18next.t('figure.label.follows-instance')} · ${one.toUpperCase()}
209
- </option>`)}
210
- ${others.flatMap(other => AXES.map(one => html `<option value=${`part:${other}:${one}`} ?selected=${value === `part:${other}:${one}`}>${other} · ${one.toUpperCase()}</option>`))}
211
- ${now.of === 'expression' || now.of === 'input'
212
- ? html `<option value=${value} selected>${now.of === 'input' ? now.input : i18next.t('figure.label.follows-expression')}</option>`
213
- : nothing}
214
- </select>
215
- ${now.of === 'instance' || now.of === 'part'
216
- ? html `<span></span>
217
- <label inline-plus>
218
- ${i18next.t('figure.label.follows-plus')}
219
- <input
220
- type="number"
221
- step="any"
222
- ?disabled=${this.locked}
223
- .value=${String(now.plus ?? 0)}
224
- @change=${(e) => this.followPlus(axis, now, Number(e.target.value) || 0)}
225
- />
226
- </label>`
227
- : nothing}
228
- </div>`;
229
- })}`;
230
- }
231
- /** The same source, the same share, a new difference: a leaf the opening less its gaps (link-dimension's `plus`). */
232
- followPlus(axis, now, plus) {
233
- if (now.of !== 'instance' && now.of !== 'part')
234
- return;
235
- if ((now.plus ?? 0) === plus)
236
- return;
237
- const source = now.of === 'instance' ? { of: 'instance', axis: now.axis } : { of: 'part', part: now.part, axis: now.axis };
238
- const times = now.of === 'instance' ? now.times : (now.times ?? 1);
239
- this.command({ kind: 'link-dimension', part: this.partId, axis, source, ...(times !== 1 ? { times } : {}), ...(plus ? { plus } : {}), replace: true });
240
- }
241
- follow(asset, axis, choice, now) {
242
- const following = now.of !== 'own';
243
- /* The measure as it stands: a dimension stops following at it, and a share is taken of it. */
244
- const current = dimensionNow(asset, this.partId, axis) ?? this.dims?.[AXES.indexOf(axis)] ?? 100;
245
- if (choice === 'own') {
246
- if (!following)
247
- return;
248
- this.command({ kind: 'unlink-dimension', part: this.partId, axis, mm: Math.max(1, Math.round(current)) });
249
- return;
250
- }
251
- const [of, a, b] = choice.split(':');
252
- if (of === 'share') {
253
- /* A proportional rule: this measure is the share of the instance size it is now, so it grows with the figure. */
254
- const size = asset.designInputs[`size.${a}`];
255
- if (!size)
256
- return;
257
- const times = Math.round((current / size) * 1e6) / 1e6;
258
- this.command({ kind: 'link-dimension', part: this.partId, axis, source: { of: 'instance', axis: a }, times, replace: following });
259
- return;
260
- }
261
- const source = of === 'instance' ? { of: 'instance', axis: a } : { of: 'part', part: a, axis: b };
262
- this.command({ kind: 'link-dimension', part: this.partId, axis, source, replace: following });
263
- }
264
- /**
265
- * Positions worked out from other values, each with the way to
266
- * keep it where it is. This is the way out the release gate names when a resize leaves a part in the air.
267
- */
268
- renderPositions(asset) {
269
- return positionsWorkedOut(asset, this.partId).map(axis => html `<div pair>
270
- <span>${axis.toUpperCase()} · ${i18next.t(positionIsShare(asset, this.partId, axis) ? 'figure.label.position-in-proportion' : 'figure.label.position-worked-out')}</span>
271
- <button act ?disabled=${this.locked} @click=${() => this.command({ kind: 'fix-position', part: this.partId, axis })}>
272
- <md-icon>push_pin</md-icon>${i18next.t('figure.button.fix-position')}
273
- </button>
274
- ${hasInstanceSize(asset, axis) && !positionIsShare(asset, this.partId, axis)
275
- ? html `<button act ?disabled=${this.locked} title=${i18next.t('figure.text.scale-position-explained')} @click=${() => this.command({ kind: 'scale-position', part: this.partId, axis })}>
276
- <md-icon>open_in_full</md-icon>${i18next.t('figure.button.scale-position')}
277
- </button>`
278
- : nothing}
279
- </div>`);
280
- }
281
- /**
282
- * A new aim for the fastening: where on the face is filled in as where the part stands now, so fastening it keeps it
283
- * there (`placeKeptOf`). The author changes it from there.
284
- */
285
- aimAt(asset, f) {
286
- const theirFaces = f.target ? facesOf(asset, f.target) : FACES;
287
- const targetFace = theirFaces.includes(f.targetFace) ? f.targetFace : theirFaces[0];
288
- const aim = f.toPlane ? 'plane' : f.target ? { part: f.target, face: targetFace } : undefined;
289
- this.fasten = { ...f, ...(aim ? placeKeptOf(asset, this.partId, aim, !!f.twist) : { align: {}, alignMm: {} }) };
290
- }
291
- renderFastening(asset) {
292
- const held = heldBy(asset, this.partId);
293
- const f = this.fasten;
294
- /* Not a part that hangs from this one: fastening a frame to its own roller would close a loop. */
295
- const targets = asset.appearance.map(look => look.target).filter(one => one !== this.partId && canFasten(asset, one) && !hangsFrom(asset, one, this.partId));
296
- /* The faces are the ones each shape declares: a part is fastened by one of its outer faces, to any face of the target. */
297
- /* On the mounting plane only the faces it takes: a bottom or a top on the floor, a back on a wall. */
298
- const plane = mountingPlaneOf(asset);
299
- /* Its outer faces, and its centre -- a roller spins about its middle (ADR-0093); the mounting plane takes a face. */
300
- const mineFaces = [
301
- ...facesOf(asset, this.partId).filter(face => FACES.includes(face) && (!f.toPlane || plane.faces.includes(face))),
302
- ...(f.toPlane ? [] : ['centre'])
303
- ];
304
- const myFace = mineFaces.includes(f.face) ? f.face : mineFaces[0];
305
- const theirFaces = f.target ? facesOf(asset, f.target) : FACES;
306
- const targetFace = theirFaces.includes(f.targetFace) ? f.targetFace : theirFaces[0];
307
- const aim = f.toPlane ? 'plane' : { part: f.target, face: targetFace };
308
- const align = f.target || f.toPlane ? alignOf(asset, this.partId, aim, f.align, f.alignMm, !!f.twist) : undefined;
309
- return html `
310
- ${held
311
- ? html `<div pair>
312
- <span>${i18next.t('figure.label.fastened-to')}: <b bound>${'plane' in held ? i18next.t('figure.label.mounting-plane') : held.part}</b></span>
313
- <button act ?disabled=${this.locked} @click=${() => this.command({ kind: 'detach', part: this.partId })}>
314
- <md-icon>link_off</md-icon>${i18next.t('figure.button.detach')}
315
- </button>
316
- </div>`
317
- : nothing}
318
- <div form>
319
- <span>${i18next.t('figure.label.my-face')}</span>
320
- <select @change=${(e) => (this.fasten = { ...f, face: e.target.value })}>
321
- ${mineFaces.map(face => html `<option value=${face} ?selected=${face === myFace}>${i18next.t('figure.label.face-' + face)}</option>`)}
322
- </select>
323
- </div>
324
- <label toggle>
325
- <input type="checkbox" .checked=${f.toPlane} @change=${(e) => this.aimAt(asset, { ...f, toPlane: e.target.checked })} />
326
- ${i18next.t('figure.label.stands-on-plane')}
327
- </label>
328
- ${f.toPlane
329
- ? nothing
330
- : html `<div form>
331
- <span>${i18next.t('figure.label.target-part')}</span>
332
- <select @change=${(e) => this.aimAt(asset, { ...f, target: e.target.value })}>
333
- <option value="" ?selected=${!f.target}>—</option>
334
- ${targets.map(one => html `<option value=${one} ?selected=${one === f.target}>${one}</option>`)}
335
- </select>
336
- <span>${i18next.t('figure.label.target-face')}</span>
337
- <select @change=${(e) => this.aimAt(asset, { ...f, targetFace: e.target.value })}>
338
- ${theirFaces.map(face => html `<option value=${face} ?selected=${face === targetFace}>${i18next.t('figure.label.face-' + face)}</option>`)}
339
- </select>
340
- <span>${i18next.t('figure.label.facing-meet')}</span>
341
- <select @change=${(e) => (this.fasten = { ...f, facing: e.target.value })}>
342
- <option value="meet" ?selected=${f.facing === 'meet'}>${i18next.t('figure.label.facing-meet')}</option>
343
- <option value="flush" ?selected=${f.facing === 'flush'}>${i18next.t('figure.label.facing-flush')}</option>
344
- </select>
345
- </div>`}
346
- ${this.renderAlignment(f.toPlane || f.target ? alignAxesOf(asset, this.partId, aim, !!f.twist) : [])}
347
- <div form>
348
- <span>${i18next.t('figure.label.gap')}</span>
349
- <input type="number" step="any" .value=${String(f.gap)} @change=${(e) => (this.fasten = { ...f, gap: Number(e.target.value) || 0 })} />
350
- <span>${i18next.t('figure.label.twist')}</span>
351
- <input type="number" step="any" .value=${String(f.twist)} @change=${(e) => (this.fasten = { ...f, twist: Number(e.target.value) || 0 })} />
352
- </div>
353
- <button
354
- act
355
- ?disabled=${this.locked || (!f.toPlane && !f.target)}
356
- @click=${() => this.command({
357
- kind: 'attach',
358
- part: this.partId,
359
- face: myFace,
360
- to: f.toPlane ? { plane: 'mounting-plane' } : { part: f.target, face: targetFace },
361
- ...(f.toPlane ? {} : { facing: f.facing }),
362
- ...(f.gap ? { gap: f.gap } : {}),
363
- twist: f.twist,
364
- ...(align ? { align } : {}),
365
- replace: !!held
366
- })}
367
- >
368
- <md-icon>link</md-icon>${i18next.t('figure.button.fasten')}
369
- </button>
370
- `;
371
- }
372
- /**
373
- * Where on the target face the part sits, on the two axes across it: centred, level with the target's edge on
374
- * either side, or a distance off centre. A door hung level with the frame's front edge turns about that edge.
375
- */
376
- renderAlignment(axes) {
377
- const f = this.fasten;
378
- const set = (axis, how) => (this.fasten = { ...f, align: { ...f.align, [axis]: how } });
379
- const setMm = (axis, mm) => (this.fasten = { ...f, alignMm: { ...f.alignMm, [axis]: mm } });
380
- return html `<div form>
381
- ${axes.map(({ axis, edges }) => {
382
- const how = f.align[axis] === 'min' || f.align[axis] === 'max' ? (edges ? f.align[axis] : 'centre') : (f.align[axis] ?? 'centre');
383
- return html `<span>${i18next.t('figure.label.align-' + axis)}</span>
384
- <select @change=${(e) => set(axis, e.target.value)}>
385
- <option value="centre" ?selected=${how === 'centre'}>${i18next.t('figure.label.align-centre')}</option>
386
- ${edges
387
- ? html `<option value="min" ?selected=${how === 'min'}>${i18next.t(`figure.label.align-${axis}-min`)}</option>
388
- <option value="max" ?selected=${how === 'max'}>${i18next.t(`figure.label.align-${axis}-max`)}</option>`
389
- : nothing}
390
- <option value="mm" ?selected=${how === 'mm'}>${i18next.t('figure.label.align-mm')}</option>
391
- ${this.asset && hasInstanceSize(this.asset, axis)
392
- ? html `<option value="share" ?selected=${how === 'share'}>${i18next.t('figure.label.align-share')}</option>`
393
- : nothing}
394
- </select>
395
- ${how === 'mm' || how === 'share'
396
- ? html `<span></span><input type="number" step="any" aria-label=${i18next.t('figure.label.align-mm')} .value=${String(f.alignMm[axis] ?? 0)} @change=${(e) => setMm(axis, Number(e.target.value) || 0)} />`
397
- : nothing}`;
398
- })}
399
- </div>`;
400
- }
401
- /**
402
- * Making the part a row across what it is fastened to (figure-model `make-repeat`): along one of the axes across its
403
- * face, copies a pitch apart filling the parent's length, or a count spread over it. Once made, the row's own section
404
- * (the editor's) shows it and takes it away.
405
- */
406
- renderRow(asset) {
407
- const axes = rowAxesOf(asset, this.partId);
408
- if (!axes.length)
409
- return nothing;
410
- const r = this.row;
411
- const axis = axes.includes(r.axis) ? r.axis : axes[0];
412
- const ok = r.pitch > 0 && (r.fills || (Number.isSafeInteger(r.count) && r.count >= 1));
413
- return html `<section>
414
- <h3>${i18next.t('figure.label.repeat')}</h3>
415
- <div form>
416
- <span>${i18next.t('figure.label.repeat-axis')}</span>
417
- <select @change=${(e) => (this.row = { ...r, axis: e.target.value })}>
418
- ${axes.map(a => html `<option value=${a} ?selected=${a === axis}>${a.toUpperCase()}</option>`)}
419
- </select>
420
- <span>${i18next.t('figure.label.repeat-pitch')} (mm)</span>
421
- <input type="number" step="any" min="0" .value=${String(r.pitch)} @change=${(e) => (this.row = { ...r, pitch: Number(e.target.value) })} />
422
- </div>
423
- <label toggle>
424
- <input type="checkbox" .checked=${r.fills} @change=${(e) => (this.row = { ...r, fills: e.target.checked })} />
425
- ${i18next.t('figure.label.repeat-fills')}
426
- </label>
427
- ${r.fills
428
- ? nothing
429
- : html `<div form>
430
- <span>${i18next.t('figure.label.repeat-count')}</span>
431
- <input type="number" step="1" min="1" .value=${String(r.count)} @change=${(e) => (this.row = { ...r, count: Number(e.target.value) })} />
432
- </div>`}
433
- <button
434
- act
435
- ?disabled=${this.locked || !ok}
436
- @click=${() => this.command({ kind: 'make-repeat', part: this.partId, axis, pitch: r.pitch, ...(r.fills ? {} : { count: r.count }) })}
437
- >
438
- <md-icon>view_week</md-icon>${i18next.t('figure.button.make-repeat')}
439
- </button>
440
- </section>`;
441
- }
442
- /**
443
- * When the part is drawn: always, or while a control is at least a value -- a lamp lit while the gate is open. One
444
- * part edit (`set-visibility`), one step. Offered once the figure has a control; a motion is what makes one.
445
- */
446
- renderShown(asset) {
447
- const controls = visibilityControlsOf(asset);
448
- if (!controls.length)
449
- return nothing;
450
- const now = shownWhenOf(asset, this.partId);
451
- const send = (shownWhen) => this.dispatchEvent(new CustomEvent('v3-part-edit', { detail: { edit: { kind: 'set-visibility', target: this.partId, shownWhen } }, bubbles: true, composed: true }));
452
- const first = (id) => {
453
- const c = controls.find(one => one.id === id);
454
- return Math.round((c.min + (c.max - c.min) * 0.01) * 1000) / 1000;
455
- };
456
- const unit = now ? controls.find(c => c.id === now.input)?.unit : '';
457
- return html `<div form>
458
- <span>${i18next.t('figure.label.shown-when')}</span>
459
- <select
460
- ?disabled=${this.locked}
461
- @change=${(e) => {
462
- const id = e.target.value;
463
- send(id ? { input: id, atLeast: now?.input === id ? now.atLeast : first(id) } : null);
464
- }}
465
- >
466
- <option value="" ?selected=${!now}>${i18next.t('figure.label.shown-always')}</option>
467
- ${controls.map(c => html `<option value=${c.id} ?selected=${now?.input === c.id}>${i18next.t('figure.label.shown-while', { control: c.label })}</option>`)}
468
- </select>
469
- ${now
470
- ? html `<span>${i18next.t('figure.label.shown-at-least')}${unit ? ` (${unit})` : ''}</span>
471
- <input
472
- type="number"
473
- step="any"
474
- ?disabled=${this.locked}
475
- .value=${String(now.atLeast)}
476
- @change=${(e) => {
477
- const v = e.target.valueAsNumber;
478
- if (Number.isFinite(v))
479
- send({ input: now.input, atLeast: v });
480
- }}
481
- />`
482
- : nothing}
483
- </div>`;
484
- }
485
- renderMotion(asset) {
486
- const now = motionOf(asset, this.partId);
487
- const joint = asset.joints?.find(j => j.body1.part === this.partId);
488
- if (now && !(this.editingMotion && joint)) {
489
- return html `<div pair>
490
- <span>
491
- ${i18next.t('figure.label.motion-' + now.kind)} · ${String(now.axis).toUpperCase()}
492
- ${now.travel
493
- ? html `<small>${i18next.t('figure.label.motion-travel-is', { part: now.travel.part, axis: now.travel.axis.toUpperCase(), plus: now.travel.plus ?? 0 })}</small>`
494
- : nothing}
495
- ${now.follows
496
- ? html `<small>${i18next.t('figure.label.motion-follows', { joint: now.follows.joint, multiplier: now.follows.multiplier, offset: now.follows.offset ?? 0 })}</small>`
497
- : now.control
498
- ? html `<small>${now.control.id} (${now.control.unit}) ${now.control.min} … ${now.control.max}</small>`
499
- : nothing}
500
- </span>
501
- <button
502
- act
503
- ?disabled=${this.locked || !joint}
504
- @click=${() => joint && this.command({ kind: 'set-joint', joint: { id: joint.id, type: 'fixed', body0: joint.body0, body1: joint.body1, ...(joint.origin ? { origin: joint.origin } : {}) } })}
505
- >
506
- <md-icon>close</md-icon>${i18next.t('figure.button.remove-motion')}
507
- </button>
508
- <button
509
- act
510
- ?disabled=${this.locked || !joint}
511
- @click=${() => {
512
- if (!joint)
513
- return;
514
- this.motion = motionFormOf(joint);
515
- this.editingMotion = true;
516
- }}
517
- >
518
- <md-icon>edit</md-icon>${i18next.t('figure.button.change-motion')}
519
- </button>
520
- </div>`;
521
- }
522
- /* A joint needs a parent (URDF): a part fastened to nothing is fastened first. */
523
- if (!joint)
524
- return html `<p hint>${i18next.t('figure.text.fasten-before-moving')}</p>`;
525
- const m = this.motion;
526
- const unit = m.kind === 'slide' ? 'mm' : 'deg';
527
- /* A follower follows a joint that moves the same way; a choice the kind no longer allows is dropped. */
528
- const leaders = leadersFor(asset, this.partId, m.kind);
529
- const follow = leaders.some(j => j.id === m.follow) ? m.follow : '';
530
- return html `
531
- <div pair>
532
- <select @change=${(e) => this.setKind(e.target.value)}>
533
- <option value="slide" ?selected=${m.kind === 'slide'}>${i18next.t('figure.label.motion-slide')}</option>
534
- <option value="turn" ?selected=${m.kind === 'turn'}>${i18next.t('figure.label.motion-turn')}</option>
535
- <option value="continuous" ?selected=${m.kind === 'continuous'}>${i18next.t('figure.label.motion-continuous')}</option>
536
- </select>
537
- <select @change=${(e) => (this.motion = { ...m, axis: e.target.value })}>
538
- ${AXES.map(a => html `<option value=${a} ?selected=${a === m.axis}>${i18next.t('figure.label.motion-axis')} ${a.toUpperCase()}</option>`)}
539
- </select>
540
- </div>
541
- ${leaders.length
542
- ? html `<div form>
543
- <span>${i18next.t('figure.label.motion-follow')}</span>
544
- <select @change=${(e) => (this.motion = { ...m, follow: e.target.value })}>
545
- <option value="" ?selected=${!follow}>${i18next.t('figure.label.motion-own-control')}</option>
546
- ${leaders.map(j => html `<option value=${j.id} ?selected=${j.id === follow}>${j.body1.part}${j.state ? ` · ${j.state.id}` : ''}</option>`)}
547
- </select>
548
- </div>`
549
- : nothing}
550
- ${follow
551
- ? html `<div form>
552
- <span>${i18next.t('figure.label.motion-multiplier')}</span>
553
- <input type="number" step="any" .value=${String(m.multiplier)} @change=${(e) => (this.motion = { ...m, multiplier: Number(e.target.value) })} />
554
- <span>${i18next.t('figure.label.motion-offset')} (${unit})</span>
555
- <input type="number" step="any" .value=${String(m.offset)} @change=${(e) => (this.motion = { ...m, offset: Number(e.target.value) || 0 })} />
556
- </div>`
557
- : html `<div form>
558
- ${m.kind === 'slide'
559
- ? html `<span>${i18next.t('figure.label.motion-travel')}</span>
560
- <select @change=${(e) => (this.motion = { ...m, travel: e.target.value })}>
561
- <option value="" ?selected=${!m.travel}>${i18next.t('figure.label.motion-travel-mm')}</option>
562
- ${asset.appearance
563
- .map(look => look.target)
564
- .filter(one => canFollow(asset, one))
565
- .flatMap(one => AXES.map(a => html `<option value=${`${one}:${a}`} ?selected=${m.travel === `${one}:${a}`}>${one} · ${a.toUpperCase()}</option>`))}
566
- </select>
567
- ${m.travel
568
- ? html `<span>${i18next.t('figure.label.follows-plus')}</span>
569
- <input type="number" step="any" .value=${String(m.travelPlus ?? 0)} @change=${(e) => (this.motion = { ...m, travelPlus: Number(e.target.value) || 0 })} />`
570
- : nothing}`
571
- : nothing}
572
- <span>${i18next.t('figure.label.control-id')}</span>
573
- <input .value=${m.id} placeholder=${`${this.partId}-${m.kind}`} @change=${(e) => (this.motion = { ...m, id: e.target.value.trim() })} />
574
- ${m.kind === 'continuous' || (m.kind === 'slide' && m.travel)
575
- ? nothing
576
- : html `<span>${i18next.t('figure.label.control-range')} (${unit})</span>
577
- <div pair>
578
- <input type="number" step="any" .value=${String(m.min)} @change=${(e) => (this.motion = { ...m, min: Number(e.target.value) })} />
579
- <input type="number" step="any" .value=${String(m.max)} @change=${(e) => (this.motion = { ...m, max: Number(e.target.value) })} />
580
- </div>`}
581
- </div>`}
582
- <button
583
- act
584
- ?disabled=${this.locked || (follow ? !(Number.isFinite(m.multiplier) && m.multiplier !== 0) : m.kind !== 'continuous' && !(m.kind === 'slide' && m.travel) && !(m.max > m.min && m.min <= 0 && 0 <= m.max))}
585
- title=${!follow && m.kind !== 'continuous' && !(m.min <= 0 && 0 <= m.max) ? i18next.t('figure.text.joint-range-holds-zero') : ''}
586
- @click=${() => {
587
- this.command({ kind: 'set-joint', joint: motionJoint(joint, { ...m, follow }, this.partId) });
588
- this.editingMotion = false;
589
- }}
590
- >
591
- <md-icon>animation</md-icon>${i18next.t(this.editingMotion ? 'figure.button.apply-motion-change' : 'figure.button.add-motion')}
592
- </button>
593
- ${this.editingMotion
594
- ? html `<button act @click=${() => (this.editingMotion = false)}><md-icon>undo</md-icon>${i18next.t('figure.button.cancel-motion-change')}</button>`
595
- : nothing}
596
- `;
597
- }
598
- setKind(kind) {
599
- this.motion = kind === 'slide' ? { ...this.motion, kind, min: 0, max: 500 } : { ...this.motion, kind, min: -90, max: 90 };
600
- }
601
- command(action) {
602
- this.dispatchEvent(new CustomEvent('v3-command', { detail: { action }, bubbles: true, composed: true }));
603
- }
604
- };
605
- __decorate([
606
- property({ attribute: false }),
607
- __metadata("design:type", Object)
608
- ], FigureV3Links.prototype, "asset", void 0);
609
- __decorate([
610
- property(),
611
- __metadata("design:type", Object)
612
- ], FigureV3Links.prototype, "partId", void 0);
613
- __decorate([
614
- property({ attribute: false }),
615
- __metadata("design:type", Array)
616
- ], FigureV3Links.prototype, "dims", void 0);
617
- __decorate([
618
- property({ type: Boolean }),
619
- __metadata("design:type", Object)
620
- ], FigureV3Links.prototype, "locked", void 0);
621
- __decorate([
622
- property(),
623
- __metadata("design:type", String)
624
- ], FigureV3Links.prototype, "section", void 0);
625
- __decorate([
626
- state(),
627
- __metadata("design:type", Object)
628
- ], FigureV3Links.prototype, "fasten", void 0);
629
- __decorate([
630
- state(),
631
- __metadata("design:type", Object)
632
- ], FigureV3Links.prototype, "motion", void 0);
633
- __decorate([
634
- state(),
635
- __metadata("design:type", Object)
636
- ], FigureV3Links.prototype, "place", void 0);
637
- __decorate([
638
- state(),
639
- __metadata("design:type", Object)
640
- ], FigureV3Links.prototype, "row", void 0);
641
- __decorate([
642
- state(),
643
- __metadata("design:type", Object)
644
- ], FigureV3Links.prototype, "editingMotion", void 0);
645
- FigureV3Links = __decorate([
646
- customElement('figure-v3-links')
647
- ], FigureV3Links);
648
- export { FigureV3Links };
649
- //# sourceMappingURL=figure-v3-links.js.map