Skip to content

Commit 801a861

Browse files
committed
fix(plugin): handle draco; clean up
1 parent b87c16e commit 801a861

File tree

6 files changed

+220
-160
lines changed

6 files changed

+220
-160
lines changed

libs/plugin/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"dependencies": {
2222
"@nx/devkit": "^20.0.0",
2323
"@rosskevin/gltfjsx": "7.0.20",
24+
"node-three-gltf": "^2.0.0",
2425
"nx": "^20.0.0",
2526
"ts-morph": "^25.0.0",
2627
"tslib": "^2.3.0"

libs/plugin/src/generators/gltf/files/__fileName__.ts__tmpl__

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import type * as THREE from 'three';
99
import { Group<%= threeImports %> } from 'three';
1010
import { extend, type NgtThreeElements, NgtObjectEvents<% if (args) { %>, NgtArgs<% } %> } from 'angular-three';
11-
import { Component, ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA, input, viewChild, ElementRef, inject, effect<% if (animations.length) { %>, computed, model<% } %> } from '@angular/core';
11+
import { Component, ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA, input, viewChild, ElementRef, inject, effect<% if (animations.length) { %>, model<% } %> } from '@angular/core';
1212
import { injectGLTF } from 'angular-three-soba/loaders';
1313
import type { GLTF } from 'three-stdlib';<% if (animations.length) { %>
1414
import { injectAnimations, type NgtsAnimationClips, type NgtsAnimationApi } from 'angular-three-soba/misc';<% } %><% if (perspective) { %>

libs/plugin/src/generators/gltf/gltf.ts

Lines changed: 137 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -32,161 +32,160 @@ export interface GltfGeneratorSchema {
3232
verbose: boolean;
3333
}
3434

35-
export async function gltfGenerator(tree: Tree, options: GltfGeneratorSchema) {
36-
const { loadGLTF, AnalyzedGLTF, gltfTransform, Log, allPruneStrategies, compareFileSizes } = await import(
37-
'@rosskevin/gltfjsx'
38-
);
35+
// @ts-expect-error - type only import
36+
function normalizeOptions(tree: Tree, options: GltfGeneratorSchema, gltfJsx: typeof import('@rosskevin/gltfjsx')) {
37+
const { Log } = gltfJsx;
3938

40-
const modelPath = join(tree.root, options.modelPath);
39+
const { fileName, className } = names(options.className);
4140
const log = new Log({ debug: options.verbose, silent: false });
4241

42+
const gltfJsxOptions = {
43+
log,
44+
bones: options.bones,
45+
meta: options.meta,
46+
shadows: options.shadows,
47+
instance: options.instance,
48+
instanceall: options.instanceAll,
49+
keepgroups: options.keepGroups,
50+
keepnames: options.keepNames,
51+
precision: options.precision,
52+
};
53+
54+
const transformOptions = {
55+
format: options.format,
56+
degrade: options.degrade,
57+
degraderesolution: options.degradeResolution,
58+
simplify: options.simplify ? { ratio: options.ratio, error: options.error } : false,
59+
keepattributes: options.keepAttributes,
60+
keepmeshes: options.keepMeshes,
61+
keepmaterials: options.keepMaterials,
62+
resolution: options.resolution,
63+
};
64+
65+
const modelPathFromRoot = join(tree.root, options.modelPath);
66+
const outputDir = dirname(options.output);
67+
68+
const injectGLTFOptions = options.draco ? `{ useDraco: true }` : '';
69+
70+
const selector = `${options.selectorPrefix}-${fileName}`;
71+
72+
const gltfAnimationTypeName = className + 'AnimationClips';
73+
const gltfAnimationApiTypeName = className + 'AnimationApi';
74+
const gltfName = className + 'GLTF';
75+
const gltfResultTypeName = gltfName + 'GLTFResult';
76+
77+
return {
78+
log,
79+
selector,
80+
fileName,
81+
className,
82+
gltfJsxOptions,
83+
transformOptions,
84+
modelPathFromRoot,
85+
outputDir,
86+
injectGLTFOptions,
87+
gltfAnimationTypeName,
88+
gltfAnimationApiTypeName,
89+
gltfName,
90+
gltfResultTypeName,
91+
};
92+
}
93+
94+
export async function gltfGenerator(tree: Tree, options: GltfGeneratorSchema) {
95+
const gltfjsx = await import('@rosskevin/gltfjsx');
96+
const { loadGLTF, AnalyzedGLTF, gltfTransform, compareFileSizes } = gltfjsx;
97+
98+
const {
99+
selector,
100+
className,
101+
fileName,
102+
modelPathFromRoot,
103+
log,
104+
gltfJsxOptions,
105+
transformOptions,
106+
outputDir,
107+
injectGLTFOptions,
108+
gltfAnimationTypeName,
109+
gltfAnimationApiTypeName,
110+
gltfName,
111+
gltfResultTypeName,
112+
} = normalizeOptions(tree, options, gltfjsx);
113+
43114
//
44115
// Transform the GLTF file if necessary using gltf-transform
45116
//
46117
let size = '';
47118
let transformedModelPath: string | undefined = undefined;
48119
if (options.transform) {
49-
transformedModelPath = resolve(modelPath + '-transformed.glb');
50-
await gltfTransform(modelPath, transformedModelPath, {
51-
format: options.format,
52-
degrade: options.degrade,
53-
degraderesolution: options.degradeResolution,
54-
simplify: options.simplify ? { ratio: options.ratio, error: options.error } : false,
55-
log,
56-
bones: options.bones,
57-
meta: options.meta,
58-
shadows: options.shadows,
59-
instance: options.instance,
60-
instanceall: options.instanceAll,
61-
keepgroups: options.keepGroups,
62-
keepnames: options.keepNames,
63-
precision: options.precision,
64-
keepattributes: options.keepAttributes,
65-
keepmeshes: options.keepMeshes,
66-
keepmaterials: options.keepMaterials,
67-
resolution: options.resolution,
68-
});
69-
size = compareFileSizes(modelPath, transformedModelPath);
120+
transformedModelPath = resolve(modelPathFromRoot + '-transformed.glb');
121+
await gltfTransform(modelPathFromRoot, transformedModelPath, Object.assign(transformOptions, gltfJsxOptions));
122+
size = compareFileSizes(modelPathFromRoot, transformedModelPath);
70123
}
71124

72-
const gltf = await loadGLTF(modelPath);
73-
74-
const analyzed = new AnalyzedGLTF(
75-
gltf,
76-
{
77-
log,
78-
bones: options.bones,
79-
meta: options.meta,
80-
shadows: options.shadows,
81-
instance: options.instance,
82-
instanceall: options.instanceAll,
83-
keepgroups: options.keepGroups,
84-
keepnames: options.keepNames,
85-
precision: options.precision,
86-
},
87-
allPruneStrategies,
88-
);
89-
90-
const generateNGT = new GenerateNGT(analyzed, options);
91-
92-
const scene = await generateNGT.generate();
93-
94-
const args = generateNGT.args;
95-
const perspective = generateNGT.ngtTypes.has('PerspectiveCamera');
96-
const orthographic = generateNGT.ngtTypes.has('OrthographicCamera');
97-
98-
const { className, fileName } = names(options.className);
99-
const gltfExtras = analyzed.gltf.parser.json.asset && analyzed.gltf.parser.json.asset.extras;
100-
const extras = gltfExtras
101-
? Object.keys(gltfExtras)
102-
.map((key) => `${key.charAt(0).toUpperCase() + key.slice(1)}: ${gltfExtras[key]}`)
103-
.join('\n')
104-
: '';
105-
106-
const ngtTypesArr = Array.from(generateNGT.ngtTypes).filter(
107-
(t) =>
108-
// group always is the top-level object
109-
t !== 'Group' &&
110-
// we render ngts-perspective-camera instead of ngt-perspective-camera
111-
t !== 'PerspectiveCamera' &&
112-
// we render ngts-orthographic-camera instead of ngt-orthographic-camera
113-
t !== 'OrthographicCamera' &&
114-
// we don't render ngt-bone
115-
t !== 'Bone' &&
116-
// we don't render ngt-object3D
117-
t !== 'Object3D',
118-
);
119-
const threeImports = ngtTypesArr.length ? `, ${ngtTypesArr.join(',')}` : '';
120-
let gltfPath =
121-
!transformedModelPath && modelPath.startsWith('http')
122-
? modelPath
123-
: relative(dirname(options.output), transformedModelPath || modelPath);
124-
125-
if (!gltfPath.startsWith('http') && !gltfPath.startsWith('.')) {
126-
gltfPath = `./${gltfPath}`;
127-
}
125+
const modelPath = transformedModelPath || modelPathFromRoot;
128126

129-
const gltfAnimationTypeName = className + 'AnimationClips';
130-
const gltfAnimationApiTypeName = className + 'AnimationApi';
131-
const gltfName = className + 'GLTF';
132-
const gltfResultTypeName = gltfName + 'GLTFResult';
133-
134-
const angularImports = [];
135-
136-
if (args) {
137-
angularImports.push('NgtArgs');
127+
//
128+
// Read the model
129+
//
130+
let dracoLoader: import('node-three-gltf').DRACOLoader | undefined = undefined; // global instance, instantiate once, dispose once
131+
if (options.draco) {
132+
log.debug('Instantiating DracoLoader');
133+
const { DRACOLoader } = await import('node-three-gltf');
134+
dracoLoader = new DRACOLoader();
138135
}
139136

140-
if (perspective) {
141-
angularImports.push('NgtsPerspectiveCamera');
142-
}
137+
log.debug('Loading model: ', modelPath);
138+
139+
try {
140+
const gltf = await loadGLTF(modelPath, dracoLoader);
141+
const analyzed = new AnalyzedGLTF(gltf, gltfJsxOptions);
142+
const generateNGT = new GenerateNGT(analyzed, gltfjsx, options);
143+
144+
const scene = generateNGT.generate();
145+
const generateOptions = generateNGT.getGenerateOptions();
146+
147+
let gltfPath =
148+
!transformedModelPath && modelPath.startsWith('http') ? modelPath : relative(outputDir, modelPath);
149+
150+
if (!gltfPath.startsWith('http') && !gltfPath.startsWith('.')) {
151+
gltfPath = `./${gltfPath}`;
152+
}
153+
154+
generateFiles(tree, join(__dirname, 'files'), outputDir, {
155+
tmpl: '',
156+
...generateOptions,
157+
scene,
158+
fileName,
159+
className,
160+
selector,
161+
animations: analyzed.gltf.animations || [],
162+
useImportAttribute: !modelPath.startsWith('http'),
163+
preload: true,
164+
gltfName,
165+
gltfAnimationTypeName,
166+
gltfAnimationApiTypeName,
167+
gltfResultTypeName,
168+
gltfPath,
169+
gltfOptions: injectGLTFOptions,
170+
header: options.header,
171+
size,
172+
});
143173

144-
if (perspective) {
145-
angularImports.push('NgtsOrthographicCamera');
174+
if (options.console) {
175+
const outputPath = join(outputDir, `${fileName}.ts`);
176+
const outputContent = tree.read(outputPath, 'utf8');
177+
console.log(outputContent);
178+
tree.delete(outputPath);
179+
}
180+
} catch (err) {
181+
log.error(err);
182+
dracoLoader?.dispose();
183+
return process.exit(1);
184+
} finally {
185+
log.debug('Disposing of DracoLoader');
186+
dracoLoader?.dispose();
146187
}
147188

148-
const gltfOptions = options.draco ? `{ useDraco: true }` : '';
149-
const meshesTypes = analyzed
150-
.getMeshes()
151-
.map(({ name, type }) => "\'" + name + "\'" + ': THREE.' + type)
152-
.join(';\n');
153-
const bonesTypes = analyzed
154-
.getBones()
155-
.map(({ name, type }) => "\'" + name + "\'" + ': THREE.' + type)
156-
.join(';\n');
157-
const materials = analyzed.getMaterials();
158-
const materialsTypes = materials.map(({ name, type }) => "\'" + name + "\'" + ': THREE.' + type).join(';\n');
159-
160-
log.debug(materialsTypes);
161-
162-
generateFiles(tree, join(__dirname, 'files'), dirname(options.output), {
163-
tmpl: '',
164-
scene,
165-
fileName,
166-
className,
167-
selector: `${options.selectorPrefix}-${fileName}`,
168-
animations: analyzed.gltf.animations || [],
169-
extras,
170-
threeImports,
171-
args,
172-
perspective,
173-
orthographic,
174-
useImportAttribute: !modelPath.startsWith('http'),
175-
preload: true,
176-
gltfName,
177-
gltfAnimationTypeName,
178-
gltfAnimationApiTypeName,
179-
gltfResultTypeName,
180-
gltfPath,
181-
gltfOptions,
182-
meshesTypes,
183-
bonesTypes,
184-
materialsTypes,
185-
angularImports,
186-
header: options.header,
187-
size,
188-
});
189-
190189
await formatFiles(tree);
191190
}
192191

0 commit comments

Comments
 (0)