@things-factory/figure-ui 10.1.105 → 10.1.106
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 +6 -13
- package/client/modeller/editor.ts +13 -2
- package/client/modeller/figure-cavity-drawing.ts +16 -10
- package/client/modeller/figure-links.ts +49 -35
- package/client/modeller/figure-parts.ts +3 -3
- package/client/modeller/figure-section-editor.ts +106 -11
- package/client/modeller/section-drawing.ts +27 -4
- package/dist-client/modeller/canvas.d.ts +0 -2
- package/dist-client/modeller/canvas.js +6 -17
- package/dist-client/modeller/canvas.js.map +1 -1
- package/dist-client/modeller/clips.d.ts +1 -1
- package/dist-client/modeller/editor.js +15 -2
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-cavity-drawing.js +14 -8
- package/dist-client/modeller/figure-cavity-drawing.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +2 -2
- package/dist-client/modeller/figure-links.js +49 -34
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-parts.d.ts +2 -1
- package/dist-client/modeller/figure-parts.js +2 -2
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-section-editor.d.ts +13 -0
- package/dist-client/modeller/figure-section-editor.js +109 -9
- package/dist-client/modeller/figure-section-editor.js.map +1 -1
- package/dist-client/modeller/section-drawing.d.ts +12 -1
- package/dist-client/modeller/section-drawing.js +23 -3
- package/dist-client/modeller/section-drawing.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/test/cavity-drawing.test.ts +5 -0
- package/test/command-preview.test.ts +11 -4
- package/test/first-minute.test.ts +6 -2
- package/test/layout.test.ts +11 -0
- package/test/section-editor.test.ts +29 -2
- package/translations/en.json +2 -9
- package/translations/ja.json +2 -9
- package/translations/ko.json +2 -9
- package/translations/ms.json +2 -9
- package/translations/zh.json +2 -9
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { LitElement, html, css,
|
|
1
|
+
import { LitElement, html, css, unsafeCSS, type PropertyValues } from 'lit'
|
|
2
2
|
import { customElement, property, query } from 'lit/decorators.js'
|
|
3
|
-
import { i18next } from '@operato/i18n'
|
|
4
3
|
import { create, Model, niceGridStep } from '@hatiolab/things-scene'
|
|
5
4
|
|
|
6
5
|
import type { GizmoReach } from './gizmo.js'
|
|
@@ -71,14 +70,6 @@ export class FigureCanvas extends LitElement {
|
|
|
71
70
|
width: 100%;
|
|
72
71
|
height: 100%;
|
|
73
72
|
}
|
|
74
|
-
p {
|
|
75
|
-
position: absolute;
|
|
76
|
-
inset: 45% 1rem auto;
|
|
77
|
-
margin: 0;
|
|
78
|
-
text-align: center;
|
|
79
|
-
pointer-events: none;
|
|
80
|
-
color: #f4f4f4;
|
|
81
|
-
}
|
|
82
73
|
`
|
|
83
74
|
@property({ attribute: false }) parts: FigureRenderPart[] = []
|
|
84
75
|
@property({ attribute: false }) palette: Record<string, string> = {}
|
|
@@ -96,8 +87,6 @@ export class FigureCanvas extends LitElement {
|
|
|
96
87
|
* would reach the editor as an edit.
|
|
97
88
|
*/
|
|
98
89
|
@property({ type: Boolean }) viewOnly = false
|
|
99
|
-
/** The editor shows a new figure's start card over the canvas: the canvas's own one-line hint gives way to it. */
|
|
100
|
-
@property({ type: Boolean }) startShown = false
|
|
101
90
|
/** Where the selected part turns and how far (`joint-fan`): shown, never picked, never saved. */
|
|
102
91
|
@property({ attribute: false }) fan: FigureRenderPart | null = null
|
|
103
92
|
/**
|
|
@@ -117,7 +106,11 @@ export class FigureCanvas extends LitElement {
|
|
|
117
106
|
private writing = false
|
|
118
107
|
|
|
119
108
|
render() {
|
|
120
|
-
|
|
109
|
+
/*
|
|
110
|
+
* No line of its own over an empty stage: a new figure's start card says how to begin, and an empty stage otherwise
|
|
111
|
+
* is one loading, or with every part hidden -- where 「도형을 추가해…」 flashed and said the wrong thing (2026-10-02).
|
|
112
|
+
*/
|
|
113
|
+
return html`<div stage></div>`
|
|
121
114
|
}
|
|
122
115
|
|
|
123
116
|
firstUpdated() {
|
|
@@ -60,6 +60,17 @@ import { startingShapeKindOfBody } from './starting-shape.js'
|
|
|
60
60
|
*/
|
|
61
61
|
type SideTab = 'properties' | 'capability' | 'motion' | 'verdict' | 'view' | 'history'
|
|
62
62
|
|
|
63
|
+
/** Whether the narrow fold was said in this browser before; true from now on. Storage refused: said, as before. */
|
|
64
|
+
function foldSaid(): boolean {
|
|
65
|
+
try {
|
|
66
|
+
if (localStorage.getItem('figure-fold-said')) return true
|
|
67
|
+
localStorage.setItem('figure-fold-said', '1')
|
|
68
|
+
} catch {
|
|
69
|
+
/* No storage here: said every time, as it was. */
|
|
70
|
+
}
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
|
|
63
74
|
/** Product editor surface. The injected session is shared by page persistence and AI commands. */
|
|
64
75
|
@customElement('figure-editor')
|
|
65
76
|
export class FigureEditor extends LitElement {
|
|
@@ -506,6 +517,7 @@ export class FigureEditor extends LitElement {
|
|
|
506
517
|
Narrow, the parts list gives way to the canvas rather than all three shrinking (designer 2026-09-27, U11): under
|
|
507
518
|
FOLD_START_UNDER px the canvas would be left under about 560 px beside both sides. The fold is said once, and the
|
|
508
519
|
bar's own toggle opens it again; opened by the author, it is not folded again until the screen has been wide.
|
|
520
|
+
Said once in this browser, not on every opening (2026-10-02): the list folds as before, the notice does not come back.
|
|
509
521
|
*/
|
|
510
522
|
private static readonly FOLD_START_UNDER = 1100
|
|
511
523
|
private foldedForWidth = false
|
|
@@ -519,7 +531,7 @@ export class FigureEditor extends LitElement {
|
|
|
519
531
|
if (!panes.foldStart && !this.keptOpenByAuthor) {
|
|
520
532
|
panes.foldStart = true
|
|
521
533
|
this.foldedForWidth = true
|
|
522
|
-
notify({ level: 'info', message: i18next.t('figure.text.parts-panel-folded-narrow') })
|
|
534
|
+
if (!foldSaid()) notify({ level: 'info', message: i18next.t('figure.text.parts-panel-folded-narrow') })
|
|
523
535
|
}
|
|
524
536
|
} else {
|
|
525
537
|
if (this.foldedForWidth && panes.foldStart) panes.foldStart = false
|
|
@@ -2016,7 +2028,6 @@ export class FigureEditor extends LitElement {
|
|
|
2016
2028
|
${view.geometry.length === 0 && !unavailable ? this.renderStart() : nothing}
|
|
2017
2029
|
${this.sectionEditing && this.sectionEditing === this.selected && !unavailable ? this.renderSectionEditor(view.geometry) : nothing}
|
|
2018
2030
|
<figure-canvas
|
|
2019
|
-
?startShown=${view.geometry.length === 0 && !unavailable}
|
|
2020
2031
|
.view=${this.view}
|
|
2021
2032
|
.parts=${this.drawnParts(view.geometry, asset)}
|
|
2022
2033
|
.anchors=${this.anchorsOf(asset as FigureAsset | undefined)}
|
|
@@ -175,7 +175,8 @@ export class FigureCavityDrawing extends localize(i18next)(LitElement) {
|
|
|
175
175
|
const W = 300,
|
|
176
176
|
H = 170,
|
|
177
177
|
pad = 26
|
|
178
|
-
|
|
178
|
+
/* Wider room left and right: a wall's number stands outside the section there. */
|
|
179
|
+
const k = Math.min((W - 2 * (pad + 10)) / across(h), (H - 2 * pad) / across(v))
|
|
179
180
|
const ox = W / 2,
|
|
180
181
|
oy = H / 2
|
|
181
182
|
const inside = figureCavitySectionOf(s as FigureSection, e.axis, w.wall)
|
|
@@ -193,18 +194,23 @@ export class FigureCavityDrawing extends localize(i18next)(LitElement) {
|
|
|
193
194
|
const handles = [`${h}-`, `${h}+`, `${v}-`, `${v}+`].map(side => {
|
|
194
195
|
const a = side[0] as Axis,
|
|
195
196
|
low = side[1] === '-'
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
197
|
+
const sign = low ? -1 : 1
|
|
198
|
+
const edge = sign * (half[a] - w.wall[side]! * k)
|
|
199
|
+
/* The two handles across a narrow inside would stand on each other: then each steps aside, the low one first. */
|
|
200
|
+
const room = 2 * half[a] - (w.wall[`${a}-`]! + w.wall[`${a}+`]!) * k
|
|
201
|
+
const aside = room < 16 ? sign * 9 : 0
|
|
202
|
+
const x = a === h ? ox + edge : ox + aside,
|
|
203
|
+
y = a === v ? oy + edge : oy + aside
|
|
204
|
+
/*
|
|
205
|
+
The number stands just past the section's outer edge on its own side, so the opposite wall's never meets it
|
|
206
|
+
however thin the walls or shallow the section (2026-10-02: at 70 mm deep the two walls' numbers lay on each other).
|
|
207
|
+
*/
|
|
208
|
+
const tx = a === h ? ox + sign * (half[a] + 5) : ox,
|
|
209
|
+
ty = a === v ? oy + sign * (half[a] + (low ? 5 : 13)) : oy + 4
|
|
204
210
|
return svg`<g wall=${side} style=${`cursor:${a === h ? 'ew-resize' : 'ns-resize'}`}
|
|
205
211
|
@pointerdown=${(ev: PointerEvent) => this.grab(ev, { kind: 'wall', side })}>
|
|
206
212
|
<rect x=${x - 6} y=${y - 6} width="12" height="12" rx="2" style="fill:var(--md-sys-color-surface);stroke:var(--md-sys-color-primary);stroke-width:1.5" />
|
|
207
|
-
<text typed x=${tx} y=${ty} font-size="11" text-anchor=${a === h ? '
|
|
213
|
+
<text typed x=${tx} y=${ty} font-size="11" text-anchor=${a === h ? (low ? 'end' : 'start') : 'middle'} style="fill:var(--md-sys-color-on-surface)"
|
|
208
214
|
@pointerdown=${(ev: PointerEvent) => this.type(ev, { kind: 'wall', side }, 'top', w.wall[side]!)}>${mm(w.wall[side]!)}</text>
|
|
209
215
|
<title>${i18next.t('figure.label.face-' + SIDE_FACE[side])} ${i18next.t('figure.label.section-wall')}</title>
|
|
210
216
|
</g>`
|
|
@@ -121,6 +121,10 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
121
121
|
p[fasten-mode] { margin: 0; font-weight: 600; }
|
|
122
122
|
section[fastening][spotlit] { outline: 2px solid var(--md-sys-color-primary, #477b9e); outline-offset: 4px; border-radius: 6px; transition: outline-color 0.3s; }
|
|
123
123
|
details { margin-top: 8px; }
|
|
124
|
+
details[row-fold] { margin: 0; }
|
|
125
|
+
details[row-fold] > summary { padding: 0; color: var(--md-sys-color-on-surface); }
|
|
126
|
+
details[row-fold] > summary h3 { margin: 0; }
|
|
127
|
+
details[row-fold][open] > summary { margin-bottom: 8px; }
|
|
124
128
|
/* What the command would do, said beside the result drawn on the canvas. */
|
|
125
129
|
ul[preview-words] { margin: 8px 0; padding: 8px 12px 8px 26px; border-radius: 6px; background: var(--md-sys-color-surface-container, #8881); display: grid; gap: 4px; }
|
|
126
130
|
p[preview-words] { margin: 8px 0; padding: 8px 12px; border-radius: 6px; background: var(--md-sys-color-surface-container, #8881); }
|
|
@@ -399,15 +403,14 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
399
403
|
* attached" while it was selected, so the form still held what it had before (designer 2026-09-27: it opened the
|
|
400
404
|
* holding places instead).
|
|
401
405
|
*/
|
|
402
|
-
/** The
|
|
403
|
-
private async
|
|
404
|
-
const detail = this.renderRoot.querySelector<HTMLDetailsElement>('details[fasten-detail]')
|
|
405
|
-
if (!detail) return
|
|
406
|
-
detail.open = true
|
|
406
|
+
/** The face the part sits on, in view with the focus on it: where the place across the face is dragged or keyed. */
|
|
407
|
+
private async showWhere() {
|
|
407
408
|
this.working = 'fasten'
|
|
408
409
|
await this.updateComplete
|
|
409
|
-
|
|
410
|
-
|
|
410
|
+
const where = this.renderRoot.querySelector('figure-fasten-drawing')?.shadowRoot?.querySelector<HTMLElement>('div[where]')
|
|
411
|
+
if (!where) return
|
|
412
|
+
where.scrollIntoView({ block: 'nearest', behavior: 'smooth' })
|
|
413
|
+
where.focus({ preventScroll: true })
|
|
411
414
|
}
|
|
412
415
|
|
|
413
416
|
/** The fastening form, open on this part and in view: for "fasten it first" from elsewhere. */
|
|
@@ -555,19 +558,22 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
555
558
|
} as FigureAuthoringAction)
|
|
556
559
|
: undefined
|
|
557
560
|
if (action) this.fastenCandidate = { action, ...(f.toPlane ? {} : { face: { part: f.target, face: targetFace } }) }
|
|
558
|
-
/*
|
|
561
|
+
/*
|
|
562
|
+
What the folded details hold that is not their default, said while folded; the way and the place on the face are
|
|
563
|
+
the drawing's, and said there (user 2026-10-02: the details had selects repeating the drawing). Nothing is said
|
|
564
|
+
for the default.
|
|
565
|
+
*/
|
|
559
566
|
const detail = [
|
|
560
|
-
...(f.facing === 'flush' && !f.toPlane ? [i18next.t('figure.label.facing-flush')] : []),
|
|
561
|
-
...Object.entries(align ?? {}).map(([axis, one]) =>
|
|
562
|
-
one === 'min' || one === 'max'
|
|
563
|
-
? i18next.t(`figure.text.fasten-summary-edge-${one}`, { axis: axis.toUpperCase() })
|
|
564
|
-
: typeof one.mm === 'number'
|
|
565
|
-
? i18next.t('figure.text.fasten-summary-off-centre', { axis: axis.toUpperCase(), mm: String(Math.round(one.mm * 10) / 10) })
|
|
566
|
-
: i18next.t('figure.text.fasten-summary-follows', { axis: axis.toUpperCase() })
|
|
567
|
-
),
|
|
568
567
|
...(f.gap ? [i18next.t('figure.text.fasten-summary-gap', { mm: String(f.gap) })] : []),
|
|
569
568
|
...(f.twist ? [i18next.t('figure.text.fasten-summary-twist', { degrees: String(f.twist) })] : [])
|
|
570
569
|
]
|
|
570
|
+
/* Across the face, a row only where the drawing cannot set it: no drawing of the face, or a share or a field to follow. */
|
|
571
|
+
const view = f.toPlane || f.target ? faceViewOf(asset, this.partId, aim) : undefined
|
|
572
|
+
const alignRows = (f.toPlane || f.target ? alignAxesOf(asset, this.partId, aim, !!f.twist) : []).filter(
|
|
573
|
+
({ axis }) => !view || !view.axes.includes(axis) || !!f.alignInput?.[axis] || hasInstanceSize(asset, axis)
|
|
574
|
+
)
|
|
575
|
+
/* A fastening the part has is changed as soon as something is chosen, as the drawing does; a new one waits for 붙이기. */
|
|
576
|
+
const settle = () => this.onFastenRelease(!!held)
|
|
571
577
|
/*
|
|
572
578
|
Fastening it anew is said, so the form is not taken for the one it has (designer 2026-09-27). The one it has is
|
|
573
579
|
said once, by the sentence over the drawing -- the panel's head says it too; a third and a fourth line saying the
|
|
@@ -590,7 +596,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
590
596
|
? html`<div overhang role="status">
|
|
591
597
|
${this.overhangWords.map(line => html`<p>${line}</p>`)}
|
|
592
598
|
${current
|
|
593
|
-
? html`<button act @click=${() => this.
|
|
599
|
+
? html`<button act @click=${() => this.showWhere()}><md-icon>tune</md-icon>${i18next.t('figure.button.fix-alignment')}</button>`
|
|
594
600
|
: nothing}
|
|
595
601
|
</div>`
|
|
596
602
|
: nothing}
|
|
@@ -607,6 +613,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
607
613
|
<select @change=${(e: Event) => {
|
|
608
614
|
const value = (e.target as HTMLSelectElement).value
|
|
609
615
|
this.aimAt(asset, value === PLANE ? { ...f, toPlane: true, target: '' } : { ...f, toPlane: false, target: value })
|
|
616
|
+
if (value) settle()
|
|
610
617
|
}}>
|
|
611
618
|
<option value="" ?selected=${!f.target && !f.toPlane}>${i18next.t('figure.label.target-part-placeholder')}</option>
|
|
612
619
|
<option value=${PLANE} ?selected=${f.toPlane}>${planeWord}</option>
|
|
@@ -626,7 +633,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
626
633
|
.face=${targetFace}
|
|
627
634
|
.modes=${f.toPlane ? (['meet'] as FastenMode[]) : ([...(['meet', 'flush'] as FastenMode[]), ...(turnsInPlace(asset, this.partId) ? (['centre'] as FastenMode[]) : [])])}
|
|
628
635
|
.mode=${mode}
|
|
629
|
-
.view=${
|
|
636
|
+
.view=${view}
|
|
630
637
|
.align=${f.align}
|
|
631
638
|
.alignMm=${f.alignMm}
|
|
632
639
|
.gap=${f.gap}
|
|
@@ -638,19 +645,21 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
638
645
|
></figure-fasten-drawing>`
|
|
639
646
|
: html`<p hint>${i18next.t('figure.text.fasten-target-first')}</p>`}
|
|
640
647
|
${(f.toPlane || f.target) ? html`<details fasten-detail>
|
|
641
|
-
<summary>${i18next.t('figure.label.fasten-detail')} · <span fasten-summary>${detail.
|
|
642
|
-
${
|
|
648
|
+
<summary>${i18next.t('figure.label.fasten-detail')}${detail.length ? html` · <span fasten-summary>${detail.join(' · ')}</span>` : nothing}</summary>
|
|
649
|
+
${alignRows.length ? this.renderAlignment(alignRows, settle) : nothing}
|
|
643
650
|
<div form>
|
|
644
651
|
<span>${i18next.t('figure.label.gap')}</span>
|
|
645
|
-
<input type="number" step="any" .value=${String(f.gap)} @change=${(e: Event) => (this.fasten = { ...f, gap: Number((e.target as HTMLInputElement).value) || 0 })} />
|
|
652
|
+
<input type="number" step="any" .value=${String(f.gap)} @change=${(e: Event) => ((this.fasten = { ...f, gap: Number((e.target as HTMLInputElement).value) || 0 }), settle())} />
|
|
646
653
|
<span>${i18next.t('figure.label.twist')}</span>
|
|
647
|
-
<input type="number" step="any" .value=${String(f.twist)} @change=${(e: Event) => (this.fasten = { ...f, twist: Number((e.target as HTMLInputElement).value) || 0 })} />
|
|
654
|
+
<input type="number" step="any" .value=${String(f.twist)} @change=${(e: Event) => ((this.fasten = { ...f, twist: Number((e.target as HTMLInputElement).value) || 0 }), settle())} />
|
|
648
655
|
</div>
|
|
649
656
|
</details>
|
|
650
|
-
${action && this.working === 'fasten' ? this.renderPreviewWords() : nothing}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
657
|
+
${/* What 붙이기 would do; a fastening the part has is changed at once, and the canvas shows it. */ action && !held && this.working === 'fasten' ? this.renderPreviewWords() : nothing}
|
|
658
|
+
${held
|
|
659
|
+
? nothing
|
|
660
|
+
: html`<button act ?disabled=${this.locked || !action} @click=${() => action && this.command(action)}>
|
|
661
|
+
<md-icon>link</md-icon>${i18next.t('figure.button.fasten')}
|
|
662
|
+
</button>`}` : nothing}
|
|
654
663
|
`
|
|
655
664
|
}
|
|
656
665
|
|
|
@@ -690,10 +699,10 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
690
699
|
* Where on the target face the part sits, on the two axes across it: centred, level with the target's edge on
|
|
691
700
|
* either side, or a distance off centre. A door hung level with the frame's front edge turns about that edge.
|
|
692
701
|
*/
|
|
693
|
-
private renderAlignment(axes: { axis: Axis; edges: boolean }[]) {
|
|
702
|
+
private renderAlignment(axes: { axis: Axis; edges: boolean }[], settle: () => void) {
|
|
694
703
|
const f = this.fasten
|
|
695
|
-
const set = (axis: Axis, how: Alignment) => (this.fasten = { ...f, align: { ...f.align, [axis]: how } })
|
|
696
|
-
const setMm = (axis: Axis, mm: number) => (this.fasten = { ...f, alignMm: { ...f.alignMm, [axis]: mm } })
|
|
704
|
+
const set = (axis: Axis, how: Alignment) => ((this.fasten = { ...f, align: { ...f.align, [axis]: how } }), settle())
|
|
705
|
+
const setMm = (axis: Axis, mm: number) => ((this.fasten = { ...f, alignMm: { ...f.alignMm, [axis]: mm } }), settle())
|
|
697
706
|
return html`<div form>
|
|
698
707
|
${axes.map(({ axis, edges }) => {
|
|
699
708
|
const how = f.align[axis] === 'min' || f.align[axis] === 'max' ? (edges ? f.align[axis]! : 'centre') : (f.align[axis] ?? 'centre')
|
|
@@ -739,14 +748,19 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
739
748
|
const blocked = rowBlockOf(asset, this.partId)
|
|
740
749
|
const axes = rowAxesOf(asset, this.partId)
|
|
741
750
|
const r = this.row
|
|
751
|
+
/*
|
|
752
|
+
Folded, open while it is being worked (2026-10-02): a row is made now and then, and most parts stand on the plane,
|
|
753
|
+
where the section said its reason open on every one. Opened, it says why or asks how.
|
|
754
|
+
*/
|
|
755
|
+
const head = html`<summary><h3>${i18next.t('figure.label.repeat')}</h3></summary>`
|
|
742
756
|
if (blocked || !axes.length)
|
|
743
|
-
return html`<section row>
|
|
744
|
-
|
|
757
|
+
return html`<section row><details row-fold>
|
|
758
|
+
${head}
|
|
745
759
|
<p hint row-blocked>${i18next.t(`figure.text.row-blocked-${blocked ?? 'unfastened'}`, { part: this.partId })}</p>
|
|
746
760
|
${blocked === 'unfastened'
|
|
747
761
|
? html`<button act @click=${() => this.openFastening(asset)}><md-icon>link</md-icon>${i18next.t('figure.button.go-fasten')}</button>`
|
|
748
762
|
: nothing}
|
|
749
|
-
</section>`
|
|
763
|
+
</details></section>`
|
|
750
764
|
const axis = axes.includes(r.axis as Axis) ? (r.axis as Axis) : axes[0]!
|
|
751
765
|
const ok = r.fills ? r.pitch > 0 : Number.isSafeInteger(r.count) && r.count >= 1
|
|
752
766
|
const action = ok
|
|
@@ -754,8 +768,8 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
754
768
|
: undefined
|
|
755
769
|
this.rowCandidate = action
|
|
756
770
|
const set = (next: typeof r) => ((this.row = next), (this.working = 'row'))
|
|
757
|
-
return html`<section row @focusin=${() => (this.working = 'row')}>
|
|
758
|
-
|
|
771
|
+
return html`<section row @focusin=${() => (this.working = 'row')}><details row-fold ?open=${this.working === 'row'}>
|
|
772
|
+
${head}
|
|
759
773
|
<div form>
|
|
760
774
|
<span>${i18next.t('figure.label.repeat-axis')}</span>
|
|
761
775
|
<select @change=${(e: Event) => set({ ...r, axis: (e.target as HTMLSelectElement).value as Axis })}>
|
|
@@ -780,7 +794,7 @@ export class FigureLinks extends localize(i18next)(LitElement) {
|
|
|
780
794
|
<button act ?disabled=${this.locked || !action} @click=${() => action && this.command(action)}>
|
|
781
795
|
<md-icon>view_week</md-icon>${i18next.t('figure.button.make-repeat')}
|
|
782
796
|
</button>
|
|
783
|
-
</section>`
|
|
797
|
+
</details></section>`
|
|
784
798
|
}
|
|
785
799
|
|
|
786
800
|
/**
|
|
@@ -3,7 +3,7 @@ import { css, html, LitElement, nothing } from 'lit'
|
|
|
3
3
|
import { customElement, property } from 'lit/decorators.js'
|
|
4
4
|
import { i18next, localize } from '@operato/i18n'
|
|
5
5
|
import { ScrollbarStyles } from '@operato/styles'
|
|
6
|
-
import { PART_LIMIT } from '@hatiolab/figure-model'
|
|
6
|
+
import { DETAIL_LEVELS, PART_LIMIT, type DetailLevel } from '@hatiolab/figure-model'
|
|
7
7
|
import type { FigurePrimitiveCreation } from '@hatiolab/figure-model'
|
|
8
8
|
import type { FigureBudgetLine } from './maturity.js'
|
|
9
9
|
import { partLabelOf } from './fields.js'
|
|
@@ -414,7 +414,7 @@ export class FigureParts extends localize(i18next)(LitElement) {
|
|
|
414
414
|
* 디테일 등급(S/M/L)의 부품 한도(사용자 2026-09-24). 부품을 넣기 **전에** 몇 개 남았는지 보이고, 다 차면 팔레트가
|
|
415
415
|
* 잠긴다. 되풀이는 한 부품으로 센다. 등급을 안 정했으면 한도는 없다.
|
|
416
416
|
*/
|
|
417
|
-
@property({ attribute: false }) partBudget?: { level:
|
|
417
|
+
@property({ attribute: false }) partBudget?: { level: DetailLevel | null; used: number; limit: number | null }
|
|
418
418
|
/** How many recipe templates the catalog holds; none, and the section is not drawn. */
|
|
419
419
|
@property({ type: Number }) recipeCount = 0
|
|
420
420
|
|
|
@@ -494,7 +494,7 @@ export class FigureParts extends localize(i18next)(LitElement) {
|
|
|
494
494
|
}}
|
|
495
495
|
>
|
|
496
496
|
<option value="" ?selected=${!b.level}>${i18next.t('figure.label.detail-none')}</option>
|
|
497
|
-
${
|
|
497
|
+
${DETAIL_LEVELS.map(
|
|
498
498
|
one => html`<option value=${one} ?selected=${one === b.level}>
|
|
499
499
|
${one} ${i18next.t('figure.label.detail-' + one)} · ${i18next.t('figure.text.n-parts-at-most', { limit: PART_LIMIT[one] })}
|
|
500
500
|
</option>`
|
|
@@ -6,8 +6,9 @@ import { i18next, localize } from '@operato/i18n'
|
|
|
6
6
|
import { figureCavitySectionOf, figureSectionSidesOf } from '@hatiolab/figure-model'
|
|
7
7
|
import type { FigureAuthoringAction, FigureEvaluatedShape, FigureSection, FigureSectionMeasure, FigureSectionPoint } from '@hatiolab/figure-model'
|
|
8
8
|
import { SidePanelStyles } from './side-panel-styles.js'
|
|
9
|
-
import { apexOver, drawnOf, insertAfter, markedBy, movePoint, pointLabel, removePoint, sectionOf, workingIssuesOf, workingOf, type SectionWorking } from './section-drawing.js'
|
|
10
|
-
import { END_FACE } from './section-controls.js'
|
|
9
|
+
import { apexOver, drawnOf, insertAfter, markedBy, movePoint, pointLabel, removePoint, sectionOf, workingIssuesOf, workingOf, workingShapeOf, type SectionWorking } from './section-drawing.js'
|
|
10
|
+
import { cavityDepthOf, cavityRequestOf, cavityResultOf, END_FACE, sameOf, sidesOf } from './section-controls.js'
|
|
11
|
+
import './figure-cavity-drawing.js'
|
|
11
12
|
import { openPopup } from '@operato/layout'
|
|
12
13
|
import './figure-section-close-choice.js'
|
|
13
14
|
import type { SectionCloseChoice } from './figure-section-close-choice.js'
|
|
@@ -17,6 +18,10 @@ type Axis = 'x' | 'y' | 'z'
|
|
|
17
18
|
/** How the section is laid on the screen, by the axis it is pushed along: across to the right, then down. */
|
|
18
19
|
const SCREEN: Record<Axis, [Axis, Axis]> = { y: ['x', 'z'], x: ['z', 'y'], z: ['x', 'y'] }
|
|
19
20
|
|
|
21
|
+
/** The inside said so two can be compared: its walls by side, its ends. */
|
|
22
|
+
const cavityKey = (w: SectionWorking) =>
|
|
23
|
+
w.cavity ? [Object.entries(w.cavity.wall).sort(), w.cavity.start, w.cavity.end] : null
|
|
24
|
+
|
|
20
25
|
/** The editor's width and room for the solid beside it: below this the two take turns. */
|
|
21
26
|
export const NARROW_BELOW = 900
|
|
22
27
|
|
|
@@ -35,6 +40,10 @@ export const NARROW_BELOW = 900
|
|
|
35
40
|
* apex is dragged or typed like a point, and is not offered while something is fastened to the end it takes away (the
|
|
36
41
|
* kernel's own refusal, `pointBlocked`).
|
|
37
42
|
*
|
|
43
|
+
* A rectangle's or ellipse's inside is made, changed or taken away here too, by the same drawing the section panel has
|
|
44
|
+
* (figure-cavity-drawing), drawn and judged on the widths being typed: walls that fit only a wider section are taken
|
|
45
|
+
* with it, in the same command (figure-model `set-section`'s `cavity`).
|
|
46
|
+
*
|
|
38
47
|
* What the kernel would refuse is marked on the outline and said in the list; apply is held back, and the solid shows
|
|
39
48
|
* the last valid state, which the caption names. Apply sends one `set-section` command (one step of the history); cancel
|
|
40
49
|
* or Esc sends nothing and lets the preview go -- after asking, when something was changed: apply it, let it go, or keep
|
|
@@ -192,6 +201,26 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
192
201
|
min-width: 0;
|
|
193
202
|
font: inherit;
|
|
194
203
|
}
|
|
204
|
+
div[cavity] {
|
|
205
|
+
display: grid;
|
|
206
|
+
gap: 6px;
|
|
207
|
+
margin-top: 10px;
|
|
208
|
+
padding-top: 8px;
|
|
209
|
+
border-top: 1px solid var(--md-sys-color-outline-variant);
|
|
210
|
+
}
|
|
211
|
+
/* The panel's width: wider, the drawing pushed the outline out of view. */
|
|
212
|
+
div[cavity] figure-cavity-drawing {
|
|
213
|
+
max-width: 340px;
|
|
214
|
+
}
|
|
215
|
+
label[toggle] {
|
|
216
|
+
display: flex;
|
|
217
|
+
align-items: center;
|
|
218
|
+
gap: 6px;
|
|
219
|
+
flex-wrap: wrap;
|
|
220
|
+
}
|
|
221
|
+
label[toggle] small[result] {
|
|
222
|
+
margin-left: auto;
|
|
223
|
+
}
|
|
195
224
|
ul[issues] {
|
|
196
225
|
margin: 8px 0 0;
|
|
197
226
|
padding: 0 0 0 16px;
|
|
@@ -256,7 +285,9 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
256
285
|
if (!this.narrow) this.show = 'outline'
|
|
257
286
|
})
|
|
258
287
|
|
|
259
|
-
@state() private working: SectionWorking = { points: [], across: {}, length: 0, apex: null }
|
|
288
|
+
@state() private working: SectionWorking = { points: [], across: {}, length: 0, apex: null, cavity: null }
|
|
289
|
+
/** Walls one per side in the inside's drawing; on when they open differing. */
|
|
290
|
+
@state() private perSide = false
|
|
260
291
|
@state() private selected: string | null = null
|
|
261
292
|
@state() private note = ''
|
|
262
293
|
private opened!: SectionWorking
|
|
@@ -274,6 +305,10 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
274
305
|
private get kind() {
|
|
275
306
|
return this.body && drawnOf(this.body.shape)?.section.kind
|
|
276
307
|
}
|
|
308
|
+
/** A pushed rectangle or ellipse can have an inside; a drawn section's is a later stage. */
|
|
309
|
+
private get hollowable() {
|
|
310
|
+
return !!this.body?.shape.extrusion && (this.kind === 'rectangle' || this.kind === 'ellipse')
|
|
311
|
+
}
|
|
277
312
|
|
|
278
313
|
connectedCallback() {
|
|
279
314
|
super.connectedCallback()
|
|
@@ -282,6 +317,7 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
282
317
|
this.lastValid = this.working
|
|
283
318
|
this.openedAt = this.revision
|
|
284
319
|
this.extent = this.extentOf(this.working)
|
|
320
|
+
this.perSide = !!this.working.cavity && sameOf(Object.values(this.working.cavity.wall)) === undefined
|
|
285
321
|
window.addEventListener('keydown', this.keys, true)
|
|
286
322
|
if (this.parentElement) this.room.observe(this.parentElement)
|
|
287
323
|
}
|
|
@@ -310,6 +346,8 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
310
346
|
*/
|
|
311
347
|
private keys = (e: KeyboardEvent) => {
|
|
312
348
|
const inField = (e.composedPath()[0] as HTMLElement)?.tagName === 'INPUT'
|
|
349
|
+
/* A number being typed in the inside's drawing: Esc there puts the number back, not the editor away. */
|
|
350
|
+
if (inField && e.composedPath().some(el => (el as HTMLElement).tagName === 'FIGURE-CAVITY-DRAWING')) return
|
|
313
351
|
/* While the close is being asked, the question has the keys: Esc there is 계속 편집. */
|
|
314
352
|
if (this.asking) return
|
|
315
353
|
const take = () => (e.preventDefault(), e.stopImmediatePropagation())
|
|
@@ -351,10 +389,8 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
351
389
|
}
|
|
352
390
|
|
|
353
391
|
/** The solid behind shows the working state, or the last valid one while the working one is refused. */
|
|
354
|
-
private preview() {
|
|
355
|
-
this.dispatchEvent(
|
|
356
|
-
new CustomEvent('command-live', { detail: { action: this.action(this.lastValid) }, bubbles: true, composed: true })
|
|
357
|
-
)
|
|
392
|
+
private preview(w = this.lastValid) {
|
|
393
|
+
this.dispatchEvent(new CustomEvent('command-live', { detail: { action: this.action(w) }, bubbles: true, composed: true }))
|
|
358
394
|
}
|
|
359
395
|
|
|
360
396
|
/** One command for everything changed since the editor opened; a measure not changed is not sent (it may follow another). */
|
|
@@ -363,13 +399,15 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
363
399
|
const changed = (a: number | undefined, b: number | undefined) => Math.abs((a ?? 0) - (b ?? 0)) > 1e-9
|
|
364
400
|
const across = Object.fromEntries(Object.entries(w.across).filter(([k, v]) => changed(v, o.across[k as Axis])))
|
|
365
401
|
const apexMoved = !w.apex !== !o.apex || (!!w.apex && Object.entries(w.apex).some(([k, v]) => changed(v, o.apex![k as Axis])))
|
|
402
|
+
const cavityMoved = JSON.stringify(cavityKey(w)) !== JSON.stringify(cavityKey(o))
|
|
366
403
|
return {
|
|
367
404
|
kind: 'set-section',
|
|
368
405
|
part: this.body!.id,
|
|
369
406
|
...(this.kind === 'polygon' ? { points: w.points.map(p => ({ ...p })) } : {}),
|
|
370
407
|
...(Object.keys(across).length ? { across } : {}),
|
|
371
408
|
...(changed(w.length, o.length) ? { length: w.length } : {}),
|
|
372
|
-
...(apexMoved ? { apex: w.apex ? { ...w.apex } : null } : {})
|
|
409
|
+
...(apexMoved ? { apex: w.apex ? { ...w.apex } : null } : {}),
|
|
410
|
+
...(cavityMoved ? { cavity: w.cavity ? { wall: { ...w.cavity.wall }, start: w.cavity.start, end: w.cavity.end } : null } : {})
|
|
373
411
|
}
|
|
374
412
|
}
|
|
375
413
|
|
|
@@ -403,7 +441,8 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
403
441
|
w.points.map(p => [p.id, p.x ?? 0, p.y ?? 0, p.z ?? 0]),
|
|
404
442
|
Object.entries(w.across).sort(),
|
|
405
443
|
w.length,
|
|
406
|
-
w.apex ? Object.entries(w.apex).sort() : null
|
|
444
|
+
w.apex ? Object.entries(w.apex).sort() : null,
|
|
445
|
+
cavityKey(w)
|
|
407
446
|
])
|
|
408
447
|
return !!this.opened && key(this.working) !== key(this.opened)
|
|
409
448
|
}
|
|
@@ -529,8 +568,7 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
529
568
|
const grid = Math.max(10, Math.pow(10, Math.floor(Math.log10(e))) / 2)
|
|
530
569
|
const lines: number[] = []
|
|
531
570
|
for (let g = -Math.floor(e / grid) * grid; g <= e; g += grid) lines.push(g)
|
|
532
|
-
const
|
|
533
|
-
const inside = cavity && !invalid ? figureCavitySectionOf(section, this.axis, cavity.wall) : undefined
|
|
571
|
+
const inside = w.cavity && !invalid ? figureCavitySectionOf(section, this.axis, w.cavity.wall) : undefined
|
|
534
572
|
const sized = (axis: Axis) => this.measures?.measures[axis] === 'editable'
|
|
535
573
|
return html`<div caption ?last-valid=${invalid}>
|
|
536
574
|
${invalid ? i18next.t('figure.text.section-editor-last-valid') : i18next.t('figure.text.section-editor-units', { h: h.toUpperCase(), v: v.toUpperCase() })}
|
|
@@ -710,6 +748,62 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
710
748
|
: nothing}`
|
|
711
749
|
}
|
|
712
750
|
|
|
751
|
+
/** The inside, as the section panel shows it, on the working section: on or off, what it makes, its drawing. */
|
|
752
|
+
private cavityBlock(invalid: boolean) {
|
|
753
|
+
const w = this.working
|
|
754
|
+
const shape = workingShapeOf(this.body!.shape, w)
|
|
755
|
+
return html`<div cavity>
|
|
756
|
+
<label toggle>
|
|
757
|
+
<input
|
|
758
|
+
type="checkbox"
|
|
759
|
+
.checked=${live(!!w.cavity)}
|
|
760
|
+
@change=${(ev: Event) => {
|
|
761
|
+
const on = (ev.target as HTMLInputElement).checked
|
|
762
|
+
const ask = cavityRequestOf(shape, {})
|
|
763
|
+
const wall = typeof ask.wall === 'number' ? Object.fromEntries(sidesOf(this.axis).map(side => [side, ask.wall as number])) : (ask.wall as Record<string, number>)
|
|
764
|
+
this.change({ ...w, cavity: on ? { wall, start: ask.start, end: ask.end } : null })
|
|
765
|
+
}}
|
|
766
|
+
/>
|
|
767
|
+
${i18next.t('figure.label.section-cavity')}
|
|
768
|
+
${w.cavity && !invalid
|
|
769
|
+
? html`<small result>
|
|
770
|
+
${i18next.t('figure.text.section-depth', {
|
|
771
|
+
result: i18next.t(`figure.label.section-result-${cavityResultOf(shape.cavity!)}`),
|
|
772
|
+
depth: String(cavityDepthOf(shape))
|
|
773
|
+
})}
|
|
774
|
+
</small>`
|
|
775
|
+
: nothing}
|
|
776
|
+
</label>
|
|
777
|
+
${w.cavity
|
|
778
|
+
? html`<figure-cavity-drawing
|
|
779
|
+
.body=${{ id: this.body!.id, shape }}
|
|
780
|
+
.perSide=${this.perSide}
|
|
781
|
+
@command-live=${(ev: CustomEvent) => {
|
|
782
|
+
ev.stopPropagation()
|
|
783
|
+
const c = this.cavityOf(ev.detail.action)
|
|
784
|
+
this.preview(c && this.valid({ ...this.working, cavity: c }) ? { ...this.working, cavity: c } : this.lastValid)
|
|
785
|
+
}}
|
|
786
|
+
@command=${(ev: CustomEvent) => {
|
|
787
|
+
ev.stopPropagation()
|
|
788
|
+
const c = this.cavityOf(ev.detail.action)
|
|
789
|
+
if (c) this.change({ ...this.working, cavity: c })
|
|
790
|
+
}}
|
|
791
|
+
></figure-cavity-drawing>
|
|
792
|
+
<label toggle>
|
|
793
|
+
<input type="checkbox" .checked=${live(this.perSide)} @change=${(ev: Event) => (this.perSide = (ev.target as HTMLInputElement).checked)} />
|
|
794
|
+
${i18next.t('figure.label.section-wall-per-side')}
|
|
795
|
+
</label>`
|
|
796
|
+
: nothing}
|
|
797
|
+
</div>`
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/** The inside a drawing's set-cavity asks for, every wall written out; the drawing's commands stay in the editor. */
|
|
801
|
+
private cavityOf(action: FigureAuthoringAction | null): SectionWorking['cavity'] | undefined {
|
|
802
|
+
if (action?.kind !== 'set-cavity' || !action.cavity) return undefined
|
|
803
|
+
const { wall, start, end } = action.cavity
|
|
804
|
+
return { wall: typeof wall === 'number' ? Object.fromEntries(sidesOf(this.axis).map(side => [side, wall])) : ({ ...wall } as Record<string, number>), start, end }
|
|
805
|
+
}
|
|
806
|
+
|
|
713
807
|
private list(issues: ReturnType<typeof workingIssuesOf>['issues'], words: ReturnType<typeof workingIssuesOf>['words']) {
|
|
714
808
|
const [h, v] = SCREEN[this.axis]
|
|
715
809
|
const marked = markedBy(issues)
|
|
@@ -773,6 +867,7 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
773
867
|
)}
|
|
774
868
|
</ol>`
|
|
775
869
|
: nothing}
|
|
870
|
+
${this.hollowable ? this.cavityBlock(words.length > 0) : nothing}
|
|
776
871
|
${this.note ? html`<small role="status">${this.note}</small>` : nothing}
|
|
777
872
|
${words.length
|
|
778
873
|
? html`<ul issues role="alert">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FIGURE_SECTION_PLANE, FIGURE_SECTION_POINT_LIMIT, checkFigureSection, figureExtrusionFeaturesOf, figureSectionSidesOf } from '@hatiolab/figure-model'
|
|
2
|
-
import type { FigureEvaluatedShape, FigureSection, FigureSectionIssue, FigureSectionPoint } from '@hatiolab/figure-model'
|
|
2
|
+
import type { FigureCavityRequest, FigureEvaluatedShape, FigureSection, FigureSectionIssue, FigureSectionPoint } from '@hatiolab/figure-model'
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
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).
|
|
@@ -106,6 +106,8 @@ export interface SectionWorking {
|
|
|
106
106
|
length: number
|
|
107
107
|
/** A drawn section's end drawn to one point (a pyramid), across the plane axes; null for a push. */
|
|
108
108
|
apex: Partial<Record<Axis, number>> | null
|
|
109
|
+
/** A pushed rectangle's or ellipse's inside, every side's wall written out; null for none or a drawn section. */
|
|
110
|
+
cavity: { wall: Record<string, number>; start: FigureCavityRequest['start']; end: FigureCavityRequest['end'] } | null
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
/** A pushed part's section, axis and ends -- or a pyramid's, whose end is its apex (figure-model `loft@1`). */
|
|
@@ -135,7 +137,28 @@ export function workingOf(shape: FigureEvaluatedShape): SectionWorking {
|
|
|
135
137
|
: s.kind === 'ellipse'
|
|
136
138
|
? { [u]: 2 * s.radius[u]!, [v]: 2 * s.radius[v]! }
|
|
137
139
|
: {}
|
|
138
|
-
|
|
140
|
+
const c = shape.extrusion && shape.cavity
|
|
141
|
+
return {
|
|
142
|
+
points: s.kind === 'polygon' ? s.points.map(p => ({ ...p })) : [],
|
|
143
|
+
across,
|
|
144
|
+
length: e.end - e.start,
|
|
145
|
+
apex: e.apex ? { ...e.apex } : null,
|
|
146
|
+
cavity: c ? { wall: { ...c.wall }, start: c.start, end: c.end } : null
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The part as the working state would make it: its section, its length from the same start, its inside -- what the
|
|
152
|
+
* inside's drawing reads, so its walls are judged against the widths being typed and not the ones the editor opened with.
|
|
153
|
+
*/
|
|
154
|
+
export function workingShapeOf(shape: FigureEvaluatedShape, w: SectionWorking): FigureEvaluatedShape {
|
|
155
|
+
const e = shape.extrusion!
|
|
156
|
+
const { cavity: _, ...rest } = shape
|
|
157
|
+
return {
|
|
158
|
+
...rest,
|
|
159
|
+
extrusion: { ...e, section: sectionOf(shape, w), end: e.start + w.length },
|
|
160
|
+
...(w.cavity ? { cavity: { id: shape.cavity?.id ?? 'inside', wall: { ...w.cavity.wall }, start: w.cavity.start, end: w.cavity.end } } : {})
|
|
161
|
+
} as FigureEvaluatedShape
|
|
139
162
|
}
|
|
140
163
|
|
|
141
164
|
/** The section the working state makes, of the part's own kind: its corners kept, its points or widths the working ones. */
|
|
@@ -159,8 +182,8 @@ export function workingIssuesOf(
|
|
|
159
182
|
const words = issues.map(i => issueWords(i, w.points, e.axis))
|
|
160
183
|
if (!(Number.isFinite(w.length) && w.length > 0)) words.push({ key: 'figure.text.section-issue-length', params: {} })
|
|
161
184
|
if (w.apex && !Object.values(w.apex).every(n => Number.isFinite(n))) words.push({ key: 'figure.text.section-issue-number', params: {} })
|
|
162
|
-
if (!issues.length && words.length === 0 &&
|
|
163
|
-
const made = figureExtrusionFeaturesOf({ axis: e.axis, section, start: e.start, end: e.start + w.length }, [
|
|
185
|
+
if (!issues.length && words.length === 0 && w.cavity) {
|
|
186
|
+
const made = figureExtrusionFeaturesOf({ axis: e.axis, section, start: e.start, end: e.start + w.length }, [{ id: 'inside', ...w.cavity }])
|
|
164
187
|
if ('refused' in made) {
|
|
165
188
|
const r = made.refused
|
|
166
189
|
words.push({ key: r.code === 'CAVITY_NO_LENGTH' ? 'figure.text.section-issue-cavity-length' : 'figure.text.section-issue-cavity-walls', params: {} })
|
|
@@ -21,8 +21,6 @@ export declare class FigureCanvas extends LitElement {
|
|
|
21
21
|
* would reach the editor as an edit.
|
|
22
22
|
*/
|
|
23
23
|
viewOnly: boolean;
|
|
24
|
-
/** The editor shows a new figure's start card over the canvas: the canvas's own one-line hint gives way to it. */
|
|
25
|
-
startShown: boolean;
|
|
26
24
|
/** Where the selected part turns and how far (`joint-fan`): shown, never picked, never saved. */
|
|
27
25
|
fan: FigureRenderPart | null;
|
|
28
26
|
/**
|