three-cad-viewer 4.3.2 → 4.3.5
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/dist/core/types.d.ts +10 -11
- package/dist/rendering/material-factory.d.ts +3 -5
- package/dist/scene/clipping.d.ts +6 -0
- package/dist/three-cad-viewer.esm.js +62 -45
- package/dist/three-cad-viewer.esm.js.map +1 -1
- package/dist/three-cad-viewer.esm.min.js +2 -2
- package/dist/three-cad-viewer.js +62 -45
- package/dist/three-cad-viewer.min.js +2 -2
- package/package.json +2 -1
- package/src/_version.ts +1 -1
- package/src/core/studio-manager.ts +1 -1
- package/src/core/types.ts +11 -9
- package/src/rendering/material-factory.ts +26 -31
- package/src/rendering/studio-floor.ts +1 -1
- package/src/scene/clipping.ts +10 -0
- package/src/scene/nestedgroup.ts +25 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "three-cad-viewer",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "WebGL-based CAD viewer built on Three.js with clipping planes, measurement tools, and tree navigation",
|
|
6
6
|
"repository": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"url": "https://github.com/bernhard-42/three-cad-viewer"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
+
"README.md",
|
|
11
12
|
"dist/**/*.d.ts",
|
|
12
13
|
"dist/three-cad-viewer.css",
|
|
13
14
|
"dist/three-cad-viewer.esm.js",
|
package/src/_version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version: string = "4.3.
|
|
1
|
+
export const version: string = "4.3.5";
|
|
@@ -540,7 +540,7 @@ class StudioManager {
|
|
|
540
540
|
light.target.position.copy(bboxCenter);
|
|
541
541
|
|
|
542
542
|
light.castShadow = true;
|
|
543
|
-
const frustumSize = maxExtent *
|
|
543
|
+
const frustumSize = maxExtent * 6.0;
|
|
544
544
|
light.shadow.camera.left = -frustumSize;
|
|
545
545
|
light.shadow.camera.right = frustumSize;
|
|
546
546
|
light.shadow.camera.top = frustumSize;
|
package/src/core/types.ts
CHANGED
|
@@ -625,27 +625,29 @@ export interface MaterialAppearance {
|
|
|
625
625
|
*
|
|
626
626
|
* This format is produced by the threejs-materials Python library, which catalogs
|
|
627
627
|
* PBR materials from ambientCG, GPUOpen, PolyHaven, and PhysicallyBased.
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
628
|
+
* `values` contains scalar properties (e.g., color as linear RGB array,
|
|
629
|
+
* roughness as float). `textures` contains texture references (inline data URIs
|
|
630
|
+
* or file paths) keyed by property name.
|
|
631
631
|
*
|
|
632
|
-
* Detected by the presence of the `
|
|
632
|
+
* Detected by the presence of the `values` key.
|
|
633
633
|
* Extra keys from threejs-materials (id, name, source, url, license) pass through
|
|
634
634
|
* harmlessly and are not part of this interface.
|
|
635
635
|
*/
|
|
636
636
|
export interface MaterialXMaterial {
|
|
637
|
-
/**
|
|
638
|
-
|
|
637
|
+
/** Scalar PBR property values (e.g., color, metalness, roughness). */
|
|
638
|
+
values: Record<string, unknown>;
|
|
639
|
+
/** Texture map references keyed by property name (e.g., color, normal). */
|
|
640
|
+
textures: Record<string, string>;
|
|
639
641
|
/** Optional texture tiling [u, v], default [1, 1]. Applied to all textures. */
|
|
640
642
|
textureRepeat?: [number, number];
|
|
641
643
|
}
|
|
642
644
|
|
|
643
645
|
/**
|
|
644
646
|
* Type guard to check if a material entry is a threejs-materials format dict.
|
|
645
|
-
* Detected by the presence of the `
|
|
647
|
+
* Detected by the presence of the `values` key.
|
|
646
648
|
*/
|
|
647
649
|
export function isMaterialXMaterial(m: unknown): m is MaterialXMaterial {
|
|
648
|
-
return typeof m === "object" && m !== null && "
|
|
650
|
+
return typeof m === "object" && m !== null && "values" in m;
|
|
649
651
|
}
|
|
650
652
|
|
|
651
653
|
// =============================================================================
|
|
@@ -854,7 +856,7 @@ export interface Shapes {
|
|
|
854
856
|
/** User-defined material library (root node).
|
|
855
857
|
* Values can be:
|
|
856
858
|
* - string: builtin preset reference (e.g., "builtin:car-paint")
|
|
857
|
-
* - MaterialXMaterial: threejs-materials format (detected by `
|
|
859
|
+
* - MaterialXMaterial: threejs-materials format (detected by `values` key)
|
|
858
860
|
* - MaterialAppearance: preset with overrides (e.g., { builtin: "acrylic-clear", color: "#55a0e3" })
|
|
859
861
|
*/
|
|
860
862
|
materials?: Record<string, string | MaterialXMaterial | MaterialAppearance> | undefined;
|
|
@@ -478,10 +478,12 @@ class MaterialFactory {
|
|
|
478
478
|
}
|
|
479
479
|
|
|
480
480
|
// --- Anisotropy (brushed metal) ---
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
481
|
+
if (def.anisotropy !== undefined && def.anisotropy > 0) {
|
|
482
|
+
material.anisotropy = def.anisotropy;
|
|
483
|
+
if (def.anisotropyRotation !== undefined) {
|
|
484
|
+
material.anisotropyRotation = def.anisotropyRotation;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
485
487
|
|
|
486
488
|
// --- Textures ---
|
|
487
489
|
// Resolve all texture references via TextureCache.
|
|
@@ -501,14 +503,16 @@ class MaterialFactory {
|
|
|
501
503
|
* "roughness", "normal") where each entry has an optional `value` (scalar or
|
|
502
504
|
* [r,g,b] array in **linear RGB**) and/or `texture` (inline data URI).
|
|
503
505
|
*
|
|
504
|
-
* @param
|
|
506
|
+
* @param values - Scalar PBR values from threejs-materials
|
|
507
|
+
* @param textures - Texture map references from threejs-materials
|
|
505
508
|
* @param textureRepeat - Optional [u, v] texture tiling applied to all loaded textures
|
|
506
509
|
* @param textureCache - TextureCache for resolving data URI textures
|
|
507
510
|
* @param label - Optional label for GPU tracking
|
|
508
511
|
* @returns Configured MeshPhysicalMaterial
|
|
509
512
|
*/
|
|
510
513
|
async createStudioMaterialFromMaterialX(
|
|
511
|
-
|
|
514
|
+
values: Record<string, unknown>,
|
|
515
|
+
textures: Record<string, string>,
|
|
512
516
|
textureRepeat: [number, number] | undefined,
|
|
513
517
|
textureCache: TextureCacheInterface | null,
|
|
514
518
|
label?: string,
|
|
@@ -524,36 +528,31 @@ class MaterialFactory {
|
|
|
524
528
|
};
|
|
525
529
|
|
|
526
530
|
// Warn once if displacement data is present (not supported in Studio)
|
|
527
|
-
if (
|
|
531
|
+
if (textures.displacement || values.displacementScale !== undefined) {
|
|
528
532
|
logger.warn("Displacement not supported by the Studio");
|
|
529
533
|
}
|
|
530
534
|
|
|
531
|
-
for (const [key,
|
|
532
|
-
if (prop.value === undefined) continue;
|
|
533
|
-
|
|
535
|
+
for (const [key, value] of Object.entries(values)) {
|
|
534
536
|
// Skip displacement properties (not supported, would waste GPU memory)
|
|
535
537
|
if (key === "displacement" || key === "displacementScale" || key === "displacementBias") continue;
|
|
536
538
|
|
|
537
|
-
// Skip anisotropy — requires tangent vectors that CAD meshes don't have
|
|
538
|
-
if (key === "anisotropy" || key === "anisotropyRotation") continue;
|
|
539
|
-
|
|
540
539
|
// Color arrays → THREE.Color (already linear, no sRGB conversion)
|
|
541
|
-
if (COLOR_ARRAY_KEYS.has(key) && Array.isArray(
|
|
542
|
-
const [r, g, b] =
|
|
540
|
+
if (COLOR_ARRAY_KEYS.has(key) && Array.isArray(value)) {
|
|
541
|
+
const [r, g, b] = value as number[];
|
|
543
542
|
matOptions[key] = new THREE.Color(r, g, b);
|
|
544
|
-
} else if ((key === "normalScale" || key === "clearcoatNormalScale") && Array.isArray(
|
|
545
|
-
matOptions[key] = new THREE.Vector2(
|
|
546
|
-
} else if (key === "iridescenceThicknessRange" && Array.isArray(
|
|
547
|
-
matOptions[key] =
|
|
543
|
+
} else if ((key === "normalScale" || key === "clearcoatNormalScale") && Array.isArray(value)) {
|
|
544
|
+
matOptions[key] = new THREE.Vector2(value[0], value[1]);
|
|
545
|
+
} else if (key === "iridescenceThicknessRange" && Array.isArray(value)) {
|
|
546
|
+
matOptions[key] = value;
|
|
548
547
|
} else {
|
|
549
|
-
matOptions[key] =
|
|
548
|
+
matOptions[key] = value;
|
|
550
549
|
}
|
|
551
550
|
}
|
|
552
551
|
|
|
553
552
|
// --- Handle transmission ---
|
|
554
|
-
const transmissionVal =
|
|
555
|
-
const opacityVal =
|
|
556
|
-
const transparentVal =
|
|
553
|
+
const transmissionVal = values.transmission;
|
|
554
|
+
const opacityVal = values.opacity;
|
|
555
|
+
const transparentVal = values.transparent;
|
|
557
556
|
if (typeof transmissionVal === "number" && transmissionVal > 0) {
|
|
558
557
|
matOptions.transparent = false;
|
|
559
558
|
matOptions.opacity = 1.0;
|
|
@@ -571,9 +570,7 @@ class MaterialFactory {
|
|
|
571
570
|
// --- Resolve textures ---
|
|
572
571
|
let hasTextures = false;
|
|
573
572
|
if (textureCache) {
|
|
574
|
-
for (const [key,
|
|
575
|
-
if (!prop.texture) continue;
|
|
576
|
-
|
|
573
|
+
for (const [key, textureRef] of Object.entries(textures)) {
|
|
577
574
|
const mapName = PROPERTY_TO_MAP[key];
|
|
578
575
|
if (!mapName) continue;
|
|
579
576
|
|
|
@@ -584,7 +581,7 @@ class MaterialFactory {
|
|
|
584
581
|
const roleForCache = colorSpace === THREE.SRGBColorSpace
|
|
585
582
|
? "baseColorTexture"
|
|
586
583
|
: "normalTexture";
|
|
587
|
-
const tex = await textureCache.get(
|
|
584
|
+
const tex = await textureCache.get(textureRef, roleForCache);
|
|
588
585
|
if (tex) {
|
|
589
586
|
if (textureRepeat) {
|
|
590
587
|
tex.repeat.set(textureRepeat[0], textureRepeat[1]);
|
|
@@ -594,7 +591,6 @@ class MaterialFactory {
|
|
|
594
591
|
hasTextures = true;
|
|
595
592
|
}
|
|
596
593
|
}
|
|
597
|
-
|
|
598
594
|
}
|
|
599
595
|
|
|
600
596
|
// Enable alpha cutout when an alphaMap is present
|
|
@@ -719,9 +715,8 @@ class MaterialFactory {
|
|
|
719
715
|
const sheenRoughnessTex = await resolve(def.sheenRoughnessMap, "sheenRoughnessTexture");
|
|
720
716
|
if (sheenRoughnessTex) material.sheenRoughnessMap = sheenRoughnessTex;
|
|
721
717
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
// if (anisotropyTex) material.anisotropyMap = anisotropyTex;
|
|
718
|
+
const anisotropyTex = await resolve(def.anisotropyMap, "anisotropyTexture");
|
|
719
|
+
if (anisotropyTex) material.anisotropyMap = anisotropyTex;
|
|
725
720
|
}
|
|
726
721
|
|
|
727
722
|
/**
|
|
@@ -77,7 +77,7 @@ class StudioFloor {
|
|
|
77
77
|
* Create a shadow-receiving plane at the floor position.
|
|
78
78
|
*/
|
|
79
79
|
private _createShadowPlane(zPosition: number, sceneSize: number): void {
|
|
80
|
-
const floorSize = sceneSize *
|
|
80
|
+
const floorSize = sceneSize * 6;
|
|
81
81
|
|
|
82
82
|
const geometry = new THREE.PlaneGeometry(floorSize, floorSize);
|
|
83
83
|
const material = new THREE.ShadowMaterial({ opacity: 0.5, depthWrite: false });
|
package/src/scene/clipping.ts
CHANGED
|
@@ -154,6 +154,16 @@ class CenteredPlane extends THREE.Plane {
|
|
|
154
154
|
const z = this.distanceToPoint(new THREE.Vector3(0, 0, 0));
|
|
155
155
|
this.constant = z - c + value;
|
|
156
156
|
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Clone this CenteredPlane.
|
|
160
|
+
* Overrides THREE.Plane.clone() which calls `new this.constructor()` without
|
|
161
|
+
* arguments, causing `center` to be undefined during shadow map generation.
|
|
162
|
+
*/
|
|
163
|
+
// @ts-expect-error -- THREE.Plane.clone() returns `this`, but we need a concrete CenteredPlane
|
|
164
|
+
clone(): CenteredPlane {
|
|
165
|
+
return new CenteredPlane(this.normal.clone(), this.centeredConstant, [...this.center]);
|
|
166
|
+
}
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
// ============================================================================
|
package/src/scene/nestedgroup.ts
CHANGED
|
@@ -142,12 +142,9 @@ function materialHasTexture(def: MaterialAppearance): boolean {
|
|
|
142
142
|
return false;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
/** Check whether a threejs-materials entry has texture references
|
|
145
|
+
/** Check whether a threejs-materials entry has texture references. */
|
|
146
146
|
function materialXHasTextures(entry: MaterialXMaterial): boolean {
|
|
147
|
-
|
|
148
|
-
if (prop.texture) return true;
|
|
149
|
-
}
|
|
150
|
-
return false;
|
|
147
|
+
return Object.keys(entry.textures).length > 0;
|
|
151
148
|
}
|
|
152
149
|
|
|
153
150
|
class NestedGroup {
|
|
@@ -322,7 +319,7 @@ class NestedGroup {
|
|
|
322
319
|
return null;
|
|
323
320
|
}
|
|
324
321
|
|
|
325
|
-
// MaterialXMaterial entry: object with `
|
|
322
|
+
// MaterialXMaterial entry: object with `values` key
|
|
326
323
|
if (isMaterialXMaterial(entry)) {
|
|
327
324
|
this.resolvedMaterialX.set(tag, entry);
|
|
328
325
|
return entry;
|
|
@@ -339,12 +336,16 @@ class NestedGroup {
|
|
|
339
336
|
);
|
|
340
337
|
return null;
|
|
341
338
|
}
|
|
342
|
-
|
|
339
|
+
// Strip preset color unless the user explicitly provides one,
|
|
340
|
+
// so the leaf node's CAD color is used as fallback.
|
|
341
|
+
const { color: presetColor, ...presetRest } = preset;
|
|
342
|
+
const resolved: MaterialAppearance = "color" in appearance
|
|
343
|
+
? { ...preset, ...appearance }
|
|
344
|
+
: { ...presetRest, ...appearance };
|
|
343
345
|
this.resolvedMaterials.set(tag, resolved);
|
|
344
346
|
return resolved;
|
|
345
347
|
}
|
|
346
348
|
|
|
347
|
-
// Should not happen with current type, but guard anyway
|
|
348
349
|
logger.warn(`Unrecognised material entry for tag '${tag}' on '${objectPath}'`);
|
|
349
350
|
return null;
|
|
350
351
|
}
|
|
@@ -1253,7 +1254,8 @@ class NestedGroup {
|
|
|
1253
1254
|
if (resolved && isMaterialXMaterial(resolved)) {
|
|
1254
1255
|
// --- threejs-materials path ---
|
|
1255
1256
|
studioMaterial = await this.materialFactory.createStudioMaterialFromMaterialX(
|
|
1256
|
-
resolved.
|
|
1257
|
+
resolved.values,
|
|
1258
|
+
resolved.textures,
|
|
1257
1259
|
resolved.textureRepeat,
|
|
1258
1260
|
this._textureCache as TextureCacheInterface,
|
|
1259
1261
|
);
|
|
@@ -1339,6 +1341,20 @@ class NestedGroup {
|
|
|
1339
1341
|
studioBack = cachedBack as THREE.MeshPhysicalMaterial;
|
|
1340
1342
|
}
|
|
1341
1343
|
|
|
1344
|
+
// Compute tangents for anisotropic materials (required by Three.js)
|
|
1345
|
+
if (
|
|
1346
|
+
studioMaterial instanceof THREE.MeshPhysicalMaterial &&
|
|
1347
|
+
studioMaterial.anisotropy > 0 &&
|
|
1348
|
+
obj.shapeGeometry?.getAttribute("uv") != null &&
|
|
1349
|
+
obj.shapeGeometry.getAttribute("tangent") == null
|
|
1350
|
+
) {
|
|
1351
|
+
try {
|
|
1352
|
+
obj.shapeGeometry.computeTangents();
|
|
1353
|
+
} catch {
|
|
1354
|
+
logger.debug(`Studio "${path}": tangent computation failed, anisotropy may have artifacts`);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1342
1358
|
// Apply to ObjectGroup
|
|
1343
1359
|
obj.enterStudioMode(
|
|
1344
1360
|
studioMaterial instanceof THREE.MeshPhysicalMaterial ? studioMaterial : null,
|