@code3d/core 0.0.1-alpha.0 → 0.0.1-alpha.11

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 (154) hide show
  1. package/README.md +43 -807
  2. package/THIRD_PARTY.md +40 -0
  3. package/bld/THIRD_PARTY_NOTICES.txt +182 -0
  4. package/bld/chunks/chunk-7FIFPADJ.js +1 -0
  5. package/bld/chunks/chunk-7FIFPADJ.js.map +7 -0
  6. package/bld/chunks/chunk-CL3E2DE4.js +19 -0
  7. package/bld/chunks/chunk-CL3E2DE4.js.map +7 -0
  8. package/bld/chunks/chunk-HZRQUHM2.js +30 -0
  9. package/bld/chunks/chunk-HZRQUHM2.js.map +7 -0
  10. package/bld/chunks/chunk-JBVIAMJ6.js +26474 -0
  11. package/bld/chunks/chunk-JBVIAMJ6.js.map +7 -0
  12. package/bld/chunks/chunk-QK6ZET47.js +31 -0
  13. package/bld/chunks/chunk-QK6ZET47.js.map +7 -0
  14. package/bld/library/bound-solver.d.ts +29 -5
  15. package/bld/library/bound-solver.d.ts.map +1 -1
  16. package/bld/library/cached.d.ts +35 -0
  17. package/bld/library/cached.d.ts.map +1 -0
  18. package/bld/library/font.d.ts +39 -0
  19. package/bld/library/font.d.ts.map +1 -0
  20. package/bld/library/google-font.d.ts +14 -0
  21. package/bld/library/google-font.d.ts.map +1 -0
  22. package/bld/library/index.d.ts +10 -2
  23. package/bld/library/index.d.ts.map +1 -1
  24. package/bld/library/index.js +94 -3
  25. package/bld/library/index.js.map +7 -1
  26. package/bld/library/inspect.d.ts +100 -0
  27. package/bld/library/inspect.d.ts.map +1 -0
  28. package/bld/library/kernel-artifact-codec.d.ts.map +1 -1
  29. package/bld/library/kernel-cache.d.ts +28 -7
  30. package/bld/library/kernel-cache.d.ts.map +1 -1
  31. package/bld/library/loft.d.ts.map +1 -1
  32. package/bld/library/material.d.ts +1 -1
  33. package/bld/library/material.d.ts.map +1 -1
  34. package/bld/library/open-cascade.d.ts +4 -0
  35. package/bld/library/open-cascade.d.ts.map +1 -0
  36. package/bld/library/relation-solver.d.ts +19 -8
  37. package/bld/library/relation-solver.d.ts.map +1 -1
  38. package/bld/library/replicad.d.ts +3 -2
  39. package/bld/library/replicad.d.ts.map +1 -1
  40. package/bld/library/replicad.js +10 -24
  41. package/bld/library/replicad.js.map +7 -1
  42. package/bld/library/retained-memory.d.ts.map +1 -1
  43. package/bld/library/runtime.d.ts +518 -155
  44. package/bld/library/runtime.d.ts.map +1 -1
  45. package/bld/library/sketch-solver.d.ts +1 -1
  46. package/bld/library/sketch-solver.d.ts.map +1 -1
  47. package/bld/library/sketch.d.ts +21 -1
  48. package/bld/library/sketch.d.ts.map +1 -1
  49. package/bld/library/text.d.ts +53 -0
  50. package/bld/library/text.d.ts.map +1 -0
  51. package/bld/library/three.js +3 -3
  52. package/bld/library/three.js.map +7 -1
  53. package/bld/library/topology.d.ts +6 -2
  54. package/bld/library/topology.d.ts.map +1 -1
  55. package/bld/node/index.d.ts.map +1 -1
  56. package/bld/node/index.js +96 -14
  57. package/bld/node/index.js.map +7 -1
  58. package/bld/node/replicad.js +13 -3
  59. package/bld/node/replicad.js.map +7 -1
  60. package/bld/tooling/index.d.ts +18 -15
  61. package/bld/tooling/index.d.ts.map +1 -1
  62. package/bld/tooling/index.js +165 -19
  63. package/bld/tooling/index.js.map +7 -1
  64. package/docs/api.md +764 -0
  65. package/docs/custom-primitives.mdx +58 -0
  66. package/docs/local-coordinates.md +187 -0
  67. package/docs/origins-and-rotation.mdx +105 -0
  68. package/docs/relations.mdx +450 -0
  69. package/docs/runtime.md +297 -0
  70. package/docs/shells.mdx +70 -0
  71. package/docs/sketches.md +83 -0
  72. package/docs/text.md +23 -0
  73. package/docs/topology.md +152 -0
  74. package/docs/values.md +119 -0
  75. package/package.json +20 -7
  76. package/src/library/bound-solver.ts +103 -73
  77. package/src/library/cached.ts +153 -0
  78. package/src/library/font.ts +186 -0
  79. package/src/library/google-font.ts +97 -0
  80. package/src/library/index.ts +53 -5
  81. package/src/library/inspect.ts +178 -0
  82. package/src/library/kernel-artifact-codec.ts +129 -20
  83. package/src/library/kernel-cache.ts +175 -39
  84. package/src/library/loft.ts +9 -1
  85. package/src/library/material.ts +3 -4
  86. package/src/library/open-cascade.ts +13 -0
  87. package/src/library/relation-solver.ts +230 -113
  88. package/src/library/replicad.ts +5 -5
  89. package/src/library/retained-memory.ts +6 -1
  90. package/src/library/runtime.ts +3769 -1178
  91. package/src/library/shell.ts +3 -3
  92. package/src/library/sketch-solver.ts +3 -1
  93. package/src/library/sketch.ts +112 -13
  94. package/src/library/text.ts +477 -0
  95. package/src/library/topology.ts +61 -17
  96. package/src/library/tsconfig.json +2 -1
  97. package/src/node/index.ts +14 -3
  98. package/src/node/tsconfig.json +2 -1
  99. package/src/tooling/index.ts +64 -51
  100. package/src/tooling/tsconfig.json +2 -1
  101. package/bld/library/alignment-geometry.js +0 -322
  102. package/bld/library/alignment-geometry.js.map +0 -1
  103. package/bld/library/bound-solver.js +0 -252
  104. package/bld/library/bound-solver.js.map +0 -1
  105. package/bld/library/extrude.js +0 -28
  106. package/bld/library/extrude.js.map +0 -1
  107. package/bld/library/kernel-artifact-codec.js +0 -148
  108. package/bld/library/kernel-artifact-codec.js.map +0 -1
  109. package/bld/library/kernel-cache.js +0 -245
  110. package/bld/library/kernel-cache.js.map +0 -1
  111. package/bld/library/kernel-shapes.js +0 -108
  112. package/bld/library/kernel-shapes.js.map +0 -1
  113. package/bld/library/loft.js +0 -161
  114. package/bld/library/loft.js.map +0 -1
  115. package/bld/library/material.js +0 -176
  116. package/bld/library/material.js.map +0 -1
  117. package/bld/library/model-color.js +0 -36
  118. package/bld/library/model-color.js.map +0 -1
  119. package/bld/library/open-cascade-error.js +0 -22
  120. package/bld/library/open-cascade-error.js.map +0 -1
  121. package/bld/library/relation-solver.js +0 -258
  122. package/bld/library/relation-solver.js.map +0 -1
  123. package/bld/library/retained-memory.js +0 -27
  124. package/bld/library/retained-memory.js.map +0 -1
  125. package/bld/library/runtime.js +0 -2954
  126. package/bld/library/runtime.js.map +0 -1
  127. package/bld/library/shell.js +0 -221
  128. package/bld/library/shell.js.map +0 -1
  129. package/bld/library/sketch-curve-intersections.js +0 -117
  130. package/bld/library/sketch-curve-intersections.js.map +0 -1
  131. package/bld/library/sketch-curves.js +0 -89
  132. package/bld/library/sketch-curves.js.map +0 -1
  133. package/bld/library/sketch-drag-rules.js +0 -522
  134. package/bld/library/sketch-drag-rules.js.map +0 -1
  135. package/bld/library/sketch-face.js +0 -72
  136. package/bld/library/sketch-face.js.map +0 -1
  137. package/bld/library/sketch-incidence.js +0 -114
  138. package/bld/library/sketch-incidence.js.map +0 -1
  139. package/bld/library/sketch-precision.js +0 -55
  140. package/bld/library/sketch-precision.js.map +0 -1
  141. package/bld/library/sketch-regions.js +0 -193
  142. package/bld/library/sketch-regions.js.map +0 -1
  143. package/bld/library/sketch-solver.js +0 -578
  144. package/bld/library/sketch-solver.js.map +0 -1
  145. package/bld/library/sketch.js +0 -650
  146. package/bld/library/sketch.js.map +0 -1
  147. package/bld/library/spatial.js +0 -195
  148. package/bld/library/spatial.js.map +0 -1
  149. package/bld/library/topology-id.js +0 -67
  150. package/bld/library/topology-id.js.map +0 -1
  151. package/bld/library/topology-inspection.js +0 -267
  152. package/bld/library/topology-inspection.js.map +0 -1
  153. package/bld/library/topology.js +0 -455
  154. package/bld/library/topology.js.map +0 -1
