@things-factory/figure-ui 10.1.25 → 10.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/client/graphql/index.ts +4 -1
  2. package/client/modeller/figure-animations.ts +229 -29
  3. package/client/modeller/figure-history-panel.ts +2 -2
  4. package/client/modeller/figure-inspector.ts +151 -5
  5. package/client/modeller/figure-preview.ts +5 -5
  6. package/client/modeller/figure-source.ts +33 -45
  7. package/client/modeller/figure-view.ts +20 -0
  8. package/client/modeller/joint-edits.ts +201 -0
  9. package/client/modeller/proposal-session.ts +8 -2
  10. package/client/pages/figure-modeller-page.ts +50 -15
  11. package/dist-client/graphql/index.js +4 -1
  12. package/dist-client/graphql/index.js.map +1 -1
  13. package/dist-client/modeller/figure-animations.d.ts +19 -0
  14. package/dist-client/modeller/figure-animations.js +206 -22
  15. package/dist-client/modeller/figure-animations.js.map +1 -1
  16. package/dist-client/modeller/figure-history-panel.js +2 -2
  17. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  18. package/dist-client/modeller/figure-inspector.d.ts +11 -0
  19. package/dist-client/modeller/figure-inspector.js +124 -4
  20. package/dist-client/modeller/figure-inspector.js.map +1 -1
  21. package/dist-client/modeller/figure-preview.js +5 -5
  22. package/dist-client/modeller/figure-preview.js.map +1 -1
  23. package/dist-client/modeller/figure-source.d.ts +12 -1
  24. package/dist-client/modeller/figure-source.js +13 -40
  25. package/dist-client/modeller/figure-source.js.map +1 -1
  26. package/dist-client/modeller/figure-view.d.ts +16 -0
  27. package/dist-client/modeller/figure-view.js +15 -0
  28. package/dist-client/modeller/figure-view.js.map +1 -1
  29. package/dist-client/modeller/joint-edits.d.ts +29 -0
  30. package/dist-client/modeller/joint-edits.js +175 -0
  31. package/dist-client/modeller/joint-edits.js.map +1 -0
  32. package/dist-client/modeller/proposal-session.d.ts +5 -0
  33. package/dist-client/modeller/proposal-session.js +3 -2
  34. package/dist-client/modeller/proposal-session.js.map +1 -1
  35. package/dist-client/pages/figure-modeller-page.d.ts +2 -22
  36. package/dist-client/pages/figure-modeller-page.js +46 -15
  37. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  38. package/dist-client/tsconfig.tsbuildinfo +1 -0
  39. package/dist-server/tsconfig.tsbuildinfo +1 -0
  40. package/package.json +3 -3
  41. package/test/ai-proposal-contract.test.ts +3 -2
  42. package/test/figure-source.test.ts +43 -57
  43. package/test/i18n-prefix-guard.test.ts +2 -3
  44. package/test/joint-edits.test.ts +130 -0
  45. package/test/preview-board-depth.test.ts +27 -0
  46. package/translations/en.json +119 -80
  47. package/translations/ja.json +208 -58
  48. package/translations/ko.json +139 -100
  49. package/translations/ms.json +206 -56
  50. package/translations/zh.json +207 -57
@@ -6,10 +6,23 @@ import { live } from 'lit/directives/live.js';
6
6
  import { i18next, localize } from '@operato/i18n';
7
7
  import { ScrollbarStyles } from '@operato/styles';
8
8
  import { EXTRUDE_PRIMITIVES, MATERIAL_PRESETS, PRIMITIVE_KINDS, SEGMENTED_PRIMITIVES, SEGMENT_PRESETS, SIZING_RULES, trianglesOf } from '@hatiolab/figure-model';
9
- import { ANCHOR_RULES, PART_ROLES, REPEAT_LIMIT, anchorOfPart, repeatPlanOfPart } from '@hatiolab/figure-model';
9
+ import { ANCHOR_RULES, JOINT_TYPES, PART_ROLES, REPEAT_LIMIT, anchorOfPart, repeatPlanOfPart } from '@hatiolab/figure-model';
10
10
  import { activePalette } from '@hatiolab/things-scene';
11
11
  import { partToFigure, partFromFigure } from './figure-source.js';
12
12
  import { canMirror } from './part-edits.js';
