| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Rhino 3DM Load</title> | |||||
| <!-- Import maps polyfill --> | |||||
| <!-- Remove this when import maps will be widely supported --> | |||||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||||
| <script type="importmap"> | |||||
| { | |||||
| "imports": { | |||||
| "threepipe": "./../../dist/index.mjs" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style id="example-style"> | |||||
| html, body, #canvas-container, #mcanvas { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="canvas-container"> | |||||
| <canvas id="mcanvas"></canvas> | |||||
| </div> | |||||
| </body> |
| import {_testFinish, IObject3D, Rhino3dmLoadPlugin, ThreeViewer} from 'threepipe' | |||||
| async function init() { | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| msaa: true, | |||||
| dropzone: { | |||||
| allowedExtensions: ['3dm', 'hdr'], | |||||
| addOptions: { | |||||
| disposeSceneObjects: true, | |||||
| autoSetEnvironment: true, // when hdr is dropped | |||||
| autoSetBackground: true, | |||||
| }, | |||||
| }, | |||||
| }) | |||||
| viewer.addPluginSync(Rhino3dmLoadPlugin) | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | |||||
| setBackground: true, | |||||
| }) | |||||
| const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/3dm/Rhino_Logo.3dm', { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }) | |||||
| console.log(result) | |||||
| // Some objects are invisible in this file, set visible to true for all objects | |||||
| result?.traverse(object => { | |||||
| object.visible = true | |||||
| }) | |||||
| result?.setDirty() | |||||
| } | |||||
| init().then(_testFinish) |
| import {Rhino3dmLoader} from 'three/examples/jsm/loaders/3DMLoader.js' | |||||
| import {Color, DoubleSide, InstancedMesh, LoadingManager, Material, Mesh, MeshStandardMaterial, Object3D} from 'three' | |||||
| export class Rhino3dmLoader2 extends Rhino3dmLoader { | |||||
| public static LIBRARY_PATH = 'https://cdn.jsdelivr.net/npm/rhino3dm@7.15.0/' | |||||
| constructor(manager?: LoadingManager) { | |||||
| super(manager) | |||||
| this.setLibraryPath(Rhino3dmLoader2.LIBRARY_PATH) | |||||
| } | |||||
| public static ImportMaterials = true | |||||
| materials: Material[] = [] | |||||
| private _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, | |||||
| }) | |||||
| // @ts-expect-error not in ts | |||||
| return super._createMaterial(material) | |||||
| } | |||||
| private _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 | |||||
| // console.log(obj.userData.attributes) | |||||
| // instancing | |||||
| 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') { | |||||
| // 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 (!materialSource && !colorSource) return | |||||
| if (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) | |||||
| } | |||||
| } | |||||
| } | |||||
| static ReplaceWithInstancedMesh = false | |||||
| 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) | |||||
| } | |||||
| }) | |||||
| } | |||||
| } | |||||
| export {GLTFLoader2, type GLTFPreparser} from './GLTFLoader2' | export {GLTFLoader2, type GLTFPreparser} from './GLTFLoader2' | ||||
| export {DRACOLoader2} from './DRACOLoader2' | export {DRACOLoader2} from './DRACOLoader2' | ||||
| export {RGBEPNGLoader} from './RGBEPNGLoader' | export {RGBEPNGLoader} from './RGBEPNGLoader' | ||||
| export {Rhino3dmLoader2} from './Rhino3dmLoader2' |
| export * from './postprocessing/index' | export * from './postprocessing/index' | ||||
| export * from './materials/index' | export * from './materials/index' | ||||
| export * from './rendering/index' | export * from './rendering/index' | ||||
| // testing | |||||
| export {_testStart, _testFinish} from './testing/testing' | export {_testStart, _testFinish} from './testing/testing' | ||||
| // deprecated | |||||
| export {autoCenterObject3D, autoScaleObject3D} from './three/utils/object-transform' | export {autoCenterObject3D, autoScaleObject3D} from './three/utils/object-transform' |
| import {IViewerPluginSync, ThreeViewer} from '../../viewer' | |||||
| import {Importer, Rhino3dmLoader2} from '../../assetmanager' | |||||
| export class Rhino3dmLoadPlugin implements IViewerPluginSync { | |||||
| declare ['constructor']: typeof Rhino3dmLoadPlugin | |||||
| public static readonly PluginType = 'Rhino3dmLoadPlugin' | |||||
| private _importer = new Importer(Rhino3dmLoader2, ['3dm'], ['model/vnd.3dm', 'model/3dm'], true) | |||||
| onAdded(viewer: ThreeViewer) { | |||||
| viewer.assetManager.importer.addImporter(this._importer) | |||||
| } | |||||
| onRemove(viewer: ThreeViewer) { | |||||
| viewer.assetManager.importer.removeImporter(this._importer) | |||||
| } | |||||
| dispose() { | |||||
| return | |||||
| } | |||||
| } |
| // base | |||||
| export {PipelinePassPlugin} from './base/PipelinePassPlugin' | |||||
| // pipeline | |||||
| export {DepthBufferPlugin} from './pipeline/DepthBufferPlugin' | export {DepthBufferPlugin} from './pipeline/DepthBufferPlugin' | ||||
| export {NormalBufferPlugin} from './pipeline/NormalBufferPlugin' | export {NormalBufferPlugin} from './pipeline/NormalBufferPlugin' | ||||
| export type {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin' | export type {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin' | ||||
| export {PipelinePassPlugin} from './base/PipelinePassPlugin' | |||||
| // ui | |||||
| export {RenderTargetPreviewPlugin} from './ui/RenderTargetPreviewPlugin' | export {RenderTargetPreviewPlugin} from './ui/RenderTargetPreviewPlugin' | ||||
| export {TweakpaneUiPlugin} from './ui/tweakpane/TweakpaneUiPlugin' | export {TweakpaneUiPlugin} from './ui/tweakpane/TweakpaneUiPlugin' | ||||
| // interaction | |||||
| export {DropzonePlugin, type DropzonePluginOptions} from './interaction/DropzonePlugin' | export {DropzonePlugin, type DropzonePluginOptions} from './interaction/DropzonePlugin' | ||||
| // import | |||||
| export {Rhino3dmLoadPlugin} from './import/Rhino3dmLoadPlugin' |
| /** | /** | ||||
| * @deprecated use {@link IObject3D.autoScale} instead, or {@link iObjectCommons.autoScale} | * @deprecated use {@link IObject3D.autoScale} instead, or {@link iObjectCommons.autoScale} | ||||
| * @param obj | * @param obj | ||||
| * @param autoScaleRadius | |||||
| * @param isCentered | |||||
| * @param setDirty | |||||
| */ | */ | ||||
| export function autoScaleObject3D(obj: Object3D, autoScaleRadius?: number, isCentered?: boolean, setDirty?: boolean) { | export function autoScaleObject3D(obj: Object3D, autoScaleRadius?: number, isCentered?: boolean, setDirty?: boolean) { | ||||
| return iObjectCommons.autoScale.call(obj, autoScaleRadius, isCentered, setDirty) | return iObjectCommons.autoScale.call(obj, autoScaleRadius, isCentered, setDirty) |