UNPKG

threepipe

Version:

A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.

284 lines 12.7 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { AViewerPluginSync } from '../../viewer'; import { PickingPlugin } from '../interaction/PickingPlugin'; import { imageBitmapToBase64, makeColorSvgCircle, serialize } from 'ts-browser-helpers'; import { PhysicalMaterial } from '../../core'; import { MaterialPreviewGenerator } from '../../three'; /** * Material Configurator Plugin (Base) * * This plugin allows you to create variations of materials mapped to material names or uuids in the scene. * These variations can be applied to the materials in the scene. (This copies the properties to the same material instances instead of assigning new materials) * The plugin interfaces with the picking plugin and also provides uiConfig to show and edit the variations. * * See `MaterialConfiguratorPlugin` in [plugin-configurator](https://threepipe.org/plugins/configurator/docs/index.html) for example on inheriting with a custom UI renderer. * * @category Plugins */ export class MaterialConfiguratorBasePlugin extends AViewerPluginSync { constructor() { super(); this.enabled = true; this._uiNeedRefresh = false; /** * Apply all variations(by selected index or first item) when a config is loaded */ this.applyOnLoad = true; this.variations = []; this._selectedMaterial = () => (this._picking?.getSelectedObject()?.material || undefined); this.uiConfig = { label: 'Material Configurator', type: 'folder', // expanded: true, children: [ () => [ { type: 'input', label: 'uuid', property: [this._selectedMaterial(), 'uuid'], hidden: () => !this._selectedMaterial(), disabled: true, }, { type: 'input', label: 'mapping', hidden: () => !this._selectedMaterial(), property: () => [this.getSelectedVariation(), 'uuid'], onChange: async () => this.refreshUi(), }, { type: 'input', label: 'title', hidden: () => !this._selectedMaterial(), property: () => [this.getSelectedVariation(), 'title'], onChange: async () => this.refreshUi(), }, { type: 'dropdown', label: 'Preview Type', hidden: () => !this._selectedMaterial(), property: () => [this.getSelectedVariation(), 'preview'], onChange: async () => this.refreshUi(), children: ['generate:sphere', 'generate:cube', 'color', 'map', 'emissive', ...Object.keys(PhysicalMaterial.MaterialProperties).filter(x => x.endsWith('Map'))].map(k => ({ label: k, value: k, })), }, ...this.getSelectedVariation()?.materials.map(m => { return m.uiConfig ? Object.assign(m.uiConfig, { expanded: false }) : {}; }) || [], { type: 'button', label: 'Clear variations', hidden: () => !this._selectedMaterial(), value: async () => { const v = this.getSelectedVariation(); if (v && await this._viewer.dialog.confirm('Material configurator: Remove all variations for this material?')) v.materials = []; this.refreshUi(); }, }, { type: 'button', label: 'Remove completely', hidden: () => !this._selectedMaterial(), value: async () => { const v = this.getSelectedVariation(); if (v && await this._viewer.dialog.confirm('Material configurator: Remove this variation?')) { this.removeVariation(v); } }, }, { type: 'button', label: 'Add Variation', hidden: () => !this._selectedMaterial(), value: async () => { const mat = this._selectedMaterial(); if (!mat) return; if (!mat.name && !await this._viewer?.dialog.confirm('Material configurator: Material has no name. Use uuid instead?')) return; this.addVariation(mat); }, }, { type: 'button', label: 'Refresh Ui', value: () => this.refreshUi(), }, { type: 'button', label: 'Apply All', value: () => { this.variations.forEach(v => this.applyVariation(v, v.materials[0].uuid)); }, }, ], ], }; this.addEventListener('deserialize', this.refreshUi); this.refreshUi = this.refreshUi.bind(this); this._refreshUi = this._refreshUi.bind(this); this._refreshUiConfig = this._refreshUiConfig.bind(this); } onAdded(viewer) { super.onAdded(viewer); // todo subscribe to plugin add event if picking is not added yet. viewer.forPlugin(PickingPlugin, (p) => { this._picking = p; this._picking?.addEventListener('selectedObjectChanged', this._refreshUiConfig); }, () => { this._picking?.removeEventListener('selectedObjectChanged', this._refreshUiConfig); this._picking = undefined; }); this._previewGenerator = new MaterialPreviewGenerator(); viewer.addEventListener('preFrame', this._refreshUi); } /** * Reapply all selected variations again. * Useful when the scene is loaded or changed and the variations are not applied. */ reapplyAll() { this.variations.forEach(v => this.applyVariation(v, v.materials[v.selectedIndex ?? 0].uuid)); } fromJSON(data, meta) { this.variations = []; if (!super.fromJSON(data, meta)) return null; // its not a promise if (data.applyOnLoad === undefined) { // old files this.applyOnLoad = false; } if (this.applyOnLoad) this.reapplyAll(); return this; } onRemove(viewer) { this._previewGenerator?.dispose(); this._previewGenerator = undefined; this._picking?.removeEventListener('selectedObjectChanged', this._refreshUiConfig); this.removeEventListener('deserialize', this.refreshUi); viewer.removeEventListener('preFrame', this._refreshUi); this._picking = undefined; return super.onRemove(viewer); } findVariation(uuid) { return uuid ? this.variations.find(v => v.uuid === uuid) : undefined; } getSelectedVariation() { return this.findVariation(this._selectedMaterial()?.uuid) || this.findVariation(this._selectedMaterial()?.name); } /** * Apply a material variation based on index or uuid. * @param variations * @param matUuidOrIndex */ applyVariation(variations, matUuidOrIndex) { const m = this._viewer?.materialManager; if (!m) return false; const material = typeof matUuidOrIndex === 'string' ? variations.materials.find(m1 => m1.uuid === matUuidOrIndex) : variations.materials[matUuidOrIndex]; if (!material) return false; variations.selectedIndex = variations.materials.indexOf(material); return m.applyMaterial(material, variations.uuid); } /** * Get the preview for a material variation * Should be called from preFrame ideally. (or preRender but set viewerSetDirty = false) * @param preview - Type of preview. Could be generate:sphere, generate:cube, color, map, emissive, etc. * @param material - Material or index of the material in the variation. * @param viewerSetDirty - call viewer.setDirty() after setting the preview. So that the preview is cleared from the canvas. */ getPreview(material, preview, viewerSetDirty = true) { if (!this._viewer) return ''; // const m = typeof material === 'number' ? variation.materials[material] : material const m = material; if (!m) return ''; let image = ''; if (!preview.startsWith('generate:')) { const pp = m[preview] || '#ff00ff'; image = pp.image ? imageBitmapToBase64(pp.image, 100) : ''; if (!image.length) image = makeColorSvgCircle(pp.isColor ? pp.getHexString() : pp); } else { image = this._previewGenerator.generate(m, this._viewer.renderManager.renderer, this._viewer.scene.environment, preview.split(':')[1]); } if (viewerSetDirty) this._viewer.setDirty(); // because called from preFrame return image; } /** * Refreshes the UI in the next frame */ refreshUi() { if (!this.enabled || !this._viewer) return; this._uiNeedRefresh = true; } _refreshUiConfig() { if (!this.enabled) return; this.uiConfig.uiRefresh?.(); // don't call this.refreshUi here } // must be called from preFrame async _refreshUi() { if (!this.enabled) return false; if (!this._viewer || !this._uiNeedRefresh) return false; this._uiNeedRefresh = false; this._refreshUiConfig(); return true; } removeVariationForMaterial(material) { let variation = this.findVariation(material.uuid); if (!variation && material.name.length > 0) variation = this.findVariation(material.name); if (variation) this.removeVariation(variation); } removeVariation(variation) { if (!variation) return; this.variations.splice(this.variations.indexOf(variation), 1); this.refreshUi(); } addVariation(material, variationKey, cloneMaterial = true) { const clone = cloneMaterial && material?.clone ? material.clone() : material; if (material && clone) { let variation = this.findVariation(variationKey ?? material.uuid); if (!variation && !variationKey && material.name.length > 0) variation = this.findVariation(material.name); if (!variation) { variation = this.createVariation(material, variationKey); } variation.materials.push(clone); this.refreshUi(); } } createVariation(material, variationKey) { this.variations.push({ uuid: variationKey ?? material.name.length > 0 ? material.name : material.uuid, title: material.name.length > 0 ? material.name : 'No Name', preview: 'generate:sphere', materials: [], }); return this.variations[this.variations.length - 1]; } } MaterialConfiguratorBasePlugin.PluginType = 'MaterialConfiguratorPlugin'; __decorate([ serialize() ], MaterialConfiguratorBasePlugin.prototype, "variations", void 0); //# sourceMappingURL=MaterialConfiguratorBasePlugin.js.map