13
+ import { jointOf, parentChoices, patchJoint, setJointType } from './joint-edits.js';
14
+ /** Axis choices for a joint. Written as a direction the author reads, stored as a vector. */
15
+ const JOINT_AXES = [
16
+ { key: '+x', axis: { x: 1, y: 0, z: 0 } },
17
+ { key: '-x', axis: { x: -1, y: 0, z: 0 } },
18
+ { key: '+y', axis: { x: 0, y: 1, z: 0 } },
19
+ { key: '-y', axis: { x: 0, y: -1, z: 0 } },
20
+ { key: '+z', axis: { x: 0, y: 0, z: 1 } },
21
+ { key: '-z', axis: { x: 0, y: 0, z: -1 } }
22
+ ];
23
+ function axisKeyOf(axis) {
24
+ return JOINT_AXES.find(one => one.axis.x === axis.x && one.axis.y === axis.y && one.axis.z === axis.z)?.key;
25
+ }
13
26
  /**
14
27
  * 고른 부품의 속성.
15
28
  *
@@ -796,7 +809,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
796
809
  묶음은 아예 나오지 않는다 — 해 둬도 아무 일도 안 일어나는 칸을 보여 주지 않는다.
797
810
  */
798
811
  return html `
799
- ${this.renderFigure(true)} ${this.renderIdentity(shown)} ${this.renderRole(shown)}
812
+ ${this.renderFigure(true)} ${this.renderIdentity(shown)} ${this.renderRole(shown)} ${this.renderJoint(shown)}
800
813
  ${this.renderPlacement(shown)} ${this.renderShape(shown)} ${this.renderMaterial(shown)}
801
814
  ${this.renderBehaviour(shown)}
802
815
  `;
@@ -855,6 +868,107 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
855
868
  </section>
