@things-factory/figure-ui 10.1.83 → 10.1.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/modeller/command-preview.ts +4 -2
- package/client/modeller/drawn-shapes.ts +29 -0
- package/client/modeller/editor.ts +8 -1
- package/client/modeller/figure-material-panel.ts +2 -3
- package/client/modeller/figure-parts.ts +9 -11
- package/client/modeller/figure-section-panel.ts +175 -0
- package/client/modeller/gizmo.ts +26 -16
- package/client/modeller/joint-fan.ts +4 -3
- package/client/modeller/links.ts +24 -24
- package/client/modeller/maturity.ts +12 -6
- package/client/modeller/motion-ends.ts +2 -1
- package/client/modeller/refusals.ts +20 -0
- package/client/modeller/renderer.ts +3 -2
- package/client/modeller/section-controls.ts +34 -0
- package/client/modeller/starting-shape.ts +34 -0
- package/dist-client/modeller/command-preview.js +4 -2
- package/dist-client/modeller/command-preview.js.map +1 -1
- package/dist-client/modeller/drawn-shapes.d.ts +44 -0
- package/dist-client/modeller/drawn-shapes.js +12 -0
- package/dist-client/modeller/drawn-shapes.js.map +1 -0
- package/dist-client/modeller/editor.d.ts +1 -0
- package/dist-client/modeller/editor.js +8 -1
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-capability-panel.d.ts +1 -1
- package/dist-client/modeller/figure-clips.d.ts +1 -1
- package/dist-client/modeller/figure-controls.d.ts +1 -1
- package/dist-client/modeller/figure-links.d.ts +1 -1
- package/dist-client/modeller/figure-material-panel.d.ts +1 -1
- package/dist-client/modeller/figure-material-panel.js +2 -3
- package/dist-client/modeller/figure-material-panel.js.map +1 -1
- package/dist-client/modeller/figure-parts.d.ts +3 -2
- package/dist-client/modeller/figure-parts.js +6 -9
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-properties.d.ts +1 -1
- package/dist-client/modeller/figure-section-panel.d.ts +28 -0
- package/dist-client/modeller/figure-section-panel.js +172 -0
- package/dist-client/modeller/figure-section-panel.js.map +1 -0
- package/dist-client/modeller/gizmo.d.ts +3 -3
- package/dist-client/modeller/gizmo.js +24 -11
- package/dist-client/modeller/gizmo.js.map +1 -1
- package/dist-client/modeller/joint-fan.js +4 -3
- package/dist-client/modeller/joint-fan.js.map +1 -1
- package/dist-client/modeller/links.d.ts +4 -1
- package/dist-client/modeller/links.js +23 -25
- package/dist-client/modeller/links.js.map +1 -1
- package/dist-client/modeller/maturity.d.ts +3 -2
- package/dist-client/modeller/maturity.js +9 -6
- package/dist-client/modeller/maturity.js.map +1 -1
- package/dist-client/modeller/motion-ends.js +2 -1
- package/dist-client/modeller/motion-ends.js.map +1 -1
- package/dist-client/modeller/refusals.d.ts +0 -5
- package/dist-client/modeller/refusals.js +25 -0
- package/dist-client/modeller/refusals.js.map +1 -1
- package/dist-client/modeller/renderer.d.ts +3 -4
- package/dist-client/modeller/renderer.js +3 -2
- package/dist-client/modeller/renderer.js.map +1 -1
- package/dist-client/modeller/section-controls.d.ts +11 -0
- package/dist-client/modeller/section-controls.js +27 -0
- package/dist-client/modeller/section-controls.js.map +1 -0
- package/dist-client/modeller/starting-shape.d.ts +32 -0
- package/dist-client/modeller/starting-shape.js +37 -0
- package/dist-client/modeller/starting-shape.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/test/browser/acceptance-page.js +8 -0
- package/test/command-preview.test.ts +3 -2
- package/test/fixtures/joint-column.json +110 -15
- package/test/fixtures/joint-roller.json +66 -9
- package/test/fixtures/robot-arm-6dof.json +203 -27
- package/test/joint-fan.test.ts +10 -7
- package/test/links.test.ts +4 -4
- package/test/material.test.ts +7 -2
- package/test/renderer.test.ts +2 -1
- package/test/resize-with-placement.test.ts +3 -3
- package/test/scene-canvas.test.ts +2 -1
- package/test/section-panel.test.ts +93 -0
- package/translations/en.json +13 -0
- package/translations/ja.json +13 -0
- package/translations/ko.json +13 -0
- package/translations/ms.json +13 -0
- package/translations/zh.json +13 -0
- package/dist-client/tsconfig.localkernel.tsbuildinfo +0 -1
|
@@ -4,7 +4,7 @@ import { buildFigureGeometry } from '@hatiolab/things-scene'
|
|
|
4
4
|
/** Evaluated boundary: graph values are resolved before reaching the renderer. */
|
|
5
5
|
export interface FigureRenderPart {
|
|
6
6
|
id: string
|
|
7
|
-
shape: { provider: string; dimensions: Record<string, number>;
|
|
7
|
+
shape: { provider: string; dimensions: Record<string, number>; extrusion?: unknown; cavity?: unknown; revolution?: unknown }
|
|
8
8
|
pose: {r:number[][];t:number[]}
|
|
9
9
|
material: {token:string;preset?:string;transparent?:boolean;emissive?:{token:string;intensity:number;on:boolean}}
|
|
10
10
|
segments?:number
|
|
@@ -34,7 +34,8 @@ export function drawableFigureParts(geometry: readonly unknown[]): FigureRenderP
|
|
|
34
34
|
*/
|
|
35
35
|
function geometry(part: FigureRenderPart): THREE.BufferGeometry {
|
|
36
36
|
const built = buildFigureGeometry(part.shape as never, part.segments ?? 12) as THREE.BufferGeometry | undefined
|
|
37
|
-
|
|
37
|
+
/* A shape the builder does not know comes back empty: said, not drawn as nothing. */
|
|
38
|
+
if (!built?.getAttribute('position')) throw Error('Unsupported shape: ' + part.shape.provider)
|
|
38
39
|
return built
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { FigureCavityRequest, FigureEvaluatedShape } from '@hatiolab/figure-model'
|
|
2
|
+
|
|
3
|
+
/** The one value every entry holds, or undefined when they differ (or there are none). */
|
|
4
|
+
export const sameOf = (values: readonly (number | undefined)[]) =>
|
|
5
|
+
values.length && values.every(v => v !== undefined && Math.abs(v - values[0]!) < 1e-6) ? values[0] : undefined
|
|
6
|
+
|
|
7
|
+
export const mm = (value: number) => Math.round(value * 1000) / 1000
|
|
8
|
+
|
|
9
|
+
/** The wall a new inside starts with: a tenth of the section's narrower side, at least 1 mm. */
|
|
10
|
+
export function defaultWallOf(shape: FigureEvaluatedShape): number {
|
|
11
|
+
const section = shape.extrusion!.section
|
|
12
|
+
const across =
|
|
13
|
+
section.kind === 'rectangle'
|
|
14
|
+
? Object.values(section.size)
|
|
15
|
+
: section.kind === 'ellipse'
|
|
16
|
+
? Object.values(section.radius).map(r => 2 * r!)
|
|
17
|
+
: []
|
|
18
|
+
return Math.max(1, Math.round(Math.min(...(across as number[])) / 10))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The inside as the panel asks for it: the part's own, with one thing changed. A part with none yet gets a default
|
|
23
|
+
* wall all round, a closed start as thick as the wall and an open end -- a tray standing on its floor.
|
|
24
|
+
*/
|
|
25
|
+
export function cavityRequestOf(shape: FigureEvaluatedShape, patch: Partial<FigureCavityRequest> = {}): FigureCavityRequest {
|
|
26
|
+
const cavity = shape.cavity
|
|
27
|
+
return {
|
|
28
|
+
wall: cavity ? (sameOf(Object.values(cavity.wall)) ?? { ...cavity.wall }) : defaultWallOf(shape),
|
|
29
|
+
start: cavity?.start ?? { closed: defaultWallOf(shape) },
|
|
30
|
+
end: cavity?.end ?? 'open',
|
|
31
|
+
...patch
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a person calls a part's shape (ADR-0100 §2.1): the starting shape it reads as -- 상자, 둥근 상자, 위가 열린 상자,
|
|
3
|
+
* 원기둥, 문틀, 구, 원뿔, 그린 단면. A word for the screen, read from the shape node's own params; it is not a kind of
|
|
4
|
+
* stored shape, and nothing is decided by it.
|
|
5
|
+
*/
|
|
6
|
+
export type StartingShapeKind = 'cube' | 'rounded-box' | 'hollow-box' | 'cylinder' | 'portal' | 'sphere' | 'frustum' | 'polygon'
|
|
7
|
+
|
|
8
|
+
export function startingShapeKindOf(shape: { op: string; params?: Record<string, unknown> } | undefined): StartingShapeKind | undefined {
|
|
9
|
+
const p = shape?.params ?? {}
|
|
10
|
+
if (shape?.op === 'revolve@1') return p.profile === 'taper' ? 'frustum' : 'sphere'
|
|
11
|
+
if (shape?.op !== 'extrude@1') return undefined
|
|
12
|
+
if (p.section === 'ellipse') return 'cylinder'
|
|
13
|
+
if (p.section === 'polygon') return 'polygon'
|
|
14
|
+
if (p.section === 'portal') return 'portal'
|
|
15
|
+
if (p.cavity) return 'hollow-box'
|
|
16
|
+
return p.corner === 'round' ? 'rounded-box' : 'cube'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The same word for an evaluated body, when only the shape the kernel evaluated is in hand (a row of the parts list):
|
|
21
|
+
* a door frame is the section with jambs, a drawn section the rest of the drawn ones.
|
|
22
|
+
*/
|
|
23
|
+
export function startingShapeKindOfBody(shape: { provider?: string; dimensions?: Record<string, number>; extrusion?: { section: { kind: string; corner?: unknown } }; cavity?: unknown; revolution?: { profile: { kind: string } } } | undefined): StartingShapeKind | undefined {
|
|
24
|
+
if (shape?.revolution) return shape.revolution.profile.kind === 'taper' ? 'frustum' : 'sphere'
|
|
25
|
+
const s = shape?.extrusion?.section
|
|
26
|
+
if (!s) return undefined
|
|
27
|
+
if (s.kind === 'ellipse') return 'cylinder'
|
|
28
|
+
if (s.kind === 'polygon') return shape!.dimensions?.jamb !== undefined ? 'portal' : 'polygon'
|
|
29
|
+
if (shape!.cavity) return 'hollow-box'
|
|
30
|
+
return s.corner ? 'rounded-box' : 'cube'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Whether a body is round across -- a circle or an ellipse pushed, anything turned: it takes a segment count. */
|
|
34
|
+
export const isRoundBody = (shape: Parameters<typeof startingShapeKindOfBody>[0]) => ['cylinder', 'sphere', 'frustum'].includes(startingShapeKindOfBody(shape) ?? '')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { boxShape, discShape } from './drawn-shapes.js';
|
|
1
2
|
/*
|
|
2
3
|
* A command seen before it is done, as the modeller shows it (ADR-0097 §5.5, designer 2026-09-27): the bodies it would
|
|
3
4
|
* move, translucent where they would stand; the face they would meet, lit; and in words, how far and how each turns --
|
|
@@ -25,9 +26,10 @@ export function faceHighlightOf(face) {
|
|
|
25
26
|
/* Its own x · y · z are the face's two sides and its normal, as the kernel gives them. */
|
|
26
27
|
const r = [0, 1, 2].map(i => [face.u[i], face.normal[i], face.v[i]]);
|
|
27
28
|
const t = [0, 1, 2].map(i => face.centre[i] + face.normal[i] * (LIFT + THICKNESS / 2));
|
|
29
|
+
/* A plane touching a curved side is a line (Q3): drawn as a narrow strip, never a plate. */
|
|
28
30
|
const shape = face.round
|
|
29
|
-
?
|
|
30
|
-
:
|
|
31
|
+
? discShape(face.size.u / 2, THICKNESS)
|
|
32
|
+
: boxShape(Math.max(face.size.u, THICKNESS), THICKNESS, Math.max(face.size.v, THICKNESS));
|
|
31
33
|
/* Solid: a translucent plate over a light face was hardly seen (2026-09-27, :3391); it covers the face it names. */
|
|
32
34
|
return { id: TARGET_FACE, shape, pose: { r, t }, material: { token: TARGET_FACE } };
|
|
33
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-preview.js","sourceRoot":"","sources":["../../client/modeller/command-preview.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FigureBodyMove, FigureClipLoss, FigureFaceArea, FigureRowReading } from '@hatiolab/figure-model'\n\nimport type { FigureRenderPart } from './renderer.js'\n\n/*\n * A command seen before it is done, as the modeller shows it (ADR-0097 §5.5, designer 2026-09-27): the bodies it would\n * move, translucent where they would stand; the face they would meet, lit; and in words, how far and how each turns --\n * the turn as figure-model gives it, one axis and one angle in the figure's frame.\n */\n\n/* The material tokens of what a command would do: the bodies where they would stand, and the face they would meet. */\nexport const PREVIEW = '__preview'\nexport const TARGET_FACE = '__target-face'\n\nexport type Say = (key: string, params?: Record<string, string>) => string\n\n/** A preview as the editor holds it: bound to the figure, version and command it was worked out for. */\nexport interface HeldPreview {\n context: string\n revision: number\n /** The command, as text: a preview for another command is not this one's. */\n key: string\n geometry?: unknown[]\n moves?: FigureBodyMove[]\n /** Bodies it would draw that are not drawn now: a row's copies. */\n added?: string[]\n /** The row it would make, read on the preview's figure. */\n row?: FigureRowReading\n clipsWouldGo?: FigureClipLoss[]\n face?: FigureFaceArea\n /** Whether a face was asked for and could not be shown. */\n faceUnknown?: boolean\n /** Why the command would be refused; nothing else is shown then. */\n refused?: string\n}\n\n/** The moving bodies where they would stand, drawn translucent. The ones that stay are not drawn twice. */\nexport function ghostPartsOf(preview: HeldPreview | undefined): FigureRenderPart[] {\n if (!preview?.geometry || !(preview.moves?.length || preview.added?.length)) return []\n const moving = new Set([...(preview.moves ?? []).map(m => m.body), ...(preview.added ?? [])])\n return (preview.geometry as FigureRenderPart[])\n .filter(body => body && typeof body === 'object' && 'shape' in body && 'pose' in body && moving.has(body.id))\n .map(body => ({ ...body, id: `${PREVIEW}:${body.id}`, material: { token: PREVIEW, transparent: true } }))\n}\n\n/** How thick the lit face is, and how far off the face it floats, so it neither hides nor flickers with it. */\nconst THICKNESS = 2\nconst LIFT = 1\n\n/** The face the part would meet, lit: a thin plate over a flat face, a thin disc over a round one. */\nexport function faceHighlightOf(face: FigureFaceArea | undefined): FigureRenderPart | null {\n if (!face) return null\n /* Its own x · y · z are the face's two sides and its normal, as the kernel gives them. */\n const r = [0, 1, 2].map(i => [face.u[i]!, face.normal[i]!, face.v[i]!])\n const t = [0, 1, 2].map(i => face.centre[i]! + face.normal[i]! * (LIFT + THICKNESS / 2))\n const shape: FigureRenderPart['shape'] = face.round\n ? { provider: 'cylinder', dimensions: { radius: face.size.u / 2, length: THICKNESS } }\n : { provider: 'box', dimensions: { width: face.size.u, height: THICKNESS, depth: face.size.v } }\n /* Solid: a translucent plate over a light face was hardly seen (2026-09-27, :3391); it covers the face it names. */\n return { id: TARGET_FACE, shape, pose: { r, t }, material: { token: TARGET_FACE } }\n}\n\nconst mm = (v: number) => `${v > 0 ? '+' : v < 0 ? '−' : ''}${Math.abs(Math.round(v * 10) / 10).toLocaleString()}`\n\n/**\n * What the command would do, in words: the part's own move and turn, what goes with it, the clips it would stop, and\n * why nothing can be shown when the command would be refused.\n */\nexport function previewWordsOf(preview: HeldPreview | undefined, part: string, say: Say): string[] {\n if (!preview) return []\n if (preview.refused) return [say('figure.text.preview-refused', { reason: preview.refused })]\n /* A row: what it would be, read as drawn; its part does not move, it is copied. */\n if (preview.row) return [...rowWordsOf(preview.row, say), ...(preview.clipsWouldGo?.length ? [say('figure.text.preview-clips', { clips: preview.clipsWouldGo.map(c => c.clip).join(', ') })] : [])]\n const words: string[] = []\n const own = preview.moves?.find(m => m.body === part)\n if (!own) words.push(say('figure.text.preview-stays'))\n else {\n const axes = (['x', 'y', 'z'] as const).map((axis, i) => [axis, own.translate[i]!] as const).filter(([, v]) => Math.abs(v) >= 0.05)\n if (axes.length) words.push(say('figure.text.preview-moves', { by: axes.map(([axis, v]) => `${axis.toUpperCase()} ${mm(v)}`).join(' · ') }))\n if (own.turn) {\n /* Signed by the right hand about the axis, as the figure's frame has it. */\n const degrees = mm(own.turn.degrees)\n words.push(\n typeof own.turn.axis === 'string'\n ? say('figure.text.preview-turns-named', { axis: own.turn.axis.toUpperCase(), degrees })\n : say('figure.text.preview-turns-slanted', { axis: own.turn.axis.map(v => v.toFixed(2)).join(', '), degrees })\n )\n }\n }\n /* What goes with it: parts fastened to it, each row once, with its count. */\n const others = new Map<string, number>()\n for (const m of preview.moves ?? []) if (m.part !== part) others.set(m.part, (others.get(m.part) ?? 0) + 1)\n if (others.size)\n words.push(say('figure.text.preview-carries', { parts: [...others].map(([name, n]) => (n > 1 ? say('figure.text.preview-copies', { part: name, n: String(n) }) : name)).join(', ') }))\n if (preview.clipsWouldGo?.length) words.push(say('figure.text.preview-clips', { clips: preview.clipsWouldGo.map(c => c.clip).join(', ') }))\n if (preview.faceUnknown) words.push(say('figure.text.preview-face-unknown'))\n return words\n}\n\n/** A row in words, as figure-model reads it from what is drawn: how many, how far apart, what is left at the ends, what rides it. */\nexport function rowWordsOf(row: FigureRowReading | undefined, say: Say): string[] {\n if (!row) return []\n const words = [\n row.pitch !== undefined\n ? say('figure.text.row-count-pitch', { part: row.part, count: String(row.count), pitch: String(row.pitch), axis: row.axis.toUpperCase() })\n : say('figure.text.row-count-uneven', { part: row.part, count: String(row.count), axis: row.axis.toUpperCase() })\n ]\n if (!row.margins) words.push(say('figure.text.row-ends-unknown'))\n else if (row.centred) words.push(say('figure.text.row-ends-centred', { mm: String(row.margins.low) }))\n else words.push(say('figure.text.row-ends', { low: String(row.margins.low), high: String(row.margins.high) }))\n if (row.companions.length) words.push(say('figure.text.row-companions', { parts: row.companions.map(c => say('figure.text.preview-copies', { part: c.part, n: String(c.count) })).join(', ') }))\n return words\n}\n"]}
|
|
1
|
+
{"version":3,"file":"command-preview.js","sourceRoot":"","sources":["../../client/modeller/command-preview.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FigureBodyMove, FigureClipLoss, FigureFaceArea, FigureRowReading } from '@hatiolab/figure-model'\n\nimport type { FigureRenderPart } from './renderer.js'\nimport { boxShape, discShape } from './drawn-shapes.js'\n\n/*\n * A command seen before it is done, as the modeller shows it (ADR-0097 §5.5, designer 2026-09-27): the bodies it would\n * move, translucent where they would stand; the face they would meet, lit; and in words, how far and how each turns --\n * the turn as figure-model gives it, one axis and one angle in the figure's frame.\n */\n\n/* The material tokens of what a command would do: the bodies where they would stand, and the face they would meet. */\nexport const PREVIEW = '__preview'\nexport const TARGET_FACE = '__target-face'\n\nexport type Say = (key: string, params?: Record<string, string>) => string\n\n/** A preview as the editor holds it: bound to the figure, version and command it was worked out for. */\nexport interface HeldPreview {\n context: string\n revision: number\n /** The command, as text: a preview for another command is not this one's. */\n key: string\n geometry?: unknown[]\n moves?: FigureBodyMove[]\n /** Bodies it would draw that are not drawn now: a row's copies. */\n added?: string[]\n /** The row it would make, read on the preview's figure. */\n row?: FigureRowReading\n clipsWouldGo?: FigureClipLoss[]\n face?: FigureFaceArea\n /** Whether a face was asked for and could not be shown. */\n faceUnknown?: boolean\n /** Why the command would be refused; nothing else is shown then. */\n refused?: string\n}\n\n/** The moving bodies where they would stand, drawn translucent. The ones that stay are not drawn twice. */\nexport function ghostPartsOf(preview: HeldPreview | undefined): FigureRenderPart[] {\n if (!preview?.geometry || !(preview.moves?.length || preview.added?.length)) return []\n const moving = new Set([...(preview.moves ?? []).map(m => m.body), ...(preview.added ?? [])])\n return (preview.geometry as FigureRenderPart[])\n .filter(body => body && typeof body === 'object' && 'shape' in body && 'pose' in body && moving.has(body.id))\n .map(body => ({ ...body, id: `${PREVIEW}:${body.id}`, material: { token: PREVIEW, transparent: true } }))\n}\n\n/** How thick the lit face is, and how far off the face it floats, so it neither hides nor flickers with it. */\nconst THICKNESS = 2\nconst LIFT = 1\n\n/** The face the part would meet, lit: a thin plate over a flat face, a thin disc over a round one. */\nexport function faceHighlightOf(face: FigureFaceArea | undefined): FigureRenderPart | null {\n if (!face) return null\n /* Its own x · y · z are the face's two sides and its normal, as the kernel gives them. */\n const r = [0, 1, 2].map(i => [face.u[i]!, face.normal[i]!, face.v[i]!])\n const t = [0, 1, 2].map(i => face.centre[i]! + face.normal[i]! * (LIFT + THICKNESS / 2))\n /* A plane touching a curved side is a line (Q3): drawn as a narrow strip, never a plate. */\n const shape: FigureRenderPart['shape'] = face.round\n ? discShape(face.size.u / 2, THICKNESS)\n : boxShape(Math.max(face.size.u, THICKNESS), THICKNESS, Math.max(face.size.v, THICKNESS))\n /* Solid: a translucent plate over a light face was hardly seen (2026-09-27, :3391); it covers the face it names. */\n return { id: TARGET_FACE, shape, pose: { r, t }, material: { token: TARGET_FACE } }\n}\n\nconst mm = (v: number) => `${v > 0 ? '+' : v < 0 ? '−' : ''}${Math.abs(Math.round(v * 10) / 10).toLocaleString()}`\n\n/**\n * What the command would do, in words: the part's own move and turn, what goes with it, the clips it would stop, and\n * why nothing can be shown when the command would be refused.\n */\nexport function previewWordsOf(preview: HeldPreview | undefined, part: string, say: Say): string[] {\n if (!preview) return []\n if (preview.refused) return [say('figure.text.preview-refused', { reason: preview.refused })]\n /* A row: what it would be, read as drawn; its part does not move, it is copied. */\n if (preview.row) return [...rowWordsOf(preview.row, say), ...(preview.clipsWouldGo?.length ? [say('figure.text.preview-clips', { clips: preview.clipsWouldGo.map(c => c.clip).join(', ') })] : [])]\n const words: string[] = []\n const own = preview.moves?.find(m => m.body === part)\n if (!own) words.push(say('figure.text.preview-stays'))\n else {\n const axes = (['x', 'y', 'z'] as const).map((axis, i) => [axis, own.translate[i]!] as const).filter(([, v]) => Math.abs(v) >= 0.05)\n if (axes.length) words.push(say('figure.text.preview-moves', { by: axes.map(([axis, v]) => `${axis.toUpperCase()} ${mm(v)}`).join(' · ') }))\n if (own.turn) {\n /* Signed by the right hand about the axis, as the figure's frame has it. */\n const degrees = mm(own.turn.degrees)\n words.push(\n typeof own.turn.axis === 'string'\n ? say('figure.text.preview-turns-named', { axis: own.turn.axis.toUpperCase(), degrees })\n : say('figure.text.preview-turns-slanted', { axis: own.turn.axis.map(v => v.toFixed(2)).join(', '), degrees })\n )\n }\n }\n /* What goes with it: parts fastened to it, each row once, with its count. */\n const others = new Map<string, number>()\n for (const m of preview.moves ?? []) if (m.part !== part) others.set(m.part, (others.get(m.part) ?? 0) + 1)\n if (others.size)\n words.push(say('figure.text.preview-carries', { parts: [...others].map(([name, n]) => (n > 1 ? say('figure.text.preview-copies', { part: name, n: String(n) }) : name)).join(', ') }))\n if (preview.clipsWouldGo?.length) words.push(say('figure.text.preview-clips', { clips: preview.clipsWouldGo.map(c => c.clip).join(', ') }))\n if (preview.faceUnknown) words.push(say('figure.text.preview-face-unknown'))\n return words\n}\n\n/** A row in words, as figure-model reads it from what is drawn: how many, how far apart, what is left at the ends, what rides it. */\nexport function rowWordsOf(row: FigureRowReading | undefined, say: Say): string[] {\n if (!row) return []\n const words = [\n row.pitch !== undefined\n ? say('figure.text.row-count-pitch', { part: row.part, count: String(row.count), pitch: String(row.pitch), axis: row.axis.toUpperCase() })\n : say('figure.text.row-count-uneven', { part: row.part, count: String(row.count), axis: row.axis.toUpperCase() })\n ]\n if (!row.margins) words.push(say('figure.text.row-ends-unknown'))\n else if (row.centred) words.push(say('figure.text.row-ends-centred', { mm: String(row.margins.low) }))\n else words.push(say('figure.text.row-ends', { low: String(row.margins.low), high: String(row.margins.high) }))\n if (row.companions.length) words.push(say('figure.text.row-companions', { parts: row.companions.map(c => say('figure.text.preview-copies', { part: c.part, n: String(c.count) })).join(', ') }))\n return words\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shapes the modeller draws of its own -- a lit face, a joint's fan, a slide's line -- written as the shapes figure-model
|
|
3
|
+
* evaluates (ADR-0100): a section pushed along Y, centred. things-scene's one builder draws them as it draws a part.
|
|
4
|
+
*/
|
|
5
|
+
export interface DrawnShape {
|
|
6
|
+
provider: 'extrude';
|
|
7
|
+
dimensions: Record<string, number>;
|
|
8
|
+
extrusion: {
|
|
9
|
+
axis: 'y';
|
|
10
|
+
section: DrawnSection;
|
|
11
|
+
start: number;
|
|
12
|
+
end: number;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
type DrawnSection = {
|
|
16
|
+
kind: 'rectangle';
|
|
17
|
+
size: {
|
|
18
|
+
x: number;
|
|
19
|
+
z: number;
|
|
20
|
+
};
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'ellipse';
|
|
23
|
+
radius: {
|
|
24
|
+
x: number;
|
|
25
|
+
z: number;
|
|
26
|
+
};
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'polygon';
|
|
29
|
+
points: Array<{
|
|
30
|
+
id: string;
|
|
31
|
+
x: number;
|
|
32
|
+
z: number;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
/** A box: width along X, height along Y, depth along Z. */
|
|
36
|
+
export declare const boxShape: (width: number, height: number, depth: number) => DrawnShape;
|
|
37
|
+
/** A disc of a radius, `height` thick along Y. */
|
|
38
|
+
export declare const discShape: (radius: number, height: number) => DrawnShape;
|
|
39
|
+
/** A section in X-Z, `height` thick along Y. */
|
|
40
|
+
export declare const sectionShape: (points: ReadonlyArray<{
|
|
41
|
+
x: number;
|
|
42
|
+
z: number;
|
|
43
|
+
}>, height: number) => DrawnShape;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const centred = (section, height) => ({
|
|
2
|
+
provider: 'extrude',
|
|
3
|
+
dimensions: { length: height },
|
|
4
|
+
extrusion: { axis: 'y', section, start: -height / 2, end: height / 2 }
|
|
5
|
+
});
|
|
6
|
+
/** A box: width along X, height along Y, depth along Z. */
|
|
7
|
+
export const boxShape = (width, height, depth) => centred({ kind: 'rectangle', size: { x: width, z: depth } }, height);
|
|
8
|
+
/** A disc of a radius, `height` thick along Y. */
|
|
9
|
+
export const discShape = (radius, height) => centred({ kind: 'ellipse', radius: { x: radius, z: radius } }, height);
|
|
10
|
+
/** A section in X-Z, `height` thick along Y. */
|
|
11
|
+
export const sectionShape = (points, height) => centred({ kind: 'polygon', points: points.map((p, i) => ({ id: `p${i + 1}`, x: p.x, z: p.z })) }, height);
|
|
12
|
+
//# sourceMappingURL=drawn-shapes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drawn-shapes.js","sourceRoot":"","sources":["../../client/modeller/drawn-shapes.ts"],"names":[],"mappings":"AAcA,MAAM,OAAO,GAAG,CAAC,OAAqB,EAAE,MAAc,EAAc,EAAE,CAAC,CAAC;IACtE,QAAQ,EAAE,SAAS;IACnB,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;IAC9B,SAAS,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE;CACvE,CAAC,CAAA;AAEF,2DAA2D;AAC3D,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,MAAc,EAAE,KAAa,EAAc,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;AAE1J,kDAAkD;AAClD,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAc,EAAE,MAAc,EAAc,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,CAAA;AAE/I,gDAAgD;AAChD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAA+C,EAAE,MAAc,EAAc,EAAE,CAC1G,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA","sourcesContent":["/**\n * Shapes the modeller draws of its own -- a lit face, a joint's fan, a slide's line -- written as the shapes figure-model\n * evaluates (ADR-0100): a section pushed along Y, centred. things-scene's one builder draws them as it draws a part.\n */\nexport interface DrawnShape {\n provider: 'extrude'\n dimensions: Record<string, number>\n extrusion: { axis: 'y'; section: DrawnSection; start: number; end: number }\n}\ntype DrawnSection =\n | { kind: 'rectangle'; size: { x: number; z: number } }\n | { kind: 'ellipse'; radius: { x: number; z: number } }\n | { kind: 'polygon'; points: Array<{ id: string; x: number; z: number }> }\n\nconst centred = (section: DrawnSection, height: number): DrawnShape => ({\n provider: 'extrude',\n dimensions: { length: height },\n extrusion: { axis: 'y', section, start: -height / 2, end: height / 2 }\n})\n\n/** A box: width along X, height along Y, depth along Z. */\nexport const boxShape = (width: number, height: number, depth: number): DrawnShape => centred({ kind: 'rectangle', size: { x: width, z: depth } }, height)\n\n/** A disc of a radius, `height` thick along Y. */\nexport const discShape = (radius: number, height: number): DrawnShape => centred({ kind: 'ellipse', radius: { x: radius, z: radius } }, height)\n\n/** A section in X-Z, `height` thick along Y. */\nexport const sectionShape = (points: ReadonlyArray<{ x: number; z: number }>, height: number): DrawnShape =>\n centred({ kind: 'polygon', points: points.map((p, i) => ({ id: `p${i + 1}`, x: p.x, z: p.z })) }, height)\n"]}
|
|
@@ -6,6 +6,7 @@ import type { FigureProductSession } from './product-session.js';
|
|
|
6
6
|
import './canvas.js';
|
|
7
7
|
import './figure-parts.js';
|
|
8
8
|
import './figure-material-panel.js';
|
|
9
|
+
import './figure-section-panel.js';
|
|
9
10
|
import './figure-links.js';
|
|
10
11
|
import './figure-controls.js';
|
|
11
12
|
import './figure-clips.js';
|
|
@@ -14,6 +14,7 @@ import { drawableFigureParts } from './renderer.js';
|
|
|
14
14
|
import './canvas.js';
|
|
15
15
|
import './figure-parts.js';
|
|
16
16
|
import './figure-material-panel.js';
|
|
17
|
+
import './figure-section-panel.js';
|
|
17
18
|
import { faceHighlightOf, ghostPartsOf, previewWordsOf, rowWordsOf } from './command-preview.js';
|
|
18
19
|
import { motionEndsOf } from './motion-ends.js';
|
|
19
20
|
import './figure-links.js';
|
|
@@ -37,6 +38,7 @@ import { fieldWordsOf, followOf, measureNow, partSummaryOf, instanceRangeOf, ins
|
|
|
37
38
|
import { jointFanOf } from './joint-fan.js';
|
|
38
39
|
import { jointDragOf } from './joint-drag.js';
|
|
39
40
|
import { liveOverSession, NOTHING_LIVE, scrubbed, scrubStep } from './live.js';
|
|
41
|
+
import { startingShapeKindOfBody } from './starting-shape.js';
|
|
40
42
|
/** Product editor surface. The injected session is shared by page persistence and AI commands. */
|
|
41
43
|
let FigureEditor = class FigureEditor extends LitElement {
|
|
42
44
|
constructor() {
|
|
@@ -1368,7 +1370,7 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
1368
1370
|
}
|
|
1369
1371
|
rows.set(id, {
|
|
1370
1372
|
id,
|
|
1371
|
-
|
|
1373
|
+
kind: startingShapeKindOfBody(g.shape) ?? '',
|
|
1372
1374
|
hidden: this.hiddenParts.has(id),
|
|
1373
1375
|
invisible: g.visible === false,
|
|
1374
1376
|
...(id !== g.id ? { copies: 1 } : {}),
|
|
@@ -2047,6 +2049,11 @@ let FigureEditor = class FigureEditor extends LitElement {
|
|
|
2047
2049
|
${this.renderGroups(this.withSizeRow(groupFields(partFields, this.selected), held), 'figure.label.group-placement-size', 'figure.label.group-shape', unavailable, this.renderTools(unavailable), held)}
|
|
2048
2050
|
${roomFields.length ? this.renderRoomOnly(roomFields, unavailable) : nothing}
|
|
2049
2051
|
${this.renderLinks(unavailable, 'shape')}
|
|
2052
|
+
<figure-section-panel
|
|
2053
|
+
.body=${{ ...part, id: this.selected }}
|
|
2054
|
+
?locked=${unavailable}
|
|
2055
|
+
@command=${(e) => this.linkCommand(e.detail.action)}
|
|
2056
|
+
></figure-section-panel>
|
|
2050
2057
|
<figure-material-panel
|
|
2051
2058
|
.body=${{ ...part, id: this.selected }}
|
|
2052
2059
|
.palette=${view.palette ?? {}}
|