@things-factory/figure-ui 10.1.203 → 10.1.205
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.
- package/client/modeller/canvas.ts +91 -9
- package/client/modeller/editor-styles.ts +7 -22
- package/client/modeller/editor.ts +176 -145
- package/client/modeller/fasten-form.ts +91 -9
- package/client/modeller/figure-fasten-dialog.ts +42 -9
- package/client/modeller/figure-fasten-drawing.ts +233 -12
- package/client/modeller/figure-section-editor.ts +6 -6
- package/client/modeller/figure-slot-layout-panel.ts +1 -1
- package/client/modeller/gizmo.ts +12 -1
- package/client/modeller/links.ts +48 -6
- package/client/modeller/section-drawing.ts +7 -7
- package/client/modeller/size-drag.ts +112 -0
- package/client/modeller/slot-layout-host.ts +3 -3
- package/client/modeller/stage-model.ts +6 -9
- package/client/pages/figure-modeller-page.ts +1 -4
- package/dist-client/modeller/canvas.d.ts +24 -3
- package/dist-client/modeller/canvas.js +96 -11
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/editor-styles.js +7 -22
- package/dist-client/modeller/editor-styles.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +28 -32
- package/dist-client/modeller/editor.js +176 -153
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-form.d.ts +36 -3
- package/dist-client/modeller/fasten-form.js +86 -8
- package/dist-client/modeller/fasten-form.js.map +1 -1
- package/dist-client/modeller/figure-fasten-dialog.d.ts +17 -3
- package/dist-client/modeller/figure-fasten-dialog.js +47 -10
- package/dist-client/modeller/figure-fasten-dialog.js.map +1 -1
- package/dist-client/modeller/figure-fasten-drawing.d.ts +33 -1
- package/dist-client/modeller/figure-fasten-drawing.js +235 -11
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
- package/dist-client/modeller/figure-section-editor.d.ts +3 -3
- package/dist-client/modeller/figure-section-editor.js +4 -4
- package/dist-client/modeller/figure-section-editor.js.map +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js +1 -1
- package/dist-client/modeller/figure-slot-layout-panel.js.map +1 -1
- package/dist-client/modeller/gizmo.d.ts +12 -1
- package/dist-client/modeller/gizmo.js +8 -0
- package/dist-client/modeller/gizmo.js.map +1 -1
- package/dist-client/modeller/links.d.ts +14 -2
- package/dist-client/modeller/links.js +42 -4
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/section-drawing.d.ts +3 -4
- package/dist-client/modeller/section-drawing.js +10 -9
- package/dist-client/modeller/section-drawing.js.map +1 -1
- package/dist-client/modeller/size-drag.d.ts +38 -0
- package/dist-client/modeller/size-drag.js +85 -0
- package/dist-client/modeller/size-drag.js.map +1 -0
- package/dist-client/modeller/slot-layout-host.d.ts +1 -3
- package/dist-client/modeller/slot-layout-host.js +3 -3
- package/dist-client/modeller/slot-layout-host.js.map +1 -1
- package/dist-client/modeller/stage-model.d.ts +5 -7
- package/dist-client/modeller/stage-model.js +6 -6
- package/dist-client/modeller/stage-model.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +0 -2
- package/dist-client/pages/figure-modeller-page.js +1 -6
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/test/align.test.ts +7 -11
- package/test/browser/acceptance-page.js +10 -0
- package/test/browser/editing.check.mjs +17 -13
- package/test/browser/links-page.js +2 -2
- package/test/browser/links.check.mjs +94 -56
- package/test/browser/modeller-page.js +30 -18
- package/test/browser/modeller-revision.check.mjs +39 -0
- package/test/browser/modeller.check.mjs +32 -10
- package/test/browser/screen-page.js +0 -2
- package/test/browser/screen.check.mjs +11 -13
- package/test/browser/section.check.mjs +2 -2
- package/test/fasten-form.test.ts +82 -2
- package/test/gizmo.test.ts +13 -15
- package/test/links.test.ts +8 -3
- package/test/resize-with-placement.test.ts +14 -13
- package/test/revision.test.ts +3 -2
- package/test/section-editor.test.ts +9 -10
- package/test/size-drag.test.ts +114 -0
- package/test/slot-layout-host.test.ts +13 -8
- package/translations/en.json +25 -22
- package/translations/es.json +25 -22
- package/translations/ja.json +25 -22
- package/translations/ko.json +25 -22
- package/translations/ms.json +25 -22
- package/translations/zh.json +25 -22
|
@@ -2,16 +2,23 @@ import { css, html, LitElement, nothing, svg } from 'lit'
|
|
|
2
2
|
import { customElement, property, state } from 'lit/decorators.js'
|
|
3
3
|
|
|
4
4
|
import { i18next, localize } from '@operato/i18n'
|
|
5
|
-
import
|
|
5
|
+
import { SidePanelStyles } from '@things-factory/figure-viewer/dist-client/side-panel-styles.js'
|
|
6
|
+
import { figureAroundNormalOf, figureAroundOf, figureCaughtAngleOf, figureSurfaceAnglesOf, figureSurfacePlaceOf } from '@hatiolab/figure-model'
|
|
7
|
+
import type { FigureFace, FigureSurfacePlace } from '@hatiolab/figure-model'
|
|
6
8
|
|
|
7
9
|
import { alignmentAt, fastenLabelsOf, offsetOf, overhangOf, screenOf, type FaceView, type FastenMode } from './fasten-geometry.js'
|
|
8
10
|
import type { Alignment } from './links.js'
|
|
9
11
|
|
|
10
12
|
type Axis = 'x' | 'y' | 'z'
|
|
13
|
+
const AXES: Axis[] = ['x', 'y', 'z']
|
|
14
|
+
type SidePlace = Extract<FigureSurfacePlace, { around: number }>
|
|
15
|
+
type RoundPlace = Extract<FigureSurfacePlace, { toward: unknown }>
|
|
11
16
|
|
|
12
17
|
/** A change the drawing asks of the fastening form. */
|
|
13
18
|
export type FastenChange =
|
|
14
19
|
| { targetFace: string }
|
|
20
|
+
| { onSurface: true }
|
|
21
|
+
| { surface: FigureSurfacePlace }
|
|
15
22
|
| { mode: FastenMode }
|
|
16
23
|
| { axis: Axis; how: Alignment; mm?: number }
|
|
17
24
|
| { gap: number }
|
|
@@ -49,7 +56,8 @@ const WHERE_H = 200
|
|
|
49
56
|
*/
|
|
50
57
|
@customElement('figure-fasten-drawing')
|
|
51
58
|
export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
52
|
-
|
|
59
|
+
/* The panel's own field rows (where along a cylinder's side), as the window's other rows are. */
|
|
60
|
+
static styles = [SidePanelStyles, css`
|
|
53
61
|
:host {
|
|
54
62
|
display: grid;
|
|
55
63
|
gap: 8px;
|
|
@@ -90,6 +98,29 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
90
98
|
div[modes] button:hover:not([on]):not(:disabled) {
|
|
91
99
|
background: var(--md-sys-color-surface-container);
|
|
92
100
|
}
|
|
101
|
+
/* Faces beyond the box's six and a curved surface: tags as the hidden faces are drawn beside the box -- one look for every face. */
|
|
102
|
+
div[face-tags] {
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-wrap: wrap;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
gap: 6px;
|
|
107
|
+
}
|
|
108
|
+
button[face-tag] {
|
|
109
|
+
height: 20px;
|
|
110
|
+
padding: 0 10px;
|
|
111
|
+
font: inherit;
|
|
112
|
+
font-size: var(--ui-t-small, 11.5px);
|
|
113
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
114
|
+
background: transparent;
|
|
115
|
+
border: 1px dashed var(--md-sys-color-outline);
|
|
116
|
+
border-radius: 10px;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
}
|
|
119
|
+
button[face-tag][on] {
|
|
120
|
+
color: var(--md-sys-color-on-primary-container);
|
|
121
|
+
background: var(--md-sys-color-primary-container);
|
|
122
|
+
border: 1px solid var(--md-sys-color-primary);
|
|
123
|
+
}
|
|
93
124
|
div[modes] button[on] {
|
|
94
125
|
background: var(--md-sys-color-primary-container);
|
|
95
126
|
color: var(--md-sys-color-on-primary-container);
|
|
@@ -105,8 +136,12 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
105
136
|
display: grid;
|
|
106
137
|
gap: 8px;
|
|
107
138
|
}
|
|
139
|
+
/* The place is drawn at its own size, never blown up past it: its words stay the window's size. */
|
|
108
140
|
div[where] {
|
|
109
141
|
position: relative;
|
|
142
|
+
width: 100%;
|
|
143
|
+
max-width: 360px;
|
|
144
|
+
justify-self: center;
|
|
110
145
|
}
|
|
111
146
|
div[where] input {
|
|
112
147
|
position: absolute;
|
|
@@ -119,7 +154,7 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
119
154
|
small[off] {
|
|
120
155
|
color: var(--md-sys-color-error);
|
|
121
156
|
}
|
|
122
|
-
`
|
|
157
|
+
`]
|
|
123
158
|
|
|
124
159
|
/** The target's name, or empty for the plane the figure stands by. */
|
|
125
160
|
@property() target = ''
|
|
@@ -140,6 +175,12 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
140
175
|
@property({ type: Boolean }) said = false
|
|
141
176
|
/** Which of its questions it draws: the face, or the way. */
|
|
142
177
|
@property() show: 'face' | 'how' = 'face'
|
|
178
|
+
/** The curved surface the target offers to be fastened to at a point (ADR-0140 3.9), if any. */
|
|
179
|
+
@property() surfaceForm: 'around' | 'toward' | '' = ''
|
|
180
|
+
/** Fastened to a point of that surface: which, about which axis, and where; undefined while on a face. */
|
|
181
|
+
@property({ attribute: false }) surface?: { form: 'around' | 'toward'; axis: Axis; place: FigureSurfacePlace }
|
|
182
|
+
/** The angles while one is dragged round the surface, told to the form when let go. */
|
|
183
|
+
@state() private turning?: { around?: number; azimuth?: number; elevation?: number }
|
|
143
184
|
|
|
144
185
|
/** Where the footprint is while dragging, and what it is caught by; undefined when nothing is dragged. */
|
|
145
186
|
@state() private dragged?: { at: Record<Axis, number>; caught: Partial<Record<Axis, string>> }
|
|
@@ -156,6 +197,8 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
156
197
|
|
|
157
198
|
render() {
|
|
158
199
|
if (this.show === 'how') return this.renderModes()
|
|
200
|
+
/* A part with no face (a sphere) has only its surface: its tag above, the place across the whole width. */
|
|
201
|
+
if (this.target && !this.faces.length) return html`${this.renderTarget()}<div>${this.renderWhere()}</div>`
|
|
159
202
|
return html`<div face-row>${this.target ? html`<div>${this.renderTarget()}</div>` : nothing}<div>${this.renderWhere()}</div></div>`
|
|
160
203
|
}
|
|
161
204
|
|
|
@@ -166,18 +209,18 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
166
209
|
const has = (f: string) => this.faces.includes(f)
|
|
167
210
|
/* A press is a whole change, as a drag let go is: the form applies a fastening the part already has. */
|
|
168
211
|
const pick = (f: string) => () => {
|
|
169
|
-
if (this.locked || !has(f) || f === this.face) return
|
|
212
|
+
if (this.locked || !has(f) || (!this.surface && f === this.face)) return
|
|
170
213
|
this.change({ targetFace: f })
|
|
171
214
|
this.released()
|
|
172
215
|
}
|
|
173
216
|
const seen = SEEN.map(([f, d]) => {
|
|
174
|
-
const on = this.face === f
|
|
217
|
+
const on = !this.surface && this.face === f
|
|
175
218
|
return svg`<path face=${f} d=${d} @click=${pick(f)}
|
|
176
219
|
style=${`cursor:${has(f) ? 'pointer' : 'default'};fill:${on ? 'var(--md-sys-color-primary-container)' : 'var(--md-sys-color-surface-container)'};fill-opacity:${has(f) ? 1 : 0.4};stroke:${on ? 'var(--md-sys-color-primary)' : 'var(--md-sys-color-outline)'};stroke-width:${on ? 2 : 1}`}>
|
|
177
220
|
<title>${i18next.t('figure.label.face-' + f)}</title></path>`
|
|
178
221
|
})
|
|
179
222
|
const hidden = HIDDEN.map(([f, x, y]) => {
|
|
180
|
-
const on = this.face === f
|
|
223
|
+
const on = !this.surface && this.face === f
|
|
181
224
|
return svg`<g face=${f} @click=${pick(f)} style=${`cursor:${has(f) ? 'pointer' : 'default'};opacity:${has(f) ? 1 : 0.35}`}>
|
|
182
225
|
<rect x=${x - 26} y=${y - 10} width="52" height="20" rx="10"
|
|
183
226
|
style=${`fill:${on ? 'var(--md-sys-color-primary-container)' : 'transparent'};stroke:${on ? 'var(--md-sys-color-primary)' : 'var(--md-sys-color-outline)'};stroke-dasharray:${on ? '' : '3 2'}`} />
|
|
@@ -186,14 +229,25 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
186
229
|
})
|
|
187
230
|
/* A shape's faces beyond its six outer ones -- a door frame's opening -- are tags of their own under the box. */
|
|
188
231
|
const others = this.faces.filter(f => !SEEN.some(([s]) => s === f) && !HIDDEN.some(([h]) => h === f))
|
|
189
|
-
|
|
232
|
+
/* A sphere has no face: no box of faces it has not, only its surface. */
|
|
233
|
+
return html`${this.faces.length
|
|
234
|
+
? svg`<svg viewBox="-20 0 ${PANEL_W} 126" role="img" aria-label=${this.target}>
|
|
190
235
|
${seen}
|
|
191
236
|
<text x="110" y="70" text-anchor="middle" font-size="12" pointer-events="none" style="fill:var(--md-sys-color-on-surface)">${this.target}</text>
|
|
192
237
|
${hidden}
|
|
193
|
-
</svg>`
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
238
|
+
</svg>`
|
|
239
|
+
: nothing}
|
|
240
|
+
${others.length || this.surfaceForm
|
|
241
|
+
? html`<div face-tags>
|
|
242
|
+
${others.map(f => html`<button face-tag ?on=${!this.surface && f === this.face} ?disabled=${this.locked} @click=${pick(f)}>${i18next.t('figure.label.face-' + f, { defaultValue: f })}</button>`)}
|
|
243
|
+
${this.surfaceForm
|
|
244
|
+
? html`<button face-tag surface ?on=${!!this.surface} ?disabled=${this.locked}
|
|
245
|
+
@click=${() => {
|
|
246
|
+
if (this.locked || this.surface) return
|
|
247
|
+
this.change({ onSurface: true })
|
|
248
|
+
this.released()
|
|
249
|
+
}}>${i18next.t('figure.label.surface-' + this.surfaceForm)}</button>`
|
|
250
|
+
: nothing}
|
|
197
251
|
</div>`
|
|
198
252
|
: nothing}`
|
|
199
253
|
}
|
|
@@ -266,6 +320,7 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
266
320
|
}
|
|
267
321
|
|
|
268
322
|
private renderWhere() {
|
|
323
|
+
if (this.surface) return this.renderSurfaceWhere(this.surface)
|
|
269
324
|
const view = this.view
|
|
270
325
|
if (!view) return nothing
|
|
271
326
|
const { h, v, sign } = screenOf(view.axes)
|
|
@@ -286,6 +341,8 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
286
341
|
const px = cx + sign[h] * at[h] * k - pw / 2,
|
|
287
342
|
py = cy + sign[v] * at[v] * k - ph / 2
|
|
288
343
|
const faceSized = view.face[h] !== undefined && view.face[v] !== undefined
|
|
344
|
+
/* An end disc is drawn as the disc it is: its overhang is measured on it (ADR-0140 3.9). */
|
|
345
|
+
const disc = !!view.measure.disc
|
|
289
346
|
const fx = cx - fw / 2,
|
|
290
347
|
fy = cy - fh / 2
|
|
291
348
|
const ix = Math.max(fx, px),
|
|
@@ -326,7 +383,7 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
326
383
|
@pointermove=${(ev: PointerEvent) => this.drag(ev, view, cx, cy)}
|
|
327
384
|
@pointerup=${() => this.drop()} @pointercancel=${() => this.drop()}>
|
|
328
385
|
${faceSized
|
|
329
|
-
? svg`<rect x=${fx} y=${fy} width=${fw} height=${fh} rx
|
|
386
|
+
? svg`<rect x=${fx} y=${fy} width=${fw} height=${fh} rx=${disc ? fw / 2 : 4} ry=${disc ? fh / 2 : 4} style="fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary);stroke-width:2" />`
|
|
330
387
|
: svg`<rect x="1" y="1" width=${W - 2} height=${H - 2} rx="8" style="fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary);stroke-width:2" />`}
|
|
331
388
|
<text face-name x=${labels.face.x} y=${labels.face.y} font-size="11" pointer-events="none" style="fill:var(--md-sys-color-primary)">${faceName}${sizeWords}</text>
|
|
332
389
|
${guide(h)}${guide(v)}
|
|
@@ -429,4 +486,168 @@ export class FigureFastenDrawing extends localize(i18next)(LitElement) {
|
|
|
429
486
|
})
|
|
430
487
|
this.released()
|
|
431
488
|
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Where on a curved surface (ADR-0140 3.9). A cylinder's side is seen from its end: its section a circle, the point
|
|
492
|
+
* on its rim at the angle `around`, dragged round it -- caught at every 45°, where the faces it had are named -- and
|
|
493
|
+
* along its length chosen below it. A sphere is seen from above and from the side: its point dragged round the
|
|
494
|
+
* first for the direction across its axis, along the second for how high. Nothing is told the form until let go.
|
|
495
|
+
*/
|
|
496
|
+
private renderSurfaceWhere(surface: { form: 'around' | 'toward'; axis: Axis; place: FigureSurfacePlace }) {
|
|
497
|
+
const R = 56
|
|
498
|
+
const axis = surface.axis
|
|
499
|
+
const across = (['x', 'y', 'z'] as Axis[]).filter(a => a !== axis) as [Axis, Axis]
|
|
500
|
+
const { h, v, sign } = screenOf(across)
|
|
501
|
+
const deg = (r: number) => (r * 180) / Math.PI
|
|
502
|
+
const rad = (d: number) => (d * Math.PI) / 180
|
|
503
|
+
/* A direction across the axis on the circle drawn at (cx, cy): its point there, and the way back from a pointer. */
|
|
504
|
+
const onCircle = (n: number[], cx: number, cy: number, r = R) => ({ x: cx + sign[h] * n[AXES.indexOf(h)]! * r, y: cy + sign[v] * n[AXES.indexOf(v)]! * r })
|
|
505
|
+
const angleAt = (p: { x: number; y: number }, cx: number, cy: number) => {
|
|
506
|
+
const d = [0, 0, 0]
|
|
507
|
+
d[AXES.indexOf(h)] = sign[h] * (p.x - cx)
|
|
508
|
+
d[AXES.indexOf(v)] = sign[v] * (p.y - cy)
|
|
509
|
+
return figureAroundOf(axis, d)
|
|
510
|
+
}
|
|
511
|
+
const caught = (a: number) => figureCaughtAngleOf(a) % 360
|
|
512
|
+
/* The faces the four quarters were named by, round the circle: where a part fastened to one of them now stands. */
|
|
513
|
+
const FACE_AT: Record<Axis, [string, string]> = { x: ['left', 'right'], y: ['bottom', 'top'], z: ['back', 'front'] }
|
|
514
|
+
const ticks = (cx: number, cy: number) =>
|
|
515
|
+
[0, 90, 180, 270].map(t => {
|
|
516
|
+
const n = figureAroundNormalOf(axis, t)
|
|
517
|
+
const k = n.findIndex(c => Math.abs(c) > 0.5)
|
|
518
|
+
const word = i18next.t('figure.label.face-' + FACE_AT[AXES[k]!][n[k]! > 0 ? 1 : 0])
|
|
519
|
+
const p = onCircle(n, cx, cy, R + 16)
|
|
520
|
+
return svg`<text x=${p.x} y=${p.y + 4} text-anchor="middle" font-size="10" pointer-events="none" style="fill:var(--md-sys-color-on-surface-variant)">${word}</text>`
|
|
521
|
+
})
|
|
522
|
+
const marker = (p: { x: number; y: number }, cx: number, cy: number, label: string) => svg`
|
|
523
|
+
<line x1=${cx} y1=${cy} x2=${p.x} y2=${p.y} style="stroke:var(--md-sys-color-primary);stroke-dasharray:3 2" />
|
|
524
|
+
<circle marker cx=${p.x} cy=${p.y} r="8" style=${`fill:var(--md-sys-color-tertiary-container);stroke:var(--md-sys-color-on-surface);cursor:${this.locked ? 'default' : 'grab'}`} />
|
|
525
|
+
<text x=${cx} y=${Math.hypot(p.x - cx, p.y - cy) < 16 ? cy - 14 : cy + 4} text-anchor="middle" font-size="11" pointer-events="none" style="fill:var(--md-sys-color-primary)">${label}</text>`
|
|
526
|
+
const drag = (on: (p: { x: number; y: number }) => void) => ({
|
|
527
|
+
down: (ev: PointerEvent) => {
|
|
528
|
+
if (this.locked) return
|
|
529
|
+
;(ev.currentTarget as Element).setPointerCapture(ev.pointerId)
|
|
530
|
+
on(this.pointIn(ev))
|
|
531
|
+
},
|
|
532
|
+
move: (ev: PointerEvent) => {
|
|
533
|
+
if (!this.turning || !(ev.currentTarget as Element).hasPointerCapture?.(ev.pointerId)) return
|
|
534
|
+
on(this.pointIn(ev))
|
|
535
|
+
},
|
|
536
|
+
up: () => this.letGo(surface)
|
|
537
|
+
})
|
|
538
|
+
if (surface.form === 'around') {
|
|
539
|
+
const place = surface.place as SidePlace
|
|
540
|
+
const around = this.turning?.around ?? place.around
|
|
541
|
+
/* The section in the middle under its name, the ticks clear of both. */
|
|
542
|
+
const cx = WHERE_W / 2,
|
|
543
|
+
cy = 110
|
|
544
|
+
const d = drag(p => (this.turning = { around: caught(angleAt(p, cx, cy)) }))
|
|
545
|
+
return html`<div where surface tabindex="0" @keydown=${(ev: KeyboardEvent) => this.turnKeys(ev, surface)}>
|
|
546
|
+
${svg`<svg viewBox="0 0 ${WHERE_W} ${WHERE_H}" role="img" aria-label=${i18next.t('figure.label.surface-seen-end', { part: this.target })}
|
|
547
|
+
@pointerdown=${d.down} @pointermove=${d.move} @pointerup=${d.up} @pointercancel=${d.up}>
|
|
548
|
+
<text x=${WHERE_W / 2} y="14" text-anchor="middle" font-size="11" pointer-events="none" style="fill:var(--md-sys-color-primary)">${i18next.t('figure.label.surface-seen-end', { part: this.target })}</text>
|
|
549
|
+
<circle cx=${cx} cy=${cy} r=${R} style="fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary);stroke-width:2" />
|
|
550
|
+
${ticks(cx, cy)}
|
|
551
|
+
${marker(onCircle(figureAroundNormalOf(axis, around), cx, cy), cx, cy, `${Math.round(around)}°`)}
|
|
552
|
+
</svg>`}
|
|
553
|
+
</div>
|
|
554
|
+
${this.renderAlong(surface.axis, place.along)}`
|
|
555
|
+
}
|
|
556
|
+
/* A sphere: its direction across its axis (the azimuth, by the same rule as a cylinder's around) and how high. */
|
|
557
|
+
const now = figureSurfaceAnglesOf(surface, surface.place) as { azimuth: number; elevation: number }
|
|
558
|
+
const azimuth = this.turning?.azimuth ?? now.azimuth
|
|
559
|
+
const elevation = this.turning?.elevation ?? now.elevation
|
|
560
|
+
const ax = 110,
|
|
561
|
+
ay = 110,
|
|
562
|
+
sx = 250,
|
|
563
|
+
sy = 110
|
|
564
|
+
const aboveAt = onCircle(figureAroundNormalOf(axis, azimuth), ax, ay, R * Math.cos(rad(elevation)) || 0)
|
|
565
|
+
const sideAt = { x: sx + R * Math.cos(rad(elevation)), y: sy - R * Math.sin(rad(elevation)) }
|
|
566
|
+
const d = drag(p => {
|
|
567
|
+
const above = Math.hypot(p.x - ax, p.y - ay) <= R + 16 && p.x < (ax + sx) / 2
|
|
568
|
+
this.turning = above
|
|
569
|
+
? { azimuth: caught(angleAt(p, ax, ay)), elevation }
|
|
570
|
+
: { azimuth, elevation: Math.max(-90, Math.min(90, figureCaughtAngleOf(deg(Math.atan2(sy - p.y, Math.max(0, p.x - sx)))))) }
|
|
571
|
+
})
|
|
572
|
+
return html`<div where surface tabindex="0" @keydown=${(ev: KeyboardEvent) => this.turnKeys(ev, surface)}>
|
|
573
|
+
${svg`<svg viewBox="0 0 ${WHERE_W} ${WHERE_H}" role="img" aria-label=${i18next.t('figure.label.surface-toward')}
|
|
574
|
+
@pointerdown=${d.down} @pointermove=${d.move} @pointerup=${d.up} @pointercancel=${d.up}>
|
|
575
|
+
<text x=${ax} y="14" text-anchor="middle" font-size="11" pointer-events="none" style="fill:var(--md-sys-color-primary)">${i18next.t('figure.label.surface-seen-above')}</text>
|
|
576
|
+
<circle cx=${ax} cy=${ay} r=${R} style="fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary);stroke-width:2" />
|
|
577
|
+
${ticks(ax, ay)}
|
|
578
|
+
${marker(aboveAt, ax, ay, `${Math.round(azimuth)}°`)}
|
|
579
|
+
<text x=${sx + R / 2} y="14" text-anchor="middle" font-size="11" pointer-events="none" style="fill:var(--md-sys-color-primary)">${i18next.t('figure.label.surface-seen-side')}</text>
|
|
580
|
+
<path d=${`M ${sx} ${sy - R} A ${R} ${R} 0 0 1 ${sx} ${sy + R}`} style="fill:var(--md-sys-color-primary-container);stroke:var(--md-sys-color-primary);stroke-width:2" />
|
|
581
|
+
<line x1=${sx} y1=${sy - R} x2=${sx} y2=${sy + R} style="stroke:var(--md-sys-color-primary)" />
|
|
582
|
+
${marker(sideAt, sx, sy, `${Math.round(elevation)}°`)}
|
|
583
|
+
</svg>`}
|
|
584
|
+
</div>`
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** Where along a cylinder's side: centred, level with an end, or a distance from the middle -- its alignment words. */
|
|
588
|
+
private renderAlong(axis: Axis, along: unknown) {
|
|
589
|
+
const how = along === 'min' || along === 'max' ? along : along && typeof along === 'object' ? 'mm' : 'centre'
|
|
590
|
+
const mm = how === 'mm' ? Number((along as { mm: number }).mm) || 0 : 0
|
|
591
|
+
const place = this.surface!.place as SidePlace
|
|
592
|
+
const tell = (next: unknown) => {
|
|
593
|
+
this.change({ surface: { around: place.around, along: next as never } })
|
|
594
|
+
this.released()
|
|
595
|
+
}
|
|
596
|
+
return html`<div form along>
|
|
597
|
+
<span>${i18next.t('figure.label.align-' + axis)}</span>
|
|
598
|
+
<select ?disabled=${this.locked} @change=${(e: Event) => {
|
|
599
|
+
const v = (e.target as HTMLSelectElement).value
|
|
600
|
+
tell(v === 'mm' ? { mm } : v)
|
|
601
|
+
}}>
|
|
602
|
+
<option value="centre" ?selected=${how === 'centre'}>${i18next.t('figure.label.align-centre')}</option>
|
|
603
|
+
<option value="min" ?selected=${how === 'min'}>${i18next.t(`figure.label.align-${axis}-min`)}</option>
|
|
604
|
+
<option value="max" ?selected=${how === 'max'}>${i18next.t(`figure.label.align-${axis}-max`)}</option>
|
|
605
|
+
<option value="mm" ?selected=${how === 'mm'}>${i18next.t('figure.label.align-mm')}</option>
|
|
606
|
+
</select>
|
|
607
|
+
${how === 'mm'
|
|
608
|
+
? html`<span></span><input type="number" step="any" aria-label=${i18next.t('figure.label.align-mm')} .value=${String(mm)}
|
|
609
|
+
?disabled=${this.locked} @change=${(e: Event) => tell({ mm: Number((e.target as HTMLInputElement).value) || 0 })} />`
|
|
610
|
+
: nothing}
|
|
611
|
+
</div>`
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/** A pointer's place in the drawing's own units. */
|
|
615
|
+
private pointIn(ev: PointerEvent) {
|
|
616
|
+
const svgEl = (ev.currentTarget as Element).closest('svg') as SVGSVGElement
|
|
617
|
+
const pt = svgEl.createSVGPoint()
|
|
618
|
+
pt.x = ev.clientX
|
|
619
|
+
pt.y = ev.clientY
|
|
620
|
+
return pt.matrixTransform(svgEl.getScreenCTM()!.inverse())
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** Let go: the angles dragged become the place, told to the form once. */
|
|
624
|
+
private letGo(surface: { form: 'around' | 'toward'; axis: Axis; place: FigureSurfacePlace }) {
|
|
625
|
+
const t = this.turning
|
|
626
|
+
if (!t) return
|
|
627
|
+
this.turning = undefined
|
|
628
|
+
this.change({ surface: this.placeOf(surface, t) })
|
|
629
|
+
this.released()
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** The place the angles come to: the kernel's (`figureSurfacePlaceOf`), along a side kept. */
|
|
633
|
+
private placeOf(surface: { form: 'around' | 'toward'; axis: Axis; place: FigureSurfacePlace }, t: { around?: number; azimuth?: number; elevation?: number }): FigureSurfacePlace {
|
|
634
|
+
const now = figureSurfaceAnglesOf(surface, surface.place) as { around?: number; azimuth?: number; elevation?: number }
|
|
635
|
+
const angles = surface.form === 'around' ? { around: t.around ?? now.around! } : { azimuth: t.azimuth ?? now.azimuth!, elevation: t.elevation ?? now.elevation! }
|
|
636
|
+
return figureSurfacePlaceOf(surface, surface.place, angles)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/** Arrows turn it a degree, fifteen with Shift: round the side, or round and up and down a sphere. */
|
|
640
|
+
private turnKeys(ev: KeyboardEvent, surface: { form: 'around' | 'toward'; axis: Axis; place: FigureSurfacePlace }) {
|
|
641
|
+
const step = { ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, 1], ArrowDown: [0, -1] }[ev.key]
|
|
642
|
+
if (!step || this.locked) return
|
|
643
|
+
ev.preventDefault()
|
|
644
|
+
ev.stopPropagation()
|
|
645
|
+
const by = ev.shiftKey ? 15 : 1
|
|
646
|
+
const now = figureSurfaceAnglesOf(surface, surface.place) as { around?: number; azimuth?: number; elevation?: number }
|
|
647
|
+
if (surface.form === 'around') {
|
|
648
|
+
if (!step[0]) return
|
|
649
|
+
this.change({ surface: this.placeOf(surface, { around: now.around! + step[0] * by }) })
|
|
650
|
+
} else this.change({ surface: this.placeOf(surface, { azimuth: now.azimuth! + step[0] * by, elevation: Math.max(-90, Math.min(90, now.elevation! + step[1] * by)) }) })
|
|
651
|
+
this.released()
|
|
652
|
+
}
|
|
432
653
|
}
|
|
@@ -267,8 +267,8 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
267
267
|
@property({ attribute: false }) body?: { id: string; shape: FigureEvaluatedShape }
|
|
268
268
|
/** Which of its measures can be set (figure-model's `figureSectionMeasuresOf`). */
|
|
269
269
|
@property({ attribute: false }) measures?: { measures: Record<Axis, FigureSectionMeasure>; round: boolean }
|
|
270
|
-
/**
|
|
271
|
-
@property({ attribute: false }) kept?:
|
|
270
|
+
/** What a length typed keeps: the part's middle, or its fastening (ADR-0140 4.1); undefined when it keeps no point. */
|
|
271
|
+
@property({ attribute: false }) kept?: 'centre' | 'relation' | 'none'
|
|
272
272
|
/** Why a push's end cannot be drawn to a point now, in the kernel's words (what is fastened to it); empty when it can. */
|
|
273
273
|
@property({ attribute: false }) pointBlocked = ''
|
|
274
274
|
/** The figure's revision now; the editor remembers the one it opened at. */
|
|
@@ -490,10 +490,10 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
490
490
|
return { h: Math.round(p.x), v: Math.round(p.y) }
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
-
/** What a measure's field says under it: following another value, or for the length
|
|
494
|
-
private noteWords(axis: Axis, kept?:
|
|
495
|
-
const note = measureNoteOf(this.measures?.measures[axis], kept
|
|
496
|
-
return note ? i18next.t(note.key
|
|
493
|
+
/** What a measure's field says under it: following another value, or for the length what it keeps. */
|
|
494
|
+
private noteWords(axis: Axis, kept?: 'centre' | 'relation' | 'none'): string {
|
|
495
|
+
const note = measureNoteOf(this.measures?.measures[axis], kept)
|
|
496
|
+
return note ? i18next.t(note.key) : ''
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
render() {
|
|
@@ -169,7 +169,7 @@ export class FigureSlotLayoutPanel extends localize(i18next)(LitElement) {
|
|
|
169
169
|
const anchorFaces = anchorFacesOf(form.anchors, geometry)
|
|
170
170
|
const value = {
|
|
171
171
|
canUseDefault: false,
|
|
172
|
-
presets: slotPresetsOf(form.anchors
|
|
172
|
+
presets: slotPresetsOf(form.anchors),
|
|
173
173
|
/* The first anchor's face is the size a preset is laid out over; the slot height is the author's. */
|
|
174
174
|
presetSize: anchorFaces[form.anchors[0]?.id ?? ''] ?? {},
|
|
175
175
|
anchors: form.anchors.map(a => ({ id: a.id, label: `${a.id} · ${a.part} ${i18next.t('figure.label.face-' + a.face)}` })),
|
package/client/modeller/gizmo.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { figureAttachmentsOf, figureShapeProviderOf } from '@hatiolab/figure-model'
|
|
2
|
-
import type { FigureAsset, FigureInputChange } from '@hatiolab/figure-model'
|
|
2
|
+
import type { FigureAsset, FigureInputChange, FigureSizeKeep } from '@hatiolab/figure-model'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* What the gizmo may write for a part: move · turn · size, on a asset.
|
|
@@ -115,6 +115,17 @@ export interface GizmoResult {
|
|
|
115
115
|
position?: [number, number, number]
|
|
116
116
|
rotation?: [number, number, number]
|
|
117
117
|
scale?: [number, number, number]
|
|
118
|
+
/** The handle a size drag held, per own axis -1 · 0 · 1 (the scene's `scaleGrab`): what figure-model keeps from. */
|
|
119
|
+
grab?: { x: number; y: number; z: number }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* What a size drag keeps (ADR-0140 4.1): the side opposite a handle on one axis; the middle for the uniform handle, or
|
|
124
|
+
* when no handle is known -- figure-model's own words for it, so the drag and its commit ask the same.
|
|
125
|
+
*/
|
|
126
|
+
export function sizeKeepOf(grab: GizmoResult['grab']): FigureSizeKeep {
|
|
127
|
+
const held = (['x', 'y', 'z'] as const).filter(axis => grab?.[axis])
|
|
128
|
+
return held.length === 1 ? { keep: 'opposite', axis: held[0]!, side: grab![held[0]!]! > 0 ? 1 : -1 } : { keep: 'centre' }
|
|
118
129
|
}
|
|
119
130
|
|
|
120
131
|
/**
|
package/client/modeller/links.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { FIGURE_FACES, figureShapeFaces, figurePartsExtentOf, compileFigureAsset, figureFollowOf, proposeFigureProportion, defaultInstanceSizeRange, foldInstanceSizeRange, figureAttachmentsOf, figureBindingsOf, figureInstanceSizeLimitsOf, figureFollowableAxesOf, figureShapeProviderOf, figureMimicTermsOf, figureFastenKeptPlaceOf, figureFastenAxesOf, figureJointAsFixed, figureJointWithMotion } from '@hatiolab/figure-model'
|
|
1
|
+
import { FIGURE_FACES, figureShapeFaces, figurePartsExtentOf, compileFigureAsset, figureFollowOf, proposeFigureProportion, defaultInstanceSizeRange, foldInstanceSizeRange, figureAttachmentsOf, figureBindingsOf, figureInstanceSizeLimitsOf, figureFollowableAxesOf, figureShapeProviderOf, figureMimicTermsOf, figureFastenKeptPlaceOf, figureFastenAxesOf, figureJointAsFixed, figureJointWithMotion, figureSurfaceOf } from '@hatiolab/figure-model'
|
|
2
2
|
import type { FigureFollow, FigureSizeRange } from '@hatiolab/figure-model'
|
|
3
|
-
import type { FigureAsset, FigureAuthoringAction, FigureBindingPlace, FigureDimensionSource, FigureFace, FigureFacing, FigureJoint } from '@hatiolab/figure-model'
|
|
3
|
+
import type { FigureAsset, FigureAuthoringAction, FigureBindingPlace, FigureDimensionSource, FigureFace, FigureFacing, FigureJoint, FigureSurfacePlace } from '@hatiolab/figure-model'
|
|
4
4
|
import { startingShapeKindOf } from './starting-shape.js'
|
|
5
5
|
import { boxOf } from './align.js'
|
|
6
6
|
import { graphValuesOf, type FigureRevision } from './revision.js'
|
|
@@ -35,6 +35,23 @@ export function followOf(asset: FigureAsset, part: string, axis: Axis): Follow {
|
|
|
35
35
|
return figureFollowOf(asset, part, axis)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The parts whose size follows the figure's size on some axis, in the model's order: the ones that change when the
|
|
40
|
+
* figure is resized on a drawing. Read by figure-model's own follow (`figureFollowOf`), not worked out again here.
|
|
41
|
+
*/
|
|
42
|
+
export function sizeFollowersOf(asset: FigureAsset): string[] {
|
|
43
|
+
const parts = (modelOf(asset).nodes as Array<{ id: string; op: string }>).filter(n => n.op === 'place@1').map(n => n.id)
|
|
44
|
+
return parts.filter(part =>
|
|
45
|
+
AXES.some(axis => {
|
|
46
|
+
try {
|
|
47
|
+
return figureFollowOf(asset, part, axis).of === 'instance'
|
|
48
|
+
} catch {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
/** Whether the asset declares an instance size on an axis — only then can a dimension follow it. */
|
|
39
56
|
export const hasInstanceSize = (asset: FigureAsset, axis: Axis) => modelOf(asset).inputs.some(i => i.id === `size.${axis}` && i.role === 'design')
|
|
40
57
|
|
|
@@ -224,7 +241,14 @@ export function measureNow(revision: FigureRevision, part: string, axis: Axis):
|
|
|
224
241
|
*/
|
|
225
242
|
export function facesOf(asset: FigureAsset, part: string): string[] {
|
|
226
243
|
const shape = modelOf(asset).nodes.find(n => n.id === `${part}.shape`)
|
|
227
|
-
|
|
244
|
+
const faces = (shape && figureShapeFaces(shape as never)) ?? {}
|
|
245
|
+
/* A cylinder's curved side is no face: a part is fastened to a point on it (ADR-0140 3.9, `surfaceOf`). */
|
|
246
|
+
return inScreenOrder(Object.keys(faces).filter(f => faces[f]!.contact !== 'line'))
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The curved surface a part offers to be fastened to at a point: a cylinder's side or a sphere's round (ADR-0140 3.9). */
|
|
250
|
+
export function surfaceOf(asset: FigureAsset, part: string): { form: 'around' | 'toward'; axis: Axis } | undefined {
|
|
251
|
+
return figureSurfaceOf(asset, part)
|
|
228
252
|
}
|
|
229
253
|
|
|
230
254
|
/** Faces in the order the screen lists them: the six outer ones left to front, then the rest as the shape gives them. */
|
|
@@ -242,7 +266,7 @@ export function faceAxisOf(asset: FigureAsset, part: string, face: string): Axis
|
|
|
242
266
|
|
|
243
267
|
/** Whether `attach` can take this part: any part whose shape has a flat face. */
|
|
244
268
|
export function canFasten(asset: FigureAsset, part: string): boolean {
|
|
245
|
-
return facesOf(asset, part).length > 0
|
|
269
|
+
return facesOf(asset, part).length > 0 || !!surfaceOf(asset, part)
|
|
246
270
|
}
|
|
247
271
|
|
|
248
272
|
const OUTER: Record<Axis, [string, string]> = { x: ['left', 'right'], y: ['bottom', 'top'], z: ['back', 'front'] }
|
|
@@ -267,13 +291,15 @@ export function rowAxesOf(asset: FigureAsset, part: string): Axis[] {
|
|
|
267
291
|
* Why a part cannot be made into a row here, in the terms `rowAxesOf` refuses it: said, not hidden (ADR-0097 §5.5).
|
|
268
292
|
* Nothing when it can, or when it already is a row (that row is shown where it stands).
|
|
269
293
|
*/
|
|
270
|
-
export function rowBlockOf(asset: FigureAsset, part: string): 'unfastened' | 'on-plane' | 'holds-parts' | 'at-origin' | undefined {
|
|
294
|
+
export function rowBlockOf(asset: FigureAsset, part: string): 'unfastened' | 'on-plane' | 'holds-parts' | 'at-origin' | 'on-surface' | undefined {
|
|
271
295
|
const joints = asset.joints ?? []
|
|
272
296
|
const j = joints.find(o => o.body1.part === part)
|
|
273
297
|
if (!j) return 'unfastened'
|
|
274
298
|
if (j.repeat) return undefined
|
|
275
299
|
if (!('part' in j.body0)) return 'on-plane'
|
|
276
300
|
if (joints.some(o => 'part' in o.body0 && o.body0.part === part)) return 'holds-parts'
|
|
301
|
+
/* On a point of a curved surface a row has no face to run across (ADR-0140 3.9): the kernel refuses it. */
|
|
302
|
+
if ('surface' in j.body0) return 'on-surface'
|
|
277
303
|
if (!('face' in j.body0) || 'at' in j.body1) return 'at-origin'
|
|
278
304
|
return undefined
|
|
279
305
|
}
|
|
@@ -372,13 +398,29 @@ export function fasteningOf(
|
|
|
372
398
|
alignMm: Partial<Record<Axis, number>>
|
|
373
399
|
/** Placed by a part's origin (ADR-0107 decision 4): no face to fasten by, and the form does not write it. */
|
|
374
400
|
atOrigin?: true
|
|
401
|
+
/** On a point of the target's curved surface (ADR-0140 3.9), as the record says it. */
|
|
402
|
+
surface?: FigureSurfacePlace
|
|
375
403
|
} | null {
|
|
376
404
|
const record = revision.asset.joints?.find(j => j.body1.part === part)
|
|
377
405
|
if (!record) return null
|
|
378
406
|
const toPlane = 'plane' in record.body0
|
|
379
407
|
const target = 'part' in record.body0 ? record.body0.part : ''
|
|
408
|
+
const surface = 'surface' in record.body0 ? record.body0.surface : undefined
|
|
380
409
|
const targetFace = 'face' in record.body0 ? record.body0.face : 'top'
|
|
381
|
-
const atOrigin = !('plane' in record.body0) && (!('face' in record.body0) || 'at' in record.body1)
|
|
410
|
+
const atOrigin = !('plane' in record.body0) && ((!('face' in record.body0) && !surface) || 'at' in record.body1)
|
|
411
|
+
if (surface)
|
|
412
|
+
return {
|
|
413
|
+
toPlane,
|
|
414
|
+
target,
|
|
415
|
+
targetFace,
|
|
416
|
+
facing: record.origin?.facing ?? 'meet',
|
|
417
|
+
gap: typeof record.origin?.gap === 'number' ? record.origin.gap : 0,
|
|
418
|
+
...(record.origin?.gap !== undefined && typeof record.origin.gap !== 'number' ? { gapKept: record.origin.gap } : {}),
|
|
419
|
+
align: {},
|
|
420
|
+
alignMm: {},
|
|
421
|
+
surface,
|
|
422
|
+
...(atOrigin ? { atOrigin: true as const } : {})
|
|
423
|
+
}
|
|
382
424
|
/* Where across the face: as the record says it, and where it says nothing, where the part stands now. */
|
|
383
425
|
const kept = placeKeptOf(revision, part, toPlane ? 'plane' : { part: target, face: targetFace })
|
|
384
426
|
for (const [axis, how] of Object.entries(record.origin?.align ?? {}) as Array<[Axis, unknown]>) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { FIGURE_SECTION_PLANE, FIGURE_SECTION_POINT_LIMIT, checkFigureSection, figureExtrusionFeaturesOf, figureSectionSidesOf } from '@hatiolab/figure-model'
|
|
2
2
|
import type { FigureAuthoringAction, FigureCavityRequest, FigureEvaluatedShape, FigureSection, FigureSectionIssue, FigureSectionMeasure, FigureSectionPoint } from '@hatiolab/figure-model'
|
|
3
|
-
import { END_FACE } from './section-controls.js'
|
|
4
3
|
|
|
5
4
|
/*
|
|
6
5
|
* What the section editor does to a drawn section's points, and how it says what is wrong with them (ADR-0100 §3.2).
|
|
@@ -218,14 +217,15 @@ export function sectionActionOf(part: string, kind: FigureSection['kind'] | unde
|
|
|
218
217
|
}
|
|
219
218
|
|
|
220
219
|
/**
|
|
221
|
-
* What a measure's field says under it: that it follows another value, or
|
|
222
|
-
*
|
|
220
|
+
* What a measure's field says under it: that it follows another value, or what a measure typed keeps -- the part's
|
|
221
|
+
* middle, or its fastening (ADR-0140 4.1, figure-model's `figureSizeEditOf`); null when there is nothing to say.
|
|
223
222
|
*/
|
|
224
|
-
export function measureNoteOf(measure: FigureSectionMeasure | undefined, kept:
|
|
223
|
+
export function measureNoteOf(measure: FigureSectionMeasure | undefined, kept: 'centre' | 'relation' | 'none' | undefined): { key: string } | null {
|
|
225
224
|
if (measure === 'linked') return { key: 'figure.text.section-editor-linked' }
|
|
226
|
-
if (kept ===
|
|
227
|
-
if (kept ===
|
|
228
|
-
return { key: 'figure.text.
|
|
225
|
+
if (kept === 'centre') return { key: 'figure.text.size-keeps-centre' }
|
|
226
|
+
if (kept === 'relation') return { key: 'figure.text.size-keeps-fastening' }
|
|
227
|
+
if (kept === 'none') return { key: 'figure.text.size-keeps-none' }
|
|
228
|
+
return null
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
/** The words beside a drawn section's edit button: how many points it has, and the apex when its end is one point. */
|