abstract-3d 4.1.5 → 4.2.0

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 (60) hide show
  1. package/lib/abstract-3d.d.ts +63 -6
  2. package/lib/abstract-3d.d.ts.map +1 -1
  3. package/lib/abstract-3d.js +277 -74
  4. package/lib/abstract-3d.js.map +1 -1
  5. package/lib/renderers/dxf/dxf-encoding/dxf-dimension.d.ts +2 -2
  6. package/lib/renderers/dxf/dxf-encoding/dxf-dimension.d.ts.map +1 -1
  7. package/lib/renderers/dxf/dxf-encoding/dxf-dimension.js +6 -6
  8. package/lib/renderers/dxf/dxf-encoding/dxf-dimension.js.map +1 -1
  9. package/lib/renderers/dxf/dxf-geometries/dxf-dimension.d.ts +2 -2
  10. package/lib/renderers/dxf/dxf-geometries/dxf-dimension.d.ts.map +1 -1
  11. package/lib/renderers/dxf/dxf-geometries/dxf-dimension.js +3 -3
  12. package/lib/renderers/dxf/dxf-geometries/dxf-dimension.js.map +1 -1
  13. package/lib/renderers/dxf/dxf.d.ts.map +1 -1
  14. package/lib/renderers/dxf/dxf.js +12 -8
  15. package/lib/renderers/dxf/dxf.js.map +1 -1
  16. package/lib/renderers/ifc/ifc.d.ts.map +1 -1
  17. package/lib/renderers/ifc/ifc.js +5 -3
  18. package/lib/renderers/ifc/ifc.js.map +1 -1
  19. package/lib/renderers/step/step.d.ts.map +1 -1
  20. package/lib/renderers/step/step.js +3 -2
  21. package/lib/renderers/step/step.js.map +1 -1
  22. package/lib/renderers/stl/stl.d.ts.map +1 -1
  23. package/lib/renderers/stl/stl.js +7 -3
  24. package/lib/renderers/stl/stl.js.map +1 -1
  25. package/lib/renderers/svg/svg-geometries/svg-text.d.ts +1 -1
  26. package/lib/renderers/svg/svg-geometries/svg-text.d.ts.map +1 -1
  27. package/lib/renderers/svg/svg-geometries/svg-text.js +2 -2
  28. package/lib/renderers/svg/svg-geometries/svg-text.js.map +1 -1
  29. package/lib/renderers/svg/svg.d.ts.map +1 -1
  30. package/lib/renderers/svg/svg.js +13 -10
  31. package/lib/renderers/svg/svg.js.map +1 -1
  32. package/lib/renderers/svg-bsp/svg-geometries/svg-text.d.ts +1 -1
  33. package/lib/renderers/svg-bsp/svg-geometries/svg-text.d.ts.map +1 -1
  34. package/lib/renderers/svg-bsp/svg-geometries/svg-text.js +2 -2
  35. package/lib/renderers/svg-bsp/svg-geometries/svg-text.js.map +1 -1
  36. package/lib/renderers/svg-bsp/svg.d.ts.map +1 -1
  37. package/lib/renderers/svg-bsp/svg.js +13 -10
  38. package/lib/renderers/svg-bsp/svg.js.map +1 -1
  39. package/lib/renderers/three/build/build.js +1 -1
  40. package/lib/renderers/three/build/build.js.map +1 -1
  41. package/lib/renderers/three/compile/compile.d.ts +2 -0
  42. package/lib/renderers/three/compile/compile.d.ts.map +1 -1
  43. package/lib/renderers/three/compile/compile.js +6 -4
  44. package/lib/renderers/three/compile/compile.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/abstract-3d.test.ts +254 -0
  47. package/src/abstract-3d.ts +427 -85
  48. package/src/renderers/dxf/dxf-encoding/dxf-dimension.ts +22 -2
  49. package/src/renderers/dxf/dxf-geometries/dxf-dimension.ts +12 -2
  50. package/src/renderers/dxf/dxf.ts +21 -4
  51. package/src/renderers/ifc/ifc.ts +16 -3
  52. package/src/renderers/step/step.ts +3 -2
  53. package/src/renderers/stl/stl.ts +12 -4
  54. package/src/renderers/svg/svg-geometries/svg-text.ts +3 -2
  55. package/src/renderers/svg/svg.ts +29 -9
  56. package/src/renderers/svg-bsp/svg-geometries/svg-text.ts +3 -2
  57. package/src/renderers/svg-bsp/svg.ts +29 -9
  58. package/src/renderers/three/build/build.test.ts +2 -2
  59. package/src/renderers/three/build/build.ts +1 -1
  60. package/src/renderers/three/compile/compile.ts +22 -4
