Skip to content

Commit 22b1b02

Browse files
committed
add support for generating slots from blender properties
1 parent 70a7b77 commit 22b1b02

File tree

1 file changed

+47
-12
lines changed

1 file changed

+47
-12
lines changed

src/utils/parser.js

+47-12
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,23 @@ function parse(gltf, { fileName = 'model', ...options } = {}) {
1313
const animations = gltf.animations
1414
const hasAnimations = animations.length > 0
1515

16+
/** @type {Record<string, Object3D[]> */
17+
const slots = {}
18+
1619
// Collect all objects
1720
const objects = []
18-
gltf.scene.traverse((child) => objects.push(child))
21+
gltf.scene.traverse((child) => {
22+
objects.push(child);
23+
24+
// Collect slots
25+
const slot = child.userData?.prop;
26+
const hasSlot = (slot && typeof slot === "string" && slot.length > 0);
27+
if (hasSlot)
28+
{
29+
const slotname = sanitizeSlotName(slot);
30+
slots[slotname] ? slots[slotname].push(child) : (slots[slotname] = [child]);
31+
}
32+
})
1933

2034
// Browse for duplicates
2135
const duplicates = {
@@ -75,6 +89,11 @@ function parse(gltf, { fileName = 'model', ...options } = {}) {
7589
return isVarName(name) ? `.${name}` : `['${name}']`
7690
}
7791

92+
/** Ensure that a slot is a valid variable name e.g. must not contain spaces */
93+
function sanitizeSlotName(slotname) {
94+
return slotname.replaceAll(/[^a-zA-Z0-9]/g, '');
95+
}
96+
7897
const rNbr = (number) => {
7998
return parseFloat(number.toFixed(Math.round(options.precision || 2)))
8099
}
@@ -221,17 +240,18 @@ function parse(gltf, { fileName = 'model', ...options } = {}) {
221240
duplicates.geometries[obj.geometry.uuid + obj.material.name] &&
222241
duplicates.geometries[obj.geometry.uuid + obj.material.name].count > (options.instanceall ? 0 : 1)
223242
let animated = gltf.animations && gltf.animations.length > 0
224-
return { type, node, instanced, animated }
243+
const hasSlots = obj.userData?.prop && typeof obj.userData.prop === "string" && obj.userData.prop.length > 0;
244+
return { type, node, instanced, animated, hasSlots }
225245
}
226246

227247
function equalOrNegated(a, b) {
228248
return (a.x === b.x || a.x === -b.x) && (a.y === b.y || a.y === -b.y) && (a.z === b.z || a.z === -b.z)
229249
}
230250

231251
function prune(obj, children, result, oldResult, silent) {
232-
let { type, animated } = getInfo(obj)
252+
let { type, animated, hasSlots } = getInfo(obj)
233253
// Prune ...
234-
if (!obj.__removed && !options.keepgroups && !animated && (type === 'group' || type === 'scene')) {
254+
if (!obj.__removed && !options.keepgroups && !animated && !hasSlots && (type === 'group' || type === 'scene')) {
235255
/** Empty or no-property groups
236256
* <group>
237257
* <mesh geometry={nodes.foo} material={materials.bar} />
@@ -379,13 +399,24 @@ function parse(gltf, { fileName = 'model', ...options } = {}) {
379399
// Bail out if the object was pruned
380400
if (pruned !== undefined) return pruned
381401

382-
// Close tag
383-
result += `${children.length ? '>' : '/>'}\n`
402+
// Add custom slots if defined in the object's userData
403+
// E.g. userData: { "prop" : "mySlot" } becomes `{ mySlot }`
404+
const slot = obj.userData?.prop;
405+
const hasSlot = (slot && typeof slot === "string" && slot.length > 0);
406+
const hasContent = children.length || hasSlot;
384407

385-
// Add children and return
386-
if (children.length) {
387-
if (type === 'bone') result += children + `</primitive>`
388-
else result += children + `</${type}>`
408+
409+
// Close tag if no children
410+
result += `${hasContent ? '>' : '/>'}\n`
411+
412+
// Add children
413+
if (children.length) result += `${children.trimEnd("\n")}\n`
414+
// Add custom slot
415+
if (hasSlot) result += `{${sanitizeSlotName(slot)}}\n`;
416+
// Close tag
417+
if (hasContent) {
418+
if (type === 'bone') result += `</primitive>`
419+
else result += `</${type}>`
389420
}
390421
return result
391422
}
@@ -449,10 +480,14 @@ function parse(gltf, { fileName = 'model', ...options } = {}) {
449480
} catch (e) {
450481
console.log('Error while parsing glTF', e)
451482
}
483+
484+
const slotParams = Object.keys(slots).length > 0 ? (Object.keys(slots).join(", ") + ", ") : "";
485+
452486
const header = `/*
453487
${options.header ? options.header : 'Auto-generated by: https://github.com/pmndrs/gltfjsx'} ${
454488
options.size ? `\nFiles: ${options.size}` : ''
455489
}
490+
456491
${parseExtras(gltf.parser.json.asset && gltf.parser.json.asset.extras)}*/`
457492
const hasPrimitives = scene.includes('<primitive')
458493
const result = `${options.types ? `\nimport * as THREE from 'three'` : ''}
@@ -474,7 +509,7 @@ ${parseExtras(gltf.parser.json.asset && gltf.parser.json.asset.extras)}*/`
474509
? `
475510
const context = React.createContext(${options.types ? '{} as ContextType' : ''})
476511
477-
export function Instances({ children, ...props }${options.types ? ': JSX.IntrinsicElements["group"]' : ''}) {
512+
export function Instances({ children, ${slotParams}...props }${options.types ? ': JSX.IntrinsicElements["group"]' : ''}) {
478513
const { nodes } = useGLTF('${url}'${options.draco ? `, ${JSON.stringify(options.draco)}` : ''})${
479514
options.types ? ' as GLTFResult' : ''
480515
}
@@ -495,7 +530,7 @@ ${parseExtras(gltf.parser.json.asset && gltf.parser.json.asset.extras)}*/`
495530
: ''
496531
}
497532
498-
export ${options.exportdefault ? 'default' : ''} function Model(props${
533+
export ${options.exportdefault ? 'default' : ''} function Model({ ${slotParams}...props }${
499534
options.types ? ": JSX.IntrinsicElements['group']" : ''
500535
}) {
501536
${hasInstances ? 'const instances = React.useContext(context);' : ''} ${

0 commit comments

Comments
 (0)