@things-factory/figure-ui 10.1.206 → 10.1.208

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 (70) hide show
  1. package/client/modeller/editor.ts +11 -97
  2. package/client/modeller/figure-facts.ts +1 -1
  3. package/client/modeller/figure-history-panel.ts +1 -29
  4. package/client/modeller/figure-preview.ts +5 -7
  5. package/client/modeller/figure-verdict.ts +2 -17
  6. package/client/modeller/maturity.ts +11 -27
  7. package/client/modeller/refusals.ts +1 -7
  8. package/client/pages/figure-modeller-page.ts +7 -22
  9. package/dist-client/modeller/editor.d.ts +1 -8
  10. package/dist-client/modeller/editor.js +12 -98
  11. package/dist-client/modeller/editor.js.map +1 -1
  12. package/dist-client/modeller/figure-facts.js +1 -1
  13. package/dist-client/modeller/figure-facts.js.map +1 -1
  14. package/dist-client/modeller/figure-fasten-dialog.d.ts +1 -1
  15. package/dist-client/modeller/figure-history-panel.d.ts +0 -16
  16. package/dist-client/modeller/figure-history-panel.js +1 -27
  17. package/dist-client/modeller/figure-history-panel.js.map +1 -1
  18. package/dist-client/modeller/figure-links.d.ts +1 -1
  19. package/dist-client/modeller/figure-preview.js +5 -8
  20. package/dist-client/modeller/figure-preview.js.map +1 -1
  21. package/dist-client/modeller/figure-verdict.d.ts +0 -5
  22. package/dist-client/modeller/figure-verdict.js +2 -21
  23. package/dist-client/modeller/figure-verdict.js.map +1 -1
  24. package/dist-client/modeller/maturity.d.ts +2 -4
  25. package/dist-client/modeller/maturity.js +12 -26
  26. package/dist-client/modeller/maturity.js.map +1 -1
  27. package/dist-client/modeller/refusals.d.ts +1 -3
  28. package/dist-client/modeller/refusals.js +1 -7
  29. package/dist-client/modeller/refusals.js.map +1 -1
  30. package/dist-client/pages/figure-modeller-page.d.ts +1 -3
  31. package/dist-client/pages/figure-modeller-page.js +7 -23
  32. package/dist-client/pages/figure-modeller-page.js.map +1 -1
  33. package/dist-client/route.d.ts +1 -1
  34. package/dist-client/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +8 -8
  36. package/test/browser/acceptance-page.js +3 -3
  37. package/test/browser/detail-page.js +1 -3
  38. package/test/browser/detail-tabs-page.js +1 -2
  39. package/test/browser/links.check.mjs +2 -37
  40. package/test/browser/modeller.check.mjs +1 -3
  41. package/test/browser/parts-page.js +2 -2
  42. package/test/browser/parts.check.mjs +2 -12
  43. package/test/browser/product-page.check.mjs +6 -9
  44. package/test/browser/product-page.js +1 -2
  45. package/test/browser/screen-page.js +2 -6
  46. package/test/browser/screen.check.mjs +8 -65
  47. package/test/first-minute.test.ts +2 -3
  48. package/test/fixtures/joint-column.json +1 -66
  49. package/test/fixtures/joint-roller.json +1 -66
  50. package/test/fixtures/robot-arm-6dof.json +2 -67
  51. package/test/harness-serve.test.ts +1 -1
  52. package/test/heading-scale-guard.test.ts +3 -3
  53. package/test/links.test.ts +1 -1
  54. package/test/parts-wiring.test.ts +1 -1
  55. package/test/properties.test.ts +9 -12
  56. package/test/refusals.test.ts +3 -21
  57. package/test/resize-with-placement.test.ts +13 -15
  58. package/test/revision.test.ts +1 -2
  59. package/test/verdict.test.ts +1 -15
  60. package/translations/en.json +0 -19
  61. package/translations/es.json +0 -19
  62. package/translations/ja.json +0 -19
  63. package/translations/ko.json +9 -28
  64. package/translations/ms.json +0 -19
  65. package/translations/zh.json +0 -19
  66. package/client/modeller/occupancy.ts +0 -51
  67. package/dist-client/modeller/occupancy.d.ts +0 -31
  68. package/dist-client/modeller/occupancy.js +0 -36
  69. package/dist-client/modeller/occupancy.js.map +0 -1
  70. package/test/release-panel.test.ts +0 -53
@@ -148,11 +148,6 @@
148
148
  "unit": "ratio",
149
149
  "value": 0.058333
150
150
  },
151
- {
152
- "id": "k.minusHalf",
153
- "unit": "ratio",
154
- "value": -0.5
155
- },
156
151
  {
157
152
  "id": "k.zero",
158
153
  "unit": "mm",
@@ -777,50 +772,6 @@
777
772
  "value": "finger-right.y.fromBottom.value"
778
773
  }
779
774
  },
780
- {
781
- "id": "occupancy.x.min",
782
- "op": "mul@1",
783
- "args": [
784
- "size.x",
785
- "k.minusHalf"
786
- ],
787
- "outputs": {
788
- "value": "occupancy.x.min.value"
789
- }
790
- },
791
- {
792
- "id": "occupancy.x.max",
793
- "op": "mul@1",
794
- "args": [
795
- "size.x",
796
- "k.half"
797
- ],
798
- "outputs": {
799
- "value": "occupancy.x.max.value"
800
- }
801
- },
802
- {
803
- "id": "occupancy.z.min",
804
- "op": "mul@1",
805
- "args": [
806
- "size.z",
807
- "k.minusHalf"
808
- ],
809
- "outputs": {
810
- "value": "occupancy.z.min.value"
811
- }
812
- },
813
- {
814
- "id": "occupancy.z.max",
815
- "op": "mul@1",
816
- "args": [
817
- "size.z",
818
- "k.half"
819
- ],
820
- "outputs": {
821
- "value": "occupancy.z.max.value"
822
- }
823
- },
824
775
  {
825
776
  "id": "gripper.finger-left.translation.u",
826
777
  "op": "add@1",
@@ -2901,23 +2852,6 @@
2901
2852
  }
2902
2853
  }
2903
2854
  ],
