@hatiolab/figure-model 0.1.95 → 0.2.1
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/CHANGELOG.md +14 -0
- package/dist/asset.d.ts.map +1 -1
- package/dist/asset.js +3 -1
- package/dist/asset.js.map +1 -1
- package/dist/authoring-actions.d.ts +19 -0
- package/dist/authoring-actions.d.ts.map +1 -1
- package/dist/authoring-actions.js +106 -63
- package/dist/authoring-actions.js.map +1 -1
- package/dist/command-preview.d.ts +3 -1
- package/dist/command-preview.d.ts.map +1 -1
- package/dist/command-preview.js +52 -27
- package/dist/command-preview.js.map +1 -1
- package/dist/cost.d.ts +1 -1
- package/dist/cost.d.ts.map +1 -1
- package/dist/cost.js +30 -25
- package/dist/cost.js.map +1 -1
- package/dist/create.d.ts.map +1 -1
- package/dist/create.js +112 -31
- package/dist/create.js.map +1 -1
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +3 -2
- package/dist/document.js.map +1 -1
- package/dist/fixtures/shape-nodes.d.ts +12 -0
- package/dist/fixtures/shape-nodes.d.ts.map +1 -0
- package/dist/fixtures/shape-nodes.js +20 -0
- package/dist/fixtures/shape-nodes.js.map +1 -0
- package/dist/follow.d.ts.map +1 -1
- package/dist/follow.js +5 -2
- package/dist/follow.js.map +1 -1
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +31 -59
- package/dist/gate.js.map +1 -1
- package/dist/graph-types.d.ts +7 -6
- package/dist/graph-types.d.ts.map +1 -1
- package/dist/graph.d.ts +17 -12
- package/dist/graph.d.ts.map +1 -1
- package/dist/graph.js +24 -292
- package/dist/graph.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/kernel-version.d.ts +1 -1
- package/dist/kernel-version.d.ts.map +1 -1
- package/dist/kernel-version.js +1 -1
- package/dist/kernel-version.js.map +1 -1
- package/dist/part-edit.d.ts.map +1 -1
- package/dist/part-edit.js +8 -6
- package/dist/part-edit.js.map +1 -1
- package/dist/proposal.d.ts +1 -1
- package/dist/proposal.d.ts.map +1 -1
- package/dist/proposal.js +14 -2
- package/dist/proposal.js.map +1 -1
- package/dist/recipe-from-parts.d.ts.map +1 -1
- package/dist/recipe-from-parts.js +107 -24
- package/dist/recipe-from-parts.js.map +1 -1
- package/dist/render-batches.d.ts.map +1 -1
- package/dist/render-batches.js +11 -2
- package/dist/render-batches.js.map +1 -1
- package/dist/resize-plan.d.ts.map +1 -1
- package/dist/resize-plan.js +22 -8
- package/dist/resize-plan.js.map +1 -1
- package/dist/section.d.ts +164 -0
- package/dist/section.d.ts.map +1 -0
- package/dist/section.js +277 -0
- package/dist/section.js.map +1 -0
- package/dist/shape-edit.d.ts +22 -0
- package/dist/shape-edit.d.ts.map +1 -0
- package/dist/shape-edit.js +225 -0
- package/dist/shape-edit.js.map +1 -0
- package/dist/shape-features.d.ts +83 -0
- package/dist/shape-features.d.ts.map +1 -0
- package/dist/shape-features.js +102 -0
- package/dist/shape-features.js.map +1 -0
- package/dist/shape-ops.d.ts +154 -0
- package/dist/shape-ops.d.ts.map +1 -0
- package/dist/shape-ops.js +571 -0
- package/dist/shape-ops.js.map +1 -0
- package/dist/shape-provider.d.ts +16 -23
- package/dist/shape-provider.d.ts.map +1 -1
- package/dist/shape-provider.js +18 -56
- package/dist/shape-provider.js.map +1 -1
- package/dist/shape-sampling.d.ts +0 -8
- package/dist/shape-sampling.d.ts.map +1 -1
- package/dist/shape-sampling.js +67 -101
- package/dist/shape-sampling.js.map +1 -1
- package/dist/size-edit.js +2 -2
- package/dist/size-edit.js.map +1 -1
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +10 -17
- package/dist/surface.js.map +1 -1
- package/dist/words.d.ts +7 -0
- package/dist/words.d.ts.map +1 -1
- package/dist/words.js +7 -0
- package/dist/words.js.map +1 -1
- package/docs/document-contract.md +1 -1
- package/docs/shape-contract.md +439 -0
- package/package.json +2 -1
package/dist/words.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"words.d.ts","sourceRoot":"","sources":["../src/words.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,IAAI,0BAA2B,CAAA;AAC5C,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,CAAA;AAExC,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,+DAAgE,CAAA;AAChG,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnE;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC,CAEjF,CAAA;AAEF,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,gFAAiF,CAAA;AAC9G,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE9D;;;GAGG;AACH,eAAO,MAAM,YAAY,MAAM,CAAA;AAE/B;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,0BAA2B,CAAA;AACrD,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD,2CAA2C;AAC3C,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAyB,CAAA;AAE5E;;;GAGG;AACH,eAAO,MAAM,IAAI,MAAM,CAAA;AAEvB,MAAM,MAAM,WAAW,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;AAE/C;;;GAGG;AACH,eAAO,MAAM,MAAM;IACjB,2GAA2G;;IAE3G,qEAAqE;;IAErE,0EAA0E;;IAE1E;;;;OAIG;;IAEH;;;OAGG;;IAEH;;;;OAIG;;IAEH,mDAAmD;;IAEnD,mGAAmG;;IAEnG,iDAAiD;;CAEzC,CAAA;AAEV;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAA4B,CAAA;AAEzE,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAA4D,CAAA;AAE1H;;;;GAIG;AACH,eAAO,MAAM,YAAY,QAAkC,CAAA;AAC3D,eAAO,MAAM,WAAW;;;CAA+B,CAAA;AAEvD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAmB,GAAG,MAAM,CAEhE;AAID,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD"}
|
|
1
|
+
{"version":3,"file":"words.d.ts","sourceRoot":"","sources":["../src/words.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,IAAI,0BAA2B,CAAA;AAC5C,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,CAAA;AAExC,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,+DAAgE,CAAA;AAChG,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnE;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,CAAC,CAEjF,CAAA;AAEF,gFAAgF;AAChF,eAAO,MAAM,gBAAgB,gFAAiF,CAAA;AAC9G,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE9D;;;GAGG;AACH,eAAO,MAAM,YAAY,MAAM,CAAA;AAE/B;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,0BAA2B,CAAA;AACrD,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD,2CAA2C;AAC3C,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAyB,CAAA;AAE5E;;;GAGG;AACH,eAAO,MAAM,IAAI,MAAM,CAAA;AAEvB,MAAM,MAAM,WAAW,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;AAE/C;;;GAGG;AACH,eAAO,MAAM,MAAM;IACjB,2GAA2G;;IAE3G,qEAAqE;;IAErE,0EAA0E;;IAE1E;;;;OAIG;;IAEH;;;OAGG;;IAEH;;;;OAIG;;IAEH,mDAAmD;;IAEnD,mGAAmG;;IAEnG,iDAAiD;;CAEzC,CAAA;AAEV;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAA4B,CAAA;AAEzE,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAA4D,CAAA;AAE1H;;;;GAIG;AACH,eAAO,MAAM,YAAY,QAAkC,CAAA;AAC3D,eAAO,MAAM,WAAW;;;CAA+B,CAAA;AAEvD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAmB,GAAG,MAAM,CAEhE;AAID,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,OAAO,CAAA"}
|
package/dist/words.js
CHANGED
|
@@ -113,4 +113,11 @@ const PLACEHOLDER_TYPE = /^FIGURE_[0-9A-Z]{8}$/;
|
|
|
113
113
|
export function isPlaceholderType(type) {
|
|
114
114
|
return PLACEHOLDER_TYPE.test(type);
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* The most nodes one figure's graph may have: a fence against a malformed document, set large (designer's ruling,
|
|
118
|
+
* docs/shape-contract.md Q10, 2026-09-29). The 1 MiB document limit binds first -- stored nodes run 125 to 190 bytes, so a
|
|
119
|
+
* mebibyte holds some 5,500 to 8,400 -- and the largest stored figure has 517. A graph this size compiles in about 25 ms and
|
|
120
|
+
* evaluates in about 2 ms.
|
|
121
|
+
*/
|
|
122
|
+
export const FIGURE_DOCUMENT_NODE_LIMIT = 8192;
|
|
116
123
|
//# sourceMappingURL=words.js.map
|
package/dist/words.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"words.js","sourceRoot":"","sources":["../src/words.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AAEH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAA;AAS5C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,UAAU,CAAU,CAAA;AAGhG;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA0D,MAAM,CAAC,MAAM,CAAC;IACnG,YAAY,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC;CACvC,CAAC,CAAA;AAEF,gFAAgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAU,CAAA;AAG9G;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;AAE/B;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAA;AAGrD,2CAA2C;AAC3C,MAAM,CAAC,MAAM,UAAU,GAAgC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAA;AAE5E;;;GAGG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,CAAA;AAIvB;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,2GAA2G;IAC3G,iBAAiB,EAAE,CAAC;IACpB,qEAAqE;IACrE,cAAc,EAAE,IAAI;IACpB,0EAA0E;IAC1E,iBAAiB,EAAE,OAAO;IAC1B;;;;OAIG;IACH,eAAe,EAAE,GAAG;IACpB;;;OAGG;IACH,gBAAgB,EAAE,EAAE;IACpB;;;;OAIG;IACH,oBAAoB,EAAE,MAAM;IAC5B,mDAAmD;IACnD,oBAAoB,EAAE,CAAC;IACvB,mGAAmG;IACnG,eAAe,EAAE,CAAC;IAClB,iDAAiD;IACjD,WAAW,EAAE,CAAC;CACN,CAAA;AAEV;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAsB,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AAEzE,iEAAiE;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAmC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAA;AAE1H;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,+BAA+B,CAAA;AAC3D,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAW,CAAA;AAEvD;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;IACtD,OAAO,UAAU,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,CAAA;AACnD,CAAC;AAED,MAAM,gBAAgB,GAAG,sBAAsB,CAAA;AAE/C,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACpC,CAAC"}
|
|
1
|
+
{"version":3,"file":"words.js","sourceRoot":"","sources":["../src/words.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AAEH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAA;AAS5C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,UAAU,CAAU,CAAA;AAGhG;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA0D,MAAM,CAAC,MAAM,CAAC;IACnG,YAAY,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC;CACvC,CAAC,CAAA;AAEF,gFAAgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAU,CAAA;AAG9G;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAA;AAE/B;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAA;AAGrD,2CAA2C;AAC3C,MAAM,CAAC,MAAM,UAAU,GAAgC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAA;AAE5E;;;GAGG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,CAAA;AAIvB;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,2GAA2G;IAC3G,iBAAiB,EAAE,CAAC;IACpB,qEAAqE;IACrE,cAAc,EAAE,IAAI;IACpB,0EAA0E;IAC1E,iBAAiB,EAAE,OAAO;IAC1B;;;;OAIG;IACH,eAAe,EAAE,GAAG;IACpB;;;OAGG;IACH,gBAAgB,EAAE,EAAE;IACpB;;;;OAIG;IACH,oBAAoB,EAAE,MAAM;IAC5B,mDAAmD;IACnD,oBAAoB,EAAE,CAAC;IACvB,mGAAmG;IACnG,eAAe,EAAE,CAAC;IAClB,iDAAiD;IACjD,WAAW,EAAE,CAAC;CACN,CAAA;AAEV;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAsB,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AAEzE,iEAAiE;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAmC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAA;AAE1H;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,+BAA+B,CAAA;AAC3D,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAW,CAAA;AAEvD;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;IACtD,OAAO,UAAU,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,CAAA;AACnD,CAAC;AAED,MAAM,gBAAgB,GAAG,sBAAsB,CAAA;AAE/C,iFAAiF;AACjF,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACpC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAA"}
|
|
@@ -35,7 +35,7 @@ JSON Schema는 필드 구조·자료형·기본 enum·버전을 검사한다. op
|
|
|
35
35
|
- 알 수 없는 문서/계약 버전과 추가 필드를 거부한다. 현재 capability V3는 transform 또는 표면 참조 pose를 허용한다. V1/V2를 추측 변환하지 않는다.
|
|
36
36
|
- JSON 키 중복은 마지막 값으로 덮지 않고 거부한다. Unicode escape로 같은 키가 표현된 경우도 포함한다.
|
|
37
37
|
- 잘린 JSON, 비유한 수, undefined, 함수, Date, 순환, sparse array, accessor·숨겨진 속성 등 JSON으로 손실되는 입력을 거부한다.
|
|
38
|
-
- 실험 자원 제한: 1MiB UTF-8, 객체 깊이64, 그래프
|
|
38
|
+
- 실험 자원 제한: 1MiB UTF-8, 객체 깊이64, 그래프 node 8192(`FIGURE_DOCUMENT_NODE_LIMIT`, 2026-09-29 판정 — shape-contract Q10: 충분히 크게. 실제로는 1MiB 가 먼저 걸린다).
|
|
39
39
|
- 선언 공간(occupancy)의 경계는 `affine-bounds@1` 한 노드의 출력 여섯이다. 압축 인자 모양(`layout` = `L` + 여섯 묶음 + 후보별 항 수)은 [연산 계약 §4](operator-contracts.md#4-연산-catalog)에 적었다 — AI 저작·내보내기는 거기서 풀어 읽는다.
|
|
40
40
|
- round-trip은 의미·수치·선언 동일성을 대상으로 한다. 원본 JSON의 공백·키 표기·서식은 보존하지 않는다.
|
|
41
41
|
- 함수는 파일/DB에 직접 쓰지 않는다. 저장 API의 원자성, 권한, 버전 충돌, 마이그레이션은 제품 통합 시 별도로 구현해야 한다.
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# Shape contract — sections, pushes, cavities and their named features
|
|
2
|
+
|
|
3
|
+
Status: **contract, Q1–Q9 ruled by the designer as recommended (2026-09-29, 「추천대로 진행하라」).** Stage 1 is being
|
|
4
|
+
built against it. The 2026-09-22 Bézier-corner ruling is replaced by Q4: a corner is an elliptic quarter.
|
|
5
|
+
|
|
6
|
+
Stage 0 record: Nothing here is wired into the asset, the compiler,
|
|
7
|
+
the renderer or the modeller yet. What is code today:
|
|
8
|
+
|
|
9
|
+
- `src/section.ts`: a section's meaning. Validity, the one written form, side ids, topology change, area and bounds,
|
|
10
|
+
and the inside section of a cavity.
|
|
11
|
+
- `src/shape-features.ts`: the named features of a pushed section and its cavity.
|
|
12
|
+
|
|
13
|
+
Both are tested against figures worked out by hand (`src/section.test.ts`, `src/shape-features.test.ts`). Every
|
|
14
|
+
choice this document still needs from the designer is marked **Qn** and listed in §12. The Korean question sheet for
|
|
15
|
+
the user is `operato-application/packages/operato-figure/docs/shape-editing-stage0-2026-09-29.md`.
|
|
16
|
+
|
|
17
|
+
This contract replaces the eleven shape operators (`box-shape@1` … `polygon-shape@1`) and their provider names. It
|
|
18
|
+
does not sit beside them. Old assets are rewritten once, and nothing reads the old operators after the switch
|
|
19
|
+
(ADR-0100 decision 9, §10).
|
|
20
|
+
|
|
21
|
+
## What a person sees
|
|
22
|
+
|
|
23
|
+
An author adds a **상자** (box). They type a width, a height and a depth, and the box is done. They drag the corner
|
|
24
|
+
radius from 0 to 10. The four corners round off, the box keeps its outer size, and the lid fastened to its top stays
|
|
25
|
+
where it is. They turn on **속 공간** (inside space) with a 10 mm wall, a 15 mm floor and the top open. The box
|
|
26
|
+
becomes a tote. The lid now sits on a rim, and the rim highlights as a ring, not as a filled square. A divider can be
|
|
27
|
+
fastened to the tote's inside floor. Then they open both ends: the floor goes and the tote becomes a duct. Nothing
|
|
28
|
+
is left behind at the bottom.
|
|
29
|
+
|
|
30
|
+
A second author starts from **단면 그려 만들기** (draw a section) and gets a triangle. They click a side to add a
|
|
31
|
+
point and drag it out, and the triangle becomes a trapezoid. They drag one point across the opposite side. The two
|
|
32
|
+
crossing sides turn red, the message reads 「두 선이 교차합니다. 표시된 점을 옮겨 주세요」, and `적용` (apply) is
|
|
33
|
+
disabled. The last valid shape stays drawn, faded, labelled 「마지막으로 유효했던 모양」. They press Esc, and the
|
|
34
|
+
part is exactly what it was before.
|
|
35
|
+
|
|
36
|
+
A third author deletes a point of a section whose side has a bracket fastened to it. Before anything changes, the
|
|
37
|
+
screen names the bracket and the side it is fastened to. It asks which new side to use, or offers to leave the
|
|
38
|
+
bracket where it is, unfastened. It never picks the nearest side on its own.
|
|
39
|
+
|
|
40
|
+
## 0. As built (stage 1, 2026-09-29)
|
|
41
|
+
|
|
42
|
+
What the kernel does now, where it differs from the text below, and why. Branch `shape-extrude`, unpublished.
|
|
43
|
+
|
|
44
|
+
- **The node is the record.** A shape is one graph node, `extrude@1` or `revolve@1`. Its params say what it is: the
|
|
45
|
+
axis, the section kind, the point names, and whether it has an inside and which ends are open. Its arguments are the
|
|
46
|
+
values that size it, in an order the params fix (`figureExtrudeLayoutOf`). There is no second record beside it, so
|
|
47
|
+
there is no drift to check. Every reader asks `figureShapeDescriptionOf(node)` which argument is which, and never
|
|
48
|
+
reads an argument by its place.
|
|
49
|
+
- **A push is `start` and `length`**, and its end is `start + length` (Q1's two ends). A new part's start is one node,
|
|
50
|
+
`<start> = <length> × −0.5`, so it is centred on its origin as parts always were. Given the graph, such a push's ends
|
|
51
|
+
are written as ±half its length, which keeps the joints of a rewritten figure made of the same nodes. A later
|
|
52
|
+
"keep this face" edit writes an explicit start instead (`set-extrusion`, not built yet).
|
|
53
|
+
- **Section kinds:** `rectangle` (with `corner: round`), `ellipse`, `polygon`, and **`portal`**. A portal is a door
|
|
54
|
+
frame's Π section (width, height, jamb, head) with its faces `opening-left`, `opening-right` and `opening-top`. It is
|
|
55
|
+
the one section Q5 ruled, named so that DRY_ROOM's doors keep the faces they hang on and follow its size.
|
|
56
|
+
- **Faces keep the modeller's words in stage 1:** the six outer faces by axis, `inside-*` for an inside and
|
|
57
|
+
`opening-*` for a portal. Renaming them to feature ids (`start-face`, `side-face:<id>` … §4) is its own step, done
|
|
58
|
+
once for every joint and binding reference. A plane touching a curved side is marked `contact: 'line'` (Q3): it can
|
|
59
|
+
be fastened to, it is not a surface a binding holds things on, and its preview is a line.
|
|
60
|
+
- **Commands built:** `set-section-corner` and `set-cavity` (§7). An edit that takes away a face something refers to
|
|
61
|
+
is refused with `FEATURE_MAPPING_REQUIRED`. Mapping to another face is not built yet.
|
|
62
|
+
- **The one-time rewrite:** `scripts/rewrite-shapes.ts` and `scripts/rewrite-shapes-files.ts`. A file is written only
|
|
63
|
+
when every body's world box and pose agree with the old kernel's (0.1.95) at the drawn values, at half and twice
|
|
64
|
+
the size, and at each state input's ends. All 24 fixtures agree.
|
|
65
|
+
- **Q10 (ruled 2026-09-29, "make it large enough"):** the node limit is 8192 (`FIGURE_DOCUMENT_NODE_LIMIT`), a fence against malformed documents; the 1 MiB limit binds first. No start shorthand.
|
|
66
|
+
A start node per part took two stored figures, of 493 and 497 nodes, past 512. The alternative is a `start` left out
|
|
67
|
+
for a centred push, which is a stored shorthand and a change of format.
|
|
68
|
+
|
|
69
|
+
## 1. The record
|
|
70
|
+
|
|
71
|
+
The shape of a part is a record in the asset, **the canon**. Its graph nodes are generated from it, the way a joint's
|
|
72
|
+
nodes are generated from its record (ADR-0093 decision 2). No command edits the nodes directly, and an asset whose
|
|
73
|
+
nodes disagree with its records is refused when read.
|
|
74
|
+
|
|
75
|
+
Every length in a record is a `FigureMeasure`, the same type a joint's gap uses. It is a number, or
|
|
76
|
+
`{ from, times, plus }` read from the instance's size, a design input or another part's dimension. So a section's
|
|
77
|
+
size and a drawn section's corner can follow the figure exactly as a box's width does today.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
interface FigurePartShape {
|
|
81
|
+
part: string // the part id (the place node's id, as today)
|
|
82
|
+
body: FigureExtrude | FigureRevolve | FigureLoft
|
|
83
|
+
cavities?: FigureCavityRecord[] // at most one, and only on an extrude, in the first contract (§3)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface FigureExtrude {
|
|
87
|
+
op: 'extrude'
|
|
88
|
+
axis: 'x' | 'y' | 'z' // the part's own axis the section is pushed along
|
|
89
|
+
section: FigureSectionRecord
|
|
90
|
+
start: FigureMeasure // E(S, a, b): a and b, measured from the part's origin along axis
|
|
91
|
+
end: FigureMeasure // start < end over the whole declared range
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type FigureSectionRecord =
|
|
95
|
+
| { kind: 'rectangle'; size: PlaneRecord<FigureMeasure>; corner?: PlaneRecord<FigureMeasure> }
|
|
96
|
+
| { kind: 'ellipse'; radius: PlaneRecord<FigureMeasure> }
|
|
97
|
+
| { kind: 'polygon'; points: Array<{ id: string } & PlaneRecord<FigureMeasure>> }
|
|
98
|
+
|
|
99
|
+
interface FigureCavityRecord {
|
|
100
|
+
id: string
|
|
101
|
+
wall: Record<'<a>-' | '<a>+' | '<b>-' | '<b>+', FigureMeasure> // one per flat side of the outer section
|
|
102
|
+
start: 'open' | { closed: FigureMeasure } // thickness left at the start end
|
|
103
|
+
end: 'open' | { closed: FigureMeasure }
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`PlaneRecord<T>` holds the two plane axes by name, for example `{ x, z }` for a push along Y (§2).
|
|
108
|
+
|
|
109
|
+
**Q1 — extent as two ends, not a length and an anchor.** ADR-0100 §4.1 writes the push as `E(S, a, b)` and says the
|
|
110
|
+
origin is never recentred by a shape edit. Storing `start` and `end` says exactly that. A centred box is
|
|
111
|
+
`start = -h/2, end = h/2`. A box standing on its origin is `start = 0, end = h`. The box's height is `end - start`
|
|
112
|
+
and is shown as one value (ADR-0100 §3.1). When a length is edited, the edit command decides which end stays, and it
|
|
113
|
+
reads that choice from the part's current fastening (§7). The choice is not stored on the shape. A height that
|
|
114
|
+
follows the instance links both ends, for example `start = {from: instance.y, times: -0.5}` and
|
|
115
|
+
`end = {from: instance.y, times: 0.5}`, and the linking command writes that pair.
|
|
116
|
+
|
|
117
|
+
The part's record replaces its `<part>.shape` node and nothing else. The pose (`<part>.pose`), the place
|
|
118
|
+
(`<part>`), the appearance, the joints and the bindings keep their ids.
|
|
119
|
+
|
|
120
|
+
## 2. The section
|
|
121
|
+
|
|
122
|
+
### 2.1 Kinds
|
|
123
|
+
|
|
124
|
+
| kind | written | round when | first stage |
|
|
125
|
+
| ----------- | --------------------------------------------------------------------------------- | ----------- | ----------- |
|
|
126
|
+
| `rectangle` | size along each plane axis, centred on the part's origin; `corner` per plane axis | — | 1 |
|
|
127
|
+
| `ellipse` | radius along each plane axis, centred | radii equal | 1 |
|
|
128
|
+
| `polygon` | one closed loop of straight sides, points by id, not recentred | — | 2 |
|
|
129
|
+
|
|
130
|
+
A rectangle and an ellipse are never stored as a list of points (ADR-0100 §4.1). Turning a rectangle into points is
|
|
131
|
+
an explicit command, `convert-section`, which the author runs (「점으로 편집」). A polygon becomes a rectangle again only
|
|
132
|
+
when it is one: four points, axis-aligned, centred.
|
|
133
|
+
|
|
134
|
+
**Corners are per plane axis.** The designer's rule of 2026-09-22 (shape-dimension contract §1) is that every round
|
|
135
|
+
thing has per-axis dimensions. A corner therefore has a reach along each plane axis. Equal reaches make a round
|
|
136
|
+
corner, and unequal reaches an elliptic one. The screen shows one radius and writes it to both axes. The per-axis
|
|
137
|
+
value appears in the detail view only when the two differ. A corner rounded along one axis and not the other is
|
|
138
|
+
refused (`CORNER_ONE_AXIS`).
|
|
139
|
+
|
|
140
|
+
**Q4 — which curve is a corner.** The 2026-09-22 ruling also says a corner is the quadratic Bézier with the square
|
|
141
|
+
corner as its control point, which is what things-scene's `roundedRect` draws. That ruling was made so V2's
|
|
142
|
+
drawings would match. The new canon is free to choose again, and the choice matters for cavities. The inward offset
|
|
143
|
+
of a quarter circle by `t` is a quarter circle of `r - t`, so an even wall is exact. The inward offset of a Bézier
|
|
144
|
+
corner is not a Bézier. **Recommended: a quarter of the ellipse the two reaches span.** `figureSectionAreaOf` is
|
|
145
|
+
written that way now, and it is the one function to change if the Bézier is kept.
|
|
146
|
+
|
|
147
|
+
### 2.2 The plane and the one written form
|
|
148
|
+
|
|
149
|
+
A section pushed along `n` lies across the two other local axes, `(u, v)`, chosen so that `(u, v, n)` is
|
|
150
|
+
right-handed:
|
|
151
|
+
|
|
152
|
+
| push along | plane axes (u, v) |
|
|
153
|
+
| ---------- | ----------------- |
|
|
154
|
+
| X | (Y, Z) |
|
|
155
|
+
| Y | (Z, X) |
|
|
156
|
+
| Z | (X, Y) |
|
|
157
|
+
|
|
158
|
+
**Q2 — points keep the axes' own names.** A point for a push along Y is written `{ id, x, z }`, not `{ u, v }`. A
|
|
159
|
+
reader of the JSON never has to know which axis is u, and today's drawn sections, which are X–Z pushed along Y, keep
|
|
160
|
+
their numbers. Only the winding test uses `(u, v)`.
|
|
161
|
+
|
|
162
|
+
The kernel keeps a drawn loop one way: counter-clockwise seen from `+n`, which means a positive signed area over
|
|
163
|
+
`(u, v)`. Seen from +Y, X runs right and Z runs down the page, as in the modeller's top view. Normalising reverses
|
|
164
|
+
the loop and keeps its first point first. It never moves, adds or removes a point, so every point and side keeps its
|
|
165
|
+
id (ADR-0100 §4.2).
|
|
166
|
+
|
|
167
|
+
### 2.3 Validity (ADR-0100 §4.2)
|
|
168
|
+
|
|
169
|
+
`checkFigureSection(section, axis)` returns every fault by the point, side or field that has it. It returns an empty
|
|
170
|
+
list when the section is valid. Nothing is clamped, snapped, reordered or thinned to pass.
|
|
171
|
+
|
|
172
|
+
| code | fault |
|
|
173
|
+
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
174
|
+
| `NON_FINITE` | a coordinate, size, radius or corner that is missing or not a finite number |
|
|
175
|
+
| `BAD_POINT_ID`, `DUPLICATE_POINT_ID` | a point's name is not an identifier, or is used twice |
|
|
176
|
+
| `TOO_FEW_POINTS`, `TOO_MANY_POINTS` | fewer than 3 points, or more than `FIGURE_SECTION_POINT_LIMIT` |
|
|
177
|
+
| `ZERO_LENGTH_SIDE` | two points in a row at one place |
|
|
178
|
+
| `SIDES_FOLD` | a side runs back over the one before it |
|
|
179
|
+
| `SIDES_CROSS` | two sides pass through each other |
|
|
180
|
+
| `SIDES_TOUCH` | two sides that are not neighbours meet at a point, or run along each other, without crossing |
|
|
181
|
+
| `ZERO_AREA` | the loop encloses nothing |
|
|
182
|
+
| `SIZE_NOT_POSITIVE` | a rectangle's size or an ellipse's radius is 0 or less |
|
|
183
|
+
| `CORNER_NEGATIVE`, `CORNER_ONE_AXIS`, `CORNER_TOO_LARGE` | a corner reach below 0, on one axis only, or past half its side (`max` given) |
|
|
184
|
+
|
|
185
|
+
A point added on a side before it is moved is valid: sides may meet at a straight angle. This lets "add a point,
|
|
186
|
+
then drag it" be two steps.
|
|
187
|
+
|
|
188
|
+
## 3. Cavities (ADR-0100 §4.3)
|
|
189
|
+
|
|
190
|
+
A cavity is `C = E(I, c, d)` inside `E(S, a, b)`, and the body is `E(S, a, b) \ C`. Its record does not store `I`,
|
|
191
|
+
`c` or `d`. It stores what the author sets:
|
|
192
|
+
|
|
193
|
+
- **`wall`**: how far in from each flat side of the outer section the inside begins. The screen shows one thickness
|
|
194
|
+
and writes it to all four sides. It shows the four sides separately only when the author asks for different ones
|
|
195
|
+
(ADR-0100 §3.3).
|
|
196
|
+
- **`start` / `end`**: `open`, or `{ closed: t }` for a thickness `t` left at that end. Whether an end is open is
|
|
197
|
+
written in the record. It is never inferred from two numbers happening to be equal. Open at both ends makes a
|
|
198
|
+
pipe, open at one end a container, and closed at both a sealed space. These are three results of one rule, not
|
|
199
|
+
three shapes. The floor thickness is the `closed` value, not a separate kind of shape.
|
|
200
|
+
|
|
201
|
+
`figureCavitySectionOf(outer, axis, wall)` gives the inside section:
|
|
202
|
+
|
|
203
|
+
- **Rectangle.** The size less the two walls on each axis. The inside is off-centre by half the difference when the
|
|
204
|
+
walls are uneven. Each corner reach goes in by the thicker of the two walls on its axis, `max(r - t, 0)`, which is
|
|
205
|
+
the rule the renderer draws today. With a round corner and even walls this is the exact inward offset. Otherwise it
|
|
206
|
+
is a declared rule, and it does not claim an even thickness around the bend. A corner that goes in to nothing on
|
|
207
|
+
one axis leaves the inside sharp.
|
|
208
|
+
- **Ellipse.** Each radius less the walls on its axis. This is not an inward offset of an ellipse and is not called
|
|
209
|
+
one. For a circle with even walls it is exact, which covers a pipe.
|
|
210
|
+
- **Polygon.** Refused (`SECTION_KIND`). A drawn loop's inside needs an inward offset with its own contract for
|
|
211
|
+
concave corners, narrow necks and regions that vanish or split. That is ADR-0100 stage 4. Until then the screen
|
|
212
|
+
shows 「이 윤곽에는 속 공간을 만들 수 없습니다」 with the reason, and offers no button.
|
|
213
|
+
|
|
214
|
+
Refused, never thinned: `WALL_NOT_POSITIVE` (with the side), `NO_ROOM_INSIDE`, `CAVITY_END_NOT_POSITIVE`,
|
|
215
|
+
`CAVITY_NO_LENGTH`.
|
|
216
|
+
|
|
217
|
+
**One cavity per part** in the first contract (`CAVITY_COUNT`). Two cavities raise questions about whether they
|
|
218
|
+
overlap and what wall lies between them, which nothing asks for yet. A part with two compartments is two parts, or a
|
|
219
|
+
divider fastened to the inside floor.
|
|
220
|
+
|
|
221
|
+
The body is one closed mesh. The renderer builds the outer walls, the inner walls, the rim and the closing faces as
|
|
222
|
+
one surface. It does not overlay a slab and a ring. Today's `hollowBoxXZ` does that (things-scene
|
|
223
|
+
`figure-geometry.ts:95-111`): the ring of walls stands on the full floor slab, so the walls' bottom and the slab's top lie
|
|
224
|
+
on each other inside the body.
|
|
225
|
+
|
|
226
|
+
A thing placed inside is a separate part with an `inside` relation (ADR-0100 §4.3). That relation is **not part of
|
|
227
|
+
this stage**. Until it has its own contract, a divider is fastened to `cavity-start:<id>` by an ordinary joint.
|
|
228
|
+
|
|
229
|
+
## 4. Named features (ADR-0100 §4.1.1)
|
|
230
|
+
|
|
231
|
+
`figureExtrusionFeaturesOf(extrusion, cavities)` lists what a pushed section offers:
|
|
232
|
+
|
|
233
|
+
| id | kind | what it is |
|
|
234
|
+
| ---------------------------------------------- | --------------- | ----------------------------------------------------------------------------------- |
|
|
235
|
+
| `start-face`, `end-face` | plane | the two ends; `open: true` when the cavity reaches through, so only the rim is left |
|
|
236
|
+
| `side-face:<side>` | plane or curved | each side of the section pushed along the axis |
|
|
237
|
+
| `edge:start:<side>`, `edge:end:<side>` | edge | where a side meets an end |
|
|
238
|
+
| `edge:<point>` | edge | a drawn section's corner, along the axis |
|
|
239
|
+
| `edge:<a>±<b>±` | edge | a sharp rectangle's corner, along the axis |
|
|
240
|
+
| `cavity-wall:<cavity>:<side>` | plane or curved | the inside of each wall, facing into the cavity |
|
|
241
|
+
| `cavity-start:<cavity>`, `cavity-end:<cavity>` | plane | the face closing the cavity at that end, when it is closed |
|
|
242
|
+
| `centre` | point | the middle of the span, at the section's origin |
|
|
243
|
+
|
|
244
|
+
**Side ids.**
|
|
245
|
+
|
|
246
|
+
- **Rectangle.** Each side is named by the plane axis it faces along and the direction: `x-`, `x+`, `z-`, `z+` for
|
|
247
|
+
a push along Y. A rounded corner is named by the two sides it joins, for example `corner:x-z+`, and exists only
|
|
248
|
+
while the corner is rounded.
|
|
249
|
+
- **Ellipse.** One curved side, `round`.
|
|
250
|
+
- **Drawn section.** A side is named by **the pair of points it joins**, in name order: `a~b`. So the name does not
|
|
251
|
+
depend on the loop's direction or on its first point. Moving a point keeps every side. Adding a point on side
|
|
252
|
+
`c~a` removes that side and adds `c~d` and `a~d`; neither half inherits the old name. Removing a point removes its
|
|
253
|
+
two sides and adds the side that joins its neighbours. **Every split, merge or deletion is a topology change, and
|
|
254
|
+
it is reported, not resolved**: `figureSectionTopologyChange(before, after)` lists the sides kept, removed and
|
|
255
|
+
added (ADR-0100 §5).
|
|
256
|
+
|
|
257
|
+
**Q9 — what a point is called, and what a screen shows.** The prototype first numbered points by their place in the
|
|
258
|
+
loop. Deleting a point then renumbered the others, and the side the author picked as 「옆면 1–3」 in the editor showed
|
|
259
|
+
as 「옆면 1–2」 once applied. **Recommended:** a drawn point's id is `p<n>`, and a new point takes the highest `n` in
|
|
260
|
+
the loop plus one. Every screen shows `n`, so point 3 is 「3」 before and after any edit, and deleting 2 leaves 1, 3
|
|
261
|
+
and 4. The kernel still accepts any identifier; the rule is for the commands that make points.
|
|
262
|
+
|
|
263
|
+
**What a box's six faces become** (the modeller's current words):
|
|
264
|
+
|
|
265
|
+
| today (`FIGURE_FACES`) | box pushed along Y |
|
|
266
|
+
| ---------------------- | ------------------------------- |
|
|
267
|
+
| `bottom` / `top` | `start-face` / `end-face` |
|
|
268
|
+
| `left` / `right` | `side-face:x-` / `side-face:x+` |
|
|
269
|
+
| `back` / `front` | `side-face:z-` / `side-face:z+` |
|
|
270
|
+
| `centre` | `centre` |
|
|
271
|
+
|
|
272
|
+
This is a one-time rewrite of the joint and binding references. It is not an alias kept at runtime.
|
|
273
|
+
|
|
274
|
+
**Q3 — curved sides and the 22 joints that use them.** Today a cylinder offers six box faces, and four of them lie
|
|
275
|
+
on its curved side (`cylinder-shape@1` uses `centredFaces`). In the development database, **22 joint ends are
|
|
276
|
+
fastened to a cylinder's `left`, `right`, `front` or `back`** (§11). ADR-0100 §5 says a curved face is never taken
|
|
277
|
+
for a plane. **Recommended:** a curved side offers **tangent planes**, `tangent:<axis>±`, defined as the plane that
|
|
278
|
+
touches the curve at its extreme along that axis, with the normal along it. Fastening to one is a line contact, and
|
|
279
|
+
the screen says so (「곡면에 닿는 선으로 붙습니다」). The alternative is to allow only `centre` on round parts and to
|
|
280
|
+
re-author the 22.
|
|
281
|
+
|
|
282
|
+
## 5. The other operations
|
|
283
|
+
|
|
284
|
+
ADR-0100 names six operations. Their contracts differ in how settled they are. **None of the four below is offered on
|
|
285
|
+
screen until its row in §9 says so.**
|
|
286
|
+
|
|
287
|
+
- **`revolve`**: a half-section in `(r, h)` about the part's axis, a full turn only in the first contract. It has
|
|
288
|
+
three profile kinds, each keeping its dimensions as they are named:
|
|
289
|
+
- `half-ellipse { radial, axial }` makes a sphere or a spheroid.
|
|
290
|
+
- `taper { start: {at, radius}, end: {at, radius} }` makes a cone or a frustum.
|
|
291
|
+
- `polygon` of `(r, h)` points with `r ≥ 0` makes a turned profile.
|
|
292
|
+
|
|
293
|
+
A cylinder is `extrude(ellipse)`, never a taper with equal radii. The taper allows equal radii for a moment while
|
|
294
|
+
it is edited, and does not become a cylinder on its own.
|
|
295
|
+
|
|
296
|
+
- **`loft`**: stations in order along the axis. Each station is a section of the same kind and the same point ids,
|
|
297
|
+
and the stations are matched by id. The last station may be a `point`. A **tetrahedron** is a triangle and one
|
|
298
|
+
point, and a pyramid is a rectangle and one point. There is no tetrahedron shape. The contract still needs: loop
|
|
299
|
+
correspondence when the kinds differ, twist between stations, the normal at a degenerate point, and
|
|
300
|
+
self-intersection between stations. So loft stays off until those are written.
|
|
301
|
+
- **`sweep`**: a section along a path. The path frame, twist, curvature limit and self-intersection are not
|
|
302
|
+
contracted. It stays off. This is the "curved shapes" item already ranked after render batching in the app status.
|
|
303
|
+
- **`assembly`**: several parts as one product structure, held by fixed joints. It keeps each child's meaning,
|
|
304
|
+
relations and material, and it is never a merged mesh. **Q5** asks whether the door frame, the one assembly in
|
|
305
|
+
ADR-0100's representative list, needs it. A door frame is also one `extrude` of a drawn Π section along Z. That is
|
|
306
|
+
one body with named inside faces, and it needs no assembly contract.
|
|
307
|
+
|
|
308
|
+
**Q6 — an ellipsoid with three different radii.** `sphere-shape@1` takes three radii. `revolve` makes only
|
|
309
|
+
spheroids, with the two radial radii equal. Three live figures use spheres (여의봉 and 작업자 by expression, 화성
|
|
310
|
+
충방전기 at 85/85/85). **Recommended:** rewrite them as `revolve(half-ellipse)`. If an evaluated pair of radial radii
|
|
311
|
+
differs at any size in the declared range, the rewrite reports that asset and does not convert it. The ellipsoid
|
|
312
|
+
does not become a seventh operation without a new ADR.
|
|
313
|
+
|
|
314
|
+
## 6. Numbers
|
|
315
|
+
|
|
316
|
+
| name | value | what it decides | status |
|
|
317
|
+
| ---------------------------- | ------------------------------ | ------------------------------------------------------------------------------ | -------------------------------------------------------------- |
|
|
318
|
+
| `FIGURE_SECTION_TOLERANCE` | 1e-6 mm | whether two points coincide, a side has length, two sides touch; topology only | proposed |
|
|
319
|
+
| `FIGURE_SECTION_POINT_LIMIT` | 256 | points in one drawn loop | proposed, to be measured (below) |
|
|
320
|
+
| tessellation | `appearance.segments` as today | how many segments a curve is drawn with | unchanged; no segment choice on the first screen (ADR-0100 §7) |
|
|
321
|
+
|
|
322
|
+
The tolerance is not a snapping distance, not the magnet's pixel tolerance, and not a tolerance for comparing
|
|
323
|
+
drawings (ADR-0100 §4.2). The point limit is to be confirmed by measuring the validity check (O(n²) over the sides),
|
|
324
|
+
triangulation and a full re-evaluation of the figure at 64, 256 and 1024 points on the modeller's machine class.
|
|
325
|
+
|
|
326
|
+
## 7. Commands
|
|
327
|
+
|
|
328
|
+
All of them go through the authoring session, which checks the model and revision, applies atomically, and undoes
|
|
329
|
+
in one step. The modeller, the AI and the templates use them and nothing else (ADR-0100 decision 6).
|
|
330
|
+
|
|
331
|
+
| command | what it changes |
|
|
332
|
+
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
|
333
|
+
| `edit-section { part, section, mapping? }` | the whole section, as one step: what the 2D editor's `적용` sends |
|
|
334
|
+
| `set-extrusion { part, length, keep: 'start' \| 'centre' \| 'end' }` | `end - start`; `keep` defaults to what the part's fastening holds |
|
|
335
|
+
| `set-section-corner { part, corner }` | the corner reaches of a rectangle |
|
|
336
|
+
| `set-cavity { part, cavity \| null, mapping? }` | the part's inside space: add, change, remove |
|
|
337
|
+
| `convert-section { part, to, mapping? }` | rectangle to points, points to rectangle (only when it is one), ellipse to rectangle |
|
|
338
|
+
|
|
339
|
+
`mapping` names, for each removed feature something refers to, the feature that takes its place, or `release`.
|
|
340
|
+
`release` unfastens the part and leaves it at its current rest pose, under ADR-0093 decision 7. A command without
|
|
341
|
+
the mapping it needs is refused with `FEATURE_MAPPING_REQUIRED` and the list of references. Nothing is mapped in
|
|
342
|
+
passing.
|
|
343
|
+
|
|
344
|
+
**The plan.** `planFigureShapeEdit(asset, command)` returns, without changing anything:
|
|
345
|
+
|
|
346
|
+
- the context (model, revision)
|
|
347
|
+
- the section's validity issues
|
|
348
|
+
- the topology change (§4)
|
|
349
|
+
- every reference to a removed feature, with its owner (joint, binding, occupancy contact, material, capability)
|
|
350
|
+
- the parts that will move, and by how much
|
|
351
|
+
- the clips that would be lost, which are refused under ADR-0099
|
|
352
|
+
- the evaluated result, for the preview
|
|
353
|
+
|
|
354
|
+
The preview and the apply use the same plan. A plan made against another revision is refused when applied.
|
|
355
|
+
|
|
356
|
+
## 8. What an edit keeps (ADR-0100 §5)
|
|
357
|
+
|
|
358
|
+
- The part's id, origin and rest pose never change with its shape. A fastened child is evaluated again against the
|
|
359
|
+
changed feature, and the plan shows the move before it is applied.
|
|
360
|
+
- A dimension linked to something stays linked. Setting a linked value to a number is `unlink-dimension`, shown
|
|
361
|
+
first.
|
|
362
|
+
- State may change a length, never the topology. No state input may change the number of points or cavities, or
|
|
363
|
+
whether an end is open. The compiler refuses such a dependence under the operator's `state` policy.
|
|
364
|
+
- A reference is to a feature id, never to a side's index, a triangle or a point's place in a list.
|
|
365
|
+
|
|
366
|
+
## 9. What is on when
|
|
367
|
+
|
|
368
|
+
| row | stage 1 (rectangle family) | stage 2 (drawn section) | stage 3 (product path) | later |
|
|
369
|
+
| --------------------------------------------------- | -------------------------- | ----------------------- | ---------------------- | -------------- |
|
|
370
|
+
| extrude · rectangle / ellipse | on | | | |
|
|
371
|
+
| corner (per axis) | on | | | |
|
|
372
|
+
| cavity on rectangle / ellipse | on | | | |
|
|
373
|
+
| extrude · polygon, 2D editor | | on | | |
|
|
374
|
+
| `convert-section` | rectangle ↔ points | on | | |
|
|
375
|
+
| revolve (half-ellipse, taper) | on, no editor beyond sizes | | | |
|
|
376
|
+
| revolve · polygon profile | | | | contract first |
|
|
377
|
+
| loft (point station: pyramid, tetrahedron) | | | on, after its contract | |
|
|
378
|
+
| loft · section to section | | | | contract first |
|
|
379
|
+
| sweep | | | | contract first |
|
|
380
|
+
| assembly | | | Q5 | |
|
|
381
|
+
| cavity on a drawn section | | | | stage 4 |
|
|
382
|
+
| AI grammar, templates, board, rewrite of all assets | | | on | |
|
|
383
|
+
|
|
384
|
+
## 10. What changes where
|
|
385
|
+
|
|
386
|
+
Surveyed 2026-09-29 (read-only):
|
|
387
|
+
|
|
388
|
+
- **figure-model.** It gains the record and its compiler, the gate reading features, the commands above, and the
|
|
389
|
+
proposal grammar. It loses the eleven shape operators, `feature@1`'s provider switch (`graph.ts` 863–), the
|
|
390
|
+
provider table (`shape-provider.ts`), `FOLLOWS_NOW`, `create.ts`'s recipe table, and `FIGURE_FACES` as the words
|
|
391
|
+
for a fastening.
|
|
392
|
+
- **things-scene** (about 150 lines). The provider switch in `figure-geometry.ts:73-134` becomes one mesh builder per
|
|
393
|
+
operation. The geometry cache key (`keyOf`) becomes the record's content. The top view (`figure-top-view.ts:87-108`)
|
|
394
|
+
reads the section instead of the provider. There is no per-face picking today, so features need picking added,
|
|
395
|
+
not moved.
|
|
396
|
+
- **figure-ui** (about 220 lines in about a dozen tables). The palette (`palette.ts`, `figure-parts.ts`) moves to
|
|
397
|
+
starting shapes. The gizmo's `BOXES` and round-scaling tables (`gizmo.ts:33`, `:68-76`) move to the record's own
|
|
398
|
+
sizes. `links.ts` `KIND_OF_SHAPE` and `placeFacesOf` move to features. The icons, the material panel's `ROUND`,
|
|
399
|
+
maturity's copy of the geometry key and `ROUND_FOOTPRINT`, and `faceHighlightOf` also change. The face `<select>`
|
|
400
|
+
lists of six names become feature lists. The 2D section editor is new.
|
|
401
|
+
- **figure-service** (no shape code on the server). The proposal grammar comes from figure-model. The 101 recipes'
|
|
402
|
+
`build.mts` use `kind` primitives through `addFigurePrimitive`, so the recipes are rebuilt and the 123 generated
|
|
403
|
+
models regenerated.
|
|
404
|
+
- **operato-figure.** Two landing scripts push raw `box-shape@1` nodes. The maturity row "polygon/shape editing:
|
|
405
|
+
planned" changes.
|
|
406
|
+
|
|
407
|
+
## 11. The assets to rewrite
|
|
408
|
+
|
|
409
|
+
The development database, read-only on 2026-09-29, has 58 live figures:
|
|
410
|
+
|
|
411
|
+
| operator | nodes | figures | becomes |
|
|
412
|
+
| ------------------ | ----- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
413
|
+
| `box-shape@1` | 257 | 47 | extrude · rectangle |
|
|
414
|
+
| `cylinder-shape@1` | 117 | 34 | extrude · ellipse |
|
|
415
|
+
| `rounded-box@2` | 52 | 11 | extrude · rectangle with corner per axis (in 7 one reach is a number and the other an expression; whether they differ is evaluated at the rewrite) |
|
|
416
|
+
| `rounded-box@1` | 25 | 8 | extrude · rectangle with corner |
|
|
417
|
+
| `cylinder-shape@2` | 10 | 3 | extrude · ellipse |
|
|
418
|
+
| `sphere-shape@1` | 5 | 3 | revolve · half-ellipse (Q6) |
|
|
419
|
+
| `hollow-box@2` | 2 | 2 | extrude · rectangle + cavity, end open |
|
|
420
|
+
| `polygon-shape@1` | 2 | 1 | extrude · polygon; point ids are given at the rewrite |
|
|
421
|
+
|
|
422
|
+
Joint ends on a cylinder's side faces: `left` 6, `right` 6, `front` 5, `back` 5 (Q3). Joint ends on a cylinder's
|
|
423
|
+
`bottom` 23 and `top` 26 become `start-face` and `end-face`. The 101 templates use box 177, cylinder 36, hollow-box
|
|
424
|
+
10, portal 6, rounded-box 3 and frustum 2 steps, and are rebuilt from their `build.mts`.
|
|
425
|
+
|
|
426
|
+
## 12. Questions for the designer
|
|
427
|
+
|
|
428
|
+
| | question | recommended |
|
|
429
|
+
| --- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
|
430
|
+
| Q1 | Store a push as its two ends, not a length and an anchor | two ends (§1) |
|
|
431
|
+
| Q2 | Points keep the axes' own names; `(u, v, n)` right-handed | yes (§2.2) |
|
|
432
|
+
| Q3 | Curved sides: tangent planes for the 22 joints, or `centre` only | tangent planes, said as line contact (§4) |
|
|
433
|
+
| Q4 | A corner's curve: elliptic quarter, or keep the Bézier of 2026-09-22 | elliptic quarter (§2.1) |
|
|
434
|
+
| Q5 | Door frame: one Π-section extrude, or an assembly | one extrude; assembly after (§5) |
|
|
435
|
+
| Q6 | Three-radius ellipsoid: rewrite as spheroid and report the rest, or keep it | rewrite and report (§5) |
|
|
436
|
+
| Q7 | One cavity per part in the first contract | yes (§3) |
|
|
437
|
+
| Q8 | Tolerance 1e-6 mm and 256 points, pending the measurement in §6 | yes |
|
|
438
|
+
| Q9 | A drawn point's id is `p<n>`, a new point takes the highest n + 1, and screens show n, never the point's place in the loop | yes (§4) |
|
|
439
|
+
| Q10 | The node limit, or a centred push written without its start | **Ruled: 8192**, no shorthand |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hatiolab/figure-model",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "The figure format (figure-asset-1): a kernel that evaluates a figure from its declared relations, the authoring commands a modeller and an AI share, the release gate and the score.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"figure",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"build": "tsc -p tsconfig.build.json",
|
|
50
50
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
51
51
|
"test": "npm run check && node --test src/*.test.ts",
|
|
52
|
+
"test:rewrite": "node --test scripts/*.test.ts",
|
|
52
53
|
"test:coverage": "node --test --experimental-test-coverage --test-coverage-lines=100 --test-coverage-branches=95 --test-coverage-functions=100 src/*.test.ts",
|
|
53
54
|
"format": "prettier --write \"src/**/*.ts\" \"docs/**/*.md\" \"*.md\" \"*.json\"",
|
|
54
55
|
"format:check": "prettier --check \"src/**/*.ts\" \"docs/**/*.md\" \"*.md\" \"*.json\"",
|