@@ -1,4 +1,12 @@
1
- import { type Dimension, dimensionIsOfTypeMesh, type Vec3, vec3Length, vec3Sub, vec3TransRot } from "../../../abstract-3d.js";
1
+ import {
2
+ type Dimension,
3
+ type DimensionPlacement,
4
+ dimensionIsOfTypeMesh,
5
+ type Vec3,
6
+ vec3Length,
7
+ vec3Sub,
8
+ vec3TransRot,
9
+ } from "../../../abstract-3d.js";
2
10
  import type { Handle } from "../dxf-encoding/dxf-common.js";
3
11
  import { type DxfDimensionDefinition, dxfEncDimension } from "../dxf-encoding/dxf-dimension.js";
4
12
 
@@ -8,6 +16,8 @@ export function dxfDimension(
8
16
  parentRot: Vec3,
9
17
  sceneRotation: Vec3,
10
18
  viewRotation: Vec3,
19
+ textHeight: number | undefined,
20
+ placement: DimensionPlacement | undefined,
11
21
  handleRef: Handle
12
22
  ): DxfDimensionDefinition {
13
23
  if (dimensionIsOfTypeMesh(dimension)) {
@@ -21,5 +31,5 @@ export function dxfDimension(
21
31
  const me = vec3TransRot(dimension.measurementEnd, parentPos, parentRot);
22
32
  const lp = vec3TransRot(dimension.linePosition, parentPos, parentRot);
23
33
  const text = dimension.text ?? `${vec3Length(vec3Sub(me, ms))}`;
24
- return dxfEncDimension(ms, me, lp, text, 7, sceneRotation, viewRotation, dimension.views ?? ["front"], handleRef);
34
+ return dxfEncDimension(ms, me, lp, text, 7, sceneRotation, viewRotation, dimension.views ?? ["front"], textHeight, placement, handleRef);
25
35
  }
@@ -10,8 +10,11 @@ import {
10
10
  bounds3Merge,
11
11
  bounds3ToSize,
12
12
  boundsScene,
13
+ dimensionPlacements,
13
14
  group,
15
+ groupTextBackgrounds,
14
16
  rotationForCameraPos,
17
+ sceneTextHeight,
15
18
  sizeBoundsForCameraPos,
16
19
  vec3,
17
20
  vec3Add,
@@ -103,8 +106,9 @@ const renderInternal = (
103
106
  const dxfOriginOffset = originOffsetFromBounds(bounds, options.origin);
104
107
  const pos = options.origin === "SameAsScene" ? vec3Zero : vec3NegateY(vec3Add(unitCenter, vec3Add(offset, dxfOriginOffset)));
105
108
  const visibleViews = calculateVisibleViews(options.view, scene.rotation_deprecated);
109
+ const textHeight = sceneTextHeight(scene.groups);
106
110
  return {
107
- groups: scene.groups.reduce((a, c) => a + dxfGroup(c, pos, unitRot, options, handleRef), ""),
111
+ groups: scene.groups.reduce((a, c) => a + dxfGroup(groupTextBackgrounds(c, textHeight), pos, unitRot, options, handleRef), ""),
108
112
  dimensions: dxfDimensions(
109
113
  scene.dimensions_deprecated,
110
114
  pos,
@@ -113,6 +117,7 @@ const renderInternal = (
113
117
  viewRotation,
114
118
  visibleViews,
115
119
  options,
120
+ textHeight,
116
121
  handleRef
117
122
  ),
118
123
  size,
@@ -200,6 +205,7 @@ function dxfDimensions(
200
205
  viewRotation: Vec3,
201
206
  visibleViews: Record<string, boolean>,
202
207
  options: DxfOptions,
208
+ textHeight: number,
203
209
  handleRef: Handle
204
210
  ): DxfDimensionDefinition {
205
211
  if (!d || !options.showDimensions) {
@@ -209,9 +215,13 @@ function dxfDimensions(
209
215
  blockRecord: "",
210
216
  };
211
217
  }
218
+ const placements = dimensionPlacements(d.dimensions, textHeight);
212
219
  return d.dimensions
213
- .filter((d) => d.views?.[0] && visibleViews[d.views[0]] === true)
214
- .map((d) => dxfDimension(d, parentPos, parentRot, sceneRotation, viewRotation, handleRef))
220
+ .flatMap((dim, i) =>
221
+ dim.views?.[0] && visibleViews[dim.views[0]] === true
222
+ ? [dxfDimension(dim, parentPos, parentRot, sceneRotation, viewRotation, textHeight, placements[i], handleRef)]
223
+ : []
224
+ )
215
225
  .reduce<DxfDimensionDefinition>(
216
226
  (prev, curr) => ({
217
227
  block: prev.block + curr.block,
@@ -243,7 +253,13 @@ export const renderOld = (scene: Scene, options?: Optional<DxfOptions>): string
243
253
  const visibleViews = calculateVisibleViews(opts.view, scene.rotation_deprecated);
244
254
 
245
255
  const bounds2 = bounds3FromPosAndSize(offset, scene.size_deprecated);
246
- const groupRoot = group([], offset, vec3Zero, scene.groups);
256
+ const textHeight = sceneTextHeight(scene.groups);
257
+ const groupRoot = group(
258
+ [],
259
+ offset,
260
+ vec3Zero,
261
+ scene.groups.map((g) => groupTextBackgrounds(g, textHeight))
262
+ );
247
263
  const handleRef = dxfHandleInit();
248
264
  const dimensions = dxfDimensions(
249
265
  scene.dimensions_deprecated,
@@ -253,6 +269,7 @@ export const renderOld = (scene: Scene, options?: Optional<DxfOptions>): string
253
269
  viewRotation,
254
270
  visibleViews,
255
271
  opts,
272
+ textHeight,
256
273
  handleRef
257
274
  );
258
275
  return dxfBuild(dxfGroup(groupRoot, center, unitRot, opts, handleRef), dimensions, bounds2, scene.size_deprecated, center);
@@ -1,6 +1,17 @@
1
1
  import { Color, SRGBColorSpace } from "three";
2
2
  import type { Scene, Group, Material, Relation, Vec3, GroupType, AirType } from "../../abstract-3d.js";
3
- import { vec3Zero, vec3PosX, vec3PosY, vec3Rot, vec3Sub, vec3TransRot, vec3RotCombine, vec3RotInverse } from "../../abstract-3d.js";
3
+ import {
4
+ vec3Zero,
5
+ vec3PosX,
6
+ vec3PosY,
7
+ vec3Rot,
8
+ vec3Sub,
9
+ vec3TransRot,
10
+ vec3RotCombine,
11
+ vec3RotInverse,
12
+ groupTextBackgrounds,
13
+ sceneTextHeight,
14
+ } from "../../abstract-3d.js";
4
15
  import type { MutableIfc } from "./ifc-encoding.js";
5
16
  import { add, guid, ifcFile, point, real, ref, refs, serialize, str } from "./ifc-encoding.js";
6
17
  import type { Polyline, Triangulation } from "./ifc-geometry.js";
@@ -259,11 +270,13 @@ export function render(scene: Scene, options: IfcOptions = {}): string {
259
270
 
260
271
  const center = scene.center_deprecated ?? vec3Zero;
261
272
  const sceneRot = scene.rotation_deprecated ?? vec3Zero;
273
+ const textHeight = sceneTextHeight(scene.groups);
274
+ const groups = scene.groups.map((g) => groupTextBackgrounds(g, textHeight));
262
275
  // A scene that is something as a whole is one element, with its groups as the parts of it
263
276
  const roots =
264
277
  scene.id !== undefined || scene.name !== undefined || scene.type !== undefined
265
- ? [{ pos: vec3Zero, id: scene.id, name: scene.name, type: scene.type, groups: scene.groups }]
266
- : scene.groups;
278
+ ? [{ pos: vec3Zero, id: scene.id, name: scene.name, type: scene.type, groups }]
279
+ : groups;
267
280
  const elements = mergeSameId(roots)
268
281
  .flatMap((g) => elementNode(g, vec3TransRot(g.pos, center, sceneRot), vec3RotCombine(sceneRot, g.rot ?? vec3Zero), g.layer) ?? [])
269
282
  .map((node, i) => element(m, node, `${i + 1}`, { element: storey, placement: storeyPlacement, pos: vec3Zero, rot: vec3Zero }, c));
@@ -1,5 +1,5 @@
1
1
  import type { Scene, Group, Vec3 } from "../../abstract-3d.js";
2
- import { vec3Zero, vec3TransRot, vec3RotCombine } from "../../abstract-3d.js";
2
+ import { vec3Zero, vec3TransRot, vec3RotCombine, groupTextBackgrounds, sceneTextHeight } from "../../abstract-3d.js";
3
3
  import type { MutableStep } from "./step-encoding.js";
4
4
  import {
5
5
  GEOMETRIC_REPRESENTATION_CONTEXT_3D,
@@ -39,8 +39,9 @@ export const render = (scene: Scene): string => {
39
39
  throw Error("Context 3d is not correct");
40
40
  }
41
41
 
42
+ const textHeight = sceneTextHeight(scene.groups);
42
43
  for (const g of scene.groups) {
43
- stepGroup(g, scene.center_deprecated ?? vec3Zero, scene.rotation_deprecated ?? vec3Zero, m);
44
+ stepGroup(groupTextBackgrounds(g, textHeight), scene.center_deprecated ?? vec3Zero, scene.rotation_deprecated ?? vec3Zero, m);
44
45
  }
45
46
 
46
47
  return `${HEADER(date ?? DEFAULT_DATE_UNIX)}${m.step}${ENDSEC()}`;
@@ -1,14 +1,22 @@
1
1
  import type { Scene, Group, Vec3 } from "../../abstract-3d.js";
2
- import { vec3Zero, vec3TransRot, vec3RotCombine } from "../../abstract-3d.js";
2
+ import { vec3Zero, vec3TransRot, vec3RotCombine, groupTextBackgrounds, sceneTextHeight } from "../../abstract-3d.js";
3
3
  import { stlBox } from "./stl-geometries/stl-box.js";
4
4
  import { stlCone } from "./stl-geometries/stl-cone.js";
5
5
  import { stlCylinder } from "./stl-geometries/stl-cylinder.js";
6
6
  import { stlPlane } from "./stl-geometries/stl-plane.js";
7
7
  import { stlPolygon } from "./stl-geometries/stl-polygon.js";
8
8
 
9
- export const render = (scene: Scene): string =>
10
- `solid
11
- ` + scene.groups.reduce((a, c) => a + stlGroup(c, scene.center_deprecated ?? vec3Zero, scene.rotation_deprecated ?? vec3Zero), "");
9
+ export const render = (scene: Scene): string => {
10
+ const textHeight = sceneTextHeight(scene.groups);
11
+ return (
12
+ `solid
13
+ ` +
14
+ scene.groups.reduce(
15
+ (a, c) => a + stlGroup(groupTextBackgrounds(c, textHeight), scene.center_deprecated ?? vec3Zero, scene.rotation_deprecated ?? vec3Zero),
16
+ ""
17
+ )
18
+ );
19
+ };
12
20
 
13
21
  function stlGroup(g: Group, parentPos: Vec3, parentRot: Vec3): string {
14
22
  const pos = vec3TransRot(g.pos, parentPos, parentRot);
@@ -14,7 +14,8 @@ export function text(
14
14
  fill: string,
15
15
  opts: SvgOptions,
16
16
  parentPos: Vec3,
17
- parentRot: Vec3
17
+ parentRot: Vec3,
18
+ textHeight: number
18
19
  ): ReadonlyArray<zOrderElement> {
19
20
  const pos = vec3TransRot(t.pos, parentPos, parentRot);
20
21
  const rot = vec3RotCombine(parentRot, t.rot ?? vec3Zero);
@@ -27,7 +28,7 @@ export function text(
27
28
  }
28
29
 
29
30
  const texts = Array<zOrderElement>();
30
- const fontSize = t.fontSize;
31
+ const fontSize = t.fontSize ?? textHeight;
31
32
  const strings = t.text.split("\n");
32
33
  let posY = strings.length === 1 ? 0 : (fontSize * strings.length - fontSize) / 2;
33
34
  for (const s of strings) {
@@ -15,6 +15,9 @@ import {
15
15
  vec2Zero,
16
16
  vec3Flip,
17
17
  dimensionConvertToTypeMesh,
18
+ dimensionPlacements,
19
+ groupTextBackgrounds,
20
+ sceneTextHeight,
18
21
  } from "../../abstract-3d.js";
19
22
  import type { Optional } from "../../utils.js";
20
23
  import { calculateVisibleViews, isViewVisible } from "../../utils.js";
@@ -109,37 +112,53 @@ function renderInternal(
109
112
  const point = (x: number, y: number): Vec2 => vec2(svgCenter.x + x, svgCenter.y - y);
110
113
  const unitCenterFlipped = vec3Flip(unitCenter);
111
114
 
115
+ const textHeight = sceneTextHeight(scene.groups);
112
116
  const elements = Array<zOrderElement>();
113
117
  for (const g of scene.groups) {
114
- elements.push(...svgGroup(g, unitCenterFlipped, unitRot, point, opts));
118
+ elements.push(...svgGroup(groupTextBackgrounds(g, textHeight), unitCenterFlipped, unitRot, point, opts, textHeight));
115
119
  }
116
120
  const dimOpts: SvgOptions = { ...opts, only_stroke: false, gray_scale: false };
117
121
  elements.sort((a, b) => a.zOrder - b.zOrder);
118
122
  const visibleViews = calculateVisibleViews(view, scene.rotation_deprecated);
119
123
 
120
124
  // Dimensions are drawn for the flat views
121
- for (const dim of isometric ? [] : (scene.dimensions_deprecated?.dimensions ?? [])) {
122
- const d = dimensionConvertToTypeMesh(dim, scene.rotation_deprecated ?? vec3Zero, scene.dimensions_deprecated?.material);
125
+ const dimensions = isometric ? [] : (scene.dimensions_deprecated?.dimensions ?? []);
126
+ const placements = dimensionPlacements(dimensions, textHeight);
127
+ for (const [i, dim] of dimensions.entries()) {
128
+ const d = dimensionConvertToTypeMesh(
129
+ dim,
130
+ scene.rotation_deprecated ?? vec3Zero,
131
+ scene.dimensions_deprecated?.material,
132
+ textHeight,
133
+ placements[i]
134
+ );
123
135
  if (isViewVisible(d.views[0], visibleViews)) {
124
136
  const pos = vec3TransRot(d.pos, unitCenterFlipped, unitRot);
125
137
  const rot = vec3RotCombine(unitRot, d.rot);
126
138
  for (const m of d.meshes) {
127
- elements.push(...svgMesh(m, pos, rot, point, scene.dimensions_deprecated?.material ?? { normal: "" }, dimOpts));
139
+ elements.push(...svgMesh(m, pos, rot, point, scene.dimensions_deprecated?.material ?? { normal: "" }, dimOpts, textHeight));
128
140
  }
129
141
  }
130
142
  }
131
143
  return { elements, size: svgSize, center: svgCenter };
132
144
  }
133
145
 
134
- function svgGroup(g: Group, parentPos: Vec3, parentRot: Vec3, point: (x: number, y: number) => Vec2, opts: SvgOptions): ReadonlyArray<zOrderElement> {
146
+ function svgGroup(
147
+ g: Group,
148
+ parentPos: Vec3,
149
+ parentRot: Vec3,
150
+ point: (x: number, y: number) => Vec2,
151
+ opts: SvgOptions,
152
+ textHeight: number
153
+ ): ReadonlyArray<zOrderElement> {
135
154
  const pos = vec3TransRot(g.pos, parentPos, parentRot);
136
155
  const rot = vec3RotCombine(parentRot, g.rot ?? vec3Zero);
137
156
  const elements = Array<zOrderElement>();
138
157
  for (const m of g.meshes ?? []) {
139
- elements.push(...svgMesh(m, pos, rot, point, m.material, opts));
158
+ elements.push(...svgMesh(m, pos, rot, point, m.material, opts, textHeight));
140
159
  }
141
160
  for (const sg of g.groups ?? []) {
142
- elements.push(...svgGroup(sg, pos, rot, point, opts));
161
+ elements.push(...svgGroup(sg, pos, rot, point, opts, textHeight));
143
162
  }
144
163
  return elements;
145
164
  }
@@ -150,7 +169,8 @@ function svgMesh(
150
169
  parentRot: Vec3,
151
170
  point: (x: number, y: number) => Vec2,
152
171
  material: Material,
153
- opts: SvgOptions
172
+ opts: SvgOptions,
173
+ textHeight: number
154
174
  ): ReadonlyArray<zOrderElement> {
155
175
  switch (mesh.geometry.type) {
156
176
  case "Box":
@@ -169,7 +189,7 @@ function svgMesh(
169
189
  case "Shape":
170
190
  return shape(mesh.geometry, point, material, opts, parentPos, parentRot);
171
191
  case "Text":
172
- return text(mesh.geometry, point, material.normal, opts, parentPos, parentRot);
192
+ return text(mesh.geometry, point, material.normal, opts, parentPos, parentRot, textHeight);
173
193
  case "Image":
174
194
  return image(mesh.geometry, point, opts, parentPos, parentRot);
175
195
  case "Tube":
@@ -14,7 +14,8 @@ export function text(
14
14
  fill: string,
15
15
  opts: SvgOptions,
16
16
  parentPos: Vec3,
17
- parentRot: Vec3
17
+ parentRot: Vec3,
18
+ textHeight: number
18
19
  ): ReadonlyArray<zOrderElement> {
19
20
  const pos = vec3TransRot(t.pos, parentPos, parentRot);
20
21
  const rot = vec3RotCombine(parentRot, t.rot ?? vec3Zero);
@@ -27,7 +28,7 @@ export function text(
27
28
  }
28
29
 
29
30
  const texts = Array<zOrderElement>();
30
- const fontSize = t.fontSize;
31
+ const fontSize = t.fontSize ?? textHeight;
31
32
  const strings = t.text.split("\n");
32
33
  let posY = strings.length === 1 ? 0 : (fontSize * strings.length - fontSize) / 2;
33
34
  for (const s of strings) {
@@ -15,6 +15,9 @@ import {
15
15
  vec2Zero,
16
16
  vec3Flip,
17
17
  dimensionConvertToTypeMesh,
18
+ dimensionPlacements,
19
+ groupTextBackgrounds,
20
+ sceneTextHeight,
18
21
  } from "../../abstract-3d.js";
19
22
  import type { Optional } from "../../utils.js";
20
23
  import { calculateVisibleViews, isViewVisible } from "../../utils.js";
@@ -110,9 +113,10 @@ function renderInternal(
110
113
  const point = (x: number, y: number): Vec2 => vec2(svgCenter.x + x, svgCenter.y - y);
111
114
  const unitCenterFlipped = vec3Flip(unitCenter);
112
115
 
116
+ const textHeight = sceneTextHeight(scene.groups);
113
117
  const elements = [
114
118
  ...orderElements(
115
- scene.groups.flatMap((g) => svgGroup(g, unitCenterFlipped, unitRot, point, opts)),
119
+ scene.groups.flatMap((g) => svgGroup(groupTextBackgrounds(g, textHeight), unitCenterFlipped, unitRot, point, opts, textHeight)),
116
120
  point
117
121
  ),
118
122
  ];
@@ -120,28 +124,43 @@ function renderInternal(
120
124
  const visibleViews = calculateVisibleViews(view, scene.rotation_deprecated);
121
125
 
122
126
  // Dimensions are drawn for the flat views
123
- for (const dim of angled ? [] : (scene.dimensions_deprecated?.dimensions ?? [])) {
124
- const d = dimensionConvertToTypeMesh(dim, scene.rotation_deprecated ?? vec3Zero, scene.dimensions_deprecated?.material);
127
+ const dimensions = angled ? [] : (scene.dimensions_deprecated?.dimensions ?? []);
128
+ const placements = dimensionPlacements(dimensions, textHeight);
129
+ for (const [i, dim] of dimensions.entries()) {
130
+ const d = dimensionConvertToTypeMesh(
131
+ dim,
132
+ scene.rotation_deprecated ?? vec3Zero,
133
+ scene.dimensions_deprecated?.material,
134
+ textHeight,
135
+ placements[i]
136
+ );
125
137
  if (isViewVisible(d.views[0], visibleViews)) {
126
138
  const pos = vec3TransRot(d.pos, unitCenterFlipped, unitRot);
127
139
  const rot = vec3RotCombine(unitRot, d.rot);
128
140
  for (const m of d.meshes) {
129
- elements.push(...svgMesh(m, pos, rot, point, scene.dimensions_deprecated?.material ?? { normal: "" }, dimOpts));
141
+ elements.push(...svgMesh(m, pos, rot, point, scene.dimensions_deprecated?.material ?? { normal: "" }, dimOpts, textHeight));
130
142
  }
131
143
  }
132
144
  }
133
145
  return { elements, size: svgSize, center: svgCenter };
134
146
  }
135
147
 
136
- function svgGroup(g: Group, parentPos: Vec3, parentRot: Vec3, point: (x: number, y: number) => Vec2, opts: SvgOptions): ReadonlyArray<zOrderElement> {
148
+ function svgGroup(
149
+ g: Group,
150
+ parentPos: Vec3,
151
+ parentRot: Vec3,
152
+ point: (x: number, y: number) => Vec2,
153
+ opts: SvgOptions,
154
+ textHeight: number
155
+ ): ReadonlyArray<zOrderElement> {
137
156
  const pos = vec3TransRot(g.pos, parentPos, parentRot);
138
157
  const rot = vec3RotCombine(parentRot, g.rot ?? vec3Zero);
139
158
  const elements = Array<zOrderElement>();
140
159
  for (const m of g.meshes ?? []) {
141
- elements.push(...svgMesh(m, pos, rot, point, m.material, opts));
160
+ elements.push(...svgMesh(m, pos, rot, point, m.material, opts, textHeight));
142
161
  }
143
162
  for (const sg of g.groups ?? []) {
144
- elements.push(...svgGroup(sg, pos, rot, point, opts));
163
+ elements.push(...svgGroup(sg, pos, rot, point, opts, textHeight));
145
164
  }
146
165
  return elements;
147
166
  }
@@ -152,7 +171,8 @@ function svgMesh(
152
171
  parentRot: Vec3,
153
172
  point: (x: number, y: number) => Vec2,
154
173
  material: Material,
155
- opts: SvgOptions
174
+ opts: SvgOptions,
175
+ textHeight: number
156
176
  ): ReadonlyArray<zOrderElement> {
157
177
  switch (mesh.geometry.type) {
158
178
  case "Box":
@@ -171,7 +191,7 @@ function svgMesh(
171
191
  case "Shape":
172
192
  return shape(mesh.geometry, point, material, opts, parentPos, parentRot);
173
193
  case "Text":
174
- return text(mesh.geometry, point, material.normal, opts, parentPos, parentRot);
194
+ return text(mesh.geometry, point, material.normal, opts, parentPos, parentRot, textHeight);
175
195
  case "Image":
176
196
  return image(mesh.geometry, point, opts, parentPos, parentRot);
177
197
  case "Tube":
@@ -96,8 +96,8 @@ describe("buildScene", () => {
96
96
  });
97
97
 
98
98
  it("keeps dimensions in their own group, and doesn't pick hidden ones", () => {
99
- // Its text is centered half a text height of 44 above the line, here at the origin
100
- const dimension = alignedDimension(vec3(-500, 78, 0), vec3(500, 78, 0), vec3(0, -22, 0), "1000", ["front"], undefined, "dim");
99
+ // Its text, the empty scene's 32 high, is centered a gap and half its height out from the line, over the origin
100
+ const dimension = alignedDimension(vec3(-500, 100, 0), vec3(500, 100, 0), vec3(0, 30, 0), "1000", ["front"], undefined, "dim");
101
101
  const built = build(scene([], { dimensions_deprecated: { dimensions: [dimension], material: red } }));
102
102
  expect(built.dimensions.children.length).toBeGreaterThan(0);
103
103
  expect(pickId(built, 0)).toBe("dim");
@@ -216,7 +216,7 @@ function createPart(
216
216
  case "text":
217
217
  return createTextPart(
218
218
  items.flatMap(({ mesh: { geometry: g } }) =>
219
- g.type === "Text" ? [{ text: g.text, fontSize: g.fontSize, local: transform(g.pos, g.rot) }] : []
219
+ g.type === "Text" ? [{ text: g.text, fontSize: g.fontSize ?? compiled.textHeight, local: transform(g.pos, g.rot) }] : []
220
220
  ),
221
221
  options.atlas
222
222
  );
@@ -1,6 +1,14 @@
1
1
  import { Euler, Matrix4, Quaternion, Vector3 } from "three";
2
2
  import type { Group, Mesh, Relation, Scene, Vec3 } from "../../../abstract-3d.js";
3
- import { dimensionConvertToTypeMesh, vec3, vec3Scale, vec3Zero } from "../../../abstract-3d.js";
3
+ import {
4
+ dimensionConvertToTypeMesh,
5
+ dimensionPlacements,
6
+ groupTextBackgrounds,
7
+ sceneTextHeight,
8
+ vec3,
9
+ vec3Scale,
10
+ vec3Zero,
11
+ } from "../../../abstract-3d.js";
4
12
 
5
13
  /**
6
14
  * Scene → flat items with world matrices, with no GPU objects, so everything here is testable in Node; groups transform as the r3f renderer's
@@ -48,6 +56,8 @@ export type CompiledScene = {
48
56
  readonly matrix: Matrix4;
49
57
  /** World matrix of each top-level group */
50
58
  readonly roots: ReadonlyArray<Matrix4>;
59
+ /** The height of the scene's texts without a font size, see `sceneTextHeight` */
60
+ readonly textHeight: number;
51
61
  };
52
62
 
53
63
  type Output = { readonly items: Array<Item>; readonly animations: Array<Animation> };
@@ -67,16 +77,24 @@ export function compileScene(scene: Scene, createGroupId?: (g: Group) => string)
67
77
  ...dimensions.map((d): Target => ({ kind: "dimension", id: d.id })),
68
78
  ...relations.map((relation): Target => ({ kind: "relation", relation })),
69
79
  ];
70
- scene.groups.forEach((g, i) => addGroup(out, g, matrix, undefined, "group", i));
80
+ const textHeight = sceneTextHeight(scene.groups);
81
+ scene.groups.forEach((g, i) => addGroup(out, groupTextBackgrounds(g, textHeight), matrix, undefined, "group", i));
82
+ const placements = dimensionPlacements(dimensions, textHeight);
71
83
  dimensions.forEach((dimension, i) => {
72
- const mesh = dimensionConvertToTypeMesh(dimension, scene.rotation_deprecated ?? vec3Zero, scene.dimensions_deprecated?.material);
84
+ const mesh = dimensionConvertToTypeMesh(
85
+ dimension,
86
+ scene.rotation_deprecated ?? vec3Zero,
87
+ scene.dimensions_deprecated?.material,
88
+ textHeight,
89
+ placements[i]
90
+ );
73
91
  const base = { style: "dimension", target: scene.groups.length + i, data: undefined, animation: undefined } as const;
74
92
  addMeshes(out, mesh.meshes, matrix.clone().multiply(transform(mesh.pos, mesh.rot)), base);
75
93
  });
76
94
  relations.forEach((relation, i) =>
77
95
  relation.groups?.forEach((g) => addGroup(out, g, matrix, undefined, "relation", scene.groups.length + dimensions.length + i))
78
96
  );
79
- return { ...out, targets, matrix, roots: scene.groups.map((g) => matrix.clone().multiply(transform(g.pos, g.rot))) };
97
+ return { ...out, targets, matrix, roots: scene.groups.map((g) => matrix.clone().multiply(transform(g.pos, g.rot))), textHeight };
80
98
  }
81
99
 
82
100
  function addGroup(out: Output, g: Group, parent: Matrix4, parentAnimation: number | undefined, style: "group" | "relation", target: number): void {