@things-factory/figure-ui 10.1.27 → 10.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/graphql/index.ts +23 -0
- package/client/modeller/angle-dial.ts +70 -0
- package/client/modeller/base-box.ts +104 -0
- package/client/modeller/figure-animations.ts +90 -17
- package/client/modeller/figure-canvas.ts +24 -6
- package/client/modeller/figure-inspector.ts +169 -5
- package/client/modeller/figure-preview.ts +110 -3
- package/client/modeller/figure-report.ts +20 -32
- package/client/modeller/figure-side.ts +10 -1
- package/client/modeller/figure-source.ts +8 -0
- package/client/modeller/joint-edits.ts +66 -5
- package/client/modeller/joint-gizmo.ts +180 -0
- package/client/modeller/part-edits.ts +75 -9
- package/client/modeller/pose-defaults.ts +67 -0
- package/client/modeller/proposal.ts +9 -3
- package/client/modeller/scene-budget.ts +31 -40
- package/client/pages/figure-list-page.ts +4 -9
- package/client/pages/figure-modeller-page.ts +144 -43
- package/client/types.ts +2 -0
- package/dist-client/graphql/index.d.ts +7 -0
- package/dist-client/graphql/index.js +21 -0
- package/dist-client/graphql/index.js.map +1 -1
- package/dist-client/modeller/angle-dial.d.ts +32 -0
- package/dist-client/modeller/angle-dial.js +41 -0
- package/dist-client/modeller/angle-dial.js.map +1 -0
- package/dist-client/modeller/base-box.d.ts +47 -0
- package/dist-client/modeller/base-box.js +52 -0
- package/dist-client/modeller/base-box.js.map +1 -0
- package/dist-client/modeller/figure-animations.d.ts +19 -4
- package/dist-client/modeller/figure-animations.js +86 -17
- package/dist-client/modeller/figure-animations.js.map +1 -1
- package/dist-client/modeller/figure-canvas.d.ts +9 -0
- package/dist-client/modeller/figure-canvas.js +20 -6
- package/dist-client/modeller/figure-canvas.js.map +1 -1
- package/dist-client/modeller/figure-inspector.d.ts +37 -0
- package/dist-client/modeller/figure-inspector.js +159 -5
- package/dist-client/modeller/figure-inspector.js.map +1 -1
- package/dist-client/modeller/figure-preview.d.ts +26 -0
- package/dist-client/modeller/figure-preview.js +99 -3
- package/dist-client/modeller/figure-preview.js.map +1 -1
- package/dist-client/modeller/figure-report.d.ts +11 -6
- package/dist-client/modeller/figure-report.js +20 -35
- package/dist-client/modeller/figure-report.js.map +1 -1
- package/dist-client/modeller/figure-side.d.ts +5 -1
- package/dist-client/modeller/figure-side.js +13 -0
- package/dist-client/modeller/figure-side.js.map +1 -1
- package/dist-client/modeller/figure-source.d.ts +7 -0
- package/dist-client/modeller/figure-source.js.map +1 -1
- package/dist-client/modeller/joint-edits.d.ts +23 -3
- package/dist-client/modeller/joint-edits.js +53 -5
- package/dist-client/modeller/joint-edits.js.map +1 -1
- package/dist-client/modeller/joint-gizmo.d.ts +34 -0
- package/dist-client/modeller/joint-gizmo.js +158 -0
- package/dist-client/modeller/joint-gizmo.js.map +1 -0
- package/dist-client/modeller/part-edits.d.ts +29 -4
- package/dist-client/modeller/part-edits.js +61 -9
- package/dist-client/modeller/part-edits.js.map +1 -1
- package/dist-client/modeller/pose-defaults.d.ts +9 -0
- package/dist-client/modeller/pose-defaults.js +35 -0
- package/dist-client/modeller/pose-defaults.js.map +1 -0
- package/dist-client/modeller/proposal.d.ts +1 -1
- package/dist-client/modeller/proposal.js +8 -3
- package/dist-client/modeller/proposal.js.map +1 -1
- package/dist-client/modeller/scene-budget.d.ts +31 -34
- package/dist-client/modeller/scene-budget.js +28 -37
- package/dist-client/modeller/scene-budget.js.map +1 -1
- package/dist-client/pages/figure-list-page.js +4 -9
- package/dist-client/pages/figure-list-page.js.map +1 -1
- package/dist-client/pages/figure-modeller-page.d.ts +50 -0
- package/dist-client/pages/figure-modeller-page.js +141 -42
- package/dist-client/pages/figure-modeller-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/types.d.ts +2 -0
- package/dist-client/types.js.map +1 -1
- package/package.json +5 -5
- package/test/ai-proposal-contract.test.ts +16 -11
- package/test/angle-dial.test.ts +78 -0
- package/test/base-box.test.ts +165 -0
- package/test/expect.ts +2 -0
- package/test/i18n-prefix-guard.test.ts +17 -0
- package/test/joint-edits.test.ts +37 -2
- package/test/joint-gizmo.test.ts +174 -0
- package/test/joint-needle.test.ts +162 -0
- package/test/part-edits.test.ts +100 -0
- package/test/pose-defaults.test.ts +71 -0
- package/test/scene-budget.test.ts +82 -0
- package/translations/en.json +16 -9
- package/translations/ja.json +16 -9
- package/translations/ko.json +16 -9
- package/translations/ms.json +16 -9
- package/translations/zh.json +16 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"part-edits.js","sourceRoot":"","sources":["../../client/modeller/part-edits.ts"],"names":[],"mappings":"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","sourcesContent":["import { EXTRUDE_PRIMITIVES, SEGMENTED_PRIMITIVES, SEGMENT_PRESETS, PART_LIMIT } from '@hatiolab/figure-model'\nimport type { DetailLevel, PrimitiveKind } from '@hatiolab/figure-model'\n\nimport { AXIS_KEYS, draftExtent } from './figure-source.js'\nimport type { FigureDraft, PartModel, PlaneAxis } from './figure-source.js'\n\n/**\n * 부품 편집 규칙. **씬 모델의 용어로** 다룬다.\n *\n * 편집 중에 기준이 되는 데이터는 씬 모델이다 — 기준 상자 위에 부품 컴포넌트가 놓인 상태.\n * 그래서 편집도 씬의 용어(`left`·`top`·`zPos`·`width`·`height`·`depth`)로 한다.\n * FigureSource 의 좌표계로 바꾸는 변환은 `figure-source.ts` 한 곳에서만 한다.\n *\n * 사용자가 보는 축(X·Y·Z)이 씬의 어느 속성인지도 `figure-source.ts` 의 `AXIS_KEYS` 가 기준이다.\n * 여기서 다시 정의하지 않는다.\n *\n * 모든 함수가 **새 배열·새 객체를 반환한다** — 원본을 수정하지 않는다. Lit 이 변경을\n * 감지하려면 참조가 달라져야 하고, 실행 취소를 붙일 때도 이전 상태가 남아 있어야 한다.\n */\n\n/** 중복되지 않는 이름을 만든다. 부품 이름은 저장되는 식별자라 중복되면 검증에서 걸린다. */\nfunction freeName(taken: Set<string>, base: string): string {\n if (!taken.has(base)) return base\n for (let n = 2; ; n++) {\n const candidate = `${base}-${n}`\n if (!taken.has(candidate)) return candidate\n }\n}\n\n/** 이 초안에 부품을 몇 개까지 넣을 수 있는지. 디테일 등급이 결정한다. */\nexport function partLimit(draft: FigureDraft): number {\n return PART_LIMIT[(draft.detailLevel ?? 'M') as DetailLevel]\n}\n\n/**\n * 새 부품 하나를 만든다.\n *\n * 기준 상자의 3분의 1 크기로, **바닥 가운데**에 놓는다. 추가하자마자 화면에 보여야 한다 —\n * 크기 0 으로 놓으면 사용자에게는 아무 일도 일어나지 않은 것으로 보인다.\n */\nexport function newPart(draft: FigureDraft, parts: PartModel[], primitive: PrimitiveKind): PartModel {\n const taken = new Set(parts.map(part => part.name))\n\n const width = Math.max(1, Math.round(draft.width / 3))\n const height = Math.max(1, Math.round(draft.height / 3))\n const depth = Math.max(1, Math.round(draft.depth / 3))\n\n const part: PartModel = {\n type: 'figure-part',\n name: freeName(taken, primitive),\n primitive,\n left: Math.round((draft.width - width) / 2),\n top: Math.round((draft.height - height) / 2),\n width,\n height,\n // 바닥에 붙인다. `zPos` 는 부피의 밑면이라 0 이 곧 상자 바닥이다.\n zPos: 0,\n depth,\n // 팔레트 토큰은 기존 부품의 것을 물려받는다 — 토큰이 늘어날 때마다 재질 그룹이 하나씩 는다.\n token: parts[0]?.token ?? 'palette.primary'\n }\n\n if (SEGMENTED_PRIMITIVES.includes(primitive)) {\n part.segments = SEGMENT_PRESETS[1]\n }\n if (EXTRUDE_PRIMITIVES.includes(primitive)) {\n part.round = 0\n }\n if (primitive === 'polygon') {\n // polygon 은 점이 3개 이상이어야 한다. 없으면 검증에서 막히므로 삼각형으로 시작한다.\n // 단면의 점은 부품 중심을 기준으로 적는다.\n part.path = [\n { x: -Math.round(width / 2), y: -Math.round(height / 2) },\n { x: Math.round(width / 2), y: -Math.round(height / 2) },\n { x: 0, y: Math.round(height / 2) }\n ]\n }\n\n return part\n}\n\nexport function addPart(draft: FigureDraft, parts: PartModel[], primitive: PrimitiveKind): PartModel[] {\n return [...parts, newPart(draft, parts, primitive)]\n}\n\nexport function removePart(parts: PartModel[], index: number): PartModel[] {\n return parts.filter((_, at) => at !== index)\n}\n\n/** 부품을 복제한다. 이름만 새로 붙인다 — 중복되면 검증에서 걸린다. */\nexport function duplicatePart(parts: PartModel[], index: number): PartModel[] {\n const origin = parts[index]\n if (!origin) return parts\n\n const copy: PartModel = JSON.parse(JSON.stringify(origin))\n copy.name = freeName(new Set(parts.map(part => part.name)), `${origin.name}-copy`)\n\n const next = [...parts]\n next.splice(index + 1, 0, copy)\n return next\n}\n\n/** 목록에서 순서를 바꾼다. 범위를 벗어나면 그대로 둔다. */\nexport function movePart(parts: PartModel[], index: number, to: number): PartModel[] {\n if (to < 0 || to >= parts.length || index === to) return parts\n\n const next = [...parts]\n const [moved] = next.splice(index, 1)\n next.splice(to, 0, moved!)\n return next\n}\n\nexport function updatePart(parts: PartModel[], index: number, part: PartModel): PartModel[] {\n return parts.map((old, at) => (at === index ? part : old))\n}\n\n/**\n * 정밀 배치 도구 — 좌표를 손으로 계산하지 않게 한다.\n *\n * 도면 편집기는 자산을 **배치**하는 도구라 대략적인 위치로도 충분하다. 여기는 자산을\n * **만드는** 도구라 다르다. 모터를 몸통 정중앙에 놓거나, 다리를 네 귀퉁이에 대칭으로\n * 놓거나, 부품을 바닥에 정확히 붙이는 작업은 손으로 계산하면 늘 조금씩 어긋난다.\n */\n\nfunction put(parts: PartModel[], index: number, fields: Partial<PartModel>): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n return updatePart(parts, index, { ...part, ...fields })\n}\n\n/** 위치의 한 축만 바꾼다. 어느 속성인지는 `AXIS_KEYS` 가 결정한다. */\nfunction putAt(parts: PartModel[], index: number, axis: PlaneAxis, value: number): PartModel[] {\n return put(parts, index, { [AXIS_KEYS[axis].at]: value } as Partial<PartModel>)\n}\n\n/** 부품을 기준 상자의 해당 축 가운데로 옮긴다. */\nexport function centerPart(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n\n return putAt(parts, index, axis, Math.round((draftExtent(draft, axis) - part[AXIS_KEYS[axis].size]) / 2))\n}\n\n/** 부품을 상자 바닥(또는 해당 축의 시작)에 붙인다. */\nexport function alignPartToStart(parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n return putAt(parts, index, axis, 0)\n}\n\n/** 부품을 상자의 끝에 붙인다. */\nexport function alignPartToEnd(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n\n return putAt(parts, index, axis, Math.round(draftExtent(draft, axis) - part[AXIS_KEYS[axis].size]))\n}\n\n/**\n * 부품을 축 기준으로 대칭 복제한다.\n *\n * 다리 넷, 팔 둘 같은 형상을 이것 없이 만들면 고생한다. 이름은 **어느 쪽인지 알 수\n * 있게** 붙인다 — `leg` 를 x 축으로 대칭하면 `leg-x`.\n */\nexport function mirrorPart(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n const part = parts[index]\n if (!part || !canMirror(draft, parts, index, axis)) return parts\n\n const copy = JSON.parse(JSON.stringify(part)) as PartModel\n copy.name = freeName(new Set(parts.map(p => p.name)), `${part.name}-${axis}`)\n\n const next = [...parts]\n next.splice(index + 1, 0, copy)\n return putAt(next, index + 1, axis, mirroredAt(draft, part, axis))\n}\n\n/** 대칭했을 때의 위치. 상자의 해당 축 안에서 뒤집는다. */\nfunction mirroredAt(draft: FigureDraft, part: PartModel, axis: PlaneAxis): number {\n const { at, size } = AXIS_KEYS[axis]\n return Math.round(draftExtent(draft, axis) - part[at] - part[size])\n}\n\n/**\n * 대칭이 의미가 있는지.\n *\n * 이미 그 축의 가운데에 있는 부품을 대칭하면 **같은 자리에 사본이 생긴다.** 화면에는\n * 아무 변화가 없어 보이는데 부품 수만 늘고, 겹친 면에서 z-fighting 이 일어난다.\n * 만들어 놓고 알리는 대신 미리 막는다.\n */\nexport function canMirror(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): boolean {\n const part = parts[index]\n if (!part) return false\n\n return mirroredAt(draft, part, axis) !== Math.round(part[AXIS_KEYS[axis].at])\n}\n\n/**\n * 부품의 위치와 크기를 격자에 맞춘다.\n *\n * 격자 간격은 상자 크기에서 계산한다 — 고정 값이면 작은 형상에서는 너무 크고 큰\n * 형상에서는 너무 잘다. 3D 바닥 격자와 같은 계산이라 화면에서 보이는 것과 일치한다.\n */\nexport function snapPart(draft: FigureDraft, parts: PartModel[], index: number): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n\n const step = gridStep(draft)\n const round = (value: number) => Math.round(value / step) * step\n\n return put(parts, index, {\n left: round(part.left),\n top: round(part.top),\n zPos: round(part.zPos),\n // 크기는 0 이 되면 안 된다 — 부품이 사라진 것처럼 보인다\n width: Math.max(step, round(part.width)),\n height: Math.max(step, round(part.height)),\n depth: Math.max(step, round(part.depth))\n })\n}\n\n/**\n * 격자 간격. 3D 바닥에 그려지는 격자와 같은 값이다.\n *\n * **세 축을 다 본다.** 평면만 보면 키가 큰 모델에서 격자가 지나치게 잘아진다. 시그널타워\n * (바닥 160 x 160, 높이 1100)는 평면 기준으로 간격이 20 이 나와서, 옆에서 보면 높이가\n * 격자 55 칸이 된다. 격자는 크기를 가늠하는 자이므로 모델 전체에 대해 읽혀야 한다.\n */\nexport function gridStep(draft: FigureDraft): number {\n return niceStep(Math.max(draft.width, draft.height, draft.depth))\n}\n\n/**\n * 주어진 길이에 어울리는 **읽기 좋은** 격자 간격. 다섯 칸 정도가 되게 잡는다.\n *\n * 그냥 나누면 27·33 같은 값이 나온다. 그 격자에 맞추면 좌표가 54·81 이 되어 읽기\n * 어렵다. 1·2·5 배수로 올림한다 — 자와 차트 눈금이 쓰는 방식이다.\n *\n * 격자가 성겨야 형상이 눈에 먼저 들어온다. 열 칸으로 잡았더니 격자가 촘촘해 배경이\n * 먼저 보였다.\n */\nexport function niceStep(span: number): number {\n const raw = Math.max(span, 1) / 5\n const magnitude = Math.pow(10, Math.floor(Math.log10(raw)))\n const n = raw / magnitude\n\n const multiple = n < 1.5 ? 1 : n < 3.5 ? 2 : n < 7.5 ? 5 : 10\n return Math.max(1, multiple * magnitude)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"part-edits.js","sourceRoot":"","sources":["../../client/modeller/part-edits.ts"],"names":[],"mappings":"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","sourcesContent":["import { EXTRUDE_PRIMITIVES, SEGMENTED_PRIMITIVES, SEGMENT_PRESETS, PART_LIMIT } from '@hatiolab/figure-model'\nimport type { DetailLevel, PrimitiveKind } from '@hatiolab/figure-model'\n\nimport { AXIS_KEYS, draftExtent } from './figure-source.js'\nimport type { FigureDraft, PartModel, PlaneAxis } from './figure-source.js'\n\n/**\n * 부품 편집 규칙. **씬 모델의 용어로** 다룬다.\n *\n * 편집 중에 기준이 되는 데이터는 씬 모델이다 — 기준 상자 위에 부품 컴포넌트가 놓인 상태.\n * 그래서 편집도 씬의 용어(`left`·`top`·`zPos`·`width`·`height`·`depth`)로 한다.\n * FigureSource 의 좌표계로 바꾸는 변환은 `figure-source.ts` 한 곳에서만 한다.\n *\n * 사용자가 보는 축(X·Y·Z)이 씬의 어느 속성인지도 `figure-source.ts` 의 `AXIS_KEYS` 가 기준이다.\n * 여기서 다시 정의하지 않는다.\n *\n * 모든 함수가 **새 배열·새 객체를 반환한다** — 원본을 수정하지 않는다. Lit 이 변경을\n * 감지하려면 참조가 달라져야 하고, 실행 취소를 붙일 때도 이전 상태가 남아 있어야 한다.\n */\n\n/** 중복되지 않는 이름을 만든다. 부품 이름은 저장되는 식별자라 중복되면 검증에서 걸린다. */\nfunction freeName(taken: Set<string>, base: string): string {\n if (!taken.has(base)) return base\n for (let n = 2; ; n++) {\n const candidate = `${base}-${n}`\n if (!taken.has(candidate)) return candidate\n }\n}\n\nconst round = (value: number) => Math.round(value)\n\n/** 값을 두 끝 사이로 당긴다. 끝이 뒤집혀 있으면(부품이 상자보다 크다) 앞의 끝을 따른다. */\nfunction clamp(value: number, least: number, most: number): number {\n return Math.max(least, Math.min(most, value))\n}\n\n/** 이 초안에 부품을 몇 개까지 넣을 수 있는지. 디테일 등급이 결정한다. */\nexport function partLimit(draft: FigureDraft): number {\n return PART_LIMIT[(draft.detailLevel ?? 'M') as DetailLevel]\n}\n\n/**\n * 새 부품 하나를 만든다.\n *\n * 기준 상자의 3분의 1 크기다. 추가하자마자 화면에 보여야 한다 — 크기 0 으로 놓으면\n * 사용자에게는 아무 일도 일어나지 않은 것으로 보인다.\n *\n * ## 어디에 놓나 — 고른 부품 **위**다\n *\n * 전에는 언제나 바닥 한가운데였다. 첫 부품은 그 자리가 맞지만 둘째부터는 **첫 부품 속에**\n * 들어간다 — 같은 자리에 같은 크기로 놓이니 화면에는 덩어리 하나뿐이고, 저작자는 무엇이\n * 늘었는지 못 본다. 「추가하자마자 보여야 한다」는 그때 이미 깨져 있었다.\n *\n * 그래서 고른 부품이 있으면 그 부품의 **윗면**에 얹고 발자국을 가운데로 맞춘다. 기계는\n * 대개 그렇게 쌓인다(받침 → 어깨 → 팔). 관절을 걸 때도 이 자리가 필요하다 — 맞닿는 면이\n * 실제로 있어야 `seamOrigin` 이 회전 중심을 그 면에서 잡는다.\n *\n * 위에 얹으면 기준 상자를 넘는 경우에는 바닥 한가운데로 간다. 넘은 채로 놓으면 발행이\n * 막히고(`part-outside-base`), 그것을 고치는 일이 「부품을 추가했다」의 대가가 된다.\n * 가로·세로는 상자 안으로 당긴다 — 고른 부품이 가장자리에 있으면 그 위가 상자 밖이다.\n */\nexport function newPart(\n draft: FigureDraft,\n parts: PartModel[],\n primitive: PrimitiveKind,\n onTopOf?: PartModel\n): PartModel {\n const taken = new Set(parts.map(part => part.name))\n\n const width = Math.max(1, Math.round(draft.width / 3))\n const height = Math.max(1, Math.round(draft.height / 3))\n const depth = Math.max(1, Math.round(draft.depth / 3))\n\n // 바닥 한가운데 — 고를 부품이 없을 때, 그리고 위가 상자 밖일 때의 자리.\n const middle = {\n left: Math.round((draft.width - width) / 2),\n top: Math.round((draft.height - height) / 2),\n // `zPos` 는 부피의 밑면이라 0 이 곧 상자 바닥이다.\n zPos: 0\n }\n const above = onTopOf && {\n left: clamp(Math.round(onTopOf.left + onTopOf.width / 2 - width / 2), 0, draft.width - width),\n top: clamp(Math.round(onTopOf.top + onTopOf.height / 2 - height / 2), 0, draft.height - height),\n zPos: Math.round(onTopOf.zPos + onTopOf.depth)\n }\n const at = above && above.zPos + depth <= draft.depth ? above : middle\n\n const part: PartModel = {\n type: 'figure-part',\n name: freeName(taken, primitive),\n primitive,\n left: at.left,\n top: at.top,\n width,\n height,\n zPos: at.zPos,\n depth,\n // 팔레트 토큰은 기존 부품의 것을 물려받는다 — 토큰이 늘어날 때마다 재질 그룹이 하나씩 는다.\n token: parts[0]?.token ?? 'palette.primary'\n }\n\n if (SEGMENTED_PRIMITIVES.includes(primitive)) {\n part.segments = SEGMENT_PRESETS[1]\n }\n if (EXTRUDE_PRIMITIVES.includes(primitive)) {\n part.round = 0\n }\n if (primitive === 'polygon') {\n // polygon 은 점이 3개 이상이어야 한다. 없으면 검증에서 막히므로 삼각형으로 시작한다.\n // 단면의 점은 부품 중심을 기준으로 적는다.\n part.path = [\n { x: -Math.round(width / 2), y: -Math.round(height / 2) },\n { x: Math.round(width / 2), y: -Math.round(height / 2) },\n { x: 0, y: Math.round(height / 2) }\n ]\n }\n\n return part\n}\n\n/**\n * 씬에서 온 부품을 밀리미터로 맞춘다.\n *\n * 기즈모는 비율을 곱해서 크기를 낸다 — 420 에 1.72 를 곱하면 722.4180046035418 이고, 손대지\n * 않은 축도 384.99999999999994 처럼 흔들린다. 저작자가 끈 결과가 소수 넷째 자리까지 남을\n * 까닭이 없고, 칸에는 「722.4…」로 잘려 보인다. 이 편집기의 다른 길은 전부 정수 밀리미터로\n * 맞춘다(부품을 새로 놓을 때 · 가운데 맞추기 · 격자 맞추기).\n *\n * 크기는 1 밑으로 내려가지 않는다 — 0 이면 부피가 사라지고 배율이 0 으로 나뉜다.\n */\nexport function roundPart(part: PartModel): PartModel {\n return {\n ...part,\n left: round(part.left),\n top: round(part.top),\n zPos: round(part.zPos),\n width: Math.max(1, round(part.width)),\n height: Math.max(1, round(part.height)),\n depth: Math.max(1, round(part.depth))\n }\n}\n\nexport function addPart(\n draft: FigureDraft,\n parts: PartModel[],\n primitive: PrimitiveKind,\n onTopOf?: PartModel\n): PartModel[] {\n return [...parts, newPart(draft, parts, primitive, onTopOf)]\n}\n\nexport function removePart(parts: PartModel[], index: number): PartModel[] {\n return parts.filter((_, at) => at !== index)\n}\n\n/** 부품을 복제한다. 이름만 새로 붙인다 — 중복되면 검증에서 걸린다. */\nexport function duplicatePart(parts: PartModel[], index: number): PartModel[] {\n const origin = parts[index]\n if (!origin) return parts\n\n const copy: PartModel = JSON.parse(JSON.stringify(origin))\n copy.name = freeName(new Set(parts.map(part => part.name)), `${origin.name}-copy`)\n\n const next = [...parts]\n next.splice(index + 1, 0, copy)\n return next\n}\n\n/** 목록에서 순서를 바꾼다. 범위를 벗어나면 그대로 둔다. */\nexport function movePart(parts: PartModel[], index: number, to: number): PartModel[] {\n if (to < 0 || to >= parts.length || index === to) return parts\n\n const next = [...parts]\n const [moved] = next.splice(index, 1)\n next.splice(to, 0, moved!)\n return next\n}\n\nexport function updatePart(parts: PartModel[], index: number, part: PartModel): PartModel[] {\n return parts.map((old, at) => (at === index ? part : old))\n}\n\n/**\n * 정밀 배치 도구 — 좌표를 손으로 계산하지 않게 한다.\n *\n * 도면 편집기는 자산을 **배치**하는 도구라 대략적인 위치로도 충분하다. 여기는 자산을\n * **만드는** 도구라 다르다. 모터를 몸통 정중앙에 놓거나, 다리를 네 귀퉁이에 대칭으로\n * 놓거나, 부품을 바닥에 정확히 붙이는 작업은 손으로 계산하면 늘 조금씩 어긋난다.\n */\n\nfunction put(parts: PartModel[], index: number, fields: Partial<PartModel>): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n return updatePart(parts, index, { ...part, ...fields })\n}\n\n/** 위치의 한 축만 바꾼다. 어느 속성인지는 `AXIS_KEYS` 가 결정한다. */\nfunction putAt(parts: PartModel[], index: number, axis: PlaneAxis, value: number): PartModel[] {\n return put(parts, index, { [AXIS_KEYS[axis].at]: value } as Partial<PartModel>)\n}\n\n/** 부품을 기준 상자의 해당 축 가운데로 옮긴다. */\nexport function centerPart(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n\n return putAt(parts, index, axis, Math.round((draftExtent(draft, axis) - part[AXIS_KEYS[axis].size]) / 2))\n}\n\n/** 부품을 상자 바닥(또는 해당 축의 시작)에 붙인다. */\nexport function alignPartToStart(parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n return putAt(parts, index, axis, 0)\n}\n\n/** 부품을 상자의 끝에 붙인다. */\nexport function alignPartToEnd(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n\n return putAt(parts, index, axis, Math.round(draftExtent(draft, axis) - part[AXIS_KEYS[axis].size]))\n}\n\n/**\n * 부품을 축 기준으로 대칭 복제한다.\n *\n * 다리 넷, 팔 둘 같은 형상을 이것 없이 만들면 고생한다. 이름은 **어느 쪽인지 알 수\n * 있게** 붙인다 — `leg` 를 x 축으로 대칭하면 `leg-x`.\n */\nexport function mirrorPart(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): PartModel[] {\n const part = parts[index]\n if (!part || !canMirror(draft, parts, index, axis)) return parts\n\n const copy = JSON.parse(JSON.stringify(part)) as PartModel\n copy.name = freeName(new Set(parts.map(p => p.name)), `${part.name}-${axis}`)\n\n const next = [...parts]\n next.splice(index + 1, 0, copy)\n return putAt(next, index + 1, axis, mirroredAt(draft, part, axis))\n}\n\n/** 대칭했을 때의 위치. 상자의 해당 축 안에서 뒤집는다. */\nfunction mirroredAt(draft: FigureDraft, part: PartModel, axis: PlaneAxis): number {\n const { at, size } = AXIS_KEYS[axis]\n return Math.round(draftExtent(draft, axis) - part[at] - part[size])\n}\n\n/**\n * 대칭이 의미가 있는지.\n *\n * 이미 그 축의 가운데에 있는 부품을 대칭하면 **같은 자리에 사본이 생긴다.** 화면에는\n * 아무 변화가 없어 보이는데 부품 수만 늘고, 겹친 면에서 z-fighting 이 일어난다.\n * 만들어 놓고 알리는 대신 미리 막는다.\n */\nexport function canMirror(draft: FigureDraft, parts: PartModel[], index: number, axis: PlaneAxis): boolean {\n const part = parts[index]\n if (!part) return false\n\n return mirroredAt(draft, part, axis) !== Math.round(part[AXIS_KEYS[axis].at])\n}\n\n/**\n * 부품의 위치와 크기를 격자에 맞춘다.\n *\n * 격자 간격은 상자 크기에서 계산한다 — 고정 값이면 작은 형상에서는 너무 크고 큰\n * 형상에서는 너무 잘다. 3D 바닥 격자와 같은 계산이라 화면에서 보이는 것과 일치한다.\n */\nexport function snapPart(draft: FigureDraft, parts: PartModel[], index: number): PartModel[] {\n const part = parts[index]\n if (!part) return parts\n\n const step = gridStep(draft)\n const round = (value: number) => Math.round(value / step) * step\n\n return put(parts, index, {\n left: round(part.left),\n top: round(part.top),\n zPos: round(part.zPos),\n // 크기는 0 이 되면 안 된다 — 부품이 사라진 것처럼 보인다\n width: Math.max(step, round(part.width)),\n height: Math.max(step, round(part.height)),\n depth: Math.max(step, round(part.depth))\n })\n}\n\n/**\n * 격자 간격. 3D 바닥에 그려지는 격자와 같은 값이다.\n *\n * **세 축을 다 본다.** 평면만 보면 키가 큰 모델에서 격자가 지나치게 잘아진다. 시그널타워\n * (바닥 160 x 160, 높이 1100)는 평면 기준으로 간격이 20 이 나와서, 옆에서 보면 높이가\n * 격자 55 칸이 된다. 격자는 크기를 가늠하는 자이므로 모델 전체에 대해 읽혀야 한다.\n */\nexport function gridStep(draft: FigureDraft): number {\n return niceStep(Math.max(draft.width, draft.height, draft.depth))\n}\n\n/**\n * 주어진 길이에 어울리는 **읽기 좋은** 격자 간격. 다섯 칸 정도가 되게 잡는다.\n *\n * 그냥 나누면 27·33 같은 값이 나온다. 그 격자에 맞추면 좌표가 54·81 이 되어 읽기\n * 어렵다. 1·2·5 배수로 올림한다 — 자와 차트 눈금이 쓰는 방식이다.\n *\n * 격자가 성겨야 형상이 눈에 먼저 들어온다. 열 칸으로 잡았더니 격자가 촘촘해 배경이\n * 먼저 보였다.\n */\nexport function niceStep(span: number): number {\n const raw = Math.max(span, 1) / 5\n const magnitude = Math.pow(10, Math.floor(Math.log10(raw)))\n const n = raw / magnitude\n\n const multiple = n < 1.5 ? 1 : n < 3.5 ? 2 : n < 7.5 ? 5 : 10\n return Math.max(1, multiple * magnitude)\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FigureDraft } from './figure-source.js';
|
|
2
|
+
/** 무엇이 달라지나 — 이름과 바뀔 값. 비어 있으면 앉힐 것이 없다. */
|
|
3
|
+
export declare function poseChanges(draft: FigureDraft | undefined, values: Record<string, number>): {
|
|
4
|
+
name: string;
|
|
5
|
+
from: number;
|
|
6
|
+
to: number;
|
|
7
|
+
}[];
|
|
8
|
+
/** 지금 자세를 기본값으로 앉힌 초안. 앉힐 것이 없으면 **받은 초안 그대로**다. */
|
|
9
|
+
export declare function poseAsDefaults(draft: FigureDraft, values: Record<string, number>): FigureDraft;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*/
|
|
4
|
+
/** 지금 이 파라미터가 서는 값. 형식의 `valueOf` 와 같은 순서로 읽는다. */
|
|
5
|
+
function standing(parameter) {
|
|
6
|
+
return parameter.default ?? parameter.range?.min ?? 0;
|
|
7
|
+
}
|
|
8
|
+
/** 범위 밖은 끝에서 문다. 막대는 범위 안에서만 움직이지만, 값은 다른 길로도 들어온다. */
|
|
9
|
+
function settled(value, parameter) {
|
|
10
|
+
const range = parameter.range;
|
|
11
|
+
const bound = range ? Math.min(Math.max(value, range.min), range.max) : value;
|
|
12
|
+
return Math.round(bound * 10000) / 10000;
|
|
13
|
+
}
|
|
14
|
+
/** 무엇이 달라지나 — 이름과 바뀔 값. 비어 있으면 앉힐 것이 없다. */
|
|
15
|
+
export function poseChanges(draft, values) {
|
|
16
|
+
return (draft?.parameters ?? []).flatMap(parameter => {
|
|
17
|
+
const given = values[parameter.name];
|
|
18
|
+
if (given === undefined || !Number.isFinite(given))
|
|
19
|
+
return [];
|
|
20
|
+
const to = settled(given, parameter);
|
|
21
|
+
const from = standing(parameter);
|
|
22
|
+
return to === from ? [] : [{ name: parameter.name, from, to }];
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** 지금 자세를 기본값으로 앉힌 초안. 앉힐 것이 없으면 **받은 초안 그대로**다. */
|
|
26
|
+
export function poseAsDefaults(draft, values) {
|
|
27
|
+
const changes = new Map(poseChanges(draft, values).map(change => [change.name, change.to]));
|
|
28
|
+
if (changes.size === 0)
|
|
29
|
+
return draft;
|
|
30
|
+
return {
|
|
31
|
+
...draft,
|
|
32
|
+
parameters: (draft.parameters ?? []).map(parameter => changes.has(parameter.name) ? { ...parameter, default: changes.get(parameter.name) } : parameter)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=pose-defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pose-defaults.js","sourceRoot":"","sources":["../../client/modeller/pose-defaults.ts"],"names":[],"mappings":"AAAA;;GAEG;AA0BH,mDAAmD;AACnD,SAAS,QAAQ,CAAC,SAAoB;IACpC,OAAO,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAA;AACvD,CAAC;AAED,uDAAuD;AACvD,SAAS,OAAO,CAAC,KAAa,EAAE,SAAoB;IAClD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAA;IAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IAC7E,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,KAAK,CAAA;AAC1C,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,WAAW,CACzB,KAA8B,EAC9B,MAA8B;IAE9B,OAAO,CAAC,KAAK,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;QACnD,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;QACpC,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,CAAA;QAE7D,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;QACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAA;QAChC,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAA;IAChE,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,cAAc,CAAC,KAAkB,EAAE,MAA8B;IAC/E,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAC3F,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAEpC,OAAO;QACL,GAAG,KAAK;QACR,UAAU,EAAE,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CACnD,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAClG;KACF,CAAA;AACH,CAAC","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport type { FigureDraft } from './figure-source.js'\n\n/**\n * 지금 세워 둔 자세를 **기본값으로 앉힌다.**\n *\n * ## 왜 필요한가\n *\n * 관절의 휴지 자세는 값이 0 인 자세다(ADR-0066). 팔이 여섯 마디면 그 자세는 곧게 선 기둥이라,\n * 목록에서도 도면에서도 로봇으로 안 보인다. 로봇다운 자세는 **파라미터의 기본값**이 만든다 —\n * 어깨 -35, 팔꿈치 70 을 적어 두면 아무도 값을 주지 않을 때 그 자세로 선다.\n *\n * 그것을 손으로 적으려면 미리보기에서 막대를 맞춘 뒤 그 수를 읽어 파라미터 칸마다 옮겨야 한다.\n * 여섯 축이면 여섯 번이고, 옮기는 동안 화면의 자세와 적은 수가 갈린다. 여기서 하는 일은 그\n * 옮겨 적기 하나다.\n *\n * ## 무엇을 건드리나\n *\n * **막대를 민 파라미터만** 건드린다. 안 민 것은 이미 제 기본값에 서 있으므로 적을 것이 없다.\n * 값이 지금 기본값과 같으면 그 파라미터는 **그대로 둔다** — 새 객체로 바꾸면 고친 것이 없는데도\n * 「저장 안 됨」이 붙는다. 하나도 안 바뀌면 받은 초안을 그대로 돌려준다.\n */\n\ntype Parameter = NonNullable<FigureDraft['parameters']>[number]\n\n/** 지금 이 파라미터가 서는 값. 형식의 `valueOf` 와 같은 순서로 읽는다. */\nfunction standing(parameter: Parameter): number {\n return parameter.default ?? parameter.range?.min ?? 0\n}\n\n/** 범위 밖은 끝에서 문다. 막대는 범위 안에서만 움직이지만, 값은 다른 길로도 들어온다. */\nfunction settled(value: number, parameter: Parameter): number {\n const range = parameter.range\n const bound = range ? Math.min(Math.max(value, range.min), range.max) : value\n return Math.round(bound * 10000) / 10000\n}\n\n/** 무엇이 달라지나 — 이름과 바뀔 값. 비어 있으면 앉힐 것이 없다. */\nexport function poseChanges(\n draft: FigureDraft | undefined,\n values: Record<string, number>\n): { name: string; from: number; to: number }[] {\n return (draft?.parameters ?? []).flatMap(parameter => {\n const given = values[parameter.name]\n if (given === undefined || !Number.isFinite(given)) return []\n\n const to = settled(given, parameter)\n const from = standing(parameter)\n return to === from ? [] : [{ name: parameter.name, from, to }]\n })\n}\n\n/** 지금 자세를 기본값으로 앉힌 초안. 앉힐 것이 없으면 **받은 초안 그대로**다. */\nexport function poseAsDefaults(draft: FigureDraft, values: Record<string, number>): FigureDraft {\n const changes = new Map(poseChanges(draft, values).map(change => [change.name, change.to]))\n if (changes.size === 0) return draft\n\n return {\n ...draft,\n parameters: (draft.parameters ?? []).map(parameter =>\n changes.has(parameter.name) ? { ...parameter, default: changes.get(parameter.name)! } : parameter\n )\n }\n}\n"]}
|
|
@@ -19,7 +19,7 @@ import type { FigurePart, FigureSource } from '@hatiolab/figure-model';
|
|
|
19
19
|
* 기준 치수와 디테일 등급도 목록에 넣는다. 부품만 골라 받으면 새 부품이 옛 기준
|
|
20
20
|
* 상자 밖으로 나가 **검증에 걸릴 수 있다** — 무엇을 함께 받아야 하는지 보여야 한다.
|
|
21
21
|
*/
|
|
22
|
-
export type ChangeKind = 'added' | 'changed' | 'removed' | 'base' | 'placement' | 'detail' | 'styleKit' | 'animations' | 'parameters' | 'capabilities';
|
|
22
|
+
export type ChangeKind = 'added' | 'changed' | 'removed' | 'base' | 'placement' | 'detail' | 'styleKit' | 'animations' | 'parameters' | 'capabilities' | 'joints';
|
|
23
23
|
export interface ProposalChange {
|
|
24
24
|
kind: ChangeKind;
|
|
25
25
|
/** 부품 변경이면 부품 이름. 그 밖이면 무엇이 바뀌는지. */
|
|
@@ -10,11 +10,15 @@
|
|
|
10
10
|
* 말없이 사라진다. 그리고 `anchor` 는 도형 수준 필드였다가 형식에서 없어진 것인데(2026-09-10)
|
|
11
11
|
* 여기 남아 있었다 — 이 목록이 형식을 따라가지 않은 것이 양쪽으로 드러난 셈이다.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* 같은 일이 `joints` 로 한 번 더 났다(2026-09-18). 형식에 관절이 서고 저작 화면도 지었는데 이
|
|
14
|
+
* 목록만 그대로여서, 관절을 더해 온 후보는 변경 목록에 안 뜨고 받아도 관절이 사라졌다. 형식의
|
|
15
|
+
* 칸을 세는 시험이 그것을 잡았다.
|
|
16
|
+
*
|
|
17
|
+
* 형식의 `FigureSource` 는 `type` · `parts` 를 빼면 이 일곱이 전부다. `type` 은 저장되는
|
|
14
18
|
* 식별자라 후보가 바꿔 와도 받지 않고(`applyProposal` 참고), `version` 은 저작자가 고를 것이
|
|
15
19
|
* 아니라 저장할 때 서버가 찍는다.
|
|
16
20
|
*/
|
|
17
|
-
const FIGURE_FIELDS = ['base', 'placement', 'detailLevel', 'styleKit', 'animations', 'parameters', 'capabilities'];
|
|
21
|
+
const FIGURE_FIELDS = ['base', 'placement', 'detailLevel', 'styleKit', 'animations', 'parameters', 'capabilities', 'joints'];
|
|
18
22
|
const FIELD_KIND = {
|
|
19
23
|
base: 'base',
|
|
20
24
|
placement: 'placement',
|
|
@@ -22,7 +26,8 @@ const FIELD_KIND = {
|
|
|
22
26
|
styleKit: 'styleKit',
|
|
23
27
|
animations: 'animations',
|
|
24
28
|
parameters: 'parameters',
|
|
25
|
-
capabilities: 'capabilities'
|
|
29
|
+
capabilities: 'capabilities',
|
|
30
|
+
joints: 'joints'
|
|
26
31
|
};
|
|
27
32
|
/** 부품 둘이 같은가. 순서까지 같은 JSON 으로 견준다 — 필드 순서는 만드는 쪽이 정한다. */
|
|
28
33
|
function samePart(a, b) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposal.js","sourceRoot":"","sources":["../../client/modeller/proposal.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"proposal.js","sourceRoot":"","sources":["../../client/modeller/proposal.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FigurePart, FigureSource } from '@hatiolab/figure-model'\n\n/**\n * AI 후보를 지금 것과 비교하고, 고른 것만 받아들인다.\n *\n * 비교는 **저장 형식**으로 한다. 편집 중의 데이터는 씬 모델이지만, 후보는 서버에서\n * FigureSource 로 오고 기준 치수·디테일 등급처럼 부품 밖의 값도 함께 봐야 한다.\n *\n * 부품 편집 규칙은 여기 없다 — `part-edits.ts` 가 씬 모델 용어로 갖고 있다.\n */\n\n/**\n * 후보가 무엇을 바꾸자는 것인가.\n *\n * 후보를 통째로만 받게 두면 「몸통은 좋은데 모터 자리가 틀렸다」에 답할 수 없다 —\n * 그게 가장 흔한 반응이다. 그래서 후보를 **변경 목록**으로 본다.\n *\n * 부품은 이름으로 짝짓는다. `FigurePart.name` 이 저장되는 식별자이고 바인딩이\n * 그 이름을 가리키므로, 이름이 같으면 같은 부품이다.\n *\n * 기준 치수와 디테일 등급도 목록에 넣는다. 부품만 골라 받으면 새 부품이 옛 기준\n * 상자 밖으로 나가 **검증에 걸릴 수 있다** — 무엇을 함께 받아야 하는지 보여야 한다.\n */\nexport type ChangeKind =\n | 'added'\n | 'changed'\n | 'removed'\n | 'base'\n | 'placement'\n | 'detail'\n | 'styleKit'\n | 'animations'\n | 'parameters'\n | 'capabilities'\n | 'joints'\n\n/**\n * 부품 밖에서 바뀔 수 있는 것 전부.\n *\n * 여기 없는 필드는 **조용히 버려진다** — 후보가 바꿔 왔어도 목록에 안 뜨고 받아도\n * 반영되지 않는다. 형식에 필드가 늘면 여기에 더해야 한다.\n *\n * 그 일이 실제로 났다(2026-09-15). `animations` · `capabilities` · `placement` 셋이 빠져 있었다.\n * 후보가 애니메이션만 더해 오면 변경 목록이 **비어서** 「n건 반영」 단추가 잠기고, 저작자는\n * 후보를 받지도 버리지도 못한 채 멈춘다. 다른 변경과 섞여 오면 단추는 눌리는데 애니메이션만\n * 말없이 사라진다. 그리고 `anchor` 는 도형 수준 필드였다가 형식에서 없어진 것인데(2026-09-10)\n * 여기 남아 있었다 — 이 목록이 형식을 따라가지 않은 것이 양쪽으로 드러난 셈이다.\n *\n * 같은 일이 `joints` 로 한 번 더 났다(2026-09-18). 형식에 관절이 서고 저작 화면도 지었는데 이\n * 목록만 그대로여서, 관절을 더해 온 후보는 변경 목록에 안 뜨고 받아도 관절이 사라졌다. 형식의\n * 칸을 세는 시험이 그것을 잡았다.\n *\n * 형식의 `FigureSource` 는 `type` · `parts` 를 빼면 이 일곱이 전부다. `type` 은 저장되는\n * 식별자라 후보가 바꿔 와도 받지 않고(`applyProposal` 참고), `version` 은 저작자가 고를 것이\n * 아니라 저장할 때 서버가 찍는다.\n */\nconst FIGURE_FIELDS = ['base', 'placement', 'detailLevel', 'styleKit', 'animations', 'parameters', 'capabilities', 'joints'] as const\nconst FIELD_KIND: Record<(typeof FIGURE_FIELDS)[number], ChangeKind> = {\n base: 'base',\n placement: 'placement',\n detailLevel: 'detail',\n styleKit: 'styleKit',\n animations: 'animations',\n parameters: 'parameters',\n capabilities: 'capabilities',\n joints: 'joints'\n}\n\nexport interface ProposalChange {\n kind: ChangeKind\n /** 부품 변경이면 부품 이름. 그 밖이면 무엇이 바뀌는지. */\n key: string\n /** 부품 변경일 때 후보 쪽 부품. */\n part?: FigurePart\n}\n\n/** 부품 둘이 같은가. 순서까지 같은 JSON 으로 견준다 — 필드 순서는 만드는 쪽이 정한다. */\nfunction samePart(a: FigurePart, b: FigurePart): boolean {\n return JSON.stringify(sortKeys(a)) === JSON.stringify(sortKeys(b))\n}\n\n/** 키 순서를 맞춰 견줄 수 있게 한다. */\nfunction sortKeys(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortKeys)\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {}\n for (const key of Object.keys(value as object).sort()) {\n out[key] = sortKeys((value as Record<string, unknown>)[key])\n }\n return out\n }\n return value\n}\n\nexport function diffProposal(current: FigureSource | undefined, proposal: FigureSource): ProposalChange[] {\n const changes: ProposalChange[] = []\n const mine = new Map((current?.parts ?? []).map(part => [part.name, part]))\n const theirs = new Map(proposal.parts.map(part => [part.name, part]))\n\n for (const field of FIGURE_FIELDS) {\n if (!current) break\n const before = JSON.stringify(sortKeys(current[field]))\n const after = JSON.stringify(sortKeys(proposal[field]))\n if (before !== after) {\n changes.push({ kind: FIELD_KIND[field], key: field })\n }\n }\n\n for (const part of proposal.parts) {\n const old = mine.get(part.name)\n if (!old) changes.push({ kind: 'added', key: part.name, part })\n else if (!samePart(old, part)) changes.push({ kind: 'changed', key: part.name, part })\n }\n for (const part of current?.parts ?? []) {\n if (!theirs.has(part.name)) changes.push({ kind: 'removed', key: part.name })\n }\n\n return changes\n}\n\n/**\n * 고른 변경만 받는다.\n *\n * **타입 이름은 지금 것을 지킨다.** 만든 뒤에는 고칠 수 없는 저장 식별자이고,\n * 배치된 인스턴스가 그 이름으로 청사진을 찾는다. 후보가 다른 이름을 지어 왔어도\n * 그것 때문에 이미 놓인 것이 빈 자리가 되면 안 된다.\n *\n * `picked` 를 주지 않으면 전부 받는다 — 그것이 보통의 길이다.\n *\n * 더해지는 부품은 **뒤에 붙인다.** 후보가 놓은 자리에 끼워 넣으면, 일부만 골랐을\n * 때 순서가 어디서 온 것인지 알 수 없게 된다.\n */\nexport function applyProposal(\n current: FigureSource | undefined,\n proposal: FigureSource,\n picked?: Set<string>\n): FigureSource {\n const takes = (change: ProposalChange) => !picked || picked.has(change.key)\n const changes = diffProposal(current, proposal)\n\n if (!current) {\n return { ...proposal }\n }\n\n const removed = new Set(changes.filter(c => c.kind === 'removed' && takes(c)).map(c => c.key))\n const replaced = new Map(\n changes.filter(c => c.kind === 'changed' && takes(c)).map(c => [c.key, c.part as FigurePart])\n )\n const added = changes.filter(c => c.kind === 'added' && takes(c)).map(c => c.part as FigurePart)\n\n const parts = [\n ...current.parts.filter(part => !removed.has(part.name)).map(part => replaced.get(part.name) ?? part),\n ...added\n ]\n\n // 고른 필드만 후보 값으로 바꾼다. 안 고른 것은 지금 값이 남는다.\n const fields: Record<string, unknown> = {}\n for (const field of FIGURE_FIELDS) {\n const change = changes.find(c => c.key === field)\n if (change && takes(change)) {\n fields[field] = proposal[field]\n }\n }\n\n return { ...current, ...fields, parts } as FigureSource\n}\n\n/** 전부 받는다. */\nexport function takeProposal(current: FigureSource | undefined, proposal: FigureSource): FigureSource {\n return applyProposal(current, proposal)\n}\n"]}
|
|
@@ -1,54 +1,51 @@
|
|
|
1
1
|
import type { FigureCost } from '@hatiolab/figure-model';
|
|
2
2
|
/**
|
|
3
|
-
* 한
|
|
3
|
+
* 한 화면이 감당하는 양 — 「몇 개까지 편한가」의 분모.
|
|
4
4
|
*
|
|
5
|
-
* ##
|
|
5
|
+
* ## 놓은 개수가 아니라 **보이는 개수**다
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* 3D 는 요청이 있어야 다시 그린다. 가만히 서 있는 도형은 프레임마다 아무 값도 치르지 않는다 —
|
|
8
|
+
* 900 대를 놓아 두어도 60fps 다. 값을 치르는 것은 **카메라 안에 들어 있고 그 프레임에 다시
|
|
9
|
+
* 그려지는** 것뿐이다. 그래서 이 파일의 분모는 「한 도면에 놓는 수」가 아니라 「한 화면에 보이는
|
|
10
|
+
* 부품 묶음 수」다.
|
|
11
11
|
*
|
|
12
|
-
* ##
|
|
12
|
+
* ## 이 수는 잰 값이다
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* 전에는 설계 문서의 목표치(도면당 draw call 2,500)를 썼고, 그 문서가 스스로 「측정 없이 정한
|
|
15
|
+
* 목표」라고 적어 두었다. 2026-09-18 에 실기에서 쟀다
|
|
16
|
+
* (`things-scene/test/browser/measure-figure-frames.mjs`, Apple M5, 저작 화면과 같은 조명 · 그림자):
|
|
15
17
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 삼각형 ≤ 50 만
|
|
18
|
+
* 묶음 9 짜리 6 축 로봇 600 대 보이면 59.5 fps · 700 대에서 56.7 fps
|
|
19
|
+
* 묶음 2 짜리 컨베이어 1,600 대 보이면 59.3 fps · 2,500 대에서 36.8 fps
|
|
19
20
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* 숫자만 크게 내걸면 재 본 값처럼 읽힌다.
|
|
23
|
-
*/
|
|
24
|
-
/**
|
|
25
|
-
* 그림자 패스가 draw call 을 두 배로 만든다.
|
|
21
|
+
* 두 도형의 꺾이는 지점이 **인스턴스 × 묶음 = 3,600 ~ 5,400** 에 들어온다. 보수적으로 4,000 을
|
|
22
|
+
* 쓴다. 도형이 달라도 이 곱이 비슷한 데서 꺾이므로, 분모는 묶음 수로 둔다.
|
|
26
23
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* 한 번 더 그리기 때문이다. figure-model 의 `at.drawCalls` 는 색 패스만 센다.
|
|
24
|
+
* ⚠ 이 수는 **이 기계에서 이 씬으로** 잰 값이다. 더 낮은 GPU 나 브라우저에서는 더 일찍 꺾인다.
|
|
25
|
+
* 화면에는 잰 기계와 날짜를 함께 적는다 — 적지 않으면 어디서나 참인 수처럼 읽힌다.
|
|
30
26
|
*
|
|
31
|
-
* ##
|
|
27
|
+
* ## 그림자는 이미 들어 있다
|
|
32
28
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* 그래서 등급을 보지 않고 언제나 곱한다. 없는 규칙을 믿고 셈하면 화면이 「S 로 하면
|
|
38
|
-
* 두 배 놓을 수 있다」고 거짓말을 한다. things-scene 이 그 규칙을 구현하면 그때
|
|
39
|
-
* 등급을 보면 된다 — 그쪽에 알릴 것.
|
|
29
|
+
* 2026-09-05 에는 draw call 을 figure-model 의 예측과 견주며 그림자 패스가 두 배로 만든다고
|
|
30
|
+
* 쟀다. 지금 값은 **그림자를 켠 채 프레임 시간을 직접 잰 것**이라 그 배수가 이미 안에 있다.
|
|
31
|
+
* 다시 곱하면 두 번 세는 것이다.
|
|
40
32
|
*/
|
|
41
|
-
export declare const SHADOW_PASS_FACTOR = 2;
|
|
42
33
|
export declare const SCENE_BUDGET: {
|
|
43
|
-
/** 한
|
|
44
|
-
readonly
|
|
45
|
-
/** 한
|
|
34
|
+
/** 한 화면에 보이는 부품 묶음. 60fps 가 꺾이는 지점, 실측. */
|
|
35
|
+
readonly groupsOnScreen: 4000;
|
|
36
|
+
/** 한 화면의 삼각형. **이 수는 아직 안 쟀다** — 설계 문서의 목표치다. */
|
|
46
37
|
readonly triangles: 500000;
|
|
47
38
|
};
|
|
39
|
+
/** 어디서 언제 잰 값인가. 화면이 함께 보여 준다 — 적지 않으면 어디서나 참인 수로 읽힌다. */
|
|
40
|
+
export declare const MEASURED: {
|
|
41
|
+
readonly gpu: "Apple M5";
|
|
42
|
+
readonly on: "2026-09-18";
|
|
43
|
+
readonly fps: 60;
|
|
44
|
+
};
|
|
48
45
|
/** 무엇이 먼저 차나. */
|
|
49
|
-
export type BindingBudget = '
|
|
46
|
+
export type BindingBudget = 'groups' | 'triangles';
|
|
50
47
|
export interface FigureCapacity {
|
|
51
|
-
/** 이
|
|
48
|
+
/** 이 형상이 **한 화면에 몇 개 보일 때까지** 60fps 로 도나. */
|
|
52
49
|
instances: number;
|
|
53
50
|
/** 먼저 차는 예산. **여기부터 고치면 된다.** */
|
|
54
51
|
bound: BindingBudget;
|
|
@@ -1,49 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 한
|
|
2
|
+
* 한 화면이 감당하는 양 — 「몇 개까지 편한가」의 분모.
|
|
3
3
|
*
|
|
4
|
-
* ##
|
|
4
|
+
* ## 놓은 개수가 아니라 **보이는 개수**다
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* 3D 는 요청이 있어야 다시 그린다. 가만히 서 있는 도형은 프레임마다 아무 값도 치르지 않는다 —
|
|
7
|
+
* 900 대를 놓아 두어도 60fps 다. 값을 치르는 것은 **카메라 안에 들어 있고 그 프레임에 다시
|
|
8
|
+
* 그려지는** 것뿐이다. 그래서 이 파일의 분모는 「한 도면에 놓는 수」가 아니라 「한 화면에 보이는
|
|
9
|
+
* 부품 묶음 수」다.
|
|
10
10
|
*
|
|
11
|
-
* ##
|
|
11
|
+
* ## 이 수는 잰 값이다
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* 전에는 설계 문서의 목표치(도면당 draw call 2,500)를 썼고, 그 문서가 스스로 「측정 없이 정한
|
|
14
|
+
* 목표」라고 적어 두었다. 2026-09-18 에 실기에서 쟀다
|
|
15
|
+
* (`things-scene/test/browser/measure-figure-frames.mjs`, Apple M5, 저작 화면과 같은 조명 · 그림자):
|
|
14
16
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 삼각형 ≤ 50 만
|
|
17
|
+
* 묶음 9 짜리 6 축 로봇 600 대 보이면 59.5 fps · 700 대에서 56.7 fps
|
|
18
|
+
* 묶음 2 짜리 컨베이어 1,600 대 보이면 59.3 fps · 2,500 대에서 36.8 fps
|
|
18
19
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* 숫자만 크게 내걸면 재 본 값처럼 읽힌다.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* 그림자 패스가 draw call 을 두 배로 만든다.
|
|
25
|
-
*
|
|
26
|
-
* 실측이다(2026-09-05, Apple M5). 자산 500~5,500 어디서나 실측이 figure-model 예측의
|
|
27
|
-
* **정확히 2.00 배**로 나왔고, 그림자 맵을 끄면 예측과 같아졌다. 그림자 맵은 장면을
|
|
28
|
-
* 한 번 더 그리기 때문이다. figure-model 의 `at.drawCalls` 는 색 패스만 센다.
|
|
20
|
+
* 두 도형의 꺾이는 지점이 **인스턴스 × 묶음 = 3,600 ~ 5,400** 에 들어온다. 보수적으로 4,000 을
|
|
21
|
+
* 쓴다. 도형이 달라도 이 곱이 비슷한 데서 꺾이므로, 분모는 묶음 수로 둔다.
|
|
29
22
|
*
|
|
30
|
-
*
|
|
23
|
+
* ⚠ 이 수는 **이 기계에서 이 씬으로** 잰 값이다. 더 낮은 GPU 나 브라우저에서는 더 일찍 꺾인다.
|
|
24
|
+
* 화면에는 잰 기계와 날짜를 함께 적는다 — 적지 않으면 어디서나 참인 수처럼 읽힌다.
|
|
31
25
|
*
|
|
32
|
-
*
|
|
33
|
-
* S 면 이 곱을 빼려 했다. **그런데 등급 S 로 세워 재 보니 draw call 이 똑같이 두 배로
|
|
34
|
-
* 나왔다** — 그 규칙은 문서에만 있고 제품에는 없다.
|
|
26
|
+
* ## 그림자는 이미 들어 있다
|
|
35
27
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
28
|
+
* 2026-09-05 에는 draw call 을 figure-model 의 예측과 견주며 그림자 패스가 두 배로 만든다고
|
|
29
|
+
* 쟀다. 지금 값은 **그림자를 켠 채 프레임 시간을 직접 잰 것**이라 그 배수가 이미 안에 있다.
|
|
30
|
+
* 다시 곱하면 두 번 세는 것이다.
|
|
39
31
|
*/
|
|
40
|
-
export const SHADOW_PASS_FACTOR = 2;
|
|
41
32
|
export const SCENE_BUDGET = {
|
|
42
|
-
/** 한
|
|
43
|
-
|
|
44
|
-
/** 한
|
|
33
|
+
/** 한 화면에 보이는 부품 묶음. 60fps 가 꺾이는 지점, 실측. */
|
|
34
|
+
groupsOnScreen: 4000,
|
|
35
|
+
/** 한 화면의 삼각형. **이 수는 아직 안 쟀다** — 설계 문서의 목표치다. */
|
|
45
36
|
triangles: 500_000
|
|
46
37
|
};
|
|
38
|
+
/** 어디서 언제 잰 값인가. 화면이 함께 보여 준다 — 적지 않으면 어디서나 참인 수로 읽힌다. */
|
|
39
|
+
export const MEASURED = { gpu: 'Apple M5', on: '2026-09-18', fps: 60 };
|
|
47
40
|
/**
|
|
48
41
|
* 몇 개까지 놓을 수 있나.
|
|
49
42
|
*
|
|
@@ -69,12 +62,10 @@ export function capacityFrom(groups, triangles) {
|
|
|
69
62
|
if (groups <= 0 && triangles <= 0) {
|
|
70
63
|
return undefined;
|
|
71
64
|
}
|
|
72
|
-
|
|
73
|
-
const calls = groups * SHADOW_PASS_FACTOR;
|
|
74
|
-
const byDrawCalls = calls > 0 ? Math.floor(SCENE_BUDGET.drawCalls / calls) : Infinity;
|
|
65
|
+
const byGroups = groups > 0 ? Math.floor(SCENE_BUDGET.groupsOnScreen / groups) : Infinity;
|
|
75
66
|
const byTriangles = triangles > 0 ? Math.floor(SCENE_BUDGET.triangles / triangles) : Infinity;
|
|
76
|
-
return
|
|
77
|
-
? { instances:
|
|
67
|
+
return byGroups <= byTriangles
|
|
68
|
+
? { instances: byGroups, bound: 'groups', per: groups, budget: SCENE_BUDGET.groupsOnScreen }
|
|
78
69
|
: { instances: byTriangles, bound: 'triangles', per: triangles, budget: SCENE_BUDGET.triangles };
|
|
79
70
|
}
|
|
80
71
|
//# sourceMappingURL=scene-budget.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-budget.js","sourceRoot":"","sources":["../../client/modeller/scene-budget.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"scene-budget.js","sourceRoot":"","sources":["../../client/modeller/scene-budget.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,2CAA2C;IAC3C,cAAc,EAAE,IAAI;IACpB,iDAAiD;IACjD,SAAS,EAAE,OAAO;CACV,CAAA;AAEV,0DAA0D;AAC1D,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,YAAY,EAAE,GAAG,EAAE,EAAE,EAAW,CAAA;AAgB/E;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,IAAgB;IACzC,OAAO,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAA;AAClD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,SAAiB;IAC5D,2CAA2C;IAC3C,IAAI,MAAM,IAAI,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QAClC,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;IACzF,MAAM,WAAW,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;IAE7F,OAAO,QAAQ,IAAI,WAAW;QAC5B,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,cAAc,EAAE;QAC5F,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,SAAS,EAAE,CAAA;AACpG,CAAC","sourcesContent":["import type { FigureCost } from '@hatiolab/figure-model'\n\n/**\n * 한 화면이 감당하는 양 — 「몇 개까지 편한가」의 분모.\n *\n * ## 놓은 개수가 아니라 **보이는 개수**다\n *\n * 3D 는 요청이 있어야 다시 그린다. 가만히 서 있는 도형은 프레임마다 아무 값도 치르지 않는다 —\n * 900 대를 놓아 두어도 60fps 다. 값을 치르는 것은 **카메라 안에 들어 있고 그 프레임에 다시\n * 그려지는** 것뿐이다. 그래서 이 파일의 분모는 「한 도면에 놓는 수」가 아니라 「한 화면에 보이는\n * 부품 묶음 수」다.\n *\n * ## 이 수는 잰 값이다\n *\n * 전에는 설계 문서의 목표치(도면당 draw call 2,500)를 썼고, 그 문서가 스스로 「측정 없이 정한\n * 목표」라고 적어 두었다. 2026-09-18 에 실기에서 쟀다\n * (`things-scene/test/browser/measure-figure-frames.mjs`, Apple M5, 저작 화면과 같은 조명 · 그림자):\n *\n * 묶음 9 짜리 6 축 로봇 600 대 보이면 59.5 fps · 700 대에서 56.7 fps\n * 묶음 2 짜리 컨베이어 1,600 대 보이면 59.3 fps · 2,500 대에서 36.8 fps\n *\n * 두 도형의 꺾이는 지점이 **인스턴스 × 묶음 = 3,600 ~ 5,400** 에 들어온다. 보수적으로 4,000 을\n * 쓴다. 도형이 달라도 이 곱이 비슷한 데서 꺾이므로, 분모는 묶음 수로 둔다.\n *\n * ⚠ 이 수는 **이 기계에서 이 씬으로** 잰 값이다. 더 낮은 GPU 나 브라우저에서는 더 일찍 꺾인다.\n * 화면에는 잰 기계와 날짜를 함께 적는다 — 적지 않으면 어디서나 참인 수처럼 읽힌다.\n *\n * ## 그림자는 이미 들어 있다\n *\n * 2026-09-05 에는 draw call 을 figure-model 의 예측과 견주며 그림자 패스가 두 배로 만든다고\n * 쟀다. 지금 값은 **그림자를 켠 채 프레임 시간을 직접 잰 것**이라 그 배수가 이미 안에 있다.\n * 다시 곱하면 두 번 세는 것이다.\n */\n\nexport const SCENE_BUDGET = {\n /** 한 화면에 보이는 부품 묶음. 60fps 가 꺾이는 지점, 실측. */\n groupsOnScreen: 4000,\n /** 한 화면의 삼각형. **이 수는 아직 안 쟀다** — 설계 문서의 목표치다. */\n triangles: 500_000\n} as const\n\n/** 어디서 언제 잰 값인가. 화면이 함께 보여 준다 — 적지 않으면 어디서나 참인 수로 읽힌다. */\nexport const MEASURED = { gpu: 'Apple M5', on: '2026-09-18', fps: 60 } as const\n\n/** 무엇이 먼저 차나. */\nexport type BindingBudget = 'groups' | 'triangles'\n\nexport interface FigureCapacity {\n /** 이 형상이 **한 화면에 몇 개 보일 때까지** 60fps 로 도나. */\n instances: number\n /** 먼저 차는 예산. **여기부터 고치면 된다.** */\n bound: BindingBudget\n /** 인스턴스 하나가 무는 값. */\n per: number\n /** 그 예산의 한 도면 총량. */\n budget: number\n}\n\n/**\n * 몇 개까지 놓을 수 있나.\n *\n * 두 예산 중 **먼저 차는 쪽**이 답이다. 평균이나 곱을 내지 않는다 — 하나만 꽉 차도\n * 그것이 병목이고, 저작자가 고쳐야 할 곳은 그 하나다.\n *\n * 셈은 여기서 하지만 재료는 전부 figure-model 이 낸 것이다. `cost.groups` 가 인스턴스\n * 하나당 draw call 이고 `cost.triangles` 가 인스턴스 하나의 삼각형 수다.\n */\nexport function capacityOf(cost: FigureCost): FigureCapacity | undefined {\n return capacityFrom(cost.groups, cost.triangles)\n}\n\n/**\n * 저장된 값만으로 셈한다.\n *\n * 목록은 정본을 받아 오지 않는다 — 카드 수십 장마다 컴파일하면 목록이 무거워진다.\n * 대신 서버가 저장해 둔 `groups` · `triangles` 를 쓴다. **목록과 저작면이 같은 함수를\n * 쓰는 것**이 요점이다. 카드가 제 나름대로 셈하면 같은 형상이 두 화면에서 다른 개수로\n * 나온다.\n */\nexport function capacityFrom(groups: number, triangles: number): FigureCapacity | undefined {\n // 부품이 없으면 「무한히 놓을 수 있다」가 아니라 **답할 것이 없다**.\n if (groups <= 0 && triangles <= 0) {\n return undefined\n }\n\n const byGroups = groups > 0 ? Math.floor(SCENE_BUDGET.groupsOnScreen / groups) : Infinity\n const byTriangles = triangles > 0 ? Math.floor(SCENE_BUDGET.triangles / triangles) : Infinity\n\n return byGroups <= byTriangles\n ? { instances: byGroups, bound: 'groups', per: groups, budget: SCENE_BUDGET.groupsOnScreen }\n : { instances: byTriangles, bound: 'triangles', per: triangles, budget: SCENE_BUDGET.triangles }\n}\n\n"]}
|
|
@@ -8,7 +8,7 @@ import { i18next, localize } from '@operato/i18n';
|
|
|
8
8
|
import { p13n } from '@operato/p13n';
|
|
9
9
|
import { auth } from '@things-factory/auth-base/dist-client';
|
|
10
10
|
import { navigate, PageView } from '@operato/shell';
|
|
11
|
-
import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles } from '@operato/styles';
|
|
11
|
+
import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles';
|
|
12
12
|
import { capacityFrom } from '../modeller/scene-budget.js';
|
|
13
13
|
import { deleteFigure, fetchFigureList } from '../graphql/index.js';
|
|
14
14
|
/**
|
|
@@ -68,14 +68,13 @@ const FigureListPageBase = p13n(localize(i18next)(PageView));
|
|
|
68
68
|
*/
|
|
69
69
|
let FigureListPage = class FigureListPage extends FigureListPageBase {
|
|
70
70
|
static { this.styles = [
|
|
71
|
+
/* The frame owns the page shell; the grist is the content it sizes (ADR-0060 decision 8). */
|
|
72
|
+
WorkSurfacePageStyles,
|
|
71
73
|
ScrollbarStyles,
|
|
72
74
|
CommonGristStyles,
|
|
73
75
|
CommonHeaderStyles,
|
|
74
76
|
css `
|
|
75
77
|
:host {
|
|
76
|
-
display: flex;
|
|
77
|
-
flex-direction: column;
|
|
78
|
-
width: 100%;
|
|
79
78
|
background-color: var(--md-sys-color-surface);
|
|
80
79
|
color: var(--md-sys-color-on-surface);
|
|
81
80
|
/* 떠 있는 단추의 기준 상자 */
|
|
@@ -119,11 +118,6 @@ let FigureListPage = class FigureListPage extends FigureListPageBase {
|
|
|
119
118
|
}
|
|
120
119
|
|
|
121
120
|
/* 격자가 바깥에 요구하는 것은 확정된 높이 하나뿐이다 — 스크롤은 격자가 안에서 한다 */
|
|
122
|
-
ox-grist {
|
|
123
|
-
flex: 1;
|
|
124
|
-
min-height: 0;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
121
|
/*
|
|
128
122
|
── 그림이 주인공인 카탈로그 ────────────────────────────────────────
|
|
129
123
|
카드 기본값은 글이 많은 목록(직원·결재함)에 맞춰져 있다. 여기서는 사람이 먼저 보는 것이
|
|
@@ -192,6 +186,7 @@ let FigureListPage = class FigureListPage extends FigureListPageBase {
|
|
|
192
186
|
render() {
|
|
193
187
|
return html `
|
|
194
188
|
<ox-grist
|
|
189
|
+
class="page-fill"
|
|
195
190
|
mode="CARD"
|
|
196
191
|
@search-tag=${(e) => this.findByTag(e.detail.tag)}
|
|
197
192
|
.config=${this.gristConfig}
|