diff --git a/src/utils/parser.js b/src/utils/parser.js index f9a1c27..137bda4 100644 --- a/src/utils/parser.js +++ b/src/utils/parser.js @@ -13,9 +13,20 @@ function parse(gltf, { fileName = 'model', ...options } = {}) { const animations = gltf.animations const hasAnimations = animations.length > 0 + /** @type {Record */ + const slots = {} + // Collect all objects const objects = [] - gltf.scene.traverse((child) => objects.push(child)) + gltf.scene.traverse((child) => { + objects.push(child); + + // Collect slots + const slot = child.userData?.slot; + const hasSlot = (slot && typeof slot === "string" && slot.length > 0); + if (hasSlot) + slots[slot] ? slots[slot].push(child) : (slots[slot] = [child]); + }) // Browse for duplicates const duplicates = { @@ -75,6 +86,11 @@ function parse(gltf, { fileName = 'model', ...options } = {}) { return isVarName(name) ? `.${name}` : `['${name}']` } + /** Ensure that a slot is a valid variable name e.g. must not contain spaces */ + function sanitizeSlotName(slotname) { + return slotname.replaceAll(/[^a-zA-Z0-9]/g, ''); + } + const rNbr = (number) => { return parseFloat(number.toFixed(Math.round(options.precision || 2))) } @@ -220,7 +236,8 @@ function parse(gltf, { fileName = 'model', ...options } = {}) { duplicates.geometries[obj.geometry.uuid + obj.material.name] && duplicates.geometries[obj.geometry.uuid + obj.material.name].count > (options.instanceall ? 0 : 1) let animated = gltf.animations && gltf.animations.length > 0 - return { type, node, instanced, animated } + const hasSlots = obj.userData?.slot && typeof obj.userData.slot === "string" && obj.userData.slot.length > 0; + return { type, node, instanced, animated, hasSlots } } function equalOrNegated(a, b) { @@ -228,9 +245,9 @@ function parse(gltf, { fileName = 'model', ...options } = {}) { } function prune(obj, children, result, oldResult, silent) { - let { type, animated } = getInfo(obj) + let { type, animated, hasSlots } = getInfo(obj) // Prune ... - if (!obj.__removed && !options.keepgroups && !animated && (type === 'group' || type === 'scene')) { + if (!obj.__removed && !options.keepgroups && !animated && !hasSlots && (type === 'group' || type === 'scene')) { /** Empty or no-property groups * * @@ -370,8 +387,8 @@ function parse(gltf, { fileName = 'model', ...options } = {}) { // Bail out if the object was pruned if (pruned !== undefined) return pruned - // Add custom slots if defined in the object's userData. - // E.g. userData: { "slot" : "mySlot" } becomes `{ props.mySlot }` + // Add custom slots if defined in the object's userData + // E.g. userData: { "slot" : "mySlot" } becomes `{ mySlot }` const slot = obj.userData?.slot; const hasSlot = (slot && typeof slot === "string" && slot.length > 0); const hasContent = children.length || hasSlot; @@ -380,9 +397,9 @@ function parse(gltf, { fileName = 'model', ...options } = {}) { result += `${hasContent ? '>' : '/>'}\n` // Add children - if (children.length) result += `${children}\n` + if (children.length) result += `${children.trimEnd("\n")}\n` // Add custom slot - if (hasSlot) result += `{props.${slot}}\n`; + if (hasSlot) result += `{${sanitizeSlotName(slot)}}\n`; // Close tag if (hasContent) result += `` return result @@ -447,10 +464,14 @@ function parse(gltf, { fileName = 'model', ...options } = {}) { } catch (e) { console.log('Error while parsing glTF', e) } + + const slotParams = Object.keys(slots).length > 0 ? (Object.keys(slots).map(sanitizeSlotName).join(", ") + ", ") : ""; + const header = `/* ${options.header ? options.header : 'Auto-generated by: https://github.com/pmndrs/gltfjsx'} ${ options.size ? `\nFiles: ${options.size}` : '' } + ${parseExtras(gltf.parser.json.asset && gltf.parser.json.asset.extras)}*/` const result = `${options.types ? `\nimport * as THREE from 'three'` : ''} import React, { useRef ${hasInstances ? ', useMemo, useContext, createContext' : ''} } from 'react' @@ -466,7 +487,7 @@ ${parseExtras(gltf.parser.json.asset && gltf.parser.json.asset.extras)}*/` hasInstances ? ` const context = createContext(${options.types ? '{} as ContextType' : ''}) - export function Instances({ children, ...props }${options.types ? ': JSX.IntrinsicElements["group"]' : ''}) { + export function Instances({ children, ${slotParams}...props }${options.types ? ': JSX.IntrinsicElements["group"]' : ''}) { const { nodes } = useGLTF('${url}'${options.draco ? `, ${JSON.stringify(options.draco)}` : ''})${ options.types ? ' as GLTFResult' : '' } @@ -485,7 +506,7 @@ ${parseExtras(gltf.parser.json.asset && gltf.parser.json.asset.extras)}*/` : '' } - export ${options.exportdefault ? 'default' : ''} function Model(props${ + export ${options.exportdefault ? 'default' : ''} function Model({ ${slotParams}...props }${ options.types ? ": JSX.IntrinsicElements['group']" : '' }) { ${hasInstances ? 'const instances = useContext(context);' : ''} ${