@things-factory/figure-ui 10.1.129 → 10.1.142
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/align.ts +24 -18
- package/client/modeller/editor.ts +165 -253
- package/client/modeller/fasten-geometry.ts +12 -9
- package/client/modeller/figure-facts.ts +12 -14
- package/client/modeller/figure-links.ts +16 -12
- package/client/modeller/figure-preview.ts +13 -4
- package/client/modeller/figure-properties.ts +6 -5
- package/client/modeller/figure-verdict.ts +14 -8
- package/client/modeller/joint-fan.ts +15 -17
- package/client/modeller/links.ts +17 -23
- package/client/modeller/live.ts +6 -3
- package/client/modeller/motion-ends.ts +6 -7
- package/client/modeller/occupancy.ts +2 -3
- package/client/modeller/product-session.ts +15 -0
- package/client/modeller/revision.ts +66 -0
- package/client/pages/figure-modeller-page.ts +5 -18
- package/dist-client/modeller/align.d.ts +12 -7
- package/dist-client/modeller/align.js +21 -17
- package/dist-client/modeller/align.js.map +1 -1
- package/dist-client/modeller/editor.d.ts +8 -34
- package/dist-client/modeller/editor.js +175 -250
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/fasten-geometry.d.ts +3 -2
- package/dist-client/modeller/fasten-geometry.js +11 -9
- package/dist-client/modeller/fasten-geometry.js.map +1 -1
- package/dist-client/modeller/figure-facts.d.ts +9 -1
- package/dist-client/modeller/figure-facts.js +11 -15
- package/dist-client/modeller/figure-facts.js.map +1 -1
- package/dist-client/modeller/figure-links.d.ts +4 -5
- package/dist-client/modeller/figure-links.js +14 -13
- package/dist-client/modeller/figure-links.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +5 -2
- package/dist-client/modeller/figure-preview.js +13 -4
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-properties.d.ts +3 -2
- package/dist-client/modeller/figure-properties.js +6 -6
- package/dist-client/modeller/figure-properties.js.map +1 -1
- package/dist-client/modeller/figure-verdict.d.ts +4 -2
- package/dist-client/modeller/figure-verdict.js +12 -9
- package/dist-client/modeller/figure-verdict.js.map +1 -1
- package/dist-client/modeller/joint-fan.d.ts +8 -4
- package/dist-client/modeller/joint-fan.js +15 -16
- package/dist-client/modeller/joint-fan.js.map +1 -1
- package/dist-client/modeller/links.d.ts +6 -5
- package/dist-client/modeller/links.js +17 -24
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/live.d.ts +3 -2
- package/dist-client/modeller/live.js +7 -3
- package/dist-client/modeller/live.js.map +1 -1
- package/dist-client/modeller/motion-ends.d.ts +2 -2
- package/dist-client/modeller/motion-ends.js +5 -6
- package/dist-client/modeller/motion-ends.js.map +1 -1
- package/dist-client/modeller/occupancy.d.ts +2 -2
- package/dist-client/modeller/occupancy.js +2 -3
- package/dist-client/modeller/occupancy.js.map +1 -1
- package/dist-client/modeller/product-session.d.ts +44 -39
- package/dist-client/modeller/product-session.js +17 -0
- package/dist-client/modeller/product-session.js.map +1 -1
- package/dist-client/modeller/revision.d.ts +37 -0
- package/dist-client/modeller/revision.js +39 -0
- package/dist-client/modeller/revision.js.map +1 -0
- package/dist-client/pages/figure-modeller-page.d.ts +0 -6
- package/dist-client/pages/figure-modeller-page.js +6 -14
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/test/align.test.ts +14 -12
- package/test/browser/modeller-page.js +52 -4
- package/test/browser/modeller-revision.check.mjs +144 -0
- package/test/command-preview.test.ts +9 -8
- package/test/fasten-drawing.test.ts +1 -1
- package/test/fasten-geometry.test.ts +10 -9
- package/test/fields.test.ts +1 -1
- package/test/first-minute.test.ts +1 -1
- package/test/gizmo.test.ts +1 -1
- package/test/joint-fan.test.ts +30 -14
- package/test/joint-frames.test.ts +9 -8
- package/test/links.test.ts +13 -12
- package/test/live.test.ts +3 -3
- package/test/parts-wiring.test.ts +1 -1
- package/test/properties.test.ts +6 -5
- package/test/release-panel.test.ts +3 -3
- package/test/resize-with-placement.test.ts +8 -6
- package/test/revision-of.ts +22 -0
- package/test/revision.test.ts +172 -0
- package/test/section-editor.test.ts +4 -3
- package/test/section-panel.test.ts +5 -4
- package/test/verdict.test.ts +1 -1
- package/client/modeller/evaluated.ts +0 -47
- package/dist-client/modeller/evaluated.d.ts +0 -12
- package/dist-client/modeller/evaluated.js +0 -42
- package/dist-client/modeller/evaluated.js.map +0 -1
- package/dist-client/tsconfig.scratch.tsbuildinfo +0 -1
- package/test/evaluated.test.ts +0 -40
- package/test/mode-switch.test.ts +0 -43
|
@@ -7,7 +7,7 @@ import { notify } from '@operato/layout';
|
|
|
7
7
|
import { OxPrompt } from '@operato/popup';
|
|
8
8
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
9
9
|
import { live } from 'lit/directives/live.js';
|
|
10
|
-
import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor,
|
|
10
|
+
import { PART_LIMIT, parseFigureRecipe, resolveFigureRecipe, figurePartCountOf, figureRecipeIdFor, figureSizeEditChanges, figureScaleAnchorOf, figureSectionMeasuresOf, figurePlaneAlignChanges, figurePartFaceOf, figureRowOf, figureFaceOverhangOf, figureRoomOnlyInputsOf, figureRoomRefitOf } from '@hatiolab/figure-model';
|
|
11
11
|
import './figure-recipe-catalog.js';
|
|
12
12
|
import { captureFigureThumbnail } from './thumbnail.js';
|
|
13
13
|
import { drawableFigureParts } from './renderer.js';
|
|
@@ -30,7 +30,7 @@ import { keyActionOf } from './keys.js';
|
|
|
30
30
|
import { groupFields, ownerOf, partLabelOf, repeatOf } from './fields.js';
|
|
31
31
|
import { SidePanelStyles } from './side-panel-styles.js';
|
|
32
32
|
import { ScrollbarStyles } from '@operato/styles';
|
|
33
|
-
import { alignChanges,
|
|
33
|
+
import { alignChanges, canMirror, mirrorChanges } from './align.js';
|
|
34
34
|
import { paletteRecipe } from './palette.js';
|
|
35
35
|
import { apexOver, drawnOf } from './section-drawing.js';
|
|
36
36
|
import { END_FACE } from './section-controls.js';
|
|
@@ -39,7 +39,7 @@ import { roomRefitRowsOf } from './occupancy.js';
|
|
|
39
39
|
import { editRefusalOf, inDeclaredRoom, inputRangeRefusalOf, narrowingRetryOf, partRefusalOf, placementRefusalOf, recipeRefusalOf, removalNoticeOf, sizeRangeRefusalOf } from './refusals.js';
|
|
40
40
|
import { endPlaceKeyOf, fieldWordsOf, followOf, measureNow, partSummaryOf, instanceRangeOf, instanceRangeToDeclare, proportionToFollow, rangeDiffers, rangeWith, turnsAboutAJoint, valueNow } from './links.js';
|
|
41
41
|
import { jointFanOf } from './joint-fan.js';
|
|
42
|
-
import {
|
|
42
|
+
import { derivedHeld, derivedOf } from './revision.js';
|
|
43
43
|
import { jointDragOf } from './joint-drag.js';
|
|
44
44
|
import { liveOverSession, NOTHING_LIVE, scrubbed, scrubStep } from './live.js';
|
|
45
45
|
import { startingShapeKindOfBody } from './starting-shape.js';
|
|
@@ -58,16 +58,9 @@ function foldSaid() {
|
|
|
58
58
|
/** Product editor surface. The injected session is shared by page persistence and AI commands. */
|
|
59
59
|
/** The words a panel is given when there are none: one array, so it does not read as a change. */
|
|
60
60
|
const NO_WORDS = [];
|
|
61
|
+
const NO_OVERHANG = { words: NO_WORDS, note: NO_WORDS };
|
|
61
62
|
/** How long a command's candidate rests before its preview is worked out. */
|
|
62
63
|
const PREVIEW_SETTLE_MS = 150;
|
|
63
|
-
function deepFreeze(value) {
|
|
64
|
-
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
65
|
-
Object.freeze(value);
|
|
66
|
-
for (const inner of Object.values(value))
|
|
67
|
-
deepFreeze(inner);
|
|
68
|
-
}
|
|
69
|
-
return value;
|
|
70
|
-
}
|
|
71
64
|
let FigureEditor = class FigureEditor extends LitElement {
|
|
72
65
|
constructor() {
|
|
73
66
|
super(...arguments);
|
|
@@ -709,33 +702,36 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
709
702
|
this.live = NOTHING_LIVE;
|
|
710
703
|
}
|
|
711
704
|
}
|
|
705
|
+
/** The bodies drawn with the value in hand, or null when nothing is in hand (or the figure cannot take it). */
|
|
706
|
+
liveParts(shown) {
|
|
707
|
+
return shown ? liveOverSession(shown, this.live) : null;
|
|
708
|
+
}
|
|
712
709
|
/** What the canvas draws: the figure with the value in hand when there is one, and without the parts hidden on the workbench. */
|
|
713
|
-
drawnParts(geometry,
|
|
714
|
-
const parts =
|
|
710
|
+
drawnParts(geometry, live) {
|
|
711
|
+
const parts = live ?? drawableFigureParts(geometry ?? []);
|
|
715
712
|
return parts.filter(body => !this.hiddenParts.has(ownerOf(body.id)));
|
|
716
713
|
}
|
|
717
714
|
/**
|
|
718
715
|
* Where the selected part turns and how far, with the pose as it is drawn now -- a value in hand included, so the
|
|
719
716
|
* fan follows the arm while a control is dragged. Its size comes from what the joint turns (`joint-fan`).
|
|
720
717
|
*/
|
|
721
|
-
fanOf(
|
|
722
|
-
if (!
|
|
718
|
+
fanOf(shown, live) {
|
|
719
|
+
if (!shown || !this.selected || this.hiddenParts.has(this.selected))
|
|
723
720
|
return null;
|
|
724
721
|
try {
|
|
725
|
-
return jointFanOf(
|
|
722
|
+
return jointFanOf(shown, this.selected, live ? { values: this.live.values, geometry: live } : undefined);
|
|
726
723
|
}
|
|
727
724
|
catch {
|
|
728
725
|
return null;
|
|
729
726
|
}
|
|
730
727
|
}
|
|
731
728
|
/**
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
* saved angle while one slider moved
|
|
729
|
+
* The figure on screen at its version: what every panel and helper reads (`revision.ts`). Every input as the session
|
|
730
|
+
* holds it now is its `inputs` -- the pose set with the controls included: drawing a value in hand over the saved
|
|
731
|
+
* defaults put every other joint back to its saved angle while one slider moved (user, 2026-09-24).
|
|
735
732
|
*/
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
return view ? Object.fromEntries(view.fields.map(field => [field.id, field.value])) : {};
|
|
733
|
+
shown() {
|
|
734
|
+
return this.session?.revisionNow();
|
|
739
735
|
}
|
|
740
736
|
/** A value in hand: an input's value (design or pose) or a part edit. Nothing is recorded. */
|
|
741
737
|
hold(values, edit) {
|
|
@@ -864,7 +860,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
864
860
|
*/
|
|
865
861
|
insertRecipe(recipe, values) {
|
|
866
862
|
try {
|
|
867
|
-
const asset = this.
|
|
863
|
+
const asset = this.shown().asset;
|
|
868
864
|
const checked = parseFigureRecipe(recipe);
|
|
869
865
|
const id = figureRecipeIdFor(asset, checked);
|
|
870
866
|
const steps = resolveFigureRecipe(checked, { id, ...(checked.target === 'part' ? { target: this.selected } : {}), values });
|
|
@@ -881,7 +877,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
881
877
|
/** The design values of the asset being edited. */
|
|
882
878
|
designValues() {
|
|
883
879
|
try {
|
|
884
|
-
return this.
|
|
880
|
+
return this.shown()?.asset.designInputs ?? {};
|
|
885
881
|
}
|
|
886
882
|
catch {
|
|
887
883
|
return {};
|
|
@@ -894,7 +890,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
894
890
|
if (!id || !this.session || !this.session.read().geometry.some(g => g.id === id))
|
|
895
891
|
return none;
|
|
896
892
|
try {
|
|
897
|
-
const asset = this.
|
|
893
|
+
const asset = this.shown().asset;
|
|
898
894
|
const reach = gizmoReachOf(asset, id);
|
|
899
895
|
/*
|
|
900
896
|
A size drag shows the face it keeps and the commit keeps the same (figureScaleAnchorOf). Where figure-model cannot
|
|
@@ -922,9 +918,10 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
922
918
|
transformed(id, result) {
|
|
923
919
|
const view = this.session.read();
|
|
924
920
|
/* A part hanging from a joint: the drag edits the joint -- its value, or where it sits (ADR-0093). */
|
|
925
|
-
const
|
|
921
|
+
const shown = this.shown();
|
|
922
|
+
const asset = shown.asset;
|
|
926
923
|
const drawn = view.geometry.find(g => g.id === id);
|
|
927
|
-
const drag = drawn ? jointDragOf(asset, ownerOf(id), drawn, result,
|
|
924
|
+
const drag = drawn ? jointDragOf(asset, ownerOf(id), drawn, result, shown.inputs) : null;
|
|
928
925
|
if (drag) {
|
|
929
926
|
if (drag.kind === 'value')
|
|
930
927
|
this.run(() => this.session.apply([{ kind: 'set-state', input: drag.input, unit: drag.unit, value: drag.value }]));
|
|
@@ -1005,8 +1002,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1005
1002
|
/** 맞추기 — one tool, one step of the history, through the same inputs as the gizmo. */
|
|
1006
1003
|
align(tool) {
|
|
1007
1004
|
this.run(() => {
|
|
1008
|
-
const
|
|
1009
|
-
const changes = alignChanges(asset, this.selected, this.reachOf(this.selected), tool);
|
|
1005
|
+
const changes = alignChanges(this.shown(), this.selected, this.reachOf(this.selected), tool);
|
|
1010
1006
|
if (changes.length)
|
|
1011
1007
|
this.session.apply(changes);
|
|
1012
1008
|
});
|
|
@@ -1020,7 +1016,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1020
1016
|
const copy = this.session.read().geometry.find(g => !before.has(g.id))?.id;
|
|
1021
1017
|
if (!copy)
|
|
1022
1018
|
return;
|
|
1023
|
-
this.session.apply(mirrorChanges(this.
|
|
1019
|
+
this.session.apply(mirrorChanges(this.shown(), id, copy, axis));
|
|
1024
1020
|
this.selected = copy;
|
|
1025
1021
|
});
|
|
1026
1022
|
}
|
|
@@ -1032,32 +1028,30 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1032
1028
|
*/
|
|
1033
1029
|
/* What a tilted part's typed size keeps, by the figure's placement: not the floor's lowest point everywhere (designer 2026-09-27). */
|
|
1034
1030
|
keptExtremeWord() {
|
|
1035
|
-
const placement = this.asset()?.placement ?? 'floor';
|
|
1031
|
+
const placement = (this.asset())?.placement ?? 'floor';
|
|
1036
1032
|
return i18next.t(`figure.label.kept-extreme-${placement}`);
|
|
1037
1033
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1034
|
+
/* How the selected part would meet the plane, worked out once per version and part. */
|
|
1035
|
+
planeContactOf(shown, part) {
|
|
1036
|
+
return derivedOf(shown, `plane-contact|${part}`, () => {
|
|
1041
1037
|
try {
|
|
1042
|
-
|
|
1038
|
+
return figurePlaneAlignChanges(shown.asset, part).contact;
|
|
1043
1039
|
}
|
|
1044
1040
|
catch {
|
|
1045
|
-
|
|
1041
|
+
return undefined;
|
|
1046
1042
|
}
|
|
1047
|
-
|
|
1048
|
-
}
|
|
1049
|
-
return this.planeContact.contact;
|
|
1043
|
+
});
|
|
1050
1044
|
}
|
|
1051
|
-
renderPlaneAlign(
|
|
1052
|
-
const placement = asset.placement ?? 'floor';
|
|
1045
|
+
renderPlaneAlign(shown, unavailable) {
|
|
1046
|
+
const placement = shown.asset.placement ?? 'floor';
|
|
1053
1047
|
/* 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). */
|
|
1054
|
-
const contact = this.planeContactOf(
|
|
1048
|
+
const contact = this.planeContactOf(shown, this.selected);
|
|
1055
1049
|
const told = contact === 'tilted' || contact === 'extreme' ? `-${contact}` : '';
|
|
1056
1050
|
return html `<button plane-align=${placement} ?disabled=${unavailable} title=${i18next.t(`figure.text.align-to-${placement}${told}`)}
|
|
1057
1051
|
@click=${() => this.alignToPlane()}>${i18next.t(`figure.button.align-to-${placement}`)}</button>`;
|
|
1058
1052
|
}
|
|
1059
1053
|
alignToPlane() {
|
|
1060
|
-
const asset = this.
|
|
1054
|
+
const asset = this.shown().asset;
|
|
1061
1055
|
const { changes, contact, refusal } = figurePlaneAlignChanges(asset, this.selected);
|
|
1062
1056
|
if (refusal) {
|
|
1063
1057
|
this.error = i18next.t(`figure.text.align-refused-${refusal.toLowerCase().replace(/_/g, '-')}`, { part: partLabelOf(this.selected) });
|
|
@@ -1070,27 +1064,21 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1070
1064
|
}
|
|
1071
1065
|
renderTools(unavailable) {
|
|
1072
1066
|
const reach = this.reachOf(this.selected);
|
|
1073
|
-
|
|
1067
|
+
const shown = this.shown();
|
|
1068
|
+
if (!this.selected || !reach.translate || !shown)
|
|
1074
1069
|
return null;
|
|
1075
|
-
let asset;
|
|
1076
|
-
try {
|
|
1077
|
-
asset = this.plainAsset();
|
|
1078
|
-
}
|
|
1079
|
-
catch {
|
|
1080
|
-
return null;
|
|
1081
|
-
}
|
|
1082
1070
|
/*
|
|
1083
1071
|
* In words, two rows by what they do (user 2026-10-01: 「무엇을 하는 것인지 모르겠다」): moving the part to the figure's
|
|
1084
1072
|
* centre lines, onto its plane or onto the grid; and making a mirrored copy, which makes a part and moves nothing.
|
|
1085
1073
|
*/
|
|
1086
1074
|
const tool = (name, label, title) => html `<button ?disabled=${unavailable} title=${i18next.t(title)} @click=${() => this.align(name)}>${i18next.t(label)}</button>`;
|
|
1087
|
-
const mirrors = ['x', 'z'].map(axis => ({ axis, able: canMirror(
|
|
1075
|
+
const mirrors = ['x', 'z'].map(axis => ({ axis, able: canMirror(shown, this.selected, axis, reach) }));
|
|
1088
1076
|
return html `<div vector-row>
|
|
1089
1077
|
<label>${i18next.t('figure.label.tools')}</label>
|
|
1090
1078
|
<div tools>
|
|
1091
1079
|
${tool('centre-x', 'figure.button.center-x', 'figure.text.center-on-x')}
|
|
1092
1080
|
${tool('centre-z', 'figure.button.center-z', 'figure.text.center-on-z')}
|
|
1093
|
-
${this.renderPlaneAlign(
|
|
1081
|
+
${this.renderPlaneAlign(shown, unavailable)}
|
|
1094
1082
|
${tool('snap', 'figure.button.snap', 'figure.text.snap-explained')}
|
|
1095
1083
|
</div>
|
|
1096
1084
|
</div>
|
|
@@ -1140,21 +1128,6 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1140
1128
|
: String(error);
|
|
1141
1129
|
}
|
|
1142
1130
|
}
|
|
1143
|
-
dimsOf(asset) {
|
|
1144
|
-
const view = this.session?.read();
|
|
1145
|
-
const key = `${view?.context}|${view?.revision}|${this.selected}`;
|
|
1146
|
-
if (this.dimsSaid?.key === key)
|
|
1147
|
-
return this.dimsSaid.dims;
|
|
1148
|
-
let dims;
|
|
1149
|
-
try {
|
|
1150
|
-
dims = boxOf(asset, this.selected, this.currentInputs())?.dims;
|
|
1151
|
-
}
|
|
1152
|
-
catch {
|
|
1153
|
-
dims = undefined;
|
|
1154
|
-
}
|
|
1155
|
-
this.dimsSaid = { key, dims };
|
|
1156
|
-
return dims;
|
|
1157
|
-
}
|
|
1158
1131
|
previewWordsNow() {
|
|
1159
1132
|
const preview = this.currentPreview();
|
|
1160
1133
|
const language = i18next.language;
|
|
@@ -1165,25 +1138,19 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1165
1138
|
return words;
|
|
1166
1139
|
}
|
|
1167
1140
|
renderLinks(unavailable, section) {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
asset = this.plainAsset();
|
|
1171
|
-
}
|
|
1172
|
-
catch {
|
|
1141
|
+
const shown = this.shown();
|
|
1142
|
+
if (!shown)
|
|
1173
1143
|
return null;
|
|
1174
|
-
}
|
|
1175
|
-
const dims = this.dimsOf(asset);
|
|
1176
1144
|
return html `<figure-links
|
|
1177
|
-
.
|
|
1145
|
+
.revision=${shown}
|
|
1178
1146
|
.section=${section}
|
|
1179
1147
|
.partId=${this.selected}
|
|
1180
|
-
.dims=${dims}
|
|
1181
1148
|
?locked=${unavailable}
|
|
1182
1149
|
@command=${(e) => this.linkCommand(e.detail.action)}
|
|
1183
1150
|
.previewWords=${this.previewWordsNow()}
|
|
1184
|
-
.detachWords=${this.detachWords(
|
|
1185
|
-
.overhangWords=${section === 'shape' ? this.
|
|
1186
|
-
.overhangNote=${section === 'shape' ? this.
|
|
1151
|
+
.detachWords=${this.detachWords(shown)}
|
|
1152
|
+
.overhangWords=${section === 'shape' ? this.overhangOf(shown).words : NO_WORDS}
|
|
1153
|
+
.overhangNote=${section === 'shape' ? this.overhangOf(shown).note : NO_WORDS}
|
|
1187
1154
|
.motionEnds=${section === 'motion' ? this.motionEnds() : null}
|
|
1188
1155
|
@go-fasten=${() => this.goFasten()}
|
|
1189
1156
|
@preview-command=${(e) => this.previewCommand(e.detail)}
|
|
@@ -1212,13 +1179,9 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1212
1179
|
}
|
|
1213
1180
|
/** The figure's controls: try a value, name it, set its range and travel time, keep the pose. */
|
|
1214
1181
|
renderControls(view, unavailable) {
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
asset = this.plainAsset();
|
|
1218
|
-
}
|
|
1219
|
-
catch {
|
|
1182
|
+
const asset = this.shown()?.asset;
|
|
1183
|
+
if (!asset)
|
|
1220
1184
|
return null;
|
|
1221
|
-
}
|
|
1222
1185
|
const values = Object.fromEntries(view.fields.filter(f => f.role === 'state').map(f => [f.id, f.value]));
|
|
1223
1186
|
return html `<figure-controls
|
|
1224
1187
|
.asset=${asset}
|
|
@@ -1303,7 +1266,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1303
1266
|
/* A size typed in keeps the face the figure's placement keeps -- figure-model's one rule, as the gizmo and "follow the size" do. */
|
|
1304
1267
|
const part = this.selected;
|
|
1305
1268
|
const change = { kind: field.role === 'state' ? 'set-state' : 'set-design', input: field.id, unit: field.unit, value };
|
|
1306
|
-
this.run(() => this.session.apply(field.role !== 'state' && part ? figureSizeEditChanges(this.
|
|
1269
|
+
this.run(() => this.session.apply(field.role !== 'state' && part ? figureSizeEditChanges(this.shown().asset, part, [change]) : [change]));
|
|
1307
1270
|
// Rejected edits must also restore the displayed numeric value.
|
|
1308
1271
|
input.value = String(this.session.read().fields.find(i => i.id === field.id).value);
|
|
1309
1272
|
}
|
|
@@ -1353,12 +1316,12 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1353
1316
|
label.addEventListener('pointercancel', up);
|
|
1354
1317
|
}
|
|
1355
1318
|
/** The sizes a board may give the figure, and the way to measure them again after its parts changed. */
|
|
1356
|
-
renderInstanceRange(
|
|
1357
|
-
if (!
|
|
1319
|
+
renderInstanceRange(shown, unavailable) {
|
|
1320
|
+
if (!shown)
|
|
1358
1321
|
return nothing;
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1322
|
+
const asset = shown.asset;
|
|
1323
|
+
/* Measured once per version: measuring evaluates the figure at its edges. */
|
|
1324
|
+
const measured = derivedOf(shown, 'instance-range', () => {
|
|
1362
1325
|
let of;
|
|
1363
1326
|
let toDeclare;
|
|
1364
1327
|
try {
|
|
@@ -1373,11 +1336,11 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1373
1336
|
catch {
|
|
1374
1337
|
toDeclare = undefined;
|
|
1375
1338
|
}
|
|
1376
|
-
|
|
1377
|
-
}
|
|
1378
|
-
const range =
|
|
1339
|
+
return { of, toDeclare };
|
|
1340
|
+
});
|
|
1341
|
+
const range = measured.of;
|
|
1379
1342
|
/* No size yet: the figure is declared its size from its parts, so a board can resize it and parts can follow it. */
|
|
1380
|
-
const toDeclare =
|
|
1343
|
+
const toDeclare = measured.toDeclare;
|
|
1381
1344
|
if (!range && toDeclare)
|
|
1382
1345
|
return html `<section>
|
|
1383
1346
|
<h3>${i18next.t('figure.label.instance-range')}</h3>
|
|
@@ -1492,9 +1455,10 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1492
1455
|
* The pitch of a row that fills its length is edited as a number (`set-repeat` makes it the part's own the first
|
|
1493
1456
|
* time); a row of a fixed count says its pitch cannot be set yet (figure-model refuses it the same way).
|
|
1494
1457
|
*/
|
|
1495
|
-
renderRepeat(
|
|
1458
|
+
renderRepeat(shown, geometry, unavailable) {
|
|
1496
1459
|
const repeat = repeatOf(this.selected, geometry);
|
|
1497
|
-
|
|
1460
|
+
const asset = shown?.asset;
|
|
1461
|
+
if (!shown || !asset || !repeat || repeat.index)
|
|
1498
1462
|
return nothing;
|
|
1499
1463
|
/* The row is set on its part; what hangs from that part is copied with it and has no row of its own. */
|
|
1500
1464
|
if (!asset.document.model.nodes.some(n => n.id === `${this.selected}.repeat`))
|
|
@@ -1504,7 +1468,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1504
1468
|
const axis = model.nodes.find(n => n.id === `${this.selected}.repeat`)?.params?.axis ?? '?';
|
|
1505
1469
|
const own = `${this.selected}.repeat.pitch`;
|
|
1506
1470
|
/* The pitch as it stands: the part's own number, a constant, or what a pitch that follows a length comes to. */
|
|
1507
|
-
const pitch = asset.designInputs[own] ?? model.constants.find(c => c.id === layout?.args[4])?.value ?? valueNow(
|
|
1471
|
+
const pitch = asset.designInputs[own] ?? model.constants.find(c => c.id === layout?.args[4])?.value ?? valueNow(shown, layout?.args[4]);
|
|
1508
1472
|
const fits = layout?.op === 'fit-pitch@1';
|
|
1509
1473
|
/* What the pitch gives, in numbers: this many copies at the length it is authored, and how long it may be placed. */
|
|
1510
1474
|
const lengthRef = layout?.args[0] ?? '';
|
|
@@ -1527,7 +1491,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1527
1491
|
@change=${(e) => this.setPitch(field, e)}
|
|
1528
1492
|
/>
|
|
1529
1493
|
</div>
|
|
1530
|
-
<ul row-reading>${rowWordsOf(this.rowNow(
|
|
1494
|
+
<ul row-reading>${rowWordsOf(this.rowNow(shown), (key, params) => i18next.t(key, params)).map(line => html `<li>${line}</li>`)}</ul>
|
|
1531
1495
|
<p hint>
|
|
1532
1496
|
${fits && length !== undefined && longest !== undefined && limit !== undefined
|
|
1533
1497
|
? i18next.t('figure.text.repeat-what-pitch-gives', {
|
|
@@ -1547,13 +1511,15 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1547
1511
|
</section>`;
|
|
1548
1512
|
}
|
|
1549
1513
|
/** The selected row as it is drawn now, read by figure-model: nothing when it cannot be read. */
|
|
1550
|
-
rowNow(
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1514
|
+
rowNow(shown) {
|
|
1515
|
+
return derivedOf(shown, `row|${this.selected}`, () => {
|
|
1516
|
+
try {
|
|
1517
|
+
return figureRowOf(shown.asset, this.selected, shown.inputs);
|
|
1518
|
+
}
|
|
1519
|
+
catch {
|
|
1520
|
+
return undefined;
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1557
1523
|
}
|
|
1558
1524
|
/** The design input a row's pitch is read from, when it is read from one (ADR-0096). */
|
|
1559
1525
|
pitchInputOf(asset, part) {
|
|
@@ -1641,7 +1607,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1641
1607
|
if (!Number.isFinite(pitch) || pitch === field.value)
|
|
1642
1608
|
return;
|
|
1643
1609
|
/* Read from a design input the board may set: the input's value changes, the row keeps reading it (ADR-0096). */
|
|
1644
|
-
const from = this.pitchInputOf(this.
|
|
1610
|
+
const from = this.pitchInputOf(this.shown().asset, this.selected);
|
|
1645
1611
|
if (from) {
|
|
1646
1612
|
this.run(() => this.session.apply([{ kind: 'set-design', input: from, unit: 'mm', value: pitch }]));
|
|
1647
1613
|
input.value = String(this.session.read().fields.find(i => i.id === from)?.value ?? field.value);
|
|
@@ -1699,14 +1665,15 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1699
1665
|
let face;
|
|
1700
1666
|
if (candidate.face) {
|
|
1701
1667
|
try {
|
|
1702
|
-
|
|
1668
|
+
const shown = this.shown();
|
|
1669
|
+
face = figurePartFaceOf(shown.asset, candidate.face.part, candidate.face.face, shown.inputs);
|
|
1703
1670
|
}
|
|
1704
1671
|
catch {
|
|
1705
1672
|
face = undefined;
|
|
1706
1673
|
}
|
|
1707
1674
|
}
|
|
1708
1675
|
/* A row is read on the figure the command would make, by figure-model, as drawn. */
|
|
1709
|
-
const row = candidate.action.kind === 'make-repeat' ? figureRowOf(seen.asset, this.selected, this.
|
|
1676
|
+
const row = candidate.action.kind === 'make-repeat' ? figureRowOf(seen.asset, this.selected, this.shown().inputs) : undefined;
|
|
1710
1677
|
this.preview = {
|
|
1711
1678
|
...held,
|
|
1712
1679
|
context: seen.context,
|
|
@@ -1738,37 +1705,36 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1738
1705
|
* Said once the figure has rested: working it out is a preview of the command on a copy (tens of milliseconds), and the
|
|
1739
1706
|
* edit that made this version should be on screen before it. Until then nothing is said, as for a part with no joint.
|
|
1740
1707
|
*/
|
|
1741
|
-
detachWords(
|
|
1742
|
-
const
|
|
1743
|
-
|
|
1744
|
-
if (!asset || !view || !joint || !this.session)
|
|
1708
|
+
detachWords(shown) {
|
|
1709
|
+
const part = this.selected;
|
|
1710
|
+
if (!this.session || !shown.asset.joints?.some(j => j.body1.part === part))
|
|
1745
1711
|
return NO_WORDS;
|
|
1746
|
-
const key =
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1712
|
+
const key = `detach|${part}`;
|
|
1713
|
+
const said = derivedHeld(shown, key);
|
|
1714
|
+
if (said)
|
|
1715
|
+
return said;
|
|
1716
|
+
if (this.detachAsked?.shown !== shown || this.detachAsked.part !== part) {
|
|
1717
|
+
this.detachAsked = { shown, part };
|
|
1751
1718
|
clearTimeout(this.detachTimer);
|
|
1752
1719
|
this.detachTimer = setTimeout(() => {
|
|
1753
|
-
|
|
1754
|
-
if (!now || `${now.context}|${now.revision}|${this.selected}` !== key)
|
|
1720
|
+
if (this.shown() !== shown || this.selected !== part)
|
|
1755
1721
|
return;
|
|
1756
|
-
this.computeDetachWords(
|
|
1722
|
+
derivedOf(shown, key, () => this.computeDetachWords(shown, part));
|
|
1757
1723
|
this.requestUpdate();
|
|
1758
1724
|
}, PREVIEW_SETTLE_MS);
|
|
1759
1725
|
}
|
|
1760
1726
|
return NO_WORDS;
|
|
1761
1727
|
}
|
|
1762
|
-
computeDetachWords(
|
|
1728
|
+
computeDetachWords(shown, part) {
|
|
1763
1729
|
const say = (k, params) => i18next.t(k, params);
|
|
1764
1730
|
let words;
|
|
1765
1731
|
try {
|
|
1766
|
-
const seen = this.session.previewCommand({ kind: 'detach', part
|
|
1767
|
-
const own = seen.moves.some(m => m.body ===
|
|
1732
|
+
const seen = this.session.previewCommand({ kind: 'detach', part });
|
|
1733
|
+
const own = seen.moves.some(m => m.body === part);
|
|
1768
1734
|
words = own
|
|
1769
|
-
? [say('figure.text.detach-moves'), ...previewWordsOf({ context: seen.context, revision: seen.revision, key
|
|
1735
|
+
? [say('figure.text.detach-moves'), ...previewWordsOf({ context: seen.context, revision: seen.revision, key: `detach|${part}`, moves: seen.moves }, part, say)]
|
|
1770
1736
|
: /* It stays where it is: as expected, so nothing is said (user 2026-10-01). */ [];
|
|
1771
|
-
const children = (asset.joints ?? []).filter(j => 'part' in j.body0 && j.body0.part ===
|
|
1737
|
+
const children = (shown.asset.joints ?? []).filter(j => 'part' in j.body0 && j.body0.part === part).map(j => partLabelOf(j.body1.part));
|
|
1772
1738
|
if (children.length)
|
|
1773
1739
|
words.push(say('figure.text.detach-keeps-children', { parts: children.join(', ') }));
|
|
1774
1740
|
if (seen.clipsWouldGo.length)
|
|
@@ -1777,67 +1743,62 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1777
1743
|
catch (error) {
|
|
1778
1744
|
words = [say('figure.text.detach-refused', { reason: this.said(error) })];
|
|
1779
1745
|
}
|
|
1780
|
-
this.detachSaid = { key, words };
|
|
1781
1746
|
return words;
|
|
1782
1747
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
const
|
|
1790
|
-
if (!
|
|
1791
|
-
return
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1748
|
+
/*
|
|
1749
|
+
Past the edge of the face a part is fastened to (ADR-0097 §5.5, Q3): the selected part's own, and the parts fastened
|
|
1750
|
+
to it -- said apart from the room, nothing moved. Read once per version, part and language. The note says it is wider
|
|
1751
|
+
than its face: a fact of the relation, said in its details, not as a warning (designer 2026-09-27).
|
|
1752
|
+
*/
|
|
1753
|
+
overhangOf(shown) {
|
|
1754
|
+
const part = this.selected;
|
|
1755
|
+
if (!part)
|
|
1756
|
+
return NO_OVERHANG;
|
|
1757
|
+
return derivedOf(shown, `overhang|${part}|${i18next.language}`, () => {
|
|
1758
|
+
const asset = shown.asset;
|
|
1759
|
+
const say = (k, params) => i18next.t(k, params);
|
|
1760
|
+
const where = (o) => ({ on: partLabelOf(o.on.part), face: say(`figure.label.face-${o.on.face}`) });
|
|
1761
|
+
const sides = (o) => o.sides.map(s => say(`figure.text.overhang-side-${s.side}`, { axis: s.axis.toUpperCase(), mm: String(s.mm) })).join(', ');
|
|
1762
|
+
const words = [];
|
|
1763
|
+
const note = [];
|
|
1764
|
+
try {
|
|
1765
|
+
const own = figureFaceOverhangOf(asset, part, shown.inputs);
|
|
1766
|
+
if (own?.state === 'larger') {
|
|
1767
|
+
note.push(say('figure.text.overhang-larger', { ...where(own), sides: sides(own) }));
|
|
1768
|
+
if (own.lopsided.length)
|
|
1769
|
+
note.push(say('figure.text.overhang-lopsided', { axes: own.lopsided.map(a => a.toUpperCase()).join(', ') }));
|
|
1770
|
+
}
|
|
1771
|
+
if (own?.state === 'out')
|
|
1772
|
+
words.push(say('figure.text.overhang-out', { ...where(own), sides: sides(own) }));
|
|
1773
|
+
else if (own?.state === 'possible')
|
|
1774
|
+
words.push(say('figure.text.overhang-possible', where(own)));
|
|
1775
|
+
else if (own?.state === 'unknown')
|
|
1776
|
+
words.push(say('figure.text.overhang-unknown', where(own)));
|
|
1777
|
+
const held = (asset.joints ?? []).filter(j => 'part' in j.body0 && j.body0.part === part).map(j => figureFaceOverhangOf(asset, j.body1.part, shown.inputs));
|
|
1778
|
+
const past = held.filter((o) => o?.state === 'out' || o?.state === 'possible');
|
|
1779
|
+
if (past.length)
|
|
1780
|
+
words.push(say('figure.text.overhang-held', { parts: past.map(o => partLabelOf(o.part)).join(', ') }));
|
|
1807
1781
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
words.push(say('figure.text.overhang-unknown', where(own)));
|
|
1814
|
-
const held = (asset.joints ?? []).filter(j => 'part' in j.body0 && j.body0.part === this.selected).map(j => figureFaceOverhangOf(asset, j.body1.part, inputs));
|
|
1815
|
-
const past = held.filter((o) => o?.state === 'out' || o?.state === 'possible');
|
|
1816
|
-
if (past.length)
|
|
1817
|
-
words.push(say('figure.text.overhang-held', { parts: past.map(o => partLabelOf(o.part)).join(', ') }));
|
|
1818
|
-
}
|
|
1819
|
-
catch {
|
|
1820
|
-
/* Not read: nothing said rather than a guess. */
|
|
1821
|
-
}
|
|
1822
|
-
this.overhangSaid = { key, words, note };
|
|
1823
|
-
return words;
|
|
1782
|
+
catch {
|
|
1783
|
+
/* Not read: nothing said rather than a guess. */
|
|
1784
|
+
}
|
|
1785
|
+
return { words, note };
|
|
1786
|
+
});
|
|
1824
1787
|
}
|
|
1788
|
+
/** The selected part's motion at both ends of its range, made once per version and part. */
|
|
1825
1789
|
motionEnds() {
|
|
1826
|
-
const
|
|
1827
|
-
|
|
1790
|
+
const shown = this.shown();
|
|
1791
|
+
const part = this.selected;
|
|
1792
|
+
if (!shown || !part)
|
|
1828
1793
|
return null;
|
|
1829
|
-
|
|
1830
|
-
if (this.motionEndsSaid?.key !== key) {
|
|
1831
|
-
let ends = null;
|
|
1794
|
+
return derivedOf(shown, `motion-ends|${part}`, () => {
|
|
1832
1795
|
try {
|
|
1833
|
-
|
|
1796
|
+
return motionEndsOf(shown, part);
|
|
1834
1797
|
}
|
|
1835
1798
|
catch {
|
|
1836
|
-
|
|
1799
|
+
return null;
|
|
1837
1800
|
}
|
|
1838
|
-
|
|
1839
|
-
}
|
|
1840
|
-
return this.motionEndsSaid.ends;
|
|
1801
|
+
});
|
|
1841
1802
|
}
|
|
1842
1803
|
/** "Fasten it first", from the motion tab: to the properties tab, the fastening form open on this part. */
|
|
1843
1804
|
async goFasten() {
|
|
@@ -1874,50 +1835,8 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1874
1835
|
if (id && (this.tab === 'verdict' || this.tab === 'view' || this.tab === 'history'))
|
|
1875
1836
|
this.tab = 'properties';
|
|
1876
1837
|
}
|
|
1877
|
-
/**
|
|
1878
|
-
* The figure as the session holds it now, parsed once per text: a render asks for it a dozen times, and parsing the whole
|
|
1879
|
-
* figure is most of what a selection costs. Read, never changed -- what changes a figure goes through the session.
|
|
1880
|
-
*/
|
|
1881
|
-
/**
|
|
1882
|
-
* The session has evaluated this figure at this version, with these inputs: the screen's questions of the text it just
|
|
1883
|
-
* parsed are answered from that, not by evaluating the figure again (each is tens of milliseconds on a real figure).
|
|
1884
|
-
*/
|
|
1885
|
-
seeded(asset) {
|
|
1886
|
-
const view = this.session?.read();
|
|
1887
|
-
if (view?.geometry.length) {
|
|
1888
|
-
const values = this.currentInputs();
|
|
1889
|
-
seedEvaluatedGeometry(asset, values, view.geometry);
|
|
1890
|
-
seedFigureEvaluation(asset, values, view.geometry);
|
|
1891
|
-
}
|
|
1892
|
-
return asset;
|
|
1893
|
-
}
|
|
1894
|
-
parsedAsset() {
|
|
1895
|
-
const text = this.session.source();
|
|
1896
|
-
if (this.parsed?.text !== text) {
|
|
1897
|
-
/* The session's text is what its workspace exported, compiled then: it is read here, not compiled again. */
|
|
1898
|
-
const plain = this.plainAsset();
|
|
1899
|
-
const upgraded = upgradeFigurePlacement(plain);
|
|
1900
|
-
this.parsed = { text, asset: upgraded === plain ? plain : this.seeded(deepFreeze(upgraded)) };
|
|
1901
|
-
}
|
|
1902
|
-
return this.parsed.asset;
|
|
1903
|
-
}
|
|
1904
|
-
/**
|
|
1905
|
-
* The figure as plain data, parsed once per text and frozen: the render, the gizmo's reach and every tool read it, and
|
|
1906
|
-
* a copy for each of them cost more than the selection they serve. A change goes through the session, never through this.
|
|
1907
|
-
*/
|
|
1908
|
-
plainAsset() {
|
|
1909
|
-
const text = this.session.source();
|
|
1910
|
-
if (this.plain?.text !== text)
|
|
1911
|
-
this.plain = { text, asset: this.seeded(deepFreeze(JSON.parse(text))) };
|
|
1912
|
-
return this.plain.asset;
|
|
1913
|
-
}
|
|
1914
1838
|
asset() {
|
|
1915
|
-
|
|
1916
|
-
return this.plainAsset();
|
|
1917
|
-
}
|
|
1918
|
-
catch {
|
|
1919
|
-
return undefined;
|
|
1920
|
-
}
|
|
1839
|
+
return this.shown()?.asset;
|
|
1921
1840
|
}
|
|
1922
1841
|
/** A side tab says what it is in words under its icon: the icons alone had to be learnt (designer 2026-09-27, U05). */
|
|
1923
1842
|
renderTab(tab, icon, label, badge) {
|
|
@@ -1984,27 +1903,32 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1984
1903
|
@section-editor-close=${() => this.closeSectionEditor()}
|
|
1985
1904
|
></figure-section-editor>`;
|
|
1986
1905
|
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Why a drawn push's end cannot be drawn to one point now: the kernel's own refusal of that command on a copy (what is
|
|
1908
|
+
* fastened to the end face), said as the face. Asked once per figure revision and part.
|
|
1909
|
+
*/
|
|
1987
1910
|
pointBlockedOf(drawn) {
|
|
1988
|
-
|
|
1911
|
+
const shown = this.shown();
|
|
1912
|
+
const session = this.session;
|
|
1913
|
+
if (drawn.apex || drawn.section.kind !== 'polygon' || !session || !shown)
|
|
1989
1914
|
return '';
|
|
1990
|
-
const
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
return words;
|
|
1915
|
+
const part = this.selected;
|
|
1916
|
+
const apex = apexOver(drawn.section.points, drawn.axis);
|
|
1917
|
+
return derivedOf(shown, `point-blocked|${part}|${i18next.language}`, () => {
|
|
1918
|
+
try {
|
|
1919
|
+
session.previewCommand({ kind: 'set-section', part, apex });
|
|
1920
|
+
}
|
|
1921
|
+
catch (error) {
|
|
1922
|
+
if (error.code === 'FEATURE_MAPPING_REQUIRED')
|
|
1923
|
+
return i18next.t('figure.text.section-editor-apex-blocked', { face: i18next.t('figure.label.face-' + END_FACE(drawn.axis, 'end')) });
|
|
1924
|
+
}
|
|
1925
|
+
return '';
|
|
1926
|
+
});
|
|
2003
1927
|
}
|
|
2004
1928
|
renderStart() {
|
|
2005
1929
|
const shapes = ['box', 'cylinder', 'plate'];
|
|
2006
1930
|
/* Where it is authored from, chosen before any part (ADR-0097): the figure's own setting, no room made up for it. */
|
|
2007
|
-
const placement = (this.asset()?.placement ?? 'floor');
|
|
1931
|
+
const placement = ((this.asset())?.placement ?? 'floor');
|
|
2008
1932
|
return html `<div start role="region" aria-label=${i18next.t('figure.text.start-question')}>
|
|
2009
1933
|
<h2>${i18next.t('figure.text.start-question')}</h2>
|
|
2010
1934
|
<div start-paths>
|
|
@@ -2047,7 +1971,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2047
1971
|
let summary;
|
|
2048
1972
|
try {
|
|
2049
1973
|
summary = this.selected
|
|
2050
|
-
? partSummaryOf(this.
|
|
1974
|
+
? partSummaryOf(this.shown().asset, ownerOf(this.selected), (key, params) => i18next.t(key, params), i18next.language || 'ko')
|
|
2051
1975
|
: undefined;
|
|
2052
1976
|
}
|
|
2053
1977
|
catch {
|
|
@@ -2085,9 +2009,11 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2085
2009
|
A part's field only the declared room still reads -- its old height position once the floor holds it -- moves
|
|
2086
2010
|
nothing; it is shown apart as what it is, not as a position (ADR-0097, designer 2026-09-27).
|
|
2087
2011
|
*/
|
|
2012
|
+
const shown = this.shown();
|
|
2013
|
+
const asset = shown?.asset;
|
|
2088
2014
|
let roomOnly = [];
|
|
2089
2015
|
try {
|
|
2090
|
-
roomOnly = this.selected ? figureRoomOnlyInputsOf(
|
|
2016
|
+
roomOnly = this.selected && asset ? figureRoomOnlyInputsOf(asset, this.selected) : [];
|
|
2091
2017
|
}
|
|
2092
2018
|
catch {
|
|
2093
2019
|
roomOnly = [];
|
|
@@ -2098,14 +2024,13 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2098
2024
|
try {
|
|
2099
2025
|
const body = view.geometry.find(g => g.id === this.selected);
|
|
2100
2026
|
const placed = body ? placementOf(body) : undefined;
|
|
2101
|
-
|
|
2102
|
-
if (placed && parsed)
|
|
2027
|
+
if (placed && shown && this.selected)
|
|
2103
2028
|
held = {
|
|
2104
2029
|
position: placed.position,
|
|
2105
2030
|
rotation: placed.rotation,
|
|
2106
|
-
size: ['x', 'y', 'z'].map(axis => measureNow(
|
|
2031
|
+
size: ['x', 'y', 'z'].map(axis => measureNow(shown, ownerOf(this.selected), axis)),
|
|
2107
2032
|
/* Only a size a rule sets: a palette part's own numbers are its shape's fields, not a rule's (found in the browser). */
|
|
2108
|
-
sizeRuled: ['x', 'y', 'z'].every(axis => followOf(
|
|
2033
|
+
sizeRuled: ['x', 'y', 'z'].every(axis => followOf(shown.asset, ownerOf(this.selected), axis).of !== 'own')
|
|
2109
2034
|
};
|
|
2110
2035
|
}
|
|
2111
2036
|
catch {
|
|
@@ -2119,8 +2044,8 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2119
2044
|
: view.dirty || !view.id
|
|
2120
2045
|
? 'figure.text.unsaved-changes'
|
|
2121
2046
|
: 'figure.text.saved';
|
|
2122
|
-
const asset = this.asset();
|
|
2123
2047
|
const tab = this.tab;
|
|
2048
|
+
const live = this.liveParts(shown);
|
|
2124
2049
|
return html `<ox-canvas-bar
|
|
2125
2050
|
history
|
|
2126
2051
|
fit
|
|
@@ -2181,7 +2106,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2181
2106
|
? html `<figure-preview
|
|
2182
2107
|
?hidden=${this.middle !== 'preview'}
|
|
2183
2108
|
.active=${this.middle === 'preview'}
|
|
2184
|
-
.
|
|
2109
|
+
.revision=${shown}
|
|
2185
2110
|
.context=${view.context ?? ''}
|
|
2186
2111
|
.view=${this.view}
|
|
2187
2112
|
.selected=${this.selected}
|
|
@@ -2200,9 +2125,9 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2200
2125
|
${this.sectionEditing && this.sectionEditing === this.selected && !unavailable ? this.renderSectionEditor(view.geometry) : nothing}
|
|
2201
2126
|
<figure-canvas
|
|
2202
2127
|
.view=${this.view}
|
|
2203
|
-
.parts=${this.drawnParts(view.geometry,
|
|
2128
|
+
.parts=${this.drawnParts(view.geometry, live)}
|
|
2204
2129
|
.anchors=${this.anchorsOf(asset)}
|
|
2205
|
-
.fan=${this.fanOf(
|
|
2130
|
+
.fan=${this.fanOf(shown, live)}
|
|
2206
2131
|
.ghost=${this.previewShown().ghost}
|
|
2207
2132
|
.highlight=${this.previewShown().highlight}
|
|
2208
2133
|
.palette=${view.palette ?? {}}
|
|
@@ -2242,7 +2167,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2242
2167
|
</div>
|
|
2243
2168
|
${this.renderRepeatNote(view.geometry)}
|
|
2244
2169
|
</section>
|
|
2245
|
-
${this.renderRepeat(
|
|
2170
|
+
${this.renderRepeat(shown, view.geometry, unavailable)}
|
|
2246
2171
|
${this.renderGroups(this.withSizeRow(groupFields(partFields, this.selected), held), 'figure.label.group-placement-size', 'figure.label.group-shape', unavailable, this.renderTools(unavailable), held)}
|
|
2247
2172
|
${roomFields.length ? this.renderRoomOnly(roomFields, unavailable) : nothing}
|
|
2248
2173
|
${this.renderLinks(unavailable, 'shape')}
|
|
@@ -2273,7 +2198,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2273
2198
|
</section>`
|
|
2274
2199
|
: html `${this.renderSubject()}<slot name="figure"></slot>
|
|
2275
2200
|
${this.renderGroups(groupFields(common), 'figure.label.group-figure-size', 'figure.label.design-inputs', unavailable)}
|
|
2276
|
-
${this.renderInstanceRange(
|
|
2201
|
+
${this.renderInstanceRange(shown, unavailable)}`}
|
|
2277
2202
|
</div>
|
|
2278
2203
|
<div panel ?off=${tab !== 'capability'}>
|
|
2279
2204
|
${this.renderSubject()}
|
|
@@ -2369,7 +2294,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2369
2294
|
* something else. A size that follows something is said at the panel's head, so not again here.
|
|
2370
2295
|
*/
|
|
2371
2296
|
renderHeldAxes(vectors) {
|
|
2372
|
-
const fastened = !!this.asset()?.joints?.some(j => j.body1.part === ownerOf(this.selected));
|
|
2297
|
+
const fastened = !!(this.asset())?.joints?.some(j => j.body1.part === ownerOf(this.selected));
|
|
2373
2298
|
const held = vectors.filter(g => g.kind === 'vector' && g.name !== 'size' && ['x', 'y', 'z'].some(axis => !g.cells[axis]));
|
|
2374
2299
|
if (!held.length)
|
|
2375
2300
|
return nothing;
|
|
@@ -2396,7 +2321,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2396
2321
|
async askRoomRefit() {
|
|
2397
2322
|
let refit;
|
|
2398
2323
|
try {
|
|
2399
|
-
refit = figureRoomRefitOf(this.
|
|
2324
|
+
refit = figureRoomRefitOf(this.shown().asset);
|
|
2400
2325
|
}
|
|
2401
2326
|
catch (error) {
|
|
2402
2327
|
this.error = this.said(error);
|