@things-factory/figure-ui 10.1.83 → 10.1.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/client/modeller/command-preview.ts +4 -2
  2. package/client/modeller/drawn-shapes.ts +29 -0
  3. package/client/modeller/editor.ts +17 -1
  4. package/client/modeller/figure-material-panel.ts +2 -3
  5. package/client/modeller/figure-parts.ts +13 -15
  6. package/client/modeller/figure-section-panel.ts +304 -0
  7. package/client/modeller/gizmo.ts +26 -16
  8. package/client/modeller/joint-fan.ts +4 -3
  9. package/client/modeller/links.ts +24 -24
  10. package/client/modeller/live.ts +8 -4
  11. package/client/modeller/maturity.ts +12 -6
  12. package/client/modeller/motion-ends.ts +2 -1
  13. package/client/modeller/refusals.ts +20 -0
  14. package/client/modeller/renderer.ts +3 -2
  15. package/client/modeller/section-controls.ts +115 -0
  16. package/client/modeller/starting-shape.ts +34 -0
  17. package/dist-client/modeller/command-preview.js +4 -2
  18. package/dist-client/modeller/command-preview.js.map +1 -1
  19. package/dist-client/modeller/drawn-shapes.d.ts +44 -0
  20. package/dist-client/modeller/drawn-shapes.js +12 -0
  21. package/dist-client/modeller/drawn-shapes.js.map +1 -0
  22. package/dist-client/modeller/editor.d.ts +3 -0
  23. package/dist-client/modeller/editor.js +16 -1
  24. package/dist-client/modeller/editor.js.map +1 -1
  25. package/dist-client/modeller/figure-capability-panel.d.ts +1 -1
  26. package/dist-client/modeller/figure-clips.d.ts +1 -1
  27. package/dist-client/modeller/figure-controls.d.ts +1 -1
  28. package/dist-client/modeller/figure-links.d.ts +1 -1
  29. package/dist-client/modeller/figure-material-panel.d.ts +1 -1
  30. package/dist-client/modeller/figure-material-panel.js +2 -3
  31. package/dist-client/modeller/figure-material-panel.js.map +1 -1
  32. package/dist-client/modeller/figure-parts.d.ts +3 -2
  33. package/dist-client/modeller/figure-parts.js +7 -12
  34. package/dist-client/modeller/figure-parts.js.map +1 -1
  35. package/dist-client/modeller/figure-properties.d.ts +1 -1
  36. package/dist-client/modeller/figure-section-panel.d.ts +42 -0
  37. package/dist-client/modeller/figure-section-panel.js +289 -0
  38. package/dist-client/modeller/figure-section-panel.js.map +1 -0
  39. package/dist-client/modeller/gizmo.d.ts +3 -3
  40. package/dist-client/modeller/gizmo.js +24 -11
  41. package/dist-client/modeller/gizmo.js.map +1 -1
  42. package/dist-client/modeller/joint-fan.js +4 -3
  43. package/dist-client/modeller/joint-fan.js.map +1 -1
  44. package/dist-client/modeller/links.d.ts +4 -1
  45. package/dist-client/modeller/links.js +23 -25
  46. package/dist-client/modeller/links.js.map +1 -1
  47. package/dist-client/modeller/live.d.ts +3 -1
  48. package/dist-client/modeller/live.js +6 -3
  49. package/dist-client/modeller/live.js.map +1 -1
  50. package/dist-client/modeller/maturity.d.ts +3 -2
  51. package/dist-client/modeller/maturity.js +9 -6
  52. package/dist-client/modeller/maturity.js.map +1 -1
  53. package/dist-client/modeller/motion-ends.js +2 -1
  54. package/dist-client/modeller/motion-ends.js.map +1 -1
  55. package/dist-client/modeller/refusals.d.ts +0 -5
  56. package/dist-client/modeller/refusals.js +25 -0
  57. package/dist-client/modeller/refusals.js.map +1 -1
  58. package/dist-client/modeller/renderer.d.ts +3 -4
  59. package/dist-client/modeller/renderer.js +3 -2
  60. package/dist-client/modeller/renderer.js.map +1 -1
  61. package/dist-client/modeller/section-controls.d.ts +44 -0
  62. package/dist-client/modeller/section-controls.js +91 -0
  63. package/dist-client/modeller/section-controls.js.map +1 -0
  64. package/dist-client/modeller/starting-shape.d.ts +32 -0
  65. package/dist-client/modeller/starting-shape.js +37 -0
  66. package/dist-client/modeller/starting-shape.js.map +1 -0
  67. package/dist-client/tsconfig.tsbuildinfo +1 -1
  68. package/dist-server/tsconfig.tsbuildinfo +1 -1
  69. package/package.json +5 -5
  70. package/test/browser/acceptance-page.js +8 -0
  71. package/test/command-preview.test.ts +3 -2
  72. package/test/fixtures/joint-column.json +110 -15
  73. package/test/fixtures/joint-roller.json +66 -9
  74. package/test/fixtures/robot-arm-6dof.json +203 -27
  75. package/test/joint-fan.test.ts +10 -7
  76. package/test/links.test.ts +4 -4
  77. package/test/material.test.ts +7 -2
  78. package/test/palette-icons.test.ts +1 -1
  79. package/test/parts-wiring.test.ts +6 -2
  80. package/test/renderer.test.ts +2 -1
  81. package/test/resize-with-placement.test.ts +3 -3
  82. package/test/scene-canvas.test.ts +2 -1
  83. package/test/section-panel.test.ts +192 -0
  84. package/translations/en.json +24 -0
  85. package/translations/ja.json +24 -0
  86. package/translations/ko.json +24 -0
  87. package/translations/ms.json +24 -0
  88. package/translations/zh.json +24 -0
  89. package/dist-client/tsconfig.localkernel.tsbuildinfo +0 -1
