UNPKG

threepipe

Version:

A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.

160 lines (145 loc) 7.37 kB
import {Rhino3dmLoader} from 'three/examples/jsm/loaders/3DMLoader.js' import { Color, DoubleSide, InstancedMesh, Line, LineSegments, LoadingManager, Material, Mesh, MeshStandardMaterial, Object3D, Points, } from 'three' import {getUrlQueryParam} from 'ts-browser-helpers' export class Rhino3dmLoader2 extends Rhino3dmLoader { // todo since 8.4.0(next version) it's not able to load some files like https://drive.google.com/file/d/1mWOCGIOWmaC4L7IxCvWM9dgeVYeDl8L-/view (request for access) // gets stuck at `rhino.File3dm.fromByteArray` call in the worker. Note three.js uses 8.4.0 version of rhino3dm. /** * Path to the rhino3dm.js library, default uses jsdelivr CDN * You may want to set this to your own path or use {@link Rhino3dmLoader2.setLibraryPath} * @default `https://cdn.jsdelivr.net/npm/rhino3dm@${getUrlQueryParam('rhino3dm', '8.0.1')}/` */ public static LIBRARY_PATH = `https://cdn.jsdelivr.net/npm/rhino3dm@${getUrlQueryParam('rhino3dm', '8.0.1')}/` constructor(manager?: LoadingManager) { super(manager) this.setLibraryPath(Rhino3dmLoader2.LIBRARY_PATH) } public static ImportMaterials = true public static ForceLayerMaterials = false public static ReplaceWithInstancedMesh = false public static HideLineMesh = false public static HidePointMesh = false public static LoadUserDataStrings = true public static LoadUserDataWarnings = true materials: Material[] = [] // eslint-disable-next-line @typescript-eslint/naming-convention _createMaterial(material: any): Material { if (!Rhino3dmLoader2.ImportMaterials) return this.materials[0] || new MeshStandardMaterial({ color: new Color(1, 1, 1), metalness: 0.8, name: 'default', side: DoubleSide, }) return super._createMaterial(material) } private declare _compareMaterials: (material: Material) => Material async loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<Object3D> { const ret = await super.loadAsync(url, onProgress) ret.rotateX(-Math.PI / 2) // since models are rotated if (ret.userData.materials) delete ret.userData.materials // we don't want them saved in the file during export // console.log(ret.userData) const layers = ret.userData.layers ret.traverse((obj) => { const castShadow = obj.userData.attributes?.castsShadows const receiveShadow = obj.userData.attributes?.receivesShadows obj.castShadow = castShadow obj.receiveShadow = receiveShadow const layerIndex = obj.userData.attributes?.layerIndex ?? obj.userData.defAttributes?.layerIndex const layer = layers[layerIndex] if (layer) obj.userData.rhinoLayer = layer obj.userData.rhino3dmRoot = ret.uuid if (!Rhino3dmLoader2.LoadUserDataStrings) obj.userData.strings = [] if (!Rhino3dmLoader2.LoadUserDataWarnings) delete obj.userData.warnings this._hideLineMesh(obj) this._useInstancedMesh(obj) this._useMaterialSource(obj, layer) }) this.materials = [] // so that next file load doesn't give the same materials. return ret } private _useMaterialSource(obj: Object3D, layer: any) { if (!Rhino3dmLoader2.ImportMaterials) return const mesh = obj as Mesh if ((mesh.material as any)?.name === 'default' || Rhino3dmLoader2.ForceLayerMaterials) { // https://developer.rhino3d.com/api/rhinoscript/object_methods/objectmaterialsource.htm const materialSource = mesh.userData.attributes?.materialSource || mesh.userData.defAttributes?.materialSource const colorSource = mesh.userData.attributes?.colorSource || mesh.userData.defAttributes?.colorSource // const materialSource = mesh.userData.defAttributes?.materialSource // console.log(materialSource, mesh.userData.attributes, mesh.userData.defAttributes) if (!Rhino3dmLoader2.ForceLayerMaterials && !materialSource && !colorSource) return if (Rhino3dmLoader2.ForceLayerMaterials || (materialSource?.value === 0 || materialSource?.value === 1 && colorSource?.value === 0) ) { // material from layer if (layer) { mesh.material = this._compareMaterials(this._createMaterial({ diffuseColor: layer.color, name: layer.name, transparency: 0, textures: [], })) } } else if (materialSource?.value === 3 || materialSource?.value === 1 && colorSource?.value === 3) { // material from parent mesh.traverseAncestors((parent: any) => { if (parent?.material) mesh.material = parent.material }) } else if (materialSource && materialSource.value !== 1) { console.warn('Unknown material source', materialSource, mesh, mesh.userData.attributes) } } } private _useInstancedMesh(obj: Object3D) { if (!Rhino3dmLoader2.ReplaceWithInstancedMesh) return if (obj.children.length <= 0) return const children = obj.children const geometries = children.map((c: any) => c.geometry) const uniqueGeometries = geometries.filter((g, i) => geometries.indexOf(g) === i) uniqueGeometries.forEach((g) => { const instances = children.filter((c: any) => c.geometry === g) const instances2 = instances.length > 0 ? instances.filter((c: any) => c.material === (instances[0] as any).material) : [] if (instances2.length > 1) { const instanced = new InstancedMesh(g, (instances2[0] as any).material, instances2.length) instanced.userData = {...instances2[0].userData} instanced.userData.instanceUserData = [] instanced.userData.attributes = instanced.userData.defAttributes || instanced.userData.attributes if (instanced.userData.defAttributes) delete instanced.userData.defAttributes instanced.name = instanced.userData.attributes?.name || instances2[0].name instances2.forEach((c: any, i: number) => { instanced.setMatrixAt(i, c.matrix) obj.remove(c) instanced.userData.instanceUserData.push(c.userData) }) obj.add(instanced) } }) } private _hideLineMesh(obj: Object3D) { if (!Rhino3dmLoader2.HideLineMesh && !Rhino3dmLoader2.HidePointMesh) return if (obj.children.length <= 0) return const toHide: any[] = [] obj.traverse((c) => { if (c && ( Rhino3dmLoader2.HideLineMesh && ((c as Line).isLine || (c as LineSegments).isLineSegments)) || Rhino3dmLoader2.HidePointMesh && (c as Points).isPoints ) toHide.push(c) }) toHide.forEach((c) => { c.userData.visible_3dm = c.visible c.visible = false }) } }