@@ -0,0 +1,186 @@
1
+ /// <reference types="emscripten" preserve="true" />
2
+ import {cachedArtifact} from './cached.js';
3
+ import type * as HarfBuzz from 'harfbuzzjs';
4
+ import {
5
+ googleFontSources,
6
+ googleFontUrl,
7
+ type GoogleFontOptions,
8
+ } from './google-font.js';
9
+ import {
10
+ kernelOperationKey,
11
+ kernelContentId,
12
+ type KernelArtifact,
13
+ } from './kernel-cache.js';
14
+
15
+ const fontBrand: unique symbol = Symbol('Font');
16
+ /** An immutable, content-addressed font resource. Size is supplied to text(). */
17
+ export type Font = Readonly<{
18
+ family: string;
19
+ style: string;
20
+ [fontBrand]: true;
21
+ }>;
22
+ export type ParsedFont = Readonly<{
23
+ face: HarfBuzz.Face;
24
+ font: HarfBuzz.Font;
25
+ numGlyphs: number;
26
+ sourceBytes: number;
27
+ }>;
28
+ let fontEngine: typeof HarfBuzz;
29
+ let shapingBuffer: HarfBuzz.Buffer;
30
+ export function installFontEngine(engine: typeof HarfBuzz): void {
31
+ fontEngine = engine;
32
+ shapingBuffer = new engine.Buffer();
33
+ }
34
+ export function shapeFontText(
35
+ font: HarfBuzz.Font,
36
+ content: string,
37
+ kerning: boolean,
38
+ ) {
39
+ shapingBuffer.reset();
40
+ shapingBuffer.addText(content);
41
+ shapingBuffer.guessSegmentProperties();
42
+ fontEngine.shape(font, shapingBuffer, [
43
+ new fontEngine.Feature('kern', Number(kerning)),
44
+ ]);
45
+ return {
46
+ infos: shapingBuffer.getGlyphInfos(),
47
+ positions: shapingBuffer.getGlyphPositions(),
48
+ };
49
+ }
50
+ export type FontPart = Readonly<{
51
+ artifact: KernelArtifact<ParsedFont>;
52
+ ranges: readonly (readonly [number, number])[];
53
+ }>;
54
+ const fonts = new WeakMap<Font, readonly FontPart[]>();
55
+ let readResource: ((url: URL) => Uint8Array | undefined) | undefined;
56
+
57
+ /** The model engine prepares resources before synchronous author code runs. */
58
+ export function installModelResourceReader(reader: typeof readResource): void {
59
+ readResource = reader;
60
+ }
61
+
62
+ /** Reads font bytes or a prepared project/HTTP(S) URL; Node also reads file URLs. */
63
+ export function font(source: URL | ArrayBuffer | Uint8Array): Font {
64
+ return fontValue([{artifact: parseFont(source), ranges: []}]);
65
+ }
66
+
67
+ /**
68
+ * A Google Fonts family/style prepared by the model engine before execution.
69
+ * @modelResource google-font
70
+ */
71
+ export function googleFont(
72
+ family: string,
73
+ options: GoogleFontOptions = {},
74
+ ): Font {
75
+ const url = googleFontUrl(family, options);
76
+ const css = readResource?.(url);
77
+ if (!css)
78
+ throw new Error(
79
+ 'googleFont() requires resources prepared by the model engine. Use a static family name and options.',
80
+ );
81
+ return fontValue(
82
+ googleFontSources(css).map(({url, ranges}) => ({
83
+ artifact: parseFont(new URL(url), options),
84
+ ranges,
85
+ })),
86
+ );
87
+ }
88
+
89
+ function parseFont(
90
+ source: URL | ArrayBuffer | Uint8Array,
91
+ options: GoogleFontOptions = {},
92
+ ): KernelArtifact<ParsedFont> {
93
+ if (!fontEngine) throw new Error('The font engine has not been initialized.');
94
+ const bytes =
95
+ source instanceof URL
96
+ ? readResource?.(source)
97
+ : source instanceof ArrayBuffer
98
+ ? new Uint8Array(source)
99
+ : source;
100
+ if (!(bytes instanceof Uint8Array)) {
101
+ throw new Error(
102
+ 'font() requires font bytes or a URL prepared by the model engine. Use a static new URL("./font.ttf", import.meta.url) or new URL("https://…/font.ttf"); outside the engine, fetch the font first and pass its bytes.',
103
+ );
104
+ }
105
+ return parsedFont(bytes, options);
106
+ }
107
+
108
+ const parsedFont = cachedArtifact(
109
+ (bytes: Uint8Array, options: GoogleFontOptions): ParsedFont => {
110
+ try {
111
+ const signature = new DataView(
112
+ bytes.buffer,
113
+ bytes.byteOffset,
114
+ bytes.byteLength,
115
+ ).getUint32(0);
116
+ if (signature !== 0x00010000 && signature !== 0x4f54544f)
117
+ throw new Error('Expected an SFNT font.');
118
+ const face = new fontEngine.Face(new fontEngine.Blob(bytes));
119
+ const maxp = face.referenceTable('maxp');
120
+ if (!maxp || maxp.byteLength < 6 || !face.referenceTable('cmap'))
121
+ throw new Error('Missing font tables.');
122
+ const numGlyphs = new DataView(
123
+ maxp.buffer,
124
+ maxp.byteOffset,
125
+ maxp.byteLength,
126
+ ).getUint16(4);
127
+ if (!numGlyphs) throw new Error('Font has no glyphs.');
128
+ const font = new fontEngine.Font(face);
129
+ const variations: HarfBuzz.Variation[] = [];
130
+ if (options.weight !== undefined)
131
+ variations.push(new fontEngine.Variation('wght', options.weight));
132
+ if (options.italic !== undefined)
133
+ variations.push(
134
+ new fontEngine.Variation('ital', Number(options.italic)),
135
+ );
136
+ font.setVariations(variations);
137
+ return {face, font, numGlyphs, sourceBytes: bytes.byteLength};
138
+ } catch (error) {
139
+ throw new Error(
140
+ 'Cannot parse font. Expected a TTF or OTF font (font collections and WOFF2 are not supported).',
141
+ {cause: error},
142
+ );
143
+ }
144
+ },
145
+ {
146
+ key: (bytes, options) =>
147
+ kernelOperationKey(
148
+ 'font',
149
+ [
150
+ kernelContentId(bytes),
151
+ bytes.byteLength,
152
+ options.weight ?? null,
153
+ options.italic ?? null,
154
+ ],
155
+ [],
156
+ ),
157
+ codec: false,
158
+ lifecycle: {
159
+ // Account for parser tables and lazy glyph expansion in the shared budget.
160
+ estimateBytes: value => value.sourceBytes * 8 + value.numGlyphs * 1024,
161
+ retain: value => value,
162
+ instantiate: value => value,
163
+ release() {},
164
+ },
165
+ },
166
+ );
167
+
168
+ function fontValue(parts: readonly FontPart[]): Font {
169
+ const parsed = parts[0].artifact.value;
170
+ const value: Font = Object.freeze({
171
+ family: parsed.face.getName(1, 'en'),
172
+ style: parsed.face.getName(2, 'en'),
173
+ [fontBrand]: true as const,
174
+ });
175
+ fonts.set(value, parts);
176
+ return value;
177
+ }
178
+
179
+ export function fontParts(value: Font): readonly FontPart[] {
180
+ const parts = fonts.get(value);
181
+ if (!parts)
182
+ throw new Error(
183
+ 'text() requires a font created by font() or googleFont().',
184
+ );
185
+ return parts;
186
+ }
@@ -0,0 +1,97 @@
1
+ import {cachedArtifact} from './cached.js';
2
+ import {kernelContentId, kernelOperationKey} from './kernel-cache.js';
3
+
4
+ export type GoogleFontOptions = Readonly<{
5
+ /** Omit to use Google Fonts' default weight. */
6
+ weight?: number;
7
+ /** Omit to use Google Fonts' default style. */
8
+ italic?: boolean;
9
+ }>;
10
+
11
+ export type GoogleFontSource = Readonly<{
12
+ url: string;
13
+ ranges: readonly (readonly [number, number])[];
14
+ }>;
15
+
16
+ /** Canonical request shared by synchronous Core and asynchronous preparation. */
17
+ export function googleFontUrl(
18
+ family: string,
19
+ options: GoogleFontOptions = {},
20
+ ): URL {
21
+ if (typeof family !== 'string' || !family.trim())
22
+ throw new Error('Google font family must be a non-empty name.');
23
+ if (
24
+ options.weight !== undefined &&
25
+ (!Number.isFinite(options.weight) ||
26
+ options.weight < 1 ||
27
+ options.weight > 1000)
28
+ )
29
+ throw new Error('Google font weight must be between 1 and 1000.');
30
+ if (options.italic !== undefined && typeof options.italic !== 'boolean')
31
+ throw new Error('Google font italic must be a boolean.');
32
+ const axes: [string, number][] = [];
33
+ if (options.italic !== undefined) axes.push(['ital', Number(options.italic)]);
34
+ if (options.weight !== undefined) axes.push(['wght', options.weight]);
35
+ const specification =
36
+ family.trim() +
37
+ (axes.length
38
+ ? ':' +
39
+ axes.map(([axis]) => axis).join(',') +
40
+ '@' +
41
+ axes.map(([, value]) => value).join(',')
42
+ : '');
43
+ return new URL(
44
+ 'https://fonts.googleapis.com/css2?' +
45
+ new URLSearchParams({family: specification}),
46
+ );
47
+ }
48
+
49
+ /** Parse only font-face resource declarations; no stylesheet is installed. */
50
+ const parseGoogleFontSources = cachedArtifact(
51
+ (bytes: Uint8Array): readonly GoogleFontSource[] => {
52
+ const css = new TextDecoder()
53
+ .decode(bytes)
54
+ .replace(/\/\*[\s\S]*?\*\//g, '');
55
+ const sources: GoogleFontSource[] = [];
56
+ for (const [, body] of css.matchAll(/@font-face\s*\{([^}]*)\}/gi)) {
57
+ const source = /(?:^|;)\s*src\s*:\s*([^;]+)/i.exec(body)?.[1];
58
+ const rawUrl =
59
+ source &&
60
+ /url\(\s*(?:"([^"]+)"|'([^']+)'|([^\s)]+))\s*\)/i.exec(source);
61
+ if (!rawUrl)
62
+ throw new Error('Google Fonts returned a font face without a URL.');
63
+ const url = new URL(rawUrl[1] ?? rawUrl[2] ?? rawUrl[3]);
64
+ if (url.protocol !== 'https:')
65
+ throw new Error('Google Fonts returned a non-HTTPS font URL.');
66
+ const ranges: [number, number][] = [];
67
+ const range = /(?:^|;)\s*unicode-range\s*:\s*([^;]+)/i.exec(body)?.[1];
68
+ if (range)
69
+ for (const part of range.split(',')) {
70
+ const match =
71
+ /^\s*U\+([0-9a-f?]{1,6})(?:-([0-9a-f]{1,6}))?\s*$/i.exec(part);
72
+ if (!match)
73
+ throw new Error('Google Fonts returned an invalid Unicode range.');
74
+ const start = parseInt(match[1].replaceAll('?', '0'), 16);
75
+ const end = parseInt(match[2] ?? match[1].replaceAll('?', 'f'), 16);
76
+ if (start > end || end > 0x10ffff)
77
+ throw new Error('Google Fonts returned an invalid Unicode range.');
78
+ ranges.push([start, end]);
79
+ }
80
+ sources.push({url: url.href, ranges});
81
+ }
82
+ if (!sources.length)
83
+ throw new Error('Google Fonts returned no font files.');
84
+ // Later CSS faces take precedence where Unicode ranges overlap.
85
+ return sources.reverse();
86
+ },
87
+ {
88
+ key: bytes =>
89
+ kernelOperationKey('googleFontSources', [kernelContentId(bytes)], []),
90
+ },
91
+ );
92
+
93
+ export function googleFontSources(
94
+ bytes: Uint8Array,
95
+ ): readonly GoogleFontSource[] {
96
+ return parseGoogleFontSources(bytes).value;
97
+ }
@@ -1,3 +1,37 @@
1
+ export {cache} from './cached.js';
2
+ export type {CacheOptions} from './cached.js';
3
+ export {
4
+ dimension,
5
+ boundsAnnotation,
6
+ anchorAnnotation,
7
+ captureInspectData,
8
+ } from './inspect.js';
9
+ /** @internal */
10
+ export {
11
+ relate,
12
+ on,
13
+ align,
14
+ expose,
15
+ inspectTopologyReference,
16
+ } from './runtime.js';
17
+ export type {
18
+ Dimension,
19
+ DimensionSegment,
20
+ BoundsAnnotation,
21
+ AnchorAnnotation,
22
+ PreviewValue,
23
+ InspectResult,
24
+ InspectCall,
25
+ InspectClosureExecution,
26
+ InspectContextFactory,
27
+ InspectClosure,
28
+ InspectContext,
29
+ Inspector,
30
+ } from './inspect.js';
31
+ export {font, googleFont} from './font.js';
32
+ export type {Font} from './font.js';
33
+ export type {GoogleFontOptions} from './google-font.js';
34
+ export type {TextOptions} from './text.js';
1
35
  export {sketch} from './sketch.js';