@@ -0,0 +1,44 @@
1
+ import type { FigureCavityRequest, FigureEvaluatedShape } from '@hatiolab/figure-model';
2
+ type Axis = 'x' | 'y' | 'z';
3
+ type End = 'start' | 'end';
4
+ /** The face each side of a section is, and each end of a push: a push's start is its low side. */
5
+ export declare const SIDE_FACE: Record<string, string>;
6
+ export declare const END_FACE: (axis: Axis, end: End) => string;
7
+ /** The one value every entry holds, or undefined when they differ (or there are none). */
8
+ export declare const sameOf: (values: readonly (number | undefined)[]) => number | undefined;
9
+ export declare const mm: (value: number) => number;
10
+ /** The four sides of a push's section, in the kernel's order: the first plane axis low and high, then the second. */
11
+ export declare const sidesOf: (axis: Axis) => string[];
12
+ /** The wall a new inside starts with: a tenth of the section's narrower side, at least 1 mm. */
13
+ export declare function defaultWallOf(shape: FigureEvaluatedShape): number;
14
+ /**
15
+ * The inside as the panel asks for it: the part's own, with one thing changed. A part with none yet gets a default
16
+ * wall all round, a closed start as thick as the wall and an open end -- a tray standing on its floor.
17
+ */
18
+ export declare function cavityRequestOf(shape: FigureEvaluatedShape, patch?: Partial<FigureCavityRequest>): FigureCavityRequest;
19
+ /** The most a rectangle's corners can reach: half its narrower side (the kernel's CORNER_TOO_LARGE says where). */
20
+ export declare function cornerLimitOf(shape: FigureEvaluatedShape): number | undefined;
21
+ /** Whether a corner radius can be taken; if not, the most it can be. */
22
+ export declare function cornerRefusalOf(shape: FigureEvaluatedShape, radius: number): {
23
+ max: number;
24
+ } | null;
25
+ /** One side's wall, the others kept: it must leave room inside, so under the width across it less the opposite wall. */
26
+ export declare function sideWallLimitOf(shape: FigureEvaluatedShape, wall: Record<string, number>, side: string): number;
27
+ /** A wall must leave room inside: under half the narrower width the section has. */
28
+ export declare const wallLimitOf: (shape: FigureEvaluatedShape) => number;
29
+ export type FigureCavityAsk = {
30
+ code: 'wall-too-thick';
31
+ max: number;
32
+ } | {
33
+ code: 'ends-too-thick';
34
+ max: number;
35
+ } | {
36
+ code: 'not-positive';
37
+ };
38
+ /** Whether an inside can be taken, asked of the kernel; if not, what to change and the most it can be. */
39
+ export declare function cavityRefusalOf(shape: FigureEvaluatedShape, request: FigureCavityRequest): FigureCavityAsk | null;
40
+ /** What the inside makes of the part (§3.3): open at both ends a pipe, at one a container, at neither a closed space. */
41
+ export declare function cavityResultOf(cavity: NonNullable<FigureEvaluatedShape['cavity']>): 'pipe' | 'container' | 'sealed';
42
+ /** How deep the inside is along the push: the length less the closed ends. */
43
+ export declare function cavityDepthOf(shape: FigureEvaluatedShape): number;
44
+ export {};
@@ -0,0 +1,91 @@
1
+ import { FIGURE_SECTION_PLANE, checkFigureSection, figureExtrusionFeaturesOf } from '@hatiolab/figure-model';
2
+ /** The face each side of a section is, and each end of a push: a push's start is its low side. */
3
+ export const SIDE_FACE = { 'x-': 'left', 'x+': 'right', 'y-': 'bottom', 'y+': 'top', 'z-': 'back', 'z+': 'front' };
4
+ export const END_FACE = (axis, end) => SIDE_FACE[`${axis}${end === 'start' ? '-' : '+'}`];
5
+ /** The one value every entry holds, or undefined when they differ (or there are none). */
6
+ export const sameOf = (values) => values.length && values.every(v => v !== undefined && Math.abs(v - values[0]) < 1e-6) ? values[0] : undefined;
7
+ export const mm = (value) => Math.round(value * 1000) / 1000;
8
+ /** The four sides of a push's section, in the kernel's order: the first plane axis low and high, then the second. */
9
+ export const sidesOf = (axis) => FIGURE_SECTION_PLANE[axis].flatMap(a => [`${a}-`, `${a}+`]);
10
+ /** The section's widths across, one per plane axis: a rectangle's sides, an ellipse's diameters. */
11
+ function acrossOf(e) {
12
+ const s = e.section;
13
+ if (s.kind === 'rectangle')
14
+ return FIGURE_SECTION_PLANE[e.axis].map(a => s.size[a]);
15
+ if (s.kind === 'ellipse')
16
+ return FIGURE_SECTION_PLANE[e.axis].map(a => 2 * s.radius[a]);
17
+ return [];
18
+ }
19
+ /** The wall a new inside starts with: a tenth of the section's narrower side, at least 1 mm. */
20
+ export function defaultWallOf(shape) {
21
+ return Math.max(1, Math.round(Math.min(...acrossOf(shape.extrusion)) / 10));
22
+ }
23
+ /**
24
+ * The inside as the panel asks for it: the part's own, with one thing changed. A part with none yet gets a default
25
+ * wall all round, a closed start as thick as the wall and an open end -- a tray standing on its floor.
26
+ */
27
+ export function cavityRequestOf(shape, patch = {}) {
28
+ const cavity = shape.cavity;
29
+ return {
30
+ wall: cavity ? (sameOf(Object.values(cavity.wall)) ?? { ...cavity.wall }) : defaultWallOf(shape),
31
+ start: cavity?.start ?? { closed: defaultWallOf(shape) },
32
+ end: cavity?.end ?? 'open',
33
+ ...patch
34
+ };
35
+ }
36
+ /** The most a rectangle's corners can reach: half its narrower side (the kernel's CORNER_TOO_LARGE says where). */
37
+ export function cornerLimitOf(shape) {
38
+ const e = shape.extrusion;
39
+ if (e?.section.kind !== 'rectangle')
40
+ return undefined;
41
+ const [u, v] = FIGURE_SECTION_PLANE[e.axis];
42
+ const far = 2 * Math.max(...acrossOf(e));
43
+ const issues = checkFigureSection({ kind: 'rectangle', size: e.section.size, corner: { [u]: far, [v]: far } }, e.axis);
44
+ const maxes = issues.flatMap(i => (i.code === 'CORNER_TOO_LARGE' ? [i.max] : []));
45
+ return maxes.length ? Math.min(...maxes) : undefined;
46
+ }
47
+ /** Whether a corner radius can be taken; if not, the most it can be. */
48
+ export function cornerRefusalOf(shape, radius) {
49
+ const e = shape.extrusion;
50
+ if (e?.section.kind !== 'rectangle' || !(radius > 0))
51
+ return null;
52
+ const [u, v] = FIGURE_SECTION_PLANE[e.axis];
53
+ const issues = checkFigureSection({ kind: 'rectangle', size: e.section.size, corner: { [u]: radius, [v]: radius } }, e.axis);
54
+ return issues.some(i => i.code === 'CORNER_TOO_LARGE') ? { max: cornerLimitOf(shape) } : null;
55
+ }
56
+ /** One side's wall, the others kept: it must leave room inside, so under the width across it less the opposite wall. */
57
+ export function sideWallLimitOf(shape, wall, side) {
58
+ const e = shape.extrusion;
59
+ const axis = side[0];
60
+ const across = acrossOf(e)[FIGURE_SECTION_PLANE[e.axis].indexOf(axis)];
61
+ return across - (wall[`${axis}${side[1] === '-' ? '+' : '-'}`] ?? 0);
62
+ }
63
+ /** A wall must leave room inside: under half the narrower width the section has. */
64
+ export const wallLimitOf = (shape) => Math.min(...acrossOf(shape.extrusion)) / 2;
65
+ /** Whether an inside can be taken, asked of the kernel; if not, what to change and the most it can be. */
66
+ export function cavityRefusalOf(shape, request) {
67
+ const e = shape.extrusion;
68
+ const sides = sidesOf(e.axis);
69
+ const wall = typeof request.wall === 'number' ? Object.fromEntries(sides.map(s => [s, request.wall])) : request.wall;
70
+ const made = figureExtrusionFeaturesOf(e, [{ id: 'inside', wall: wall, start: request.start, end: request.end }]);
71
+ if (!('refused' in made))
72
+ return null;
73
+ const r = made.refused;
74
+ if (r.code === 'CAVITY' && r.refused === 'NO_ROOM_INSIDE')
75
+ return { code: 'wall-too-thick', max: mm(wallLimitOf(shape)) };
76
+ if (r.code === 'CAVITY_NO_LENGTH')
77
+ return { code: 'ends-too-thick', max: mm(e.end - e.start) };
78
+ return { code: 'not-positive' };
79
+ }
80
+ /** What the inside makes of the part (§3.3): open at both ends a pipe, at one a container, at neither a closed space. */
81
+ export function cavityResultOf(cavity) {
82
+ const open = [cavity.start, cavity.end].filter(e => e === 'open').length;
83
+ return open === 2 ? 'pipe' : open === 1 ? 'container' : 'sealed';
84
+ }
85
+ /** How deep the inside is along the push: the length less the closed ends. */
86
+ export function cavityDepthOf(shape) {
87
+ const e = shape.extrusion, c = shape.cavity;
88
+ const closed = (x) => (x === 'open' ? 0 : x.closed);
89
+ return mm(e.end - e.start - closed(c.start) - closed(c.end));
90
+ }
91
+ //# sourceMappingURL=section-controls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-controls.js","sourceRoot":"","sources":["../../client/modeller/section-controls.ts"],"names":[],"mappings":"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","sourcesContent":["import { FIGURE_SECTION_PLANE, checkFigureSection, figureExtrusionFeaturesOf } from '@hatiolab/figure-model'\nimport type { FigureCavityRequest, FigureEvaluatedShape } from '@hatiolab/figure-model'\n\n/*\n * What the 단면 section of the properties tab reads and asks (ADR-0100 §3.3): a pushed part's corners and its inside.\n * Whether a value can be taken is asked of the kernel's own section checks -- the screen keeps no rule of its own -- and\n * the most it can take is said beside the field, so a refusal names the value to type rather than only that it failed.\n */\n\ntype Axis = 'x' | 'y' | 'z'\ntype End = 'start' | 'end'\ntype Extrusion = NonNullable<FigureEvaluatedShape['extrusion']>\n\n/** The face each side of a section is, and each end of a push: a push's start is its low side. */\nexport const SIDE_FACE: Record<string, string> = { 'x-': 'left', 'x+': 'right', 'y-': 'bottom', 'y+': 'top', 'z-': 'back', 'z+': 'front' }\nexport const END_FACE = (axis: Axis, end: End) => SIDE_FACE[`${axis}${end === 'start' ? '-' : '+'}`]!\n\n/** The one value every entry holds, or undefined when they differ (or there are none). */\nexport const sameOf = (values: readonly (number | undefined)[]) =>\n values.length && values.every(v => v !== undefined && Math.abs(v - values[0]!) < 1e-6) ? values[0] : undefined\n\nexport const mm = (value: number) => Math.round(value * 1000) / 1000\n\n/** The four sides of a push's section, in the kernel's order: the first plane axis low and high, then the second. */\nexport const sidesOf = (axis: Axis) => FIGURE_SECTION_PLANE[axis].flatMap(a => [`${a}-`, `${a}+`])\n\n/** The section's widths across, one per plane axis: a rectangle's sides, an ellipse's diameters. */\nfunction acrossOf(e: Extrusion): number[] {\n const s = e.section\n if (s.kind === 'rectangle') return FIGURE_SECTION_PLANE[e.axis].map(a => s.size[a]!)\n if (s.kind === 'ellipse') return FIGURE_SECTION_PLANE[e.axis].map(a => 2 * s.radius[a]!)\n return []\n}\n\n/** The wall a new inside starts with: a tenth of the section's narrower side, at least 1 mm. */\nexport function defaultWallOf(shape: FigureEvaluatedShape): number {\n return Math.max(1, Math.round(Math.min(...acrossOf(shape.extrusion!)) / 10))\n}\n\n/**\n * The inside as the panel asks for it: the part's own, with one thing changed. A part with none yet gets a default\n * wall all round, a closed start as thick as the wall and an open end -- a tray standing on its floor.\n */\nexport function cavityRequestOf(shape: FigureEvaluatedShape, patch: Partial<FigureCavityRequest> = {}): FigureCavityRequest {\n const cavity = shape.cavity\n return {\n wall: cavity ? (sameOf(Object.values(cavity.wall)) ?? { ...cavity.wall }) : defaultWallOf(shape),\n start: cavity?.start ?? { closed: defaultWallOf(shape) },\n end: cavity?.end ?? 'open',\n ...patch\n }\n}\n\n/** The most a rectangle's corners can reach: half its narrower side (the kernel's CORNER_TOO_LARGE says where). */\nexport function cornerLimitOf(shape: FigureEvaluatedShape): number | undefined {\n const e = shape.extrusion\n if (e?.section.kind !== 'rectangle') return undefined\n const [u, v] = FIGURE_SECTION_PLANE[e.axis]\n const far = 2 * Math.max(...acrossOf(e))\n const issues = checkFigureSection({ kind: 'rectangle', size: e.section.size, corner: { [u]: far, [v]: far } }, e.axis)\n const maxes = issues.flatMap(i => (i.code === 'CORNER_TOO_LARGE' ? [i.max] : []))\n return maxes.length ? Math.min(...maxes) : undefined\n}\n\n/** Whether a corner radius can be taken; if not, the most it can be. */\nexport function cornerRefusalOf(shape: FigureEvaluatedShape, radius: number): { max: number } | null {\n const e = shape.extrusion\n if (e?.section.kind !== 'rectangle' || !(radius > 0)) return null\n const [u, v] = FIGURE_SECTION_PLANE[e.axis]\n const issues = checkFigureSection({ kind: 'rectangle', size: e.section.size, corner: { [u]: radius, [v]: radius } }, e.axis)\n return issues.some(i => i.code === 'CORNER_TOO_LARGE') ? { max: cornerLimitOf(shape)! } : null\n}\n\n/** One side's wall, the others kept: it must leave room inside, so under the width across it less the opposite wall. */\nexport function sideWallLimitOf(shape: FigureEvaluatedShape, wall: Record<string, number>, side: string): number {\n const e = shape.extrusion!\n const axis = side[0] as Axis\n const across = acrossOf(e)[FIGURE_SECTION_PLANE[e.axis].indexOf(axis)]!\n return across - (wall[`${axis}${side[1] === '-' ? '+' : '-'}`] ?? 0)\n}\n\n/** A wall must leave room inside: under half the narrower width the section has. */\nexport const wallLimitOf = (shape: FigureEvaluatedShape) => Math.min(...acrossOf(shape.extrusion!)) / 2\n\nexport type FigureCavityAsk =\n | { code: 'wall-too-thick'; max: number }\n | { code: 'ends-too-thick'; max: number }\n | { code: 'not-positive' }\n\n/** Whether an inside can be taken, asked of the kernel; if not, what to change and the most it can be. */\nexport function cavityRefusalOf(shape: FigureEvaluatedShape, request: FigureCavityRequest): FigureCavityAsk | null {\n const e = shape.extrusion!\n const sides = sidesOf(e.axis)\n const wall = typeof request.wall === 'number' ? Object.fromEntries(sides.map(s => [s, request.wall as number])) : request.wall\n const made = figureExtrusionFeaturesOf(e, [{ id: 'inside', wall: wall as Record<string, number>, start: request.start, end: request.end }])\n if (!('refused' in made)) return null\n const r = made.refused\n if (r.code === 'CAVITY' && r.refused === 'NO_ROOM_INSIDE') return { code: 'wall-too-thick', max: mm(wallLimitOf(shape)) }\n if (r.code === 'CAVITY_NO_LENGTH') return { code: 'ends-too-thick', max: mm(e.end - e.start) }\n return { code: 'not-positive' }\n}\n\n/** What the inside makes of the part (§3.3): open at both ends a pipe, at one a container, at neither a closed space. */\nexport function cavityResultOf(cavity: NonNullable<FigureEvaluatedShape['cavity']>): 'pipe' | 'container' | 'sealed' {\n const open = [cavity.start, cavity.end].filter(e => e === 'open').length\n return open === 2 ? 'pipe' : open === 1 ? 'container' : 'sealed'\n}\n\n/** How deep the inside is along the push: the length less the closed ends. */\nexport function cavityDepthOf(shape: FigureEvaluatedShape): number {\n const e = shape.extrusion!,\n c = shape.cavity!\n const closed = (x: typeof c.start) => (x === 'open' ? 0 : x.closed)\n return mm(e.end - e.start - closed(c.start) - closed(c.end))\n}\n"]}
@@ -0,0 +1,32 @@
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
+ export declare function startingShapeKindOf(shape: {
8
+ op: string;
9
+ params?: Record<string, unknown>;
10
+ } | undefined): StartingShapeKind | undefined;
11
+ /**
12
+ * The same word for an evaluated body, when only the shape the kernel evaluated is in hand (a row of the parts list):
13
+ * a door frame is the section with jambs, a drawn section the rest of the drawn ones.
14
+ */
15
+ export declare function startingShapeKindOfBody(shape: {
16
+ provider?: string;
17
+ dimensions?: Record<string, number>;
18
+ extrusion?: {
19
+ section: {
20
+ kind: string;
21
+ corner?: unknown;
22
+ };
23
+ };
24
+ cavity?: unknown;
25
+ revolution?: {
26
+ profile: {
27
+ kind: string;
28
+ };
29
+ };
30
+ } | undefined): StartingShapeKind | undefined;
31
+ /** Whether a body is round across -- a circle or an ellipse pushed, anything turned: it takes a segment count. */
32
+ export declare const isRoundBody: (shape: Parameters<typeof startingShapeKindOfBody>[0]) => boolean;
@@ -0,0 +1,37 @@
1
+ export function startingShapeKindOf(shape) {
2
+ const p = shape?.params ?? {};
3
+ if (shape?.op === 'revolve@1')
4
+ return p.profile === 'taper' ? 'frustum' : 'sphere';
5
+ if (shape?.op !== 'extrude@1')
6
+ return undefined;
7
+ if (p.section === 'ellipse')
8
+ return 'cylinder';
9
+ if (p.section === 'polygon')
10
+ return 'polygon';
11
+ if (p.section === 'portal')
12
+ return 'portal';
13
+ if (p.cavity)
14
+ return 'hollow-box';
15
+ return p.corner === 'round' ? 'rounded-box' : 'cube';
16
+ }
17
+ /**
18
+ * The same word for an evaluated body, when only the shape the kernel evaluated is in hand (a row of the parts list):
19
+ * a door frame is the section with jambs, a drawn section the rest of the drawn ones.
20
+ */
21
+ export function startingShapeKindOfBody(shape) {
22
+ if (shape?.revolution)
23
+ return shape.revolution.profile.kind === 'taper' ? 'frustum' : 'sphere';
24
+ const s = shape?.extrusion?.section;
25
+ if (!s)
26
+ return undefined;
27
+ if (s.kind === 'ellipse')
28
+ return 'cylinder';
29
+ if (s.kind === 'polygon')
30
+ return shape.dimensions?.jamb !== undefined ? 'portal' : 'polygon';
31
+ if (shape.cavity)
32
+ return 'hollow-box';
33
+ return s.corner ? 'rounded-box' : 'cube';
34
+ }
35
+ /** Whether a body is round across -- a circle or an ellipse pushed, anything turned: it takes a segment count. */
36
+ export const isRoundBody = (shape) => ['cylinder', 'sphere', 'frustum'].includes(startingShapeKindOfBody(shape) ?? '');
37
+ //# sourceMappingURL=starting-shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"starting-shape.js","sourceRoot":"","sources":["../../client/modeller/starting-shape.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,mBAAmB,CAAC,KAAmE;IACrG,MAAM,CAAC,GAAG,KAAK,EAAE,MAAM,IAAI,EAAE,CAAA;IAC7B,IAAI,KAAK,EAAE,EAAE,KAAK,WAAW;QAAE,OAAO,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAA;IAClF,IAAI,KAAK,EAAE,EAAE,KAAK,WAAW;QAAE,OAAO,SAAS,CAAA;IAC/C,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,UAAU,CAAA;IAC9C,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC7C,IAAI,CAAC,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAC3C,IAAI,CAAC,CAAC,MAAM;QAAE,OAAO,YAAY,CAAA;IACjC,OAAO,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAA;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAwM;IAC9O,IAAI,KAAK,EAAE,UAAU;QAAE,OAAO,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAA;IAC9F,MAAM,CAAC,GAAG,KAAK,EAAE,SAAS,EAAE,OAAO,CAAA;IACnC,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAA;IACxB,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,UAAU,CAAA;IAC3C,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,KAAM,CAAC,UAAU,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;IAC7F,IAAI,KAAM,CAAC,MAAM;QAAE,OAAO,YAAY,CAAA;IACtC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAA;AAC1C,CAAC;AAED,kHAAkH;AAClH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAoD,EAAE,EAAE,CAAC,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,uBAAuB,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAA","sourcesContent":["/**\n * What a person calls a part's shape (ADR-0100 §2.1): the starting shape it reads as -- 상자, 둥근 상자, 위가 열린 상자,\n * 원기둥, 문틀, 구, 원뿔, 그린 단면. A word for the screen, read from the shape node's own params; it is not a kind of\n * stored shape, and nothing is decided by it.\n */\nexport type StartingShapeKind = 'cube' | 'rounded-box' | 'hollow-box' | 'cylinder' | 'portal' | 'sphere' | 'frustum' | 'polygon'\n\nexport function startingShapeKindOf(shape: { op: string; params?: Record<string, unknown> } | undefined): StartingShapeKind | undefined {\n const p = shape?.params ?? {}\n if (shape?.op === 'revolve@1') return p.profile === 'taper' ? 'frustum' : 'sphere'\n if (shape?.op !== 'extrude@1') return undefined\n if (p.section === 'ellipse') return 'cylinder'\n if (p.section === 'polygon') return 'polygon'\n if (p.section === 'portal') return 'portal'\n if (p.cavity) return 'hollow-box'\n return p.corner === 'round' ? 'rounded-box' : 'cube'\n}\n\n/**\n * The same word for an evaluated body, when only the shape the kernel evaluated is in hand (a row of the parts list):\n * a door frame is the section with jambs, a drawn section the rest of the drawn ones.\n */\nexport function startingShapeKindOfBody(shape: { provider?: string; dimensions?: Record<string, number>; extrusion?: { section: { kind: string; corner?: unknown } }; cavity?: unknown; revolution?: { profile: { kind: string } } } | undefined): StartingShapeKind | undefined {\n if (shape?.revolution) return shape.revolution.profile.kind === 'taper' ? 'frustum' : 'sphere'\n const s = shape?.extrusion?.section\n if (!s) return undefined\n if (s.kind === 'ellipse') return 'cylinder'\n if (s.kind === 'polygon') return shape!.dimensions?.jamb !== undefined ? 'portal' : 'polygon'\n if (shape!.cavity) return 'hollow-box'\n return s.corner ? 'rounded-box' : 'cube'\n}\n\n/** Whether a body is round across -- a circle or an ellipse pushed, anything turned: it takes a segment count. */\nexport const isRoundBody = (shape: Parameters<typeof startingShapeKindOfBody>[0]) => ['cylinder', 'sphere', 'frustum'].includes(startingShapeKindOfBody(shape) ?? '')\n"]}