@things-factory/figure-ui 10.1.91 → 10.1.93
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/editor.ts +61 -90
- package/client/modeller/fasten-geometry.ts +152 -0
- package/client/modeller/fields.ts +8 -1
- package/client/modeller/figure-cavity-drawing.ts +259 -0
- package/client/modeller/figure-fasten-drawing.ts +406 -0
- package/client/modeller/figure-links.ts +71 -35
- package/client/modeller/figure-section-close-choice.ts +52 -0
- package/client/modeller/figure-section-editor.ts +58 -3
- package/client/modeller/figure-section-panel.ts +5 -0
- package/client/modeller/section-controls.ts +19 -0
- package/client/modeller/side-panel-styles.ts +4 -11
- package/dist-client/modeller/editor.d.ts +10 -4
- package/dist-client/modeller/editor.js +59 -88
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-geometry.d.ts +60 -0
- package/dist-client/modeller/fasten-geometry.js +138 -0
- package/dist-client/modeller/fasten-geometry.js.map +1 -0
- package/dist-client/modeller/fields.js +8 -1
- package/dist-client/modeller/fields.js.map +1 -1
- package/dist-client/modeller/figure-cavity-drawing.d.ts +48 -0
- package/dist-client/modeller/figure-cavity-drawing.js +253 -0
- package/dist-client/modeller/figure-cavity-drawing.js.map +1 -0
- package/dist-client/modeller/figure-fasten-drawing.d.ts +74 -0
- package/dist-client/modeller/figure-fasten-drawing.js +452 -0
- package/dist-client/modeller/figure-fasten-drawing.js.map +1 -0
- package/dist-client/modeller/figure-links.d.ts +7 -0
- package/dist-client/modeller/figure-links.js +70 -33
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-section-close-choice.d.ts +17 -0
- package/dist-client/modeller/figure-section-close-choice.js +58 -0
- package/dist-client/modeller/figure-section-close-choice.js.map +1 -0
- package/dist-client/modeller/figure-section-editor.d.ts +11 -1
- package/dist-client/modeller/figure-section-editor.js +57 -3
- package/dist-client/modeller/figure-section-editor.js.map +1 -1
- package/dist-client/modeller/figure-section-panel.d.ts +1 -0
- package/dist-client/modeller/figure-section-panel.js +5 -0
- package/dist-client/modeller/figure-section-panel.js.map +1 -1
- package/dist-client/modeller/section-controls.d.ts +10 -0
- package/dist-client/modeller/section-controls.js +17 -0
- package/dist-client/modeller/section-controls.js.map +1 -1
- package/dist-client/modeller/side-panel-styles.js +4 -11
- package/dist-client/modeller/side-panel-styles.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/test/browser/acceptance-page.js +10 -0
- package/test/cavity-drawing.test.ts +69 -0
- package/test/fasten-drawing.test.ts +45 -0
- package/test/fasten-geometry.test.ts +79 -0
- package/test/layout.test.ts +19 -4
- package/test/links.test.ts +3 -1
- package/test/section-editor.test.ts +31 -8
- package/translations/en.json +29 -15
- package/translations/ja.json +25 -11
- package/translations/ko.json +29 -15
- package/translations/ms.json +25 -11
- package/translations/zh.json +25 -11
|
@@ -8,6 +8,9 @@ import type { FigureAuthoringAction, FigureEvaluatedShape, FigureSection, Figure
|
|
|
8
8
|
import { SidePanelStyles } from './side-panel-styles.js'
|
|
9
9
|
import { apexOver, drawnOf, insertAfter, markedBy, movePoint, pointLabel, removePoint, sectionOf, workingIssuesOf, workingOf, type SectionWorking } from './section-drawing.js'
|
|
10
10
|
import { END_FACE } from './section-controls.js'
|
|
11
|
+
import { openPopup } from '@operato/layout'
|
|
12
|
+
import './figure-section-close-choice.js'
|
|
13
|
+
import type { SectionCloseChoice } from './figure-section-close-choice.js'
|
|
11
14
|
|
|
12
15
|
type Axis = 'x' | 'y' | 'z'
|
|
13
16
|
|
|
@@ -34,7 +37,8 @@ export const NARROW_BELOW = 900
|
|
|
34
37
|
*
|
|
35
38
|
* What the kernel would refuse is marked on the outline and said in the list; apply is held back, and the solid shows
|
|
36
39
|
* the last valid state, which the caption names. Apply sends one `set-section` command (one step of the history); cancel
|
|
37
|
-
* or Esc sends nothing and lets the preview go
|
|
40
|
+
* or Esc sends nothing and lets the preview go -- after asking, when something was changed: apply it, let it go, or keep
|
|
41
|
+
* editing (`requestClose`). The undo here is the editor's own. If the figure changes while the editor
|
|
38
42
|
* is open, apply is refused until it is opened again.
|
|
39
43
|
*/
|
|
40
44
|
@customElement('figure-section-editor')
|
|
@@ -306,10 +310,12 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
306
310
|
*/
|
|
307
311
|
private keys = (e: KeyboardEvent) => {
|
|
308
312
|
const inField = (e.composedPath()[0] as HTMLElement)?.tagName === 'INPUT'
|
|
313
|
+
/* While the close is being asked, the question has the keys: Esc there is 계속 편집. */
|
|
314
|
+
if (this.asking) return
|
|
309
315
|
const take = () => (e.preventDefault(), e.stopImmediatePropagation())
|
|
310
316
|
if (e.key === 'Escape') {
|
|
311
317
|
take()
|
|
312
|
-
this.
|
|
318
|
+
this.requestClose()
|
|
313
319
|
} else if ((e.key === 'Delete' || e.key === 'Backspace') && !inField && this.selected) {
|
|
314
320
|
/* In a field the key deletes a character; outside, the chosen point. */
|
|
315
321
|
take()
|
|
@@ -390,6 +396,55 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
390
396
|
this.close()
|
|
391
397
|
}
|
|
392
398
|
|
|
399
|
+
/** Whether anything was changed since the editor opened. */
|
|
400
|
+
get dirty(): boolean {
|
|
401
|
+
const key = (w: SectionWorking) =>
|
|
402
|
+
JSON.stringify([
|
|
403
|
+
w.points.map(p => [p.id, p.x ?? 0, p.y ?? 0, p.z ?? 0]),
|
|
404
|
+
Object.entries(w.across).sort(),
|
|
405
|
+
w.length,
|
|
406
|
+
w.apex ? Object.entries(w.apex).sort() : null
|
|
407
|
+
])
|
|
408
|
+
return !!this.opened && key(this.working) !== key(this.opened)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private asking = false
|
|
412
|
+
/**
|
|
413
|
+
* Close, asking first when something was changed (user 2026-10-01): apply it, let it go, or keep editing. True when the
|
|
414
|
+
* editor closed; false when the person keeps editing.
|
|
415
|
+
*/
|
|
416
|
+
async requestClose(): Promise<boolean> {
|
|
417
|
+
if (!this.dirty) {
|
|
418
|
+
this.cancel()
|
|
419
|
+
return true
|
|
420
|
+
}
|
|
421
|
+
if (this.asking) return false
|
|
422
|
+
const held = this.stale
|
|
423
|
+
? i18next.t('figure.text.section-editor-stale')
|
|
424
|
+
: this.valid(this.working)
|
|
425
|
+
? ''
|
|
426
|
+
: i18next.t('figure.text.section-editor-close-invalid')
|
|
427
|
+
this.asking = true
|
|
428
|
+
try {
|
|
429
|
+
const choice = await openPopup<SectionCloseChoice>(html`<figure-section-close-choice .held=${held}></figure-section-close-choice>`, {
|
|
430
|
+
backdrop: true,
|
|
431
|
+
size: 'small',
|
|
432
|
+
title: i18next.t('figure.label.section-editor')
|
|
433
|
+
}).result
|
|
434
|
+
if (choice === 'apply' && !held) {
|
|
435
|
+
this.apply()
|
|
436
|
+
return true
|
|
437
|
+
}
|
|
438
|
+
if (choice === 'discard') {
|
|
439
|
+
this.cancel()
|
|
440
|
+
return true
|
|
441
|
+
}
|
|
442
|
+
return false
|
|
443
|
+
} finally {
|
|
444
|
+
this.asking = false
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
393
448
|
private cancel() {
|
|
394
449
|
this.dispatchEvent(new CustomEvent('command-live', { detail: { action: null }, bubbles: true, composed: true }))
|
|
395
450
|
this.close()
|
|
@@ -441,7 +496,7 @@ export class FigureSectionEditor extends localize(i18next)(LitElement) {
|
|
|
441
496
|
? i18next.t('figure.text.section-editor-stale')
|
|
442
497
|
: i18next.t(this.kind === 'polygon' ? 'figure.text.section-editor-hint' : 'figure.text.section-editor-hint-sized')}
|
|
443
498
|
</span>
|
|
444
|
-
<button @click=${() => this.
|
|
499
|
+
<button @click=${() => this.requestClose()}>${i18next.t('figure.button.section-editor-cancel')}</button>
|
|
445
500
|
<button primary ?disabled=${this.stale || words.length > 0} @click=${() => this.apply()}>${i18next.t('figure.button.section-editor-apply')}</button>
|
|
446
501
|
</footer>
|
|
447
502
|
`
|
|
@@ -6,6 +6,7 @@ import { i18next, localize } from '@operato/i18n'
|
|
|
6
6
|
import type { FigureAuthoringAction, FigureCavityRequest, FigureEvaluatedShape } from '@hatiolab/figure-model'
|
|
7
7
|
import { SidePanelStyles } from './side-panel-styles.js'
|
|
8
8
|
import { drawnOf } from './section-drawing.js'
|
|
9
|
+
import './figure-cavity-drawing.js'
|
|
9
10
|
import {
|
|
10
11
|
END_FACE,
|
|
11
12
|
SIDE_FACE,
|
|
@@ -106,6 +107,9 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
106
107
|
<button act ?disabled=${this.locked} @click=${() => this.dispatchEvent(new CustomEvent('section-editor-open', { bubbles: true, composed: true }))}>
|
|
107
108
|
${i18next.t('figure.button.section-editor-open')}
|
|
108
109
|
</button>
|
|
110
|
+
${e.section.kind === 'polygon'
|
|
111
|
+
? html`<small>${i18next.t(e.apex ? 'figure.text.section-points-apex' : 'figure.text.section-points', { points: String(e.section.points.length) })}</small>`
|
|
112
|
+
: nothing}
|
|
109
113
|
</div>`
|
|
110
114
|
: nothing}
|
|
111
115
|
${kind === 'rectangle' ? this.renderCorner(body) : nothing}
|
|
@@ -185,6 +189,7 @@ export class FigureSectionPanel extends localize(i18next)(LitElement) {
|
|
|
185
189
|
</small>`
|
|
186
190
|
: nothing}
|
|
187
191
|
</div>
|
|
192
|
+
${cavity ? html`<figure-cavity-drawing .body=${body} .perSide=${this.perSide} ?locked=${this.locked}></figure-cavity-drawing>` : nothing}
|
|
188
193
|
${cavity
|
|
189
194
|
? html`<div field>
|
|
190
195
|
<label>${i18next.t('figure.label.section-wall')}</label>
|
|
@@ -113,3 +113,22 @@ export function cavityDepthOf(shape: FigureEvaluatedShape): number {
|
|
|
113
113
|
const closed = (x: typeof c.start) => (x === 'open' ? 0 : x.closed)
|
|
114
114
|
return mm(e.end - e.start - closed(c.start) - closed(c.end))
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
/** The inside as it stands, every side's wall written out. */
|
|
118
|
+
export function cavityWorkingOf(shape: FigureEvaluatedShape): { wall: Record<string, number>; start: FigureCavityRequest['start']; end: FigureCavityRequest['end'] } | undefined {
|
|
119
|
+
const c = shape.cavity
|
|
120
|
+
const e = shape.extrusion
|
|
121
|
+
if (!c || !e) return undefined
|
|
122
|
+
return { wall: Object.fromEntries(sidesOf(e.axis).map(s => [s, c.wall[s]!])), start: c.start, end: c.end }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** A wall dragged to `value`: that side alone, or every side when the walls are one. */
|
|
126
|
+
export function wallDragged(wall: Record<string, number>, side: string, value: number, perSide: boolean): Record<string, number> {
|
|
127
|
+
const v = Math.max(1, Math.round(value))
|
|
128
|
+
return perSide ? { ...wall, [side]: v } : Object.fromEntries(Object.keys(wall).map(s => [s, v]))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** An end pressed: open becomes closed by the default wall, closed becomes open. */
|
|
132
|
+
export function endToggled(shape: FigureEvaluatedShape, now: FigureCavityRequest['start']): FigureCavityRequest['start'] {
|
|
133
|
+
return now === 'open' ? { closed: defaultWallOf(shape) } : 'open'
|
|
134
|
+
}
|
|
@@ -101,19 +101,17 @@ export const SidePanelStyles = css`
|
|
|
101
101
|
div[vector-row] + div[inline-field] {
|
|
102
102
|
margin-top: 5px;
|
|
103
103
|
}
|
|
104
|
-
/* A row of
|
|
104
|
+
/* A row of worded tools (align, snap; mirror copies). */
|
|
105
105
|
div[tools] {
|
|
106
106
|
display: flex;
|
|
107
107
|
flex-wrap: wrap;
|
|
108
108
|
gap: 4px;
|
|
109
109
|
}
|
|
110
110
|
div[tools] button {
|
|
111
|
-
display: inline-flex;
|
|
112
|
-
align-items: center;
|
|
113
|
-
justify-content: center;
|
|
114
|
-
width: 30px;
|
|
115
111
|
height: 28px;
|
|
116
|
-
padding: 0;
|
|
112
|
+
padding: 0 10px;
|
|
113
|
+
font: inherit;
|
|
114
|
+
font-size: 0.78rem;
|
|
117
115
|
color: var(--md-sys-color-on-surface);
|
|
118
116
|
background: var(--md-sys-color-surface-container-lowest);
|
|
119
117
|
border: 1px solid var(--md-sys-color-outline-variant);
|
|
@@ -127,11 +125,6 @@ export const SidePanelStyles = css`
|
|
|
127
125
|
opacity: 0.45;
|
|
128
126
|
cursor: default;
|
|
129
127
|
}
|
|
130
|
-
div[tools] md-icon {
|
|
131
|
-
font-size: 17px;
|
|
132
|
-
width: 17px;
|
|
133
|
-
height: 17px;
|
|
134
|
-
}
|
|
135
128
|
div[pair] {
|
|
136
129
|
display: flex;
|
|
137
130
|
gap: 4px;
|
|
@@ -114,6 +114,12 @@ export declare class FigureEditor extends LitElement {
|
|
|
114
114
|
private propertiesPanel;
|
|
115
115
|
private foldsOf;
|
|
116
116
|
protected willUpdate(_changed: PropertyValues): void;
|
|
117
|
+
/**
|
|
118
|
+
* The selection moved off the part being edited. With nothing changed there the editor closes; with changes, the part
|
|
119
|
+
* stays selected while the editor asks (apply, let go, keep editing), and the move goes on only once it closed. A part
|
|
120
|
+
* gone or another figure opened has nothing to go back to: the editor closes.
|
|
121
|
+
*/
|
|
122
|
+
private leaveSectionEditor;
|
|
117
123
|
/** The scene toggles: the mode already shown takes the gizmo away. */
|
|
118
124
|
private useGizmo;
|
|
119
125
|
/**
|
|
@@ -303,13 +309,13 @@ export declare class FigureEditor extends LitElement {
|
|
|
303
309
|
private pointBlockedOf;
|
|
304
310
|
private renderStart;
|
|
305
311
|
/**
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
312
|
+
* The part the panel speaks of, held at its top: its kind, how its size answers, what holds it (designer 2026-09-27,
|
|
313
|
+
* B). No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
|
|
314
|
+
* list's root is the way back to the model's settings.
|
|
309
315
|
*/
|
|
310
316
|
private renderSubject;
|
|
311
317
|
/**
|
|
312
|
-
* The model's settings
|
|
318
|
+
* The model's settings, from the parts list's root (B.1). It shows them on
|
|
313
319
|
* the properties tab whatever tab was open; it changes no data, no camera, no history and no AI conversation.
|
|
314
320
|
*/
|
|
315
321
|
private showModelSettings;
|
|
@@ -379,7 +379,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
379
379
|
[off] {
|
|
380
380
|
display: none !important;
|
|
381
381
|
}
|
|
382
|
-
/* Which part the panel speaks of
|
|
382
|
+
/* Which part the panel speaks of. */
|
|
383
383
|
div[subject] {
|
|
384
384
|
position: sticky;
|
|
385
385
|
top: 0;
|
|
@@ -390,41 +390,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
390
390
|
background: var(--md-sys-color-surface-container-lowest);
|
|
391
391
|
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
392
392
|
}
|
|
393
|
-
nav[path] {
|
|
394
|
-
display: flex;
|
|
395
|
-
align-items: center;
|
|
396
|
-
gap: 6px;
|
|
397
|
-
min-width: 0;
|
|
398
|
-
}
|
|
399
|
-
nav[path] button[model-crumb] {
|
|
400
|
-
flex: none;
|
|
401
|
-
max-width: 55%;
|
|
402
|
-
height: 28px;
|
|
403
|
-
padding: 0 8px;
|
|
404
|
-
overflow: hidden;
|
|
405
|
-
white-space: nowrap;
|
|
406
|
-
text-overflow: ellipsis;
|
|
407
|
-
border: 1px solid var(--md-sys-color-outline-variant);
|
|
408
|
-
border-radius: 999em;
|
|
409
|
-
background: none;
|
|
410
|
-
color: var(--md-sys-color-primary);
|
|
411
|
-
cursor: pointer;
|
|
412
|
-
}
|
|
413
|
-
nav[path] button[model-crumb][on] {
|
|
414
|
-
border-color: var(--md-sys-color-primary);
|
|
415
|
-
background: var(--md-sys-color-primary-container);
|
|
416
|
-
}
|
|
417
|
-
nav[path] button[model-crumb]:focus-visible {
|
|
418
|
-
outline: 2px solid var(--md-sys-color-primary);
|
|
419
|
-
outline-offset: 1px;
|
|
420
|
-
}
|
|
421
|
-
nav[path] span[current] {
|
|
422
|
-
min-width: 0;
|
|
423
|
-
overflow: hidden;
|
|
424
|
-
white-space: nowrap;
|
|
425
|
-
text-overflow: ellipsis;
|
|
426
|
-
font-weight: 600;
|
|
427
|
-
}
|
|
428
393
|
div[subject-summary] {
|
|
429
394
|
display: grid;
|
|
430
395
|
gap: 1px;
|
|
@@ -638,7 +603,27 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
638
603
|
* own Esc, the part removed, another figure opened -- it closes and lets go of the preview it held.
|
|
639
604
|
*/
|
|
640
605
|
if (this.sectionEditing && this.sectionEditing !== this.selected)
|
|
606
|
+
this.leaveSectionEditor(this.selected);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* The selection moved off the part being edited. With nothing changed there the editor closes; with changes, the part
|
|
610
|
+
* stays selected while the editor asks (apply, let go, keep editing), and the move goes on only once it closed. A part
|
|
611
|
+
* gone or another figure opened has nothing to go back to: the editor closes.
|
|
612
|
+
*/
|
|
613
|
+
leaveSectionEditor(next) {
|
|
614
|
+
const editing = this.sectionEditing;
|
|
615
|
+
const editor = this.renderRoot.querySelector('figure-section-editor');
|
|
616
|
+
const view = this.session?.read();
|
|
617
|
+
const stillThere = !!view?.geometry.some(g => ownerOf(g.id) === editing);
|
|
618
|
+
if (!editor?.dirty || !stillThere) {
|
|
641
619
|
this.closeSectionEditor();
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
this.selected = editing;
|
|
623
|
+
editor.requestClose().then(closed => {
|
|
624
|
+
if (closed && this.selected === editing)
|
|
625
|
+
this.selected = next;
|
|
626
|
+
});
|
|
642
627
|
}
|
|
643
628
|
/** The scene toggles: the mode already shown takes the gizmo away. */
|
|
644
629
|
useGizmo(mode) {
|
|
@@ -1019,14 +1004,11 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1019
1004
|
}
|
|
1020
1005
|
renderPlaneAlign(asset, unavailable) {
|
|
1021
1006
|
const placement = asset.placement ?? 'floor';
|
|
1022
|
-
const icon = { floor: 'vertical_align_bottom', ceiling: 'vertical_align_top', wall: 'flip_to_back', center: 'vertical_align_center' }[placement];
|
|
1023
1007
|
/* A tilted part touches the plane at a point or an edge, a ball at its lowest; the tooltip says so rather than promise a face (designer 2026-09-27). */
|
|
1024
1008
|
const contact = this.planeContactOf(this.session.source(), this.selected);
|
|
1025
1009
|
const told = contact === 'tilted' || contact === 'extreme' ? `-${contact}` : '';
|
|
1026
|
-
return html `<button plane-align=${placement} ?disabled=${unavailable}
|
|
1027
|
-
|
|
1028
|
-
<md-icon>${icon}</md-icon>
|
|
1029
|
-
</button>`;
|
|
1010
|
+
return html `<button plane-align=${placement} ?disabled=${unavailable} title=${i18next.t(`figure.text.align-to-${placement}${told}`)}
|
|
1011
|
+
@click=${() => this.alignToPlane()}>${i18next.t(`figure.button.align-to-${placement}`)}</button>`;
|
|
1030
1012
|
}
|
|
1031
1013
|
alignToPlane() {
|
|
1032
1014
|
const asset = parseFigureAsset(this.session.source());
|
|
@@ -1051,26 +1033,30 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1051
1033
|
catch {
|
|
1052
1034
|
return null;
|
|
1053
1035
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
</
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1036
|
+
/*
|
|
1037
|
+
* In words, two rows by what they do (user 2026-10-01: 「무엇을 하는 것인지 모르겠다」): moving the part to the figure's
|
|
1038
|
+
* centre lines, onto its plane or onto the grid; and making a mirrored copy, which makes a part and moves nothing.
|
|
1039
|
+
*/
|
|
1040
|
+
const tool = (name, label, title) => html `<button ?disabled=${unavailable} title=${i18next.t(title)} @click=${() => this.align(name)}>${i18next.t(label)}</button>`;
|
|
1041
|
+
const mirrors = ['x', 'z'].map(axis => ({ axis, able: canMirror(asset, this.selected, axis, reach) }));
|
|
1042
|
+
return html `<div vector-row>
|
|
1043
|
+
<label>${i18next.t('figure.label.tools')}</label>
|
|
1044
|
+
<div tools>
|
|
1045
|
+
${tool('centre-x', 'figure.button.center-x', 'figure.text.center-on-x')}
|
|
1046
|
+
${tool('centre-z', 'figure.button.center-z', 'figure.text.center-on-z')}
|
|
1047
|
+
${this.renderPlaneAlign(asset, unavailable)}
|
|
1048
|
+
${tool('snap', 'figure.button.snap', 'figure.text.snap-explained')}
|
|
1049
|
+
</div>
|
|
1050
|
+
</div>
|
|
1051
|
+
<div vector-row>
|
|
1052
|
+
<label>${i18next.t('figure.label.mirror-copy')}</label>
|
|
1053
|
+
<div tools>
|
|
1054
|
+
${mirrors.map(({ axis, able }) => html `<button ?disabled=${unavailable || !able} title=${i18next.t('figure.text.mirror-explained')} @click=${() => this.mirror(axis)}>
|
|
1055
|
+
${i18next.t('figure.button.mirror-' + axis)}
|
|
1056
|
+
</button>`)}
|
|
1057
|
+
</div>
|
|
1058
|
+
</div>
|
|
1059
|
+
${mirrors.every(m => !m.able) ? html `<p hint>${i18next.t('figure.text.mirror-would-overlap')}</p>` : nothing}`;
|
|
1074
1060
|
}
|
|
1075
1061
|
/**
|
|
1076
1062
|
* A part's name. It renames every value the part owns, and what refers to them follows
|
|
@@ -1906,12 +1892,11 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1906
1892
|
</div>`;
|
|
1907
1893
|
}
|
|
1908
1894
|
/**
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
1895
|
+
* The part the panel speaks of, held at its top: its kind, how its size answers, what holds it (designer 2026-09-27,
|
|
1896
|
+
* B). No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
|
|
1897
|
+
* list's root is the way back to the model's settings.
|
|
1912
1898
|
*/
|
|
1913
1899
|
renderSubject() {
|
|
1914
|
-
const name = this.session?.read()?.name || i18next.t('figure.text.new-figure-name');
|
|
1915
1900
|
let summary;
|
|
1916
1901
|
try {
|
|
1917
1902
|
summary = this.selected ? partSummaryOf(parseFigureAsset(this.session.source()), ownerOf(this.selected), (key, params) => i18next.t(key, params)) : undefined;
|
|
@@ -1919,24 +1904,17 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1919
1904
|
catch {
|
|
1920
1905
|
summary = undefined;
|
|
1921
1906
|
}
|
|
1907
|
+
if (!summary)
|
|
1908
|
+
return nothing;
|
|
1922
1909
|
return html `<div subject>
|
|
1923
|
-
<
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
? html `<span aria-hidden="true">›</span><span current aria-current="location" title=${partLabelOf(this.selected)}>${partLabelOf(this.selected)}</span>`
|
|
1928
|
-
: html `<span aria-hidden="true">·</span><span current aria-current="location">${i18next.t('figure.label.model-settings')}</span>`}
|
|
1929
|
-
</nav>
|
|
1930
|
-
${summary
|
|
1931
|
-
? html `<div subject-summary>
|
|
1932
|
-
${summary.kind ? html `<b>${summary.kind}</b>` : nothing}
|
|
1933
|
-
<small>${summary.size}</small><small>${summary.relation}</small>
|
|
1934
|
-
</div>`
|
|
1935
|
-
: nothing}
|
|
1910
|
+
<div subject-summary>
|
|
1911
|
+
${summary.kind ? html `<b>${summary.kind}</b>` : nothing}
|
|
1912
|
+
<small>${summary.size}</small><small>${summary.relation}</small>
|
|
1913
|
+
</div>
|
|
1936
1914
|
</div>`;
|
|
1937
1915
|
}
|
|
1938
1916
|
/**
|
|
1939
|
-
* The model's settings
|
|
1917
|
+
* The model's settings, from the parts list's root (B.1). It shows them on
|
|
1940
1918
|
* the properties tab whatever tab was open; it changes no data, no camera, no history and no AI conversation.
|
|
1941
1919
|
*/
|
|
1942
1920
|
showModelSettings() {
|
|
@@ -2172,7 +2150,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2172
2150
|
? html `<section>
|
|
2173
2151
|
<h3>${i18next.t(vectorTitle)}</h3>
|
|
2174
2152
|
${groups.vectors.map(g => this.renderVector(g, unavailable, held))}
|
|
2175
|
-
${tools
|
|
2153
|
+
${tools ?? nothing}
|
|
2176
2154
|
</section>`
|
|
2177
2155
|
: nothing}
|
|
2178
2156
|
${groups.singles.length
|
|
@@ -2282,14 +2260,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2282
2260
|
this.linkCommand(refit.step);
|
|
2283
2261
|
}
|
|
2284
2262
|
renderSingle(name, field, unavailable) {
|
|
2285
|
-
|
|
2286
|
-
const point = /^point\.p(\d+)\.([xyz])$/.exec(name);
|
|
2287
|
-
const apex = /^apex\.([xyz])$/.exec(name);
|
|
2288
|
-
const words = point
|
|
2289
|
-
? [i18next.t('figure.label.section-editor-coordinate', { point: point[1], axis: point[2].toUpperCase() })]
|
|
2290
|
-
: apex
|
|
2291
|
-
? [`${i18next.t('figure.label.section-editor-apex-mark')} ${apex[1].toUpperCase()}`]
|
|
2292
|
-
: name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }));
|
|
2263
|
+
const words = name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }));
|
|
2293
2264
|
return html `<span ?scrub=${!unavailable} @pointerdown=${(e) => this.scrub(field, e, unavailable)}>${words.join(' · ')} (${field.unit})</span
|
|
2294
2265
|
><input
|
|
2295
2266
|
aria-label=${field.id}
|