856
869
  `;
857
870
  }
871
+ /**
872
+ * How this part hangs from another and moves relative to it (ADR-0066).
873
+ *
874
+ * The parent is written on the part. The joint says how the part moves relative to that parent, and the
875
+ * parameter it comes with is what drives it; the preview shows that parameter as a slider.
876
+ */
877
+ renderJoint(part) {
878
+ const joint = jointOf(this.draft, part.name);
879
+ const choices = parentChoices(this.parts, part.name);
880
+ const unit = joint?.type === 'prismatic' ? 'mm' : 'deg';
881
+ const axisKey = joint ? axisKeyOf(joint.axis) : undefined;
882
+ const driver = joint
883
+ ? this.draft?.parameters?.find(one => one.clip?.channels?.some(channel => channel.target === joint.name))
884
+ : undefined;
885
+ return html `
886
+ <section>
887
+ <h3>${i18next.t('figure.label.joint')}${this.help('figure.text.joint-explained')}</h3>
888
+
889
+ <div inline-field>
890
+ <label>${i18next.t('figure.label.joint-parent')}</label>
891
+ <select @change=${(e) => this.setParent(e.target.value)}>
892
+ <option value="" ?selected=${part.parent === undefined}>${i18next.t('figure.label.joint-parent-none')}</option>
893
+ ${part.parent !== undefined && !choices.includes(part.parent)
894
+ ? html `<option value=${part.parent} selected>${part.parent}</option>`
895
+ : nothing}
896
+ ${choices.map(name => html `<option value=${name} ?selected=${name === part.parent}>${name}</option>`)}
897
+ </select>
898
+ </div>
899
+
900
+ <div inline-field>
901
+ <label>${i18next.t('figure.label.joint-type')}</label>
902
+ <select @change=${(e) => this.setJointType(part, e.target.value)}>
903
+ <option value="" ?selected=${!joint}>${i18next.t('figure.label.joint-type-none')}</option>
904
+ ${JOINT_TYPES.map(type => html `<option value=${type} ?selected=${joint?.type === type}>${i18next.t('figure.label.joint-type-' + type)}</option>`)}
905
+ </select>
906
+ </div>
907
+
908
+ ${joint
909
+ ? html `
910
+ <div inline-field>
911
+ <label>${i18next.t('figure.label.joint-axis')}</label>
912
+ <select @change=${(e) => this.setJointAxis(joint.name, e.target.value)}>
913
+ ${axisKey === undefined
914
+ ? html `<option value="" selected>${joint.axis.x}, ${joint.axis.y}, ${joint.axis.z}</option>`
915
+ : nothing}
916
+ ${JOINT_AXES.map(one => html `<option value=${one.key} ?selected=${one.key === axisKey}>${one.key.toUpperCase()}</option>`)}
917
+ </select>
918
+ </div>
919
+
920
+ <div vector-row>
921
+ <label>${i18next.t('figure.label.joint-origin')}</label>
922
+ <div triple>
923
+ ${['x', 'y', 'z'].map(axis => this.number(axis.toUpperCase(), joint.origin[axis], value => this.tellDraft(patchJoint(this.draft, joint.name, { origin: { ...joint.origin, [axis]: value } }))))}
924
+ </div>
925
+ </div>
926
+
927
+ ${joint.limits
928
+ ? html `
929
+ <div vector-row>
930
+ <label>${i18next.t('figure.label.joint-limits-' + unit)}</label>
931
+ <div triple>
932
+ ${this.number(i18next.t('figure.label.joint-limit-min'), joint.limits.min, value => this.tellDraft(patchJoint(this.draft, joint.name, { limits: { ...joint.limits, min: value } })))}
933
+ ${this.number(i18next.t('figure.label.joint-limit-max'), joint.limits.max, value => this.tellDraft(patchJoint(this.draft, joint.name, { limits: { ...joint.limits, max: value } })))}
934
+ </div>
935
+ </div>
936
+ ${joint.limits.min > 0 || joint.limits.max < 0
937
+ ? html `<p warn>${i18next.t('figure.text.joint-limits-must-include-zero')}</p>`
938
+ : nothing}
939
+ `
940
+ : nothing}
941
+
942
+ <p quiet>
943
+ ${driver
944
+ ? i18next.t('figure.text.joint-driven-by', { name: driver.label ?? driver.name })
945
+ : i18next.t('figure.text.joint-not-driven')}
946
+ </p>
947
+ `
948
+ : nothing}
949
+ </section>
950
+ `;
951
+ }
952
+ setParent(name) {
953
+ this.change(part => {
954
+ const { parent: _dropped, ...rest } = part;
955
+ return name === '' ? rest : { ...rest, parent: name };
956
+ });
957
+ }
958
+ setJointType(part, value) {
959
+ if (!this.draft)
960
+ return;
961
+ this.tellDraft(setJointType(this.draft, part, value === '' ? undefined : value));
962
+ }
963
+ setJointAxis(name, key) {
964
+ const choice = JOINT_AXES.find(one => one.key === key);
965
+ if (!choice || !this.draft)
966
+ return;
967
+ this.tellDraft(patchJoint(this.draft, name, { axis: { ...choice.axis } }));
968
+ }
969
+ tellDraft(draft) {
970
+ this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }));
971
+ }
858
972
  /**
859
973
  * 역할 하나를 켜고 끈다.
860
974
  *
@@ -1600,8 +1714,14 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
1600
1714
  return this.opened.has(key) ? html `<p hint>${i18next.t(key)}</p>` : nothing;
1601
1715
  }
1602
1716
  renderAnchors(part) {
1717
+ /*
1718
+ The scene draft stores the box height as depth, so format y is draft.depth and z is draft.height.
1719
+ This used to pass them swapped, and every automatic anchor still came out the same: the
1720
+ nearest-face rule reads the sign of a centred position, which does not depend on the box size.
1721
+ It is fixed so the next rule that does read the box does not inherit the mistake.
1722
+ */
1603
1723
  const base = this.draft
1604
- ? { x: this.draft.width, y: this.draft.height, z: this.draft.depth }
1724
+ ? { x: this.draft.width, y: this.draft.depth, z: this.draft.height }
1605
1725
  : { x: 0, y: 0, z: 0 };
1606
1726
  const nameOf = (rule, axis) => `${i18next.t(rule === 'min' || rule === 'max' ? `figure.label.anchor-${rule}-${axis}` : `figure.label.anchor-${rule}`)} (${rule})`;
1607
1727
  const setAnchor = (axis, rule) => {
@@ -1645,7 +1765,7 @@ let FigureInspector = class FigureInspector extends localize(i18next)(LitElement
1645
1765
  */
1646
1766
  return html `
1647
1767
  <div anchor-row>
1648
- <label>${axis.toUpperCase()}축</label>
1768
+ <label>${i18next.t('figure.label.axis-name', { axis: axis.toUpperCase() })}</label>
1649
1769
  <div anchor-options>
1650
1770
  ${['auto', 'scale', 'min', 'center', 'max', 'span'].map(rule => {
1651
1771
  const selected = rule === 'auto' ? !said : rule === said;