2904
- "occupancy": {
2905
- "bounds": {
2906
- "x": {
2907
- "min": "occupancy.x.min.value",
2908
- "max": "occupancy.x.max.value"
2909
- },
2910
- "y": {
2911
- "min": "k.zero",
2912
- "max": "size.y"
2913
- },
2914
- "z": {
2915
- "min": "occupancy.z.min.value",
2916
- "max": "occupancy.z.max.value"
2917
- }
2918
- },
2919
- "placement": "floor"
2920
- },
2921
2855
  "detailLevel": "L",
2922
2856
  "stateInputs": {
2923
2857
  "base-pan": {
@@ -2948,5 +2882,6 @@
2948
2882
  "label": "Gripper stroke",
2949
2883
  "sweep": 1
2950
2884
  }
2951
- }
2885
+ },
2886
+ "placement": "floor"
2952
2887
  }
@@ -6,7 +6,7 @@ import test from 'node:test'
6
6
  /**
7
7
  * 저작 하네스(`test/browser/serve-modeller.mjs`)가 서고, 표본이 모델러에서 열린다.
8
8
  *
9
- * import map 이 꾸러미의 `exports` 하위 경로를 따른다.
9
+ * import map 이 패키지의 `exports` 하위 경로를 따른다.
10
10
  *
11
11
  * 하네스(`test/browser/serve.mjs`)는 `name/` 접두 하나만 두고 있었다. 그래서
12
12
  * `@operato/popup/ox-popup-actions.js` 처럼 들이는 이름과 파일 자리(`dist/src/...`)가 다른 모듈이
@@ -15,7 +15,7 @@
15
15
  * 지금 있는 것을 한꺼번에 바꾸는 것은 화면 수십 개를 눈으로 확인하지 않고 움직이는 일이라 하지
16
16
  * 않았다. 대신 **더 늘지 않게** 막는다. 새 화면이 22px 을 적으면 여기서 걸린다.
17
17
  *
18
- * ── 이 꾸러미의 화면 크기는 아직 대부분 직접 적혀 있다 ──────────────────────
18
+ * ── 이 패키지의 화면 크기는 아직 대부분 직접 적혀 있다 ──────────────────────
19
19
  * font-size 선언 72개가 전부 토큰이 아니다. 그 전부를 오늘 옮기는 것은 화면을 눈으로 확인하지
20
20
  * 않고 움직이는 일이라 하지 않았다 — 제목만 정리하고, **더 늘지 않게** 여기서 막는다.
21
21
  *
@@ -102,8 +102,8 @@ test('제목 크기는 눈금 토큰에서 고른다 — 화면마다 새 값을
102
102
  *
103
103
  * 경로가 어긋나면 읽는 파일이 0이 되고, 0이면 언제나 통과한다 — 검사가 죽었는데 초록으로 보인다.
104
104
  *
105
- * 세는 것은 **제목 수가 아니라 읽은 화면 파일 수**다. 제목 요소를 안 쓰는 꾸러미가 있어서
106
- * (ops-master 는 절 제목을 클래스로 그린다) 제목 수로 재면 그 꾸러미에서는 이 가드를 놓을 수가
105
+ * 세는 것은 **제목 수가 아니라 읽은 화면 파일 수**다. 제목 요소를 안 쓰는 패키지가 있어서
106
+ * (ops-master 는 절 제목을 클래스로 그린다) 제목 수로 재면 그 패키지에서는 이 가드를 놓을 수가
107
107
  * 없다. 규칙이 도는지는 아래 「무엇을 잡고 무엇을 통과시키나」가 따로 증명한다.
108
108
  */
109
109
  test('★ 이 가드가 실제로 화면을 읽고 있다 — 0이면 검사가 죽은 것이다', () => {
@@ -568,7 +568,7 @@ test('벽 배치 — 장착 평면은 Z = 0, 벽에서 나와 서고 가로질
568
568
  const { mountingPlaneOf, alignAxesOf } = await import('../client/modeller/links.ts')
569
569
  let a = addFigureBox(createFigureAsset('Lamp'), { id: 'plate', size: { x: 200, y: 300, z: 20 }, position: { x: 0, y: 0, z: 0 } })
570
570
  assert.deepEqual(mountingPlaneOf(a), { normal: 'y', facings: ['meet', 'flush'] }, '배치 선언 전에는 바닥 — 올려 서거나 아래에 매달림')
571
- a = applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: (await import('@hatiolab/figure-model')).proposeFigureOccupancy(a), placement: 'wall' as never })
571
+ a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'wall' as never })
572
572
  assert.deepEqual(mountingPlaneOf(a), { normal: 'z', facings: ['meet'] })
573
573
  assert.deepEqual(alignAxesOf(a, 'plane').map(x => x.axis), ['x', 'y'])
574
574
  })
