Source: configurator/block_instance.js

import { Block } from '../package/block/block.js';
import { Reporter } from '../reporter/reporter.js';
import { checkPropTypes } from '../lib.js';
import { Connectable } from './connectable.js';
import { Component } from '../component/component.js';
import { Group } from '../../node_modules/three/build/three.module.js';
import { Project } from '../project.js';

/**
 * Block instance
 */
class BlockInstance extends Connectable {

    /**
     * @param {Reporter} reporter
     * @param {Object} settings
     * @param {UUID} [settings.id]
     * @param {string} [settings.name]
     * @param {Block} settings.block
     */

    constructor(reporter, settings) {

        checkPropTypes(
            settings,
            {
                block: Block
            }
        );

        super(
            reporter,
            settings
        );

        this.bestMainContent = new Group();
        this.bestMainContent.castShadow = true;
        this.bestMainContent.receiveShadow = true;


        if (!this.block) {
            /** @type {Block} */
            this.block = settings.block;

        }

        this.assignableByDefinerId = {};

        this.assignables = this.block.subAssignables.map(sa => ({
            blockInstance: this,
            ...sa
        }));

        if (this.assignable === true) {
            this.assignables.push({
                blockInstance: this,
                assigningComponent: this,
                materialVariantGroups: this.applicableMaterialVariantGroups,
                name: this.block.name || this.block.slug
            });
        }

        for (let assignable of this.assignables) {
            this.assignableByDefinerId[assignable.assigningComponent.id] = assignable;

            const biAssId = `BI:${this.slug}`;
            assignable.id = assignable.id ? `${biAssId}-${assignable.id}` : biAssId;
        }

        this.boundingBox = settings.block.boundingBox;


    }


    static _exportName = {
        singular: 'blockInstance',
        plural: 'blockInstances',
    }

    /** @type {ExportLevel} */

    static _exportLevel = 'inline';


    static connectorTemplateSetting = 'block';

    static treeLock = false;


    /**
     * @type {Object<UUID,Component>}
     */

    assignableByDefinerId;


    /**
     * Whether a block instance is assignable depends on the block settings
     * @type {Boolean} 
     */

    get assignable() {
        return this.block.assignable;
    }


    /**
     * A block instance, like a block, does not have a material
     * @type {Boolean} 
     */

    get materializable() {
        return false;
    }



    // get assignedMaterials() {
    //     return this.assignables
    // }


    _onTreeSet() {
        if (!this._tree.findComponentById(this.block.id)) {
            throw new Error('Block (template) not in tree');
        }
        super._onTreeSet();
    }


    _onTreeUnset() {
        super._onTreeUnset();
    }


    /**
     * Set content and update status to "ready"
     * @protected
     * @method
     * @param {ComponentPart} part
     * @param {LoadingQuality} quality
     * @param {any} content
     * @returns {Promise<Array<any>>}
     */

    _setContent(part, quality, content) {

        // super will do this again
        this._content[part][quality] = content;

        if (part === 'main') {
            while (this.bestMainContent.children.length > 0) {
                this.bestMainContent.remove(this.bestMainContent.children[0]);
            }

            const bestContent = this._content.main.medium;

            // const bestContent = this._content.main.high !== null
            //     ? this._content.main.high
            //     : this._content.main.medium !== null
            //         ? this._content.main.medium
            //         : this._content.main.low !== null
            //             ? this._content.main.low
            //             : this.mainPlaceholder !== undefined
            //                 ? this.mainPlaceholder
            //                 : null
            //     ;

            if (bestContent !== null) {
                // console.log( bestContent )
                this.bestMainContent.add(bestContent)
            }
        }
        return super._setContent(part, quality, content);
    }

    bestMainContent;

    /** Array<Transform> */

    // transforms;


    // addTransform(transform) {

    // }

    get assignedMaterials() {
        if (!this._tree) {
            return [];
        }
        else {
            return this._tree.materialAssignments.filter(ma => ma.blockInstance === this);
        }
    }


    async _build(part, quality, dependencies) {

        switch (part) {

            case 'UI':

                if (this._settings.thumbnail) {
                    this._setContent('UI', quality, this._settings.thumbnail.content.main[quality]);
                }
                else {
                    this._setContent('UI', quality, Project.defaultImages.missing.cloneNode(true));
                }
                break;

            case 'main':

                const block = this._settings.block.content.main[quality].block;

                const blockClone = block.clone();

                blockClone.castShadow = true;
                blockClone.receiveShadow = true;

                
                // so we can use Object3D.getObjectByName in the scene
                // https://threejs.org/docs/#api/en/core/Object3D.getObjectByName
                blockClone.name = this.id;

                // blockClone.userData.origin = this.id;
                blockClone.userData.PB = {
                    type: 'blockInstance',
                    origin: this.id,
                    blockInstance: this,
                    isAssignable: this.assignable,
                    assignableComponent: this.assignable === true
                        ? {
                            blockInstance: this
                        }
                        : null
                };

                // export the cloned block and its connectors

                const content = blockClone;


                blockClone.traverse(obj3D => {
                    if (!obj3D.userData) {
                        obj3D.userData = {};
                    }
                    if (!obj3D.userData.PB) {
                        obj3D.userData.PB = {};
                    }

                    if (obj3D.userData.PB.type === 'PositionedMeshGroup') {
                        obj3D.userData.PB.positionedMeshGroup = obj3D.userData.PB.origin;
                    }
                    else {
                        // could be undefined or a value, but copy it

                        // the parent here could be the configuration wrapper, which has no PB userdata
                        obj3D.userData.PB.positionedMeshGroup = obj3D.parent?.userData?.PB?.positionedMeshGroup;
                    }

                    if (obj3D.userData.PB.type === 'PositionedMesh') {
                        obj3D.userData.PB.positionedMesh = obj3D.userData.PB.origin;
                        obj3D.userData.PB.processed = true;
                    }
                    else {
                        // could be undefined or a value, but copy it
                        obj3D.userData.PB.positionedMesh = obj3D.parent?.userData?.PB?.positionedMesh;
                    }

                    if (obj3D.userData.PB.isAssignable === true) {
                        // obj3D.userData.PB.assignableComponent = this.assignableByDefinerId[obj3D.userData.PB.origin];
                        obj3D.userData.PB.assignable = this.assignables.find( assignable =>
                            assignable.blockInstance === obj3D.userData.PB.blockInstance
                            &&
                            assignable.positionedMeshGroup === obj3D.userData.PB.positionedMeshGroup
                            && 
                            assignable.positionedMesh === obj3D.userData.PB.positionedMesh    
                        );
                    }
                    else {
                        // obj3D.userData.PB.assignableComponent = obj3D.parent.userData?.PB?.assignableComponent;
                        obj3D.userData.PB.assignable = obj3D.parent?.userData?.PB?.assignable;
                    }
                })
                // after a time out traverse rthe 3d structure 
                // for every object3D check the assignable of the parent and copy + adapt

                this._setContent('main', quality, content);

                break;

            default:
                this._setContent(part, quality, null);
                break;
        }

        return this;
    }

    
}

export { BlockInstance }