2
36
  export type {
3
37
  Sketch,
@@ -10,6 +44,13 @@ export type {
10
44
  } from './sketch.js';
11
45
 
12
46
  export {
47
+ offset,
48
+ rotate,
49
+ pivot,
50
+ pivotVertex,
51
+ pivotPoint,
52
+ axisEdge,
53
+ axisLine,
13
54
  arc,
14
55
  bezier,
15
56
  box,
@@ -17,8 +58,10 @@ export {
17
58
  coil,
18
59
  cut,
19
60
  cylinder,
61
+ distance,
20
62
  ellipse,
21
63
  extrude,
64
+ text,
22
65
  frustum,
23
66
  group,
24
67
  intersect,
@@ -38,8 +81,13 @@ export type {
38
81
  Anchor,
39
82
  Bound,
40
83
  DirectionalBounds,
41
- ConstraintPivotChain,
42
- ConstraintAroundChain,
84
+ DistanceAxis,
85
+ PivotChain,
86
+ PivotRotation,
87
+ AxisRotation,
88
+ AxisChain,
89
+ Transformation,
90
+ Relation,
43
91
  CanonicalElements,
44
92
  Constraint,
45
93
  CurveElements,
@@ -51,6 +99,7 @@ export type {
51
99
  ExposedElements,
52
100
  ExposedValue,
53
101
  FaceAnchor,
102
+ FrameAnchor,
54
103
  FaceModel,
55
104
  GeometryCapabilities,
56
105
  GeometryQueryCapabilities,
@@ -60,10 +109,9 @@ export type {
60
109
  MergedElements,
61
110
  Model,
62
111
  ModelCapabilities,
112
+ ModelBounds,
63
113
  ModelElementKind,
64
- ModelFamily,
65
- ModelFamilyElementKind,
66
- ModelForFamily,
114
+ ModelForKind,
67
115
  ModelGeometryKind,
68
116
  ModelKind,
69
117
  NamedElements,
@@ -0,0 +1,178 @@
1
+ import type {Anchor, Model, SolidModel} from './runtime.js';
2
+ import type {Sketch, SketchPoint} from './sketch.js';
3
+ import type {RigidTransform, Vec3} from './spatial.js';
4
+
5
+ let recordData: ((data: unknown) => void) | undefined;
6
+ const inspectionIdentities = new WeakMap<object, object>();
7
+
8
+ /** Runtime-only identity of a value retained in a different inspection frame. */
9
+ export function inspectionIdentity(value: object): object {
10
+ return inspectionIdentities.get(value) ?? value;
11
+ }
12
+
13
+ /** Frame changes preserve the inspected object, unlike newly generated geometry. */
14
+ export function retainInspectionIdentity<T extends object>(
15
+ value: T,
16
+ source: object,
17
+ ): T {
18
+ inspectionIdentities.set(value, inspectionIdentity(source));
19
+ return value;
20
+ }
21
+
22
+ /** Avoid preparing call data when no execution host is recording it. */
23
+ export function isRecordingInspection(): boolean {
24
+ return recordData !== undefined;
25
+ }
26
+
27
+ /**
28
+ * Attach data to the current inspected call without changing its return value.
29
+ * The inspector receives the last recorded value as context.data. Data stays in
30
+ * the execution host by reference; capture immutable values for call-time facts.
31
+ * Without a recording scope this function does nothing.
32
+ */
33
+ export function captureInspectData(data: unknown): void {
34
+ recordData?.(data);
35
+ }
36
+
37
+ /** The execution host supplies the current call's recording scope (tooling entry only). */
38
+ export function recordInspectionCalls(
39
+ record: (data: unknown) => void,
40
+ ): () => void {
41
+ const previous = recordData;
42
+ recordData = record;
43
+ return () => {
44
+ recordData = previous;
45
+ };
46
+ }
47
+
48
+ /** A finite segment expressed in an annotation owner's local coordinates. */
49
+ export type DimensionSegment = Readonly<{start: Vec3; end: Vec3}>;
50
+
51
+ type DimensionLines =
52
+ DimensionSegment | Readonly<{candidates: readonly DimensionSegment[]}>;
53
+ type DimensionOptions = Readonly<{
54
+ owner: Model;
55
+ value: number;
56
+ axisLabel?: string;
57
+ }>;
58
+
59
+ /** A passive dimension; a candidate is selected on entry and retained while inspecting it. */
60
+ export type Dimension = Readonly<{kind: 'dimension'}> &
61
+ DimensionOptions &
62
+ DimensionLines;
63
+
64
+ /** Create a preview annotation without adding CAD geometry. */
65
+ export function dimension(value: DimensionOptions & DimensionLines): Dimension {
66
+ if ('candidates' in value && !value.candidates.length)
67
+ throw new Error(
68
+ 'A dimension annotation requires at least one candidate segment.',
69
+ );
70
+ return {kind: 'dimension', ...value};
71
+ }
72
+
73
+ /** Exact finite extent, centered in a frame local to its owner; not CAD geometry. */
74
+ export type BoundsAnnotation = Readonly<{
75
+ kind: 'bounds';
76
+ owner: Model;
77
+ size: Vec3;
78
+ frame: RigidTransform;
79
+ }>;
80
+
81
+ /** Draw the existing screen-sized bounds corners without creating a solid. */
82
+ export function boundsAnnotation(
83
+ value: Omit<BoundsAnnotation, 'kind'>,
84
+ ): BoundsAnnotation {
85
+ return {kind: 'bounds', ...value};
86
+ }
87
+
88
+ /** Render the same reference with explicit direction markers. */
89
+ export type AnchorAnnotation = Readonly<{
90
+ kind: 'anchor-annotation';
91
+ anchor: Anchor;
92
+ direction: 'none' | 'forward' | 'both';
93
+ }>;
94
+
95
+ export function anchorAnnotation(
96
+ anchor: Anchor,
97
+ options: Pick<AnchorAnnotation, 'direction'>,
98
+ ): AnchorAnnotation {
99
+ return retainInspectionIdentity(
100
+ {kind: 'anchor-annotation', anchor, ...options},
101
+ anchor,
102
+ );
103
+ }
104
+
105
+ /** Values accepted by the App's ordinary object preview. */
106
+ export type PreviewValue =
107
+ | Model
108
+ | Anchor
109
+ | Sketch
110
+ | SketchPoint
111
+ | Dimension
112
+ | BoundsAnnotation
113
+ | AnchorAnnotation;
114
+
115
+ /** One inspector owns the complete scene; undefined declines its scope. */
116
+ export type InspectResult = Readonly<{
117
+ ambient?: readonly PreviewValue[];
118
+ target?: readonly PreviewValue[];
119
+ }>;
120
+
121
+ export type InspectCall = Readonly<{
122
+ receiver: unknown;
123
+ arguments: readonly unknown[];
124
+ return: unknown;
125
+ data: unknown;
126
+ }>;
127
+
128
+ /** The actual invocation of a callback declared with @code3d.inspect.closure/context. */
129
+ export type InspectClosureExecution = Readonly<{
130
+ arguments: readonly unknown[];
131
+ return: unknown;
132
+ call: InspectCall;
133
+ parent?: InspectClosure;
134
+ }>;
135
+
136
+ export type InspectContextFactory<Data = unknown> = (
137
+ execution: InspectClosureExecution,
138
+ ) => Data;
139
+
140
+ export type InspectClosure = InspectClosureExecution &
141
+ Readonly<{
142
+ provider?: InspectContextFactory;
143
+ data: unknown;
144
+ }>;
145
+
146
+ /** Inspection belongs to an invoked call, independently of tool selection.
147
+ * A thrown call has no return value; recorded data and evaluated arguments remain available.
148
+ * Calls not reached because an argument throws have no inspection invocation.
149
+ */
150
+ export type InspectContext<
151
+ Return = unknown,
152
+ Receiver = unknown,
153
+ Data = unknown,
154
+ > = Readonly<{
155
+ receiver: Receiver;
156
+ return: Return | undefined;
157
+ data: Data;
158
+ closure?: InspectClosure;
159
+ focused: Readonly<{
160
+ value: unknown;
161
+ parameter: string | undefined;
162
+ path: readonly (string | number)[];
163
+ values: readonly PreviewValue[];
164
+ solids: readonly SolidModel<{}>[];
165
+ /** Absolute placement prefix count when the caret is at a relate array gap. */
166
+ insertion?: number;
167
+ }>;
168
+ }>;
169
+
170
+ export type Inspector<
171
+ Args extends readonly unknown[] = readonly unknown[],
172
+ Return = unknown,
173
+ Receiver = unknown,
174
+ Data = unknown,
175
+ > = (
176
+ args: Args,
177
+ context: InspectContext<Return, Receiver, Data>,
178
+ ) => InspectResult | undefined;