@@ -21,7 +21,7 @@ test('지울 수 없는 부품은 사람의 말로 거절하고, 지우며 풀
21
21
  /* The reason is told apart in refusals.test.ts; that the editor tells it, and what a removal let go of, is parts.check.mjs's. */
22
22
  for (const lang of ['ko', 'en', 'ja', 'zh', 'ms', 'es']) {
23
23
  const table = JSON.parse(read(`../translations/${lang}.json`))
24
- for (const key of ['part-is-referenced', 'part-in-declared-space', 'part-row-along', 'part-row-carries'])
24
+ for (const key of ['part-is-referenced', 'part-row-along', 'part-row-carries'])
25
25
  assert.ok(table[`figure.text.${key}`]?.includes('{part}'), `${lang} ${key}`)
26
26
  /* What a removal let go of, in every language, with the names it carries. */
27
27
  for (const [key, word] of [['removal-released', '{parts}'], ['removal-released-motion', '{control}'], ['removal-own-control', '{max}'], ['removal-fixed', '{parts}'], ['removal-bindings', '{places}'], ['edit-refused', '{code}'], ['edit-refused-node-limit', '{max}']])
@@ -9,10 +9,9 @@ import assert from 'node:assert/strict'
9
9
  import { readFileSync } from 'node:fs'
10
10
  import { fileURLToPath } from 'node:url'
11
11
 
12
- import { addFigureBox, applyFigureAuthoring, createFigureAsset, inspectFigureAsset, parseFigureAsset, serializeFigureAsset } from '@hatiolab/figure-model'
12
+ import { addFigureBox, applyFigureAuthoring, createFigureAsset, parseFigureAsset, serializeFigureAsset } from '@hatiolab/figure-model'
13
13
 
14
14
  import { tagsOf, figureFigureFacts } from '../client/modeller/figure-facts.ts'
15
- import { occupancyFromParts } from '../client/modeller/occupancy.ts'
16
15
  import { revisionOf } from './revision-of.ts'
17
16
  import { wordsOf } from './words.ts'
18
17
 
@@ -32,18 +31,16 @@ test('탭이 읽는 사실 — 이름, 놓이는 곳, 이름 붙은 장착면뿐
32
31
  assert.equal(read.mounting, undefined)
33
32
  })
34
33
 
35
- test('배치 기준을 고르면 그 기준으로 공간을 선언한다 — 판정도 풀린다', () => {
34
+ test('배치 기준을 고르면 탭이 그 기준을 다시 읽는다', () => {
36
35
  const source = serializeFigureAsset(probe())
37
- const declared = applyFigureAuthoring(parseFigureAsset(source), occupancyFromParts(parseFigureAsset(source), 'ceiling'))
36
+ const placed = applyFigureAuthoring(parseFigureAsset(source), { kind: 'set-placement', placement: 'ceiling' })
38
37
 
39
- assert.equal(facts(declared).placement, 'ceiling')
40
- assert.ok(!inspectFigureAsset(declared).violations.some(v => v.code === 'OCCUPANCY_UNDECLARED'))
38
+ assert.equal(facts(placed).placement, 'ceiling')
41
39
  })
42
40
 
43
41
  test('장착면을 정하면 탭이 그 부품과 면을 다시 읽고, 해제하면 없어진다', () => {
44
42
  const source = serializeFigureAsset(probe())
45
- const declared = applyFigureAuthoring(parseFigureAsset(source), occupancyFromParts(parseFigureAsset(source)))
46
- const mounted = applyFigureAuthoring(declared, { kind: 'set-mounting-face', part: 'body', face: 'bottom' })
43
+ const mounted = applyFigureAuthoring(parseFigureAsset(source), { kind: 'set-mounting-face', part: 'body', face: 'bottom' })
47
44
 
48
45
  assert.deepEqual(facts(mounted).mounting, { part: 'body', face: 'bottom' })
49
46
  assert.equal(facts(applyFigureAuthoring(mounted, { kind: 'clear-mounting-face' })).mounting, undefined)
@@ -76,12 +73,12 @@ test('속성 탭의 말은 다섯 말 모두에 있다', () => {
76
73
  })
77
74
 
78
75
  /*
79
- * The walk without the standing group (user 2026-10-18): nothing on the tab declares the room or names a face. The
80
- * room is declared where release asks for it; whether the figure meets its plane the release check says from the parts.
76
+ * The walk without the standing group (user 2026-10-18): nothing on the tab names a face. Whether the figure meets its
77
+ * plane the release check says from the parts.
81
78
  */
82
- test('속성 탭에는 공간을 정하거나 장착면을 고르는 명령이 없다 — 이름 붙은 장착면을 떼는 것만 있다', () => {
79
+ test('속성 탭에는 장착면을 고르는 명령이 없다 — 이름 붙은 장착면을 떼는 것만 있다', () => {
83
80
  const tab = read('../client/modeller/figure-properties.ts')
84
81
  const kinds = [...tab.matchAll(/kind: '([a-z-]+)'/g)].map(m => m[1]).sort()
85
82
  assert.deepEqual(kinds, ['clear-mounting-face', 'rename', 'set-placement'])
86
- assert.doesNotMatch(tab, /occupancyFromParts|declare-occupancy|set-mounting-face/)
83
+ assert.doesNotMatch(tab, /set-mounting-face/)
87
84
  })
@@ -15,7 +15,6 @@ import type { FigureAsset } from '@hatiolab/figure-model'
15
15
  import {
16
16
  clipLossOf,
17
17
  editRefusalOf,
18
- inDeclaredRoom,
19
18
  inputRangeRefusalOf,
20
19
  partNameRefusalOf,
21
20
  partRefusalOf,
@@ -90,7 +89,7 @@ test('부품을 줄이는 연결은 거절되지 않고, 좁혀 둔 끝이 옮
90
89
  assert.equal(sizeRangeFitsOf([], say), '', '옮긴 것이 없으면 말하지 않는다')
91
90
  })
92
91
 
93
- test('지우기는 매단 것 · 따라 움직이는 것을 풀어 남기고 그 이름을 한 줄로 말한다; 남는 거절은 공간 선언과 줄 (아키텍트 판정 2026-09-27)', () => {
92
+ test('지우기는 매단 것 · 따라 움직이는 것을 풀어 남기고 그 이름을 한 줄로 말한다; 남는 거절은 줄 (아키텍트 판정 2026-09-27)', () => {
94
93
  let a = fm.createFigureAsset('Bench')
95
94
  a = fm.addFigureBox(a, { id: 'leg', size: { x: 60, y: 700, z: 60 }, position: { x: 0, y: 0, z: 0 } })
96
95
  a = fm.addFigureBox(a, { id: 'top', size: { x: 1200, y: 40, z: 600 }, position: { x: 0, y: 0, z: 0 } })
@@ -112,37 +111,20 @@ test('지우기는 매단 것 · 따라 움직이는 것을 풀어 남기고 그
112
111
  assert.match(lamp, /removal-own-control .*"part":"shade".*"control":"shade".*"min":"-180".*"max":"180"/, '따르던 것은 제 조작값을 받는다')
113
112
  assert.equal(removalNoticeOf(fm.removePart(a, { kind: 'remove-part', target: 'shade' }).notice, keyed), null, '아무것도 풀지 않았으면 말하지 않는다')
114
113
 
115
- const declared = act(a, { kind: 'declare-occupancy', proposal: fm.proposeFigureOccupancy(a), placement: 'floor' })
116
114
  const why = (asset: FigureAsset, part: string) => partRefusalOf(refusalOf(() => fm.editFigurePart(asset, { kind: 'remove-part', target: part })), part, keyed)
117
- assert.match(why(declared, 'shade')!, /part-in-declared-space .*"part":"shade"/, '잎인데 공간 선언에 들어 있다')
118
115
  assert.match(why(conveyor(), 'frame')!, /part-row-along .*"part":"frame".*"rows":"roller"/, '줄이 따라 놓인 부품')
119
116
  let hung = fm.addFigureBox(conveyor(), { id: 'cap', size: { x: 20, y: 20, z: 20 }, position: { x: 0, y: 0, z: 0 } })
120
117
  hung = act(hung, { kind: 'attach', part: 'cap', to: { part: 'roller', face: 'top' } })
121
118
  assert.match(why(hung, 'roller')!, /part-row-carries .*"part":"roller".*"others":"cap"/, '줄에 매달린 것')
122
119
  })
123
120
 
124
- test('공간 선언에 든 부품 — 한 번 눌러 지우고, 공간은 남은 부품에 다시 맞추며 그렇다고 한 줄 말한다(묻지 않는다, 2026-09-27)', () => {
125
- let a = fm.addFigureBox(fm.createFigureAsset('Gate'), { id: 'housing', size: { x: 300, y: 1000, z: 300 }, position: { x: 0, y: 0, z: 0 } })
126
- a = fm.addFigureBox(a, { id: 'stray', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
127
- a = act(a, { kind: 'attach', part: 'housing', to: { plane: 'mounting-plane' } })
128
- a = act(a, { kind: 'attach', part: 'stray', to: { plane: 'mounting-plane' } })
129
- a = act(a, { kind: 'declare-occupancy', proposal: fm.proposeFigureOccupancy(a), placement: 'floor' })
130
- assert.equal(inDeclaredRoom(refusalOf(() => fm.editFigurePart(a, { kind: 'remove-part', target: 'stray' }))), true)
131
- assert.equal(inDeclaredRoom(refusalOf(() => fm.editFigurePart(conveyor(), { kind: 'remove-part', target: 'frame' }))), false, '줄은 이 길이 아니다')
132
- const after = fm.editFigurePart(a, { kind: 'remove-part', target: 'stray', refitRoom: true })
133
- assert.equal(after.document.model.nodes.some(n => n.id === 'stray'), false)
134
- assert.ok(after.occupancy?.bounds, '공간은 남은 부품으로 다시 선언된다')
135
- /* Removed in one click, nothing asked, the space fitted to the parts left and said so: parts.check.mjs. */
136
- assert.ok(ko['figure.text.removal-space-refitted'])
137
- })
138
-
139
121
  test('배치를 바꿀 수 없는 까닭 — 평면에 붙은 부품과 새 평면이 받는 면을 말한다(벽 배치, 09-25)', () => {
140
122
  const a = act(fm.addFigureBox(fm.createFigureAsset('Post'), { id: 'post', size: { x: 100, y: 1000, z: 100 }, position: { x: 0, y: 0, z: 0 } }), {
141
123
  kind: 'attach',
142
124
  part: 'post',
143
125
  to: { plane: 'mounting-plane' }
144
126
  })
145
- const refused = refusalOf(() => act(a, { kind: 'declare-occupancy', proposal: fm.proposeFigureOccupancy(a), placement: 'wall' }))
127
+ const refused = refusalOf(() => act(a, { kind: 'set-placement', placement: 'wall' }))
146
128
  const said = placementRefusalOf(refused, say)!
147
129
  assert.equal(said, '장착 평면에 붙은 부품이 있어 놓이는 곳을 바꿀 수 없습니다: post. 먼저 이 부품을 떼어 주십시오.')
148
130
  assert.equal(editRefusalOf(refused, say), said, '편집 거절의 말도 같다')
@@ -240,7 +222,7 @@ test('옮겨 적은 거절 — 기록은 커널의 거절과 같은 모양이라
240
222
  part: 'post',
241
223
  to: { plane: 'mounting-plane' }
242
224
  })
243
- const refused = refusalOf(() => act(a, { kind: 'declare-occupancy', proposal: fm.proposeFigureOccupancy(a), placement: 'wall' }))
225
+ const refused = refusalOf(() => act(a, { kind: 'set-placement', placement: 'wall' }))
244
226
  const record = refusalRecordOf(refused)
245
227
  assert.ok(record.details, 'its details kept')
246
228
  assert.equal(editRefusalOf(record, say), editRefusalOf(refused, say))
@@ -1,7 +1,7 @@
1
1
  import { test } from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
3
  import { readFileSync } from 'node:fs'
4
- import { addFigureBox, createFigureAsset, applyFigureAuthoring, applyFigureProposal, compileFigureAsset, figureFollowOf, figureRoomOnlyInputsOf, proposeFigureOccupancy, parseFigureAsset, planFigureResize, serializeFigureAsset } from '@hatiolab/figure-model'
4
+ import { addFigureBox, createFigureAsset, applyFigureAuthoring, applyFigureProposal, compileFigureAsset, figureFollowOf, parseFigureAsset, planFigureResize, serializeFigureAsset } from '@hatiolab/figure-model'
5
5
  import type { FigurePlacement } from '@hatiolab/figure-model'
6
6
  import { createFigureProductSession } from '../client/modeller/product-session.js'
7
7
  import { resizeRefusalOf } from '../client/modeller/refusals.js'
@@ -26,7 +26,7 @@ const START: Record<FigurePlacement, { x: number; y: number; z: number }> = {
26
26
  for (const mode of ['floor', 'ceiling', 'wall', 'center'] as const) {
27
27
  test(`${mode}: one step through the session -- undo, redo, save and open again`, async () => {
28
28
  let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: START[mode] })
29
- a = applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(a), placement: mode })
29
+ a = applyFigureAuthoring(a, { kind: 'set-placement', placement: mode })
30
30
  const before = serializeFigureAsset(a)
31
31
  let record = { id: 'ux', source: before }
32
32
  const session = createFigureProductSession({ load: async () => record, save: async q => (record = { id: 'ux', source: q.source }) })
@@ -60,7 +60,7 @@ for (const mode of ['floor', 'ceiling', 'wall', 'center'] as const) {
60
60
  test('a refusal is said in the author\'s words, never the kernel\'s English', () => {
61
61
  const said: Array<[string, Record<string, string>]> = []
62
62
  const say = (key: string, params: Record<string, string>) => (said.push([key, params]), key)
63
- /* No room declared: the figure stands on the floor by default. */
63
+ /* No placement set: the figure stands on the floor by default. */
64
64
  const a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 }, rotation: { x: 0, y: 30, z: 0 } })
65
65
  assert.equal(resizeRefusalOf(planFigureResize(a, { part: 'box' }), 'box', say), 'figure.text.resize-refused-turned')
66
66
  assert.deepEqual(said.at(-1)![1].place, 'figure.label.resize-place-floor')
@@ -74,7 +74,7 @@ test('a refusal is said in the author\'s words, never the kernel\'s English', ()
74
74
  */
75
75
  test('fresh, width only, and all set back to fixed are three different answers', async () => {
76
76
  let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 20, y: 50, z: 30 } })
77
- a = applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(a), placement: 'floor' })
77
+ a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
78
78
  /* Fresh: all three are suggested -- and the button says "all three", never passing it off as the author's choice. */
79
79
  assert.deepEqual(resizeAxesOf(a, 'box'), { axes: ['x', 'y', 'z'], basis: 'suggested' })
80
80
  /* Width only ticked: width alone is planned, and height and depth stay fixed. */
@@ -99,19 +99,17 @@ test('fresh, width only, and all set back to fixed are three different answers',
99
99
 
100
100
  test('a part off the floor is not moved by choosing its size response', () => {
101
101
  let a = addFigureBox(createFigureAsset('UX'), { id: 'shelf', size: { x: 400, y: 40, z: 300 }, position: { x: 0, y: 520, z: 0 } })
102
- a = applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(a), placement: 'floor' })
102
+ a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
103
103
  const asked = planFigureResize(a, { part: 'shelf', axes: resizeAxesOf(a, 'shelf').axes })
104
104
  assert.deepEqual([asked.refusal?.code, asked.refusal?.mm], ['GAP_CHOICE', 500])
105
105
  })
106
106
 
107
- test('a field only the room reads is taken out of the part\'s position row and shown apart', () => {
107
+ test('a part held by the floor shows X and Z in its position row, not Y', () => {
108
108
  let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 20, y: 50, z: 30 } })
109
- a = applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(a), placement: 'floor' })
109
+ a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
110
110
  const resized = applyFigureProposal(a, planFigureResize(a, { part: 'box' }).steps)
111
- const roomOnly = figureRoomOnlyInputsOf(resized, 'box')
112
- assert.ok(roomOnly.includes('box.position.y'))
113
111
  const fields = Object.entries(resized.designInputs).filter(([id]) => id.startsWith('box.')).map(([id, value]) => ({ id, unit: 'mm', role: 'design', value }))
114
- const { vectors } = groupFields(fields.filter(f => !roomOnly.includes(f.id)), 'box')
112
+ const { vectors } = groupFields(fields, 'box')
115
113
  const position = vectors.find(v => v.kind === 'vector' && v.name === 'position')!
116
114
  assert.ok(position.kind === 'vector' && position.cells.x && !position.cells.y && position.cells.z, 'X and Z still move it; Y is held by the floor')
117
115
  })
@@ -125,7 +123,7 @@ test('the part component leaves its place in a size drag to the modeller, for ea
125
123
  const { componentOf } = await import('../client/modeller/stage-model.js')
126
124
  for (const mode of ['floor', 'ceiling', 'wall', 'center'] as const) {
127
125
  let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: START[mode] })
128
- a = applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(a), placement: mode })
126
+ a = applyFigureAuthoring(a, { kind: 'set-placement', placement: mode })
129
127
  const body = compileFigureAsset(a).evaluate().geometry.find(g => g.id === 'box') as never
130
128
  const state = componentOf(body, {}, { width: 1000, height: 1000 }, true)!
131
129
  assert.equal(state.scalePlacement, 'owner', mode)
@@ -138,9 +136,9 @@ test('the part component leaves its place in a size drag to the modeller, for ea
138
136
  test('a fastened part keeps its position and turn rows, with the numbers the rule gives', async () => {
139
137
  const { placementOf } = await import('../client/modeller/stage-model.js')
140
138
  let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 20, y: 50, z: 30 } })
141
- a = applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(a), placement: 'floor' })
139
+ a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
142
140
  const resized = applyFigureProposal(a, planFigureResize(a, { part: 'box' }).steps)
143
- const fields = Object.entries(resized.designInputs).filter(([id]) => id.startsWith('box.') && !figureRoomOnlyInputsOf(resized, 'box').includes(id)).map(([id, value]) => ({ id, unit: 'mm', role: 'design', value }))
141
+ const fields = Object.entries(resized.designInputs).filter(([id]) => id.startsWith('box.')).map(([id, value]) => ({ id, unit: 'mm', role: 'design', value }))
144
142
  const { vectors } = groupFields(fields, 'box')
145
143
  const rows = Object.fromEntries(vectors.map(v => [v.name, v]))
146
144
  assert.ok(rows.rotation && rows.position, 'the turn row stands though every turn axis is the joint\'s')
@@ -171,7 +169,7 @@ test('a part fastened at any angle is sized by the gizmo too: its fastening keep
171
169
  test('"open attachment settings" opens this part\'s fastening, as it is now, and not another tab', async () => {
172
170
  const { fasteningOf } = await import('../client/modeller/links.js')
173
171
  let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 20, y: 150, z: 30 } })
174
- a = applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(a), placement: 'floor' })
172
+ a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
175
173
  const kept = applyFigureProposal(a, planFigureResize(a, { part: 'box', gap: 'keep' }).steps)
176
174
  const form = fasteningOf(revisionOf(kept), 'box')!
177
175
  assert.equal(form.toPlane, true, 'on the plane')
@@ -186,7 +184,7 @@ test('"open attachment settings" opens this part\'s fastening, as it is now, and
186
184
  test('the fastening form keeps a place that follows a position field, and fastening again keeps the field', async () => {
187
185
  const { fasteningOf, alignOf } = await import('../client/modeller/links.js')
188
186
  let a = addFigureBox(createFigureAsset('UX'), { id: 'box', size: { x: 100, y: 100, z: 100 }, position: { x: 200, y: 50, z: 150 } })
189
- a = applyFigureAuthoring(a, { kind: 'declare-occupancy', proposal: proposeFigureOccupancy(a), placement: 'floor' })
187
+ a = applyFigureAuthoring(a, { kind: 'set-placement', placement: 'floor' })
190
188
  const kept = applyFigureProposal(a, planFigureResize(a, { part: 'box' }).steps)
191
189
  const form = fasteningOf(revisionOf(kept), 'box')!
192
190
  assert.deepEqual([form.align.x, form.alignInput.x, form.align.z, form.alignInput.z], ['input', 'box.position.x', 'input', 'box.position.z'])
@@ -12,7 +12,7 @@ import assert from 'node:assert/strict'
12
12
  import { readFileSync } from 'node:fs'
13
13
  import { fileURLToPath } from 'node:url'
14
14
 
15
- import { addFigureBox, applyFigureAuthoring, createFigureAsset, figureFaceOverhangOf, figurePartFaceOf, figurePlaneAlignChanges, figureRoomOnlyInputsOf, figureRowOf, measureFigureWork, serializeFigureAsset } from '@hatiolab/figure-model'
15
+ import { addFigureBox, applyFigureAuthoring, createFigureAsset, figureFaceOverhangOf, figurePartFaceOf, figurePlaneAlignChanges, figureRowOf, measureFigureWork, serializeFigureAsset } from '@hatiolab/figure-model'
16
16
  import type { FigureAsset } from '@hatiolab/figure-model'
17
17
 
18
18
  import { createFigureProductSession } from '../client/modeller/product-session.ts'
@@ -88,7 +88,6 @@ function selectionQuestions(shown: FigureRevision, part: string) {
88
88
  gridStepOf(shown)
89
89
  jointFanOf(shown, part)
90
90
  partSummaryOf(asset, part, key => key, 'ko')
91
- figureRoomOnlyInputsOf(asset, part)
92
91
  for (const axis of ['x', 'y', 'z'] as const) measureNow(shown, part, axis)
93
92
  for (const axis of ['x', 'z'] as const) canMirror(shown, part, axis, reach)
94
93
  figurePlaneAlignChanges(asset, part)
@@ -17,7 +17,6 @@ import { addFigureBox, createFigureAsset, editFigurePart, figureCostOf } from '@
17
17
  import { budgetOf, figureReportOf, verdictMeasureOf } from '../client/modeller/maturity.ts'
18
18
  import { newestAnswer, type FigureOutcome } from '../client/modeller/newest-answer.ts'
19
19
  import { capacityFrom, SCENE_BUDGET } from '../client/modeller/scene-budget.ts'
20
- import { moving } from './joints.ts'
21
20
  import { wordsOf } from './words.ts'
22
21
 
23
22
  const read = (path: string) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), 'utf8')
@@ -37,7 +36,7 @@ test('개수는 목록 카드와 같은 셈이다 — 원가를 같은 capacityF
37
36
  assert.deepEqual(measured.report, figureReportOf(asset), '탭은 그 셈을 부르고 보여 줄 뿐이다')
38
37
  assert.equal(measured.told.instances, capacityFrom(cost.groups, cost.triangles)!.instances, '탭 이름표의 개수도 같은 셈에서 온다')
39
38
  assert.deepEqual(measured.told.budget, budgetOf(figureReportOf(asset)), '부품 목록 머리의 줄도 같은 보고서에서 온다')
40
- assert.deepEqual(verdictMeasureOf(undefined), { report: undefined, bodies: 0, told: { instances: undefined, budget: undefined } }, '연 도형이 없으면 잴 것도 알릴 개수도 없다')
39
+ assert.deepEqual(verdictMeasureOf(undefined), { report: undefined, told: { instances: undefined, budget: undefined } }, '연 도형이 없으면 잴 것도 알릴 개수도 없다')
41
40
  })
42
41
 
43
42
  test('판정은 발행 패널과 같은 서버 문에서 온다 — 화면이 따로 판정하지 않는다', () => {
@@ -80,19 +79,6 @@ test('판정 탭의 말은 다섯 말 모두에 있다 — 없으면 키가 그
80
79
  }
81
80
  })
82
81
 
83
- test('감춘 부품도 「부품에 맞추기」가 잴 수 있는 부품이다', () => {
84
- /* 새 도형은 부품 없이도 「공간 선언 없음」으로 막힌다. 그때 맞추기는 OCCUPANCY_NO_PARTS 로 던진다 — 단추는 잴 부품이 있을 때만. */
85
- let asset = createFigureAsset('Probe')
86
- asset = addFigureBox(asset, { id: 'base', size: { x: 100, y: 100, z: 100 }, position: { x: 0, y: 50, z: 0 } })
87
- asset = addFigureBox(asset, { id: 'lamp', size: { x: 100, y: 100, z: 100 }, position: { x: 200, y: 50, z: 0 } })
88
- asset = moving(asset, 'base', { kind: 'turn', axis: 'y', id: 'base.turn', min: 0, max: 90 })
89
- asset = editFigurePart(asset, { kind: 'set-visibility', target: 'lamp', shownWhen: { input: 'base.turn', atLeast: 80 } })
90
- const cost = figureCostOf(asset)
91
- assert.equal(cost.hidden, 1, '전제: 등은 지금 감춰져 있다')
92
- assert.equal(verdictMeasureOf(asset).bodies, 2, '그려지는 하나와 감춘 하나')
93
- assert.equal(verdictMeasureOf(createFigureAsset('Empty')).bodies, 0, '부품이 없으면 잴 것이 없다')
94
- })
95
-
96
82
  test('a finding\'s affected parts are the figure\'s part names, not the format\'s words (user 2026-10-16: 「occupancy · roller-row-1-roller」)', async () => {
97
83
  const { affectedPartsOf } = await import('../client/modeller/affected-parts.ts')
98
84
  assert.deepEqual(affectedPartsOf('occupancy · roller-row-1-roller', ['box', 'roller-row-1-roller']), ['roller-row-1-roller'])
@@ -12,7 +12,6 @@
12
12
  "figure.button.delete-figure": "delete",
13
13
  "figure.button.discard": "discard",
14
14
  "figure.button.duplicate-part": "duplicate",
15
- "figure.button.fit-base-to-parts": "fit to parts",
16
15
  "figure.button.fix-position": "fix here",
17
16
  "figure.button.scale-position": "in proportion",
18
17
  "figure.button.scale-uniformly": "grow by one ratio",
@@ -377,7 +376,6 @@
377
376
  "figure.text.trend-stays": "unchanged",
378
377
  "figure.text.trend-flat-lines-overlap": "the flat lines overlap at {value}.",
379
378
  "figure.text.cannot-release-now": "cannot be released yet",
380
- "figure.text.fit-needs-parts": "there are no parts to fit to — add a shape first.",
381
379
  "figure.text.nothing-blocks-release": "nothing in the figure as it stands blocks releasing it.",
382
380
  "figure.text.verdict-asking": "checking…",
383
381
  "figure.text.verdict-not-received": "the check did not come back — {reason}",
@@ -603,7 +601,6 @@
603
601
  "figure.button.propose-room": "Compute bounds",
604
602
  "figure.button.confirm-room": "Declare these bounds",
605
603
  "figure.label.room-undeclared": "not set",
606
- "figure.label.room-declared": "Declared now",
607
604
  "figure.label.mounting-face": "Mounting face",
608
605
  "figure.button.clear-mounting-face": "Clear",
609
606
  "figure.text.mounting-face-unchecked-at-center": "not checked for a centred figure",
@@ -736,7 +733,6 @@
736
733
  "figure.label.motion-travel-mm": "its own range (mm)",
737
734
  "figure.label.motion-travel-is": "travel: {part} · {axis} + {plus} mm",
738
735
  "figure.label.align-share": "offset from centre, as a share of the figure size",
739
- "figure.text.part-in-declared-space": "'{part}' is in the declared space, so it cannot simply be deleted. it can be deleted with the space fitted to the parts left.",
740
736
  "figure.button.change-motion": "change the motion",
741
737
  "figure.button.apply-motion-change": "apply the change",
742
738
  "figure.button.cancel-motion-change": "leave it",
@@ -775,7 +771,6 @@
775
771
  "figure.text.document-nesting-too-deep": "The figure data is nested {at} levels deep. Up to {max} are taken.",
776
772
  "figure.text.part-name-has-nested": "The part '{nested}' is named under this part. Rename that part first.",
777
773
  "figure.text.recipe-step-refused": "Stopped at step {step} of the recipe. {why} ({code})",
778
- "figure.text.removal-space-refitted": "The space is fitted to the parts left.",
779
774
  "figure.text.placement-intent-floor": "Floor placement: the bottom keeps its current distance from the floor (Y=0) as the size changes. Its place on the floor (X, Z) stays.",
780
775
  "figure.text.placement-intent-ceiling": "Ceiling placement: the top keeps its current distance from the ceiling (Y=0) as the size changes. Its place under the ceiling (X, Z) stays.",
781
776
  "figure.text.placement-intent-wall": "Wall placement: the back keeps its current distance from the wall (Z=0) as the size changes. Its place on the wall (X, Y) stays.",
@@ -807,17 +802,6 @@
807
802
  "figure.text.resize-refused-position-rule": "The {axis} position of {part} is set by an input or an expression. Applying this replaces it with the {place} relation.",
808
803
  "figure.text.resize-refused-no-face": "{part} has no flat face to attach to the {place}.",
809
804
  "figure.text.axis-held-by-rule": "cannot be typed here",
810
- "figure.label.room-only-inputs": "Used only to work out the placement space",
811
- "figure.text.room-only-inputs": "These values do not move the part. The declared space is worked out from them.",
812
- "figure.button.room-refit": "Work out the space again…",
813
- "figure.label.room-refit": "Work out the space again",
814
- "figure.label.room-proposed": "Worked out again",
815
- "figure.label.room-at-authored": "Drawn size",
816
- "figure.label.room-at-largest": "Largest size",
817
- "figure.text.room-same": "same",
818
- "figure.text.room-part-outside": "{part}: {mm} mm outside along {axis}",
819
- "figure.text.room-nothing-outside": "no part outside at the sizes measured",
820
- "figure.button.room-refit-confirm": "Work it out again",
821
805
  "figure.text.resize-gap-off": "This part’s face is {mm} mm off the {place} plane.",
822
806
  "figure.text.resize-gap-through": "This part reaches {mm} mm through the {place} plane.",
823
807
  "figure.button.resize-keep-gap": "{scope}: follow size, keeping the {mm} mm gap",
@@ -827,7 +811,6 @@
827
811
  "figure.button.resize-onto-plane-wall": "{scope}: follow size, brought onto the wall",
828
812
  "figure.text.resize-refused-gap-choice": "The face of {part} is not on the {place} plane. Choose whether to keep the gap or bring it onto the plane.",
829
813
  "figure.text.resize-replaces": "Applying this replaces the {fields} fields with the {place} attachment. Nothing moves now.",
830
- "figure.text.room-drops": "Confirming removes the fields nothing reads any more: {fields}",
831
814
  "figure.button.resize-unfollow": "Set back to fixed",
832
815
  "figure.label.resize-pending": "Size response to apply",
833
816
  "figure.text.resize-already-following": "(already following)",
@@ -1175,11 +1158,9 @@
1175
1158
  "figure.title.namespace-setting": "Figure namespace",
1176
1159
  "figure.label.namespace": "Namespace",
1177
1160
  "figure.label.follows-part-axis": "{part}'s {axis}",
1178
- "figure.text.size-and-space-declared-to-follow": "the figure size was set from its parts, {x} × {y} × {z} mm, and the space it takes was fitted to them.",
1179
1161
  "figure.button.release-blocked": "{count} blocks release",
1180
1162
  "figure.label.preview-way-one": "one",
1181
1163
  "figure.label.preview-way-eight": "various sizes",
1182
- "figure.text.preview-needs-space": "set the space it takes to try other sizes.",
1183
1164
  "figure.text.released-figure-not-deletable": "{name} has been released and cannot be deleted. retire it in its details.",
1184
1165
  "figure.label.retired": "retired",
1185
1166
  "figure.button.retire": "retire",
@@ -12,7 +12,6 @@
12
12
  "figure.button.delete-figure": "eliminar",
13
13
  "figure.button.discard": "descartar",
14
14
  "figure.button.duplicate-part": "duplicar",
15
- "figure.button.fit-base-to-parts": "ajustar a las piezas",
16
15
  "figure.button.fix-position": "fijar aquí",
17
16
  "figure.button.scale-position": "en proporción",
18
17
  "figure.button.scale-uniformly": "escalar con una sola proporción",
@@ -377,7 +376,6 @@
377
376
  "figure.text.trend-stays": "sin cambios",
378
377
  "figure.text.trend-flat-lines-overlap": "las líneas planas se superponen en {value}.",
379
378
  "figure.text.cannot-release-now": "aún no se puede publicar",
380
- "figure.text.fit-needs-parts": "no hay piezas a las que ajustarse: añada primero una forma.",
381
379
  "figure.text.nothing-blocks-release": "nada en la figura tal como está impide publicarla.",
382
380
  "figure.text.verdict-asking": "comprobando…",
383
381
  "figure.text.verdict-not-received": "la comprobación no obtuvo respuesta: {reason}",
@@ -603,7 +601,6 @@
603
601
  "figure.button.propose-room": "Calcular límites",
604
602
  "figure.button.confirm-room": "Declarar estos límites",
605
603
  "figure.label.room-undeclared": "aún no declarado",
606
- "figure.label.room-declared": "Declarado ahora",
607
604
  "figure.label.mounting-face": "Cara de montaje",
608
605
  "figure.button.clear-mounting-face": "Borrar",
609
606
  "figure.text.mounting-face-unchecked-at-center": "no se comprueba en una figura centrada",
@@ -736,7 +733,6 @@
736
733
  "figure.label.motion-travel-mm": "su propio rango (mm)",
737
734
  "figure.label.motion-travel-is": "recorrido: {part} · {axis} + {plus} mm",
738
735
  "figure.label.align-share": "desplazamiento desde el centro, como fracción del tamaño de la figura",
739
- "figure.text.part-in-declared-space": "'{part}' está en el espacio declarado, por lo que no se puede eliminar sin más. se puede eliminar ajustando el espacio a las piezas restantes.",
740
736
  "figure.button.change-motion": "cambiar el movimiento",
741
737
  "figure.button.apply-motion-change": "aplicar el cambio",
742
738
  "figure.button.cancel-motion-change": "dejarlo así",
@@ -775,7 +771,6 @@
775
771
  "figure.text.document-nesting-too-deep": "Los datos de la figura están anidados {at} niveles. Se admiten hasta {max}.",
776
772
  "figure.text.part-name-has-nested": "La pieza '{nested}' tiene su nombre bajo esta pieza. Cambie primero el nombre de esa pieza.",
777
773
  "figure.text.recipe-step-refused": "Se detuvo en el paso {step} de la receta. {why} ({code})",
778
- "figure.text.removal-space-refitted": "El espacio se ha ajustado a las piezas restantes.",
779
774
  "figure.text.placement-intent-floor": "Colocación en el suelo: la base mantiene su distancia actual al suelo (Y=0) al cambiar el tamaño. Su posición en el suelo (X, Z) se mantiene.",
780
775
  "figure.text.placement-intent-ceiling": "Colocación en el techo: la parte superior mantiene su distancia actual al techo (Y=0) al cambiar el tamaño. Su posición bajo el techo (X, Z) se mantiene.",
781
776
  "figure.text.placement-intent-wall": "Colocación en la pared: la parte trasera mantiene su distancia actual a la pared (Z=0) al cambiar el tamaño. Su posición en la pared (X, Y) se mantiene.",
@@ -807,17 +802,6 @@
807
802
  "figure.text.resize-refused-position-rule": "La posición en {axis} de {part} la define una entrada o una expresión. Al aplicar esto se sustituye por la relación con {place}.",
808
803
  "figure.text.resize-refused-no-face": "{part} no tiene una cara plana que unir a {place}.",
809
804
  "figure.text.axis-held-by-rule": "no se puede escribir aquí",
810
- "figure.label.room-only-inputs": "Solo se usa para calcular el espacio de colocación",
811
- "figure.text.room-only-inputs": "Estos valores no mueven la pieza. El espacio declarado se calcula a partir de ellos.",
812
- "figure.button.room-refit": "Volver a calcular el espacio…",
813
- "figure.label.room-refit": "Volver a calcular el espacio",
814
- "figure.label.room-proposed": "Calculado de nuevo",
815
- "figure.label.room-at-authored": "Tamaño dibujado",
816
- "figure.label.room-at-largest": "Tamaño máximo",
817
- "figure.text.room-same": "igual",
818
- "figure.text.room-part-outside": "{part}: {mm} mm fuera a lo largo de {axis}",
819
- "figure.text.room-nothing-outside": "ninguna pieza fuera en los tamaños medidos",
820
- "figure.button.room-refit-confirm": "Volver a calcularlo",
821
805
  "figure.text.resize-gap-off": "La cara de esta pieza está a {mm} mm del plano de {place}.",
822
806
  "figure.text.resize-gap-through": "Esta pieza atraviesa {mm} mm el plano de {place}.",
823
807
  "figure.button.resize-keep-gap": "{scope}: seguir el tamaño, manteniendo la separación de {mm} mm",
@@ -827,7 +811,6 @@
827
811
  "figure.button.resize-onto-plane-wall": "{scope}: seguir el tamaño, llevada a la pared",
828
812
  "figure.text.resize-refused-gap-choice": "La cara de {part} no está en el plano de {place}. Elija si mantener la separación o llevarla al plano.",
829
813
  "figure.text.resize-replaces": "Al aplicar esto, los campos {fields} se sustituyen por la unión a {place}. Ahora no se mueve nada.",
830
- "figure.text.room-drops": "Al confirmar se eliminan los campos que ya nada lee: {fields}",
831
814
  "figure.button.resize-unfollow": "Volver a fijo",
832
815
  "figure.label.resize-pending": "Respuesta al tamaño que se aplicará",
833
816
  "figure.text.resize-already-following": "(ya sigue)",
@@ -1175,11 +1158,9 @@
1175
1158
  "figure.title.namespace-setting": "Namespace de Figure",
1176
1159
  "figure.label.namespace": "Namespace",
1177
1160
  "figure.label.follows-part-axis": "{axis} de {part}",
1178
- "figure.text.size-and-space-declared-to-follow": "el tamaño de la figura se fijó a partir de sus piezas, {x} × {y} × {z} mm, y el espacio que ocupa se ajustó a ellas.",
1179
1161
  "figure.button.release-blocked": "{count} impide publicar",
1180
1162
  "figure.label.preview-way-one": "una",
1181
1163
  "figure.label.preview-way-eight": "varios tamaños",
1182
- "figure.text.preview-needs-space": "defina el espacio que ocupa para probar otros tamaños.",
1183
1164
  "figure.text.released-figure-not-deletable": "{name} ya se publicó y no se puede eliminar. retírela en sus detalles.",
1184
1165
  "figure.label.retired": "retirada",
1185
1166
  "figure.button.retire": "retirar",