@things-factory/figure-ui 10.1.84 → 10.1.86
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/editor.ts +46 -2
- package/client/modeller/figure-parts.ts +4 -4
- package/client/modeller/figure-section-editor.ts +484 -0
- package/client/modeller/figure-section-panel.ts +211 -75
- package/client/modeller/live.ts +13 -4
- package/client/modeller/section-controls.ts +89 -8
- package/client/modeller/section-drawing.ts +97 -0
- package/dist-client/modeller/clips.d.ts +1 -1
- package/dist-client/modeller/editor.d.ts +8 -0
- package/dist-client/modeller/editor.js +51 -1
- package/dist-client/modeller/editor.js.map +1 -1
- package/dist-client/modeller/figure-parts.js +1 -3
- package/dist-client/modeller/figure-parts.js.map +1 -1
- package/dist-client/modeller/figure-section-editor.d.ts +62 -0
- package/dist-client/modeller/figure-section-editor.js +495 -0
- package/dist-client/modeller/figure-section-editor.js.map +1 -0
- package/dist-client/modeller/figure-section-panel.d.ts +26 -12
- package/dist-client/modeller/figure-section-panel.js +193 -69
- package/dist-client/modeller/figure-section-panel.js.map +1 -1
- package/dist-client/modeller/live.d.ts +3 -1
- package/dist-client/modeller/live.js +13 -3
- package/dist-client/modeller/live.js.map +1 -1
- package/dist-client/modeller/section-controls.d.ts +33 -0
- package/dist-client/modeller/section-controls.js +71 -7
- package/dist-client/modeller/section-controls.js.map +1 -1
- package/dist-client/modeller/section-drawing.d.ts +33 -0
- package/dist-client/modeller/section-drawing.js +82 -0
- package/dist-client/modeller/section-drawing.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/test/browser/acceptance-page.js +7 -0
- package/test/palette-icons.test.ts +1 -1
- package/test/parts-wiring.test.ts +6 -2
- package/test/section-editor.test.ts +192 -0
- package/test/section-panel.test.ts +108 -6
- package/translations/en.json +37 -2
- package/translations/ja.json +37 -2
- package/translations/ko.json +37 -2
- package/translations/ms.json +37 -2
- package/translations/zh.json +37 -2
|
@@ -1,15 +1,24 @@
|
|
|
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' ? '-' : '+'}`];
|
|
1
5
|
/** The one value every entry holds, or undefined when they differ (or there are none). */
|
|
2
6
|
export const sameOf = (values) => values.length && values.every(v => v !== undefined && Math.abs(v - values[0]) < 1e-6) ? values[0] : undefined;
|
|
3
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
|
+
}
|
|
4
19
|
/** The wall a new inside starts with: a tenth of the section's narrower side, at least 1 mm. */
|
|
5
20
|
export function defaultWallOf(shape) {
|
|
6
|
-
|
|
7
|
-
const across = section.kind === 'rectangle'
|
|
8
|
-
? Object.values(section.size)
|
|
9
|
-
: section.kind === 'ellipse'
|
|
10
|
-
? Object.values(section.radius).map(r => 2 * r)
|
|
11
|
-
: [];
|
|
12
|
-
return Math.max(1, Math.round(Math.min(...across) / 10));
|
|
21
|
+
return Math.max(1, Math.round(Math.min(...acrossOf(shape.extrusion)) / 10));
|
|
13
22
|
}
|
|
14
23
|
/**
|
|
15
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
|
|
@@ -24,4 +33,59 @@ export function cavityRequestOf(shape, patch = {}) {
|
|
|
24
33
|
...patch
|
|
25
34
|
};
|
|
26
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
|
+
}
|
|
27
91
|
//# sourceMappingURL=section-controls.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"section-controls.js","sourceRoot":"","sources":["../../client/modeller/section-controls.ts"],"names":[],"mappings":"AAEA,0FAA0F;AAC1F,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,MAAuC,EAAE,EAAE,CAChE,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AAEhH,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,CAAA;AAEpE,gGAAgG;AAChG,MAAM,UAAU,aAAa,CAAC,KAA2B;IACvD,MAAM,OAAO,GAAG,KAAK,CAAC,SAAU,CAAC,OAAO,CAAA;IACxC,MAAM,MAAM,GACV,OAAO,CAAC,IAAI,KAAK,WAAW;QAC1B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;QAC7B,CAAC,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS;YAC1B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAE,CAAC;YAChD,CAAC,CAAC,EAAE,CAAA;IACV,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAI,MAAmB,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;AACxE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAA2B,EAAE,QAAsC,EAAE;IACnG,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;IAC3B,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC;QAChG,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE;QACxD,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,MAAM;QAC1B,GAAG,KAAK;KACT,CAAA;AACH,CAAC","sourcesContent":["import type { FigureCavityRequest, FigureEvaluatedShape } from '@hatiolab/figure-model'\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 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 const section = shape.extrusion!.section\n const across =\n section.kind === 'rectangle'\n ? Object.values(section.size)\n : section.kind === 'ellipse'\n ? Object.values(section.radius).map(r => 2 * r!)\n : []\n return Math.max(1, Math.round(Math.min(...(across as number[])) / 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"]}
|
|
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,33 @@
|
|
|
1
|
+
import type { FigureSectionIssue, FigureSectionPoint } from '@hatiolab/figure-model';
|
|
2
|
+
type Axis = 'x' | 'y' | 'z';
|
|
3
|
+
export type Points = readonly FigureSectionPoint[];
|
|
4
|
+
/** The number a point is shown by: its name's number. */
|
|
5
|
+
export declare const pointLabel: (id: string) => string;
|
|
6
|
+
/** The next point's name: one past the highest number the loop has, so a new point never takes a gap's number. */
|
|
7
|
+
export declare function nextPointId(points: Points): string;
|
|
8
|
+
/** A point in the middle of the side from `from` to the point after it, whole millimetres; the new point's id with it. */
|
|
9
|
+
export declare function insertAfter(points: Points, from: string, axis: Axis): {
|
|
10
|
+
points: FigureSectionPoint[];
|
|
11
|
+
id: string;
|
|
12
|
+
};
|
|
13
|
+
/** The points less one, or why not: a section is three points or more. */
|
|
14
|
+
export declare function removePoint(points: Points, id: string): FigureSectionPoint[] | {
|
|
15
|
+
refused: 'too-few';
|
|
16
|
+
};
|
|
17
|
+
export declare function movePoint(points: Points, id: string, at: Partial<Record<Axis, number>>): FigureSectionPoint[];
|
|
18
|
+
/** What is wrong with the points, by the kernel's own check. */
|
|
19
|
+
export declare const issuesOf: (points: Points, axis: Axis) => FigureSectionIssue[];
|
|
20
|
+
/** An issue as the words that say it: an i18n key and its values. */
|
|
21
|
+
export declare function issueWords(issue: FigureSectionIssue, points: Points, axis: Axis): {
|
|
22
|
+
key: string;
|
|
23
|
+
params: Record<string, string>;
|
|
24
|
+
};
|
|
25
|
+
/** Every side and point an issue names, to mark on the drawing -- by more than colour: the list says them too. */
|
|
26
|
+
export declare function markedBy(issues: readonly FigureSectionIssue[]): {
|
|
27
|
+
sides: Set<string>;
|
|
28
|
+
points: Set<string>;
|
|
29
|
+
};
|
|
30
|
+
/** How far the drawing reaches from the origin: the points' farthest coordinate with room around, in 50 mm steps. */
|
|
31
|
+
export declare function extentOf(points: Points, axis: Axis): number;
|
|
32
|
+
export declare const POINT_LIMIT = 256;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { FIGURE_SECTION_PLANE, FIGURE_SECTION_POINT_LIMIT, checkFigureSection, figureSectionSidesOf } from '@hatiolab/figure-model';
|
|
2
|
+
/** The number a point is shown by: its name's number. */
|
|
3
|
+
export const pointLabel = (id) => /^p(\d+)$/.exec(id)?.[1] ?? id;
|
|
4
|
+
/** The next point's name: one past the highest number the loop has, so a new point never takes a gap's number. */
|
|
5
|
+
export function nextPointId(points) {
|
|
6
|
+
return `p${Math.max(0, ...points.map(p => Number(/^p(\d+)$/.exec(p.id)?.[1] ?? 0))) + 1}`;
|
|
7
|
+
}
|
|
8
|
+
/** A point in the middle of the side from `from` to the point after it, whole millimetres; the new point's id with it. */
|
|
9
|
+
export function insertAfter(points, from, axis) {
|
|
10
|
+
const [u, v] = FIGURE_SECTION_PLANE[axis];
|
|
11
|
+
const i = points.findIndex(p => p.id === from);
|
|
12
|
+
const a = points[i], b = points[(i + 1) % points.length];
|
|
13
|
+
const id = nextPointId(points);
|
|
14
|
+
const mid = { id, [u]: Math.round((a[u] + b[u]) / 2), [v]: Math.round((a[v] + b[v]) / 2) };
|
|
15
|
+
return { points: [...points.slice(0, i + 1), mid, ...points.slice(i + 1)], id };
|
|
16
|
+
}
|
|
17
|
+
/** The points less one, or why not: a section is three points or more. */
|
|
18
|
+
export function removePoint(points, id) {
|
|
19
|
+
if (points.length <= 3)
|
|
20
|
+
return { refused: 'too-few' };
|
|
21
|
+
return points.filter(p => p.id !== id);
|
|
22
|
+
}
|
|
23
|
+
export function movePoint(points, id, at) {
|
|
24
|
+
return points.map(p => (p.id === id ? { ...p, ...at } : p));
|
|
25
|
+
}
|
|
26
|
+
/** What is wrong with the points, by the kernel's own check. */
|
|
27
|
+
export const issuesOf = (points, axis) => checkFigureSection({ kind: 'polygon', points: [...points] }, axis);
|
|
28
|
+
/** A side said in the loop's order, 3–4 not 4–3: its id is the pair in name order, which a person never sees. */
|
|
29
|
+
function sideLabel(points, axis, side) {
|
|
30
|
+
const s = figureSectionSidesOf({ kind: 'polygon', points: [...points] }, axis).find(x => x.id === side);
|
|
31
|
+
const [a, b] = s?.from ? [s.from, s.to] : side.split('~');
|
|
32
|
+
return `${pointLabel(a)}–${pointLabel(b)}`;
|
|
33
|
+
}
|
|
34
|
+
/** An issue as the words that say it: an i18n key and its values. */
|
|
35
|
+
export function issueWords(issue, points, axis) {
|
|
36
|
+
const line = (s) => sideLabel(points, axis, s);
|
|
37
|
+
switch (issue.code) {
|
|
38
|
+
case 'SIDES_CROSS':
|
|
39
|
+
return { key: 'figure.text.section-issue-cross', params: { a: line(issue.sides[0]), b: line(issue.sides[1]) } };
|
|
40
|
+
case 'SIDES_TOUCH':
|
|
41
|
+
return { key: 'figure.text.section-issue-touch', params: { a: line(issue.sides[0]), b: line(issue.sides[1]) } };
|
|
42
|
+
case 'SIDES_FOLD':
|
|
43
|
+
return { key: 'figure.text.section-issue-fold', params: { a: line(issue.sides[0]), b: line(issue.sides[1]) } };
|
|
44
|
+
case 'ZERO_LENGTH_SIDE':
|
|
45
|
+
return { key: 'figure.text.section-issue-zero-length', params: { a: line(issue.side) } };
|
|
46
|
+
case 'ZERO_AREA':
|
|
47
|
+
return { key: 'figure.text.section-issue-zero-area', params: {} };
|
|
48
|
+
case 'TOO_FEW_POINTS':
|
|
49
|
+
return { key: 'figure.text.section-issue-too-few', params: {} };
|
|
50
|
+
case 'TOO_MANY_POINTS':
|
|
51
|
+
return { key: 'figure.text.section-issue-too-many', params: { limit: String(issue.limit) } };
|
|
52
|
+
default:
|
|
53
|
+
return { key: 'figure.text.section-issue-number', params: {} };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Every side and point an issue names, to mark on the drawing -- by more than colour: the list says them too. */
|
|
57
|
+
export function markedBy(issues) {
|
|
58
|
+
const sides = new Set(), points = new Set();
|
|
59
|
+
for (const i of issues) {
|
|
60
|
+
if ('sides' in i)
|
|
61
|
+
for (const s of i.sides)
|
|
62
|
+
sides.add(s);
|
|
63
|
+
if ('side' in i)
|
|
64
|
+
sides.add(i.side);
|
|
65
|
+
if ('point' in i)
|
|
66
|
+
points.add(i.point);
|
|
67
|
+
if ('at' in i)
|
|
68
|
+
points.add(String(i.at).split('.')[0]);
|
|
69
|
+
}
|
|
70
|
+
for (const s of sides)
|
|
71
|
+
for (const p of s.split('~'))
|
|
72
|
+
points.add(p);
|
|
73
|
+
return { sides, points };
|
|
74
|
+
}
|
|
75
|
+
/** How far the drawing reaches from the origin: the points' farthest coordinate with room around, in 50 mm steps. */
|
|
76
|
+
export function extentOf(points, axis) {
|
|
77
|
+
const [u, v] = FIGURE_SECTION_PLANE[axis];
|
|
78
|
+
const far = Math.max(1, ...points.flatMap(p => [Math.abs(p[u] ?? 0), Math.abs(p[v] ?? 0)]));
|
|
79
|
+
return Math.ceil((far * 1.4 + 40) / 50) * 50;
|
|
80
|
+
}
|
|
81
|
+
export const POINT_LIMIT = FIGURE_SECTION_POINT_LIMIT;
|
|
82
|
+
//# sourceMappingURL=section-drawing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-drawing.js","sourceRoot":"","sources":["../../client/modeller/section-drawing.ts"],"names":[],"mappings":"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","sourcesContent":["import { FIGURE_SECTION_PLANE, FIGURE_SECTION_POINT_LIMIT, checkFigureSection, figureSectionSidesOf } from '@hatiolab/figure-model'\nimport type { FigureSectionIssue, FigureSectionPoint } from '@hatiolab/figure-model'\n\n/*\n * What the section editor does to a drawn section's points, and how it says what is wrong with them (ADR-0100 §3.2).\n * The points are the kernel's own (`{ id, x, z }` for a push along Y), their validity is the kernel's own check, and a\n * point's number on screen is its own name's number (Q9): p3 is 「3」 before and after any edit, and deleting it leaves\n * 1, 2 and 4.\n */\n\ntype Axis = 'x' | 'y' | 'z'\nexport type Points = readonly FigureSectionPoint[]\n\n/** The number a point is shown by: its name's number. */\nexport const pointLabel = (id: string) => /^p(\\d+)$/.exec(id)?.[1] ?? id\n\n/** The next point's name: one past the highest number the loop has, so a new point never takes a gap's number. */\nexport function nextPointId(points: Points): string {\n return `p${Math.max(0, ...points.map(p => Number(/^p(\\d+)$/.exec(p.id)?.[1] ?? 0))) + 1}`\n}\n\n/** A point in the middle of the side from `from` to the point after it, whole millimetres; the new point's id with it. */\nexport function insertAfter(points: Points, from: string, axis: Axis): { points: FigureSectionPoint[]; id: string } {\n const [u, v] = FIGURE_SECTION_PLANE[axis]\n const i = points.findIndex(p => p.id === from)\n const a = points[i]!,\n b = points[(i + 1) % points.length]!\n const id = nextPointId(points)\n const mid = { id, [u]: Math.round((a[u]! + b[u]!) / 2), [v]: Math.round((a[v]! + b[v]!) / 2) } as FigureSectionPoint\n return { points: [...points.slice(0, i + 1), mid, ...points.slice(i + 1)], id }\n}\n\n/** The points less one, or why not: a section is three points or more. */\nexport function removePoint(points: Points, id: string): FigureSectionPoint[] | { refused: 'too-few' } {\n if (points.length <= 3) return { refused: 'too-few' }\n return points.filter(p => p.id !== id)\n}\n\nexport function movePoint(points: Points, id: string, at: Partial<Record<Axis, number>>): FigureSectionPoint[] {\n return points.map(p => (p.id === id ? { ...p, ...at } : p))\n}\n\n/** What is wrong with the points, by the kernel's own check. */\nexport const issuesOf = (points: Points, axis: Axis) => checkFigureSection({ kind: 'polygon', points: [...points] }, axis)\n\n/** A side said in the loop's order, 3–4 not 4–3: its id is the pair in name order, which a person never sees. */\nfunction sideLabel(points: Points, axis: Axis, side: string): string {\n const s = figureSectionSidesOf({ kind: 'polygon', points: [...points] }, axis).find(x => x.id === side)\n const [a, b] = s?.from ? [s.from, s.to!] : side.split('~')\n return `${pointLabel(a!)}–${pointLabel(b!)}`\n}\n\n/** An issue as the words that say it: an i18n key and its values. */\nexport function issueWords(issue: FigureSectionIssue, points: Points, axis: Axis): { key: string; params: Record<string, string> } {\n const line = (s: string) => sideLabel(points, axis, s)\n switch (issue.code) {\n case 'SIDES_CROSS':\n return { key: 'figure.text.section-issue-cross', params: { a: line(issue.sides[0]), b: line(issue.sides[1]) } }\n case 'SIDES_TOUCH':\n return { key: 'figure.text.section-issue-touch', params: { a: line(issue.sides[0]), b: line(issue.sides[1]) } }\n case 'SIDES_FOLD':\n return { key: 'figure.text.section-issue-fold', params: { a: line(issue.sides[0]), b: line(issue.sides[1]) } }\n case 'ZERO_LENGTH_SIDE':\n return { key: 'figure.text.section-issue-zero-length', params: { a: line(issue.side) } }\n case 'ZERO_AREA':\n return { key: 'figure.text.section-issue-zero-area', params: {} }\n case 'TOO_FEW_POINTS':\n return { key: 'figure.text.section-issue-too-few', params: {} }\n case 'TOO_MANY_POINTS':\n return { key: 'figure.text.section-issue-too-many', params: { limit: String(issue.limit) } }\n default:\n return { key: 'figure.text.section-issue-number', params: {} }\n }\n}\n\n/** Every side and point an issue names, to mark on the drawing -- by more than colour: the list says them too. */\nexport function markedBy(issues: readonly FigureSectionIssue[]): { sides: Set<string>; points: Set<string> } {\n const sides = new Set<string>(),\n points = new Set<string>()\n for (const i of issues) {\n if ('sides' in i) for (const s of i.sides) sides.add(s)\n if ('side' in i) sides.add(i.side)\n if ('point' in i) points.add(i.point)\n if ('at' in i) points.add(String(i.at).split('.')[0]!)\n }\n for (const s of sides) for (const p of s.split('~')) points.add(p)\n return { sides, points }\n}\n\n/** How far the drawing reaches from the origin: the points' farthest coordinate with room around, in 50 mm steps. */\nexport function extentOf(points: Points, axis: Axis): number {\n const [u, v] = FIGURE_SECTION_PLANE[axis]\n const far = Math.max(1, ...points.flatMap(p => [Math.abs(p[u] ?? 0), Math.abs(p[v] ?? 0)]))\n return Math.ceil((far * 1.4 + 40) / 50) * 50\n}\n\nexport const POINT_LIMIT = FIGURE_SECTION_POINT_LIMIT\n"]}
|