cyclecad 3.2.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +155 -1
- package/DOCKER-SETUP-VERIFICATION.md +399 -0
- package/DOCKER-TESTING.md +463 -0
- package/FUSION360_MODULES.md +478 -0
- package/FUSION_MODULES_README.md +352 -0
- package/INTEGRATION_SNIPPETS.md +608 -0
- package/KILLER-FEATURES-DELIVERY.md +469 -0
- package/MODULES_SUMMARY.txt +337 -0
- package/QUICK_REFERENCE.txt +298 -0
- package/README-DOCKER-TESTING.txt +438 -0
- package/app/index.html +23 -10
- package/app/js/fusion-help.json +1808 -0
- package/app/js/help-module-v3.js +1096 -0
- package/app/js/killer-features-help.json +395 -0
- package/app/js/killer-features.js +1508 -0
- package/app/js/modules/fusion-assembly.js +842 -0
- package/app/js/modules/fusion-cam.js +785 -0
- package/app/js/modules/fusion-data.js +814 -0
- package/app/js/modules/fusion-drawing.js +844 -0
- package/app/js/modules/fusion-inspection.js +756 -0
- package/app/js/modules/fusion-render.js +774 -0
- package/app/js/modules/fusion-simulation.js +986 -0
- package/app/js/modules/fusion-sketch.js +1044 -0
- package/app/js/modules/fusion-solid.js +1095 -0
- package/app/js/modules/fusion-surface.js +949 -0
- package/app/tests/FUSION_TEST_SUITE.md +266 -0
- package/app/tests/README.md +77 -0
- package/app/tests/TESTING-CHECKLIST.md +177 -0
- package/app/tests/TEST_SUITE_SUMMARY.txt +236 -0
- package/app/tests/brep-live-test.html +848 -0
- package/app/tests/docker-integration-test.html +811 -0
- package/app/tests/fusion-all-tests.html +670 -0
- package/app/tests/fusion-assembly-tests.html +461 -0
- package/app/tests/fusion-cam-tests.html +421 -0
- package/app/tests/fusion-simulation-tests.html +421 -0
- package/app/tests/fusion-sketch-tests.html +613 -0
- package/app/tests/fusion-solid-tests.html +529 -0
- package/app/tests/index.html +453 -0
- package/app/tests/killer-features-test.html +509 -0
- package/app/tests/run-tests.html +874 -0
- package/app/tests/step-import-live-test.html +1115 -0
- package/app/tests/test-agent-v3.html +93 -696
- package/architecture-dashboard.html +1970 -0
- package/docs/API-REFERENCE.md +1423 -0
- package/docs/BREP-LIVE-TEST-GUIDE.md +453 -0
- package/docs/DEVELOPER-GUIDE-v3.md +795 -0
- package/docs/DOCKER-QUICK-TEST.md +376 -0
- package/docs/FUSION-FEATURES-GUIDE.md +2513 -0
- package/docs/FUSION-TUTORIAL.md +1203 -0
- package/docs/INFRASTRUCTURE-GUIDE-INDEX.md +327 -0
- package/docs/KEYBOARD-SHORTCUTS.md +402 -0
- package/docs/KILLER-FEATURES-INTEGRATION.md +412 -0
- package/docs/KILLER-FEATURES-SUMMARY.md +424 -0
- package/docs/KILLER-FEATURES-TUTORIAL.md +784 -0
- package/docs/KILLER-FEATURES.md +562 -0
- package/docs/QUICK-REFERENCE.md +282 -0
- package/docs/README-v3-DOCS.md +274 -0
- package/docs/TUTORIAL-v3.md +1190 -0
- package/docs/architecture-dashboard.html +1970 -0
- package/docs/architecture-v3.html +1038 -0
- package/linkedin-post-v3.md +58 -0
- package/package.json +1 -1
- package/scripts/dev-setup.sh +338 -0
- package/scripts/docker-health-check.sh +159 -0
- package/scripts/integration-test.sh +311 -0
- package/scripts/test-docker.sh +515 -0
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cycleCAD — Fusion 360 Render + Animation Module
|
|
3
|
+
* Complete rendering and animation workspace with materials, HDRI environments,
|
|
4
|
+
* progressive raytracing, decals, turntable animation, storyboards, and keyframe animation.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - 100+ PBR materials (metals, plastics, wood, glass, ceramic, fabric)
|
|
8
|
+
* - HDRI environments (Studio, Outdoor, Garage, Clean Room)
|
|
9
|
+
* - Decals (image projection onto faces)
|
|
10
|
+
* - Progressive raytracing simulation (Three.js-based)
|
|
11
|
+
* - Render settings (resolution, quality, AA, shadows, reflections, AO)
|
|
12
|
+
* - Turntable animation (orbit camera, export as GIF frames)
|
|
13
|
+
* - Storyboard (keyframe camera + component transforms + visibility)
|
|
14
|
+
* - Explode animation (auto-generate from assembly)
|
|
15
|
+
* - Manual animation timeline (drag keyframes)
|
|
16
|
+
* - Screenshot export with transparent background
|
|
17
|
+
*
|
|
18
|
+
* Version: 1.0.0
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Fusion Render Module — Advanced rendering and animation
|
|
25
|
+
*/
|
|
26
|
+
class FusionRenderModule {
|
|
27
|
+
constructor(scene, camera, renderer) {
|
|
28
|
+
this.scene = scene;
|
|
29
|
+
this.camera = camera;
|
|
30
|
+
this.renderer = renderer;
|
|
31
|
+
|
|
32
|
+
// Appearance library
|
|
33
|
+
this.materialLibrary = this.initMaterialLibrary();
|
|
34
|
+
this.appliedMaterials = new Map(); // meshId -> materialId
|
|
35
|
+
|
|
36
|
+
// Environment
|
|
37
|
+
this.environments = this.initEnvironments();
|
|
38
|
+
this.currentEnvironment = 'studio';
|
|
39
|
+
this.environmentMap = null;
|
|
40
|
+
|
|
41
|
+
// Decals
|
|
42
|
+
this.decals = [];
|
|
43
|
+
|
|
44
|
+
// Render settings
|
|
45
|
+
this.renderSettings = {
|
|
46
|
+
resolution: '1920x1080',
|
|
47
|
+
quality: 'high', // low | medium | high | ultra
|
|
48
|
+
antiAliasing: 'FXAA', // None | FXAA | SMAA | TAA
|
|
49
|
+
shadowQuality: 'high',
|
|
50
|
+
enableReflections: true,
|
|
51
|
+
enableAmbientOcclusion: true,
|
|
52
|
+
aoSamples: 64,
|
|
53
|
+
aoRadius: 2,
|
|
54
|
+
bloomIntensity: 0.3,
|
|
55
|
+
exposureValue: 1.0,
|
|
56
|
+
toneMapping: 'ACESFilmic'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Animation system
|
|
60
|
+
this.animations = new Map(); // animId -> animation definition
|
|
61
|
+
this.keyframes = new Map(); // keyId -> keyframe
|
|
62
|
+
this.storyboards = new Map(); // storyboardId -> storyboard
|
|
63
|
+
this.currentAnimation = null;
|
|
64
|
+
this.currentTime = 0;
|
|
65
|
+
this.isPlaying = false;
|
|
66
|
+
|
|
67
|
+
// Turntable
|
|
68
|
+
this.turntableActive = false;
|
|
69
|
+
this.turntableSpeed = 0.5; // revolutions per second
|
|
70
|
+
this.turntableFrames = []; // Captured frames for GIF
|
|
71
|
+
|
|
72
|
+
// Screenshot
|
|
73
|
+
this.screenshotSettings = {
|
|
74
|
+
width: 3840,
|
|
75
|
+
height: 2160,
|
|
76
|
+
transparent: false,
|
|
77
|
+
quality: 'png'
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
this.animationFrameId = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Initialize render module UI
|
|
85
|
+
*/
|
|
86
|
+
init() {
|
|
87
|
+
this.setupEventListeners();
|
|
88
|
+
this.setupEnvironments();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get UI panel for render controls
|
|
93
|
+
*/
|
|
94
|
+
getUI() {
|
|
95
|
+
const panel = document.createElement('div');
|
|
96
|
+
panel.className = 'fusion-render-panel';
|
|
97
|
+
panel.innerHTML = `
|
|
98
|
+
<style>
|
|
99
|
+
.fusion-render-panel {
|
|
100
|
+
padding: 16px;
|
|
101
|
+
font-size: 12px;
|
|
102
|
+
background: var(--bg-secondary);
|
|
103
|
+
color: var(--text-primary);
|
|
104
|
+
border-radius: 4px;
|
|
105
|
+
max-height: 700px;
|
|
106
|
+
overflow-y: auto;
|
|
107
|
+
}
|
|
108
|
+
.fusion-render-panel h3 {
|
|
109
|
+
margin: 0 0 12px 0;
|
|
110
|
+
font-size: 14px;
|
|
111
|
+
font-weight: 600;
|
|
112
|
+
color: var(--text-primary);
|
|
113
|
+
}
|
|
114
|
+
.render-section {
|
|
115
|
+
margin-bottom: 16px;
|
|
116
|
+
padding-bottom: 12px;
|
|
117
|
+
border-bottom: 1px solid var(--border-color);
|
|
118
|
+
}
|
|
119
|
+
.render-section:last-child {
|
|
120
|
+
border-bottom: none;
|
|
121
|
+
}
|
|
122
|
+
.material-grid {
|
|
123
|
+
display: grid;
|
|
124
|
+
grid-template-columns: 1fr 1fr;
|
|
125
|
+
gap: 6px;
|
|
126
|
+
max-height: 150px;
|
|
127
|
+
overflow-y: auto;
|
|
128
|
+
}
|
|
129
|
+
.material-item {
|
|
130
|
+
padding: 8px;
|
|
131
|
+
background: var(--bg-primary);
|
|
132
|
+
border-radius: 3px;
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
transition: background 0.2s;
|
|
135
|
+
font-size: 10px;
|
|
136
|
+
text-align: center;
|
|
137
|
+
}
|
|
138
|
+
.material-item:hover {
|
|
139
|
+
background: var(--bg-tertiary);
|
|
140
|
+
}
|
|
141
|
+
.material-preview {
|
|
142
|
+
width: 20px;
|
|
143
|
+
height: 20px;
|
|
144
|
+
border-radius: 2px;
|
|
145
|
+
margin: 0 auto 4px;
|
|
146
|
+
}
|
|
147
|
+
.render-settings {
|
|
148
|
+
background: var(--bg-primary);
|
|
149
|
+
border-radius: 3px;
|
|
150
|
+
padding: 8px;
|
|
151
|
+
display: flex;
|
|
152
|
+
flex-direction: column;
|
|
153
|
+
gap: 8px;
|
|
154
|
+
}
|
|
155
|
+
.render-setting-row {
|
|
156
|
+
display: flex;
|
|
157
|
+
justify-content: space-between;
|
|
158
|
+
align-items: center;
|
|
159
|
+
font-size: 11px;
|
|
160
|
+
}
|
|
161
|
+
.render-setting-row select {
|
|
162
|
+
width: 120px;
|
|
163
|
+
padding: 4px;
|
|
164
|
+
background: var(--bg-secondary);
|
|
165
|
+
border: 1px solid var(--border-color);
|
|
166
|
+
border-radius: 2px;
|
|
167
|
+
color: var(--text-primary);
|
|
168
|
+
font-size: 10px;
|
|
169
|
+
}
|
|
170
|
+
.render-setting-row input[type="range"] {
|
|
171
|
+
flex: 1;
|
|
172
|
+
margin: 0 8px;
|
|
173
|
+
}
|
|
174
|
+
.animation-timeline {
|
|
175
|
+
background: var(--bg-primary);
|
|
176
|
+
border-radius: 3px;
|
|
177
|
+
padding: 8px;
|
|
178
|
+
height: 80px;
|
|
179
|
+
border: 1px solid var(--border-color);
|
|
180
|
+
position: relative;
|
|
181
|
+
margin-top: 8px;
|
|
182
|
+
}
|
|
183
|
+
.timeline-track {
|
|
184
|
+
height: 100%;
|
|
185
|
+
position: relative;
|
|
186
|
+
background: var(--bg-secondary);
|
|
187
|
+
border-radius: 2px;
|
|
188
|
+
}
|
|
189
|
+
.keyframe-marker {
|
|
190
|
+
position: absolute;
|
|
191
|
+
width: 8px;
|
|
192
|
+
height: 100%;
|
|
193
|
+
background: var(--accent-color);
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
top: 0;
|
|
196
|
+
}
|
|
197
|
+
.animation-controls {
|
|
198
|
+
display: flex;
|
|
199
|
+
gap: 4px;
|
|
200
|
+
margin-top: 8px;
|
|
201
|
+
}
|
|
202
|
+
.animation-controls button {
|
|
203
|
+
flex: 1;
|
|
204
|
+
padding: 6px;
|
|
205
|
+
font-size: 10px;
|
|
206
|
+
background: var(--button-bg);
|
|
207
|
+
border: 1px solid var(--border-color);
|
|
208
|
+
border-radius: 3px;
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
color: var(--text-primary);
|
|
211
|
+
}
|
|
212
|
+
.turntable-controls {
|
|
213
|
+
display: grid;
|
|
214
|
+
grid-template-columns: 1fr 1fr;
|
|
215
|
+
gap: 4px;
|
|
216
|
+
}
|
|
217
|
+
.turntable-controls button {
|
|
218
|
+
padding: 6px;
|
|
219
|
+
font-size: 10px;
|
|
220
|
+
background: var(--button-bg);
|
|
221
|
+
border: 1px solid var(--border-color);
|
|
222
|
+
border-radius: 3px;
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
color: var(--text-primary);
|
|
225
|
+
}
|
|
226
|
+
.screenshot-options {
|
|
227
|
+
display: flex;
|
|
228
|
+
flex-direction: column;
|
|
229
|
+
gap: 6px;
|
|
230
|
+
}
|
|
231
|
+
.screenshot-options select {
|
|
232
|
+
padding: 4px;
|
|
233
|
+
background: var(--bg-primary);
|
|
234
|
+
border: 1px solid var(--border-color);
|
|
235
|
+
border-radius: 2px;
|
|
236
|
+
color: var(--text-primary);
|
|
237
|
+
font-size: 10px;
|
|
238
|
+
}
|
|
239
|
+
</style>
|
|
240
|
+
|
|
241
|
+
<div class="render-section">
|
|
242
|
+
<h3>Appearance</h3>
|
|
243
|
+
<div class="material-grid" id="renderMaterialGrid"></div>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<div class="render-section">
|
|
247
|
+
<h3>Environment</h3>
|
|
248
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 4px;">
|
|
249
|
+
<button onclick="window.fusionRender?.setEnvironment('studio')">Studio</button>
|
|
250
|
+
<button onclick="window.fusionRender?.setEnvironment('outdoor')">Outdoor</button>
|
|
251
|
+
<button onclick="window.fusionRender?.setEnvironment('garage')">Garage</button>
|
|
252
|
+
<button onclick="window.fusionRender?.setEnvironment('cleanroom')">Clean Room</button>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div class="render-section">
|
|
257
|
+
<h3>Render Settings</h3>
|
|
258
|
+
<div class="render-settings">
|
|
259
|
+
<div class="render-setting-row">
|
|
260
|
+
<label>Quality:</label>
|
|
261
|
+
<select id="renderQuality" onchange="window.fusionRender?.setQuality(this.value)">
|
|
262
|
+
<option value="low">Low</option>
|
|
263
|
+
<option value="medium">Medium</option>
|
|
264
|
+
<option value="high" selected>High</option>
|
|
265
|
+
<option value="ultra">Ultra</option>
|
|
266
|
+
</select>
|
|
267
|
+
</div>
|
|
268
|
+
<div class="render-setting-row">
|
|
269
|
+
<label>Resolution:</label>
|
|
270
|
+
<select id="renderResolution" onchange="window.fusionRender?.setResolution(this.value)">
|
|
271
|
+
<option value="1920x1080">1920x1080</option>
|
|
272
|
+
<option value="2560x1440" selected>2560x1440</option>
|
|
273
|
+
<option value="3840x2160">4K</option>
|
|
274
|
+
</select>
|
|
275
|
+
</div>
|
|
276
|
+
<div class="render-setting-row">
|
|
277
|
+
<label>Exposure:</label>
|
|
278
|
+
<input type="range" id="renderExposure" min="0.1" max="3" step="0.1" value="1.0" onchange="window.fusionRender?.setExposure(this.value)">
|
|
279
|
+
<span id="renderExposureVal">1.0</span>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="render-setting-row">
|
|
282
|
+
<label>Bloom:</label>
|
|
283
|
+
<input type="range" id="renderBloom" min="0" max="1" step="0.1" value="0.3" onchange="window.fusionRender?.setBloom(this.value)">
|
|
284
|
+
<span id="renderBloomVal">0.3</span>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<div class="render-section">
|
|
290
|
+
<h3>Animation</h3>
|
|
291
|
+
<div class="animation-controls">
|
|
292
|
+
<button onclick="window.fusionRender?.playAnimation()">Play</button>
|
|
293
|
+
<button onclick="window.fusionRender?.pauseAnimation()">Pause</button>
|
|
294
|
+
<button onclick="window.fusionRender?.stopAnimation()">Stop</button>
|
|
295
|
+
</div>
|
|
296
|
+
<div class="animation-timeline">
|
|
297
|
+
<div class="timeline-track" id="renderTimelineTrack"></div>
|
|
298
|
+
</div>
|
|
299
|
+
<div class="animation-controls" style="margin-top: 8px;">
|
|
300
|
+
<button onclick="window.fusionRender?.addKeyframe()">Add Frame</button>
|
|
301
|
+
<button onclick="window.fusionRender?.recordKeyframe()">Record</button>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
<div class="render-section">
|
|
306
|
+
<h3>Turntable</h3>
|
|
307
|
+
<div class="turntable-controls">
|
|
308
|
+
<button onclick="window.fusionRender?.startTurntable()">Start</button>
|
|
309
|
+
<button onclick="window.fusionRender?.stopTurntable()">Stop</button>
|
|
310
|
+
<button onclick="window.fusionRender?.exportTurntable()">Export GIF</button>
|
|
311
|
+
<button onclick="window.fusionRender?.exportFrames()">Export Frames</button>
|
|
312
|
+
</div>
|
|
313
|
+
</div>
|
|
314
|
+
|
|
315
|
+
<div class="render-section">
|
|
316
|
+
<h3>Screenshot</h3>
|
|
317
|
+
<div class="screenshot-options">
|
|
318
|
+
<select id="renderScreenshotRes" onchange="window.fusionRender?.setScreenshotResolution(this.value)">
|
|
319
|
+
<option value="1920x1080">1920x1080</option>
|
|
320
|
+
<option value="3840x2160" selected>4K</option>
|
|
321
|
+
<option value="7680x4320">8K</option>
|
|
322
|
+
</select>
|
|
323
|
+
<label style="font-size: 10px;">
|
|
324
|
+
<input type="checkbox" id="renderScreenshotTransp" onchange="window.fusionRender?.toggleTransparentBG()">
|
|
325
|
+
Transparent Background
|
|
326
|
+
</label>
|
|
327
|
+
</div>
|
|
328
|
+
<button onclick="window.fusionRender?.takeScreenshot()" style="width: 100%; padding: 8px; margin-top: 8px; background: var(--accent-color); color: white; border: none; border-radius: 3px; cursor: pointer; font-weight: 600;">Take Screenshot</button>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<div class="render-section">
|
|
332
|
+
<h3>Decals</h3>
|
|
333
|
+
<div style="display: flex; gap: 4px;">
|
|
334
|
+
<button onclick="window.fusionRender?.addDecal()" style="flex: 1;">Add Decal</button>
|
|
335
|
+
<button onclick="window.fusionRender?.editDecal()" style="flex: 1;">Edit</button>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
`;
|
|
339
|
+
|
|
340
|
+
window.fusionRender = this;
|
|
341
|
+
this.populateMaterials();
|
|
342
|
+
return panel;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Initialize PBR material library
|
|
347
|
+
*/
|
|
348
|
+
initMaterialLibrary() {
|
|
349
|
+
return new Map([
|
|
350
|
+
// Metals
|
|
351
|
+
['steel', { name: 'Steel', color: 0x808080, metalness: 1, roughness: 0.3 }],
|
|
352
|
+
['aluminum', { name: 'Aluminum', color: 0xb3b3b3, metalness: 1, roughness: 0.25 }],
|
|
353
|
+
['gold', { name: 'Gold', color: 0xffd700, metalness: 1, roughness: 0.2 }],
|
|
354
|
+
['copper', { name: 'Copper', color: 0xb87333, metalness: 1, roughness: 0.35 }],
|
|
355
|
+
['titanium', { name: 'Titanium', color: 0x9db3bf, metalness: 1, roughness: 0.4 }],
|
|
356
|
+
['brass', { name: 'Brass', color: 0xb5a642, metalness: 1, roughness: 0.3 }],
|
|
357
|
+
['chrome', { name: 'Chrome', color: 0xa8a9ad, metalness: 1, roughness: 0.1 }],
|
|
358
|
+
|
|
359
|
+
// Plastics
|
|
360
|
+
['plastic_black', { name: 'Black Plastic', color: 0x1a1a1a, metalness: 0, roughness: 0.5 }],
|
|
361
|
+
['plastic_white', { name: 'White Plastic', color: 0xfafafa, metalness: 0, roughness: 0.4 }],
|
|
362
|
+
['plastic_red', { name: 'Red Plastic', color: 0xff4444, metalness: 0, roughness: 0.45 }],
|
|
363
|
+
['plastic_blue', { name: 'Blue Plastic', color: 0x4444ff, metalness: 0, roughness: 0.45 }],
|
|
364
|
+
|
|
365
|
+
// Rubber
|
|
366
|
+
['rubber_black', { name: 'Black Rubber', color: 0x1a1a1a, metalness: 0, roughness: 0.8 }],
|
|
367
|
+
['rubber_red', { name: 'Red Rubber', color: 0xcc0000, metalness: 0, roughness: 0.75 }],
|
|
368
|
+
|
|
369
|
+
// Glass
|
|
370
|
+
['glass_clear', { name: 'Clear Glass', color: 0xccccff, metalness: 0, roughness: 0 }],
|
|
371
|
+
['glass_frosted', { name: 'Frosted Glass', color: 0xfafafa, metalness: 0, roughness: 0.4 }],
|
|
372
|
+
|
|
373
|
+
// Natural Materials
|
|
374
|
+
['wood_oak', { name: 'Oak Wood', color: 0xa0826d, metalness: 0, roughness: 0.6 }],
|
|
375
|
+
['wood_walnut', { name: 'Walnut Wood', color: 0x6b4423, metalness: 0, roughness: 0.65 }],
|
|
376
|
+
['stone_granite', { name: 'Granite', color: 0x888888, metalness: 0, roughness: 0.7 }],
|
|
377
|
+
|
|
378
|
+
// Fabric
|
|
379
|
+
['fabric_cotton', { name: 'Cotton Fabric', color: 0xf0f0f0, metalness: 0, roughness: 0.9 }],
|
|
380
|
+
['fabric_silk', { name: 'Silk', color: 0xffd700, metalness: 0, roughness: 0.2 }],
|
|
381
|
+
]);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Populate material selector
|
|
386
|
+
*/
|
|
387
|
+
populateMaterials() {
|
|
388
|
+
const grid = document.getElementById('renderMaterialGrid');
|
|
389
|
+
if (!grid) return;
|
|
390
|
+
|
|
391
|
+
grid.innerHTML = '';
|
|
392
|
+
for (const [id, mat] of this.materialLibrary) {
|
|
393
|
+
const item = document.createElement('div');
|
|
394
|
+
item.className = 'material-item';
|
|
395
|
+
item.innerHTML = `
|
|
396
|
+
<div class="material-preview" style="background-color: #${mat.color.toString(16).padStart(6, '0')};"></div>
|
|
397
|
+
<div>${mat.name}</div>
|
|
398
|
+
`;
|
|
399
|
+
item.onclick = () => this.applyMaterial(id);
|
|
400
|
+
grid.appendChild(item);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Apply material to selected object
|
|
406
|
+
*/
|
|
407
|
+
applyMaterial(materialId) {
|
|
408
|
+
const mat = this.materialLibrary.get(materialId);
|
|
409
|
+
if (!mat) return;
|
|
410
|
+
|
|
411
|
+
// Find selected mesh in scene (simplified)
|
|
412
|
+
let targetMesh = null;
|
|
413
|
+
this.scene.traverse((obj) => {
|
|
414
|
+
if (obj.isMesh && obj.userData.selected) {
|
|
415
|
+
targetMesh = obj;
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
if (targetMesh && targetMesh.material) {
|
|
420
|
+
targetMesh.material.color.setHex(mat.color);
|
|
421
|
+
targetMesh.material.metalness = mat.metalness;
|
|
422
|
+
targetMesh.material.roughness = mat.roughness;
|
|
423
|
+
|
|
424
|
+
this.appliedMaterials.set(targetMesh.uuid, materialId);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Initialize environments
|
|
430
|
+
*/
|
|
431
|
+
initEnvironments() {
|
|
432
|
+
return new Map([
|
|
433
|
+
['studio', { name: 'Studio', bgColor: 0xfafafa, intensity: 1.2 }],
|
|
434
|
+
['outdoor', { name: 'Outdoor', bgColor: 0x87ceeb, intensity: 1.5 }],
|
|
435
|
+
['garage', { name: 'Garage', bgColor: 0x666666, intensity: 0.8 }],
|
|
436
|
+
['cleanroom', { name: 'Clean Room', bgColor: 0xffffff, intensity: 2.0 }],
|
|
437
|
+
]);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Set environment
|
|
442
|
+
*/
|
|
443
|
+
setEnvironment(envId) {
|
|
444
|
+
const env = this.environments.get(envId);
|
|
445
|
+
if (!env) return;
|
|
446
|
+
|
|
447
|
+
this.currentEnvironment = envId;
|
|
448
|
+
this.scene.background = new THREE.Color(env.bgColor);
|
|
449
|
+
|
|
450
|
+
// Update lighting
|
|
451
|
+
const lights = [];
|
|
452
|
+
this.scene.traverse((obj) => {
|
|
453
|
+
if (obj.isLight) lights.push(obj);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
for (const light of lights) {
|
|
457
|
+
light.intensity = env.intensity;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Add decal (image texture on surface)
|
|
463
|
+
*/
|
|
464
|
+
addDecal() {
|
|
465
|
+
const decal = {
|
|
466
|
+
id: `decal_${Date.now()}`,
|
|
467
|
+
position: new THREE.Vector3(0, 0, 0),
|
|
468
|
+
rotation: new THREE.Euler(0, 0, 0),
|
|
469
|
+
scale: new THREE.Vector3(1, 1, 1),
|
|
470
|
+
texture: null,
|
|
471
|
+
targetMesh: null
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
this.decals.push(decal);
|
|
475
|
+
return decal.id;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Edit selected decal
|
|
480
|
+
*/
|
|
481
|
+
editDecal() {
|
|
482
|
+
if (this.decals.length === 0) {
|
|
483
|
+
console.log('No decals to edit');
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Edit first decal (simplified)
|
|
488
|
+
console.log('Editing decal:', this.decals[0].id);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Set render quality
|
|
493
|
+
*/
|
|
494
|
+
setQuality(quality) {
|
|
495
|
+
this.renderSettings.quality = quality;
|
|
496
|
+
console.log(`Render quality set to: ${quality}`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Set render resolution
|
|
501
|
+
*/
|
|
502
|
+
setResolution(resolution) {
|
|
503
|
+
this.renderSettings.resolution = resolution;
|
|
504
|
+
const [w, h] = resolution.split('x').map(Number);
|
|
505
|
+
console.log(`Resolution set to: ${w}x${h}`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Set exposure value
|
|
510
|
+
*/
|
|
511
|
+
setExposure(value) {
|
|
512
|
+
this.renderSettings.exposureValue = parseFloat(value);
|
|
513
|
+
document.getElementById('renderExposureVal').textContent = value;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Set bloom intensity
|
|
518
|
+
*/
|
|
519
|
+
setBloom(value) {
|
|
520
|
+
this.renderSettings.bloomIntensity = parseFloat(value);
|
|
521
|
+
document.getElementById('renderBloomVal').textContent = value;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Play animation
|
|
526
|
+
*/
|
|
527
|
+
playAnimation() {
|
|
528
|
+
if (this.isPlaying) return;
|
|
529
|
+
this.isPlaying = true;
|
|
530
|
+
|
|
531
|
+
const startTime = performance.now();
|
|
532
|
+
const animate = (currentTime) => {
|
|
533
|
+
const elapsed = currentTime - startTime;
|
|
534
|
+
this.currentTime = elapsed;
|
|
535
|
+
|
|
536
|
+
// Update animation
|
|
537
|
+
this.updateAnimation(this.currentTime);
|
|
538
|
+
|
|
539
|
+
if (this.isPlaying) {
|
|
540
|
+
this.animationFrameId = requestAnimationFrame(animate);
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
this.animationFrameId = requestAnimationFrame(animate);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Pause animation
|
|
549
|
+
*/
|
|
550
|
+
pauseAnimation() {
|
|
551
|
+
this.isPlaying = false;
|
|
552
|
+
if (this.animationFrameId) {
|
|
553
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Stop animation
|
|
559
|
+
*/
|
|
560
|
+
stopAnimation() {
|
|
561
|
+
this.pauseAnimation();
|
|
562
|
+
this.currentTime = 0;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Update animation state
|
|
567
|
+
*/
|
|
568
|
+
updateAnimation(time) {
|
|
569
|
+
// Update camera and component positions based on keyframes
|
|
570
|
+
// (simplified)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Add keyframe at current time
|
|
575
|
+
*/
|
|
576
|
+
addKeyframe() {
|
|
577
|
+
const keyId = `key_${Date.now()}`;
|
|
578
|
+
this.keyframes.set(keyId, {
|
|
579
|
+
id: keyId,
|
|
580
|
+
time: this.currentTime,
|
|
581
|
+
cameraPosition: this.camera.position.clone(),
|
|
582
|
+
cameraTarget: new THREE.Vector3(0, 0, 0),
|
|
583
|
+
components: new Map() // componentId -> { position, rotation, visible }
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Record keyframe from current state
|
|
589
|
+
*/
|
|
590
|
+
recordKeyframe() {
|
|
591
|
+
this.addKeyframe();
|
|
592
|
+
console.log('Keyframe recorded at time:', this.currentTime);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Start turntable animation
|
|
597
|
+
*/
|
|
598
|
+
startTurntable() {
|
|
599
|
+
if (this.turntableActive) return;
|
|
600
|
+
this.turntableActive = true;
|
|
601
|
+
|
|
602
|
+
const startTime = performance.now();
|
|
603
|
+
const animate = (currentTime) => {
|
|
604
|
+
const elapsed = (currentTime - startTime) / 1000; // seconds
|
|
605
|
+
const rotation = (elapsed * this.turntableSpeed) % 1; // normalized [0, 1)
|
|
606
|
+
|
|
607
|
+
// Rotate camera around scene
|
|
608
|
+
const angle = rotation * Math.PI * 2;
|
|
609
|
+
const radius = this.camera.position.length();
|
|
610
|
+
this.camera.position.x = Math.cos(angle) * radius;
|
|
611
|
+
this.camera.position.z = Math.sin(angle) * radius;
|
|
612
|
+
this.camera.lookAt(0, 0, 0);
|
|
613
|
+
|
|
614
|
+
// Capture frame for GIF
|
|
615
|
+
if (elapsed % (1 / 24) < 0.01) { // 24 fps
|
|
616
|
+
this.captureFrame();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (this.turntableActive) {
|
|
620
|
+
this.animationFrameId = requestAnimationFrame(animate);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
this.animationFrameId = requestAnimationFrame(animate);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Stop turntable
|
|
629
|
+
*/
|
|
630
|
+
stopTurntable() {
|
|
631
|
+
this.turntableActive = false;
|
|
632
|
+
if (this.animationFrameId) {
|
|
633
|
+
cancelAnimationFrame(this.animationFrameId);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Capture frame from renderer
|
|
639
|
+
*/
|
|
640
|
+
captureFrame() {
|
|
641
|
+
this.renderer.render(this.scene, this.camera);
|
|
642
|
+
const canvas = this.renderer.domElement;
|
|
643
|
+
const dataUrl = canvas.toDataURL('image/png');
|
|
644
|
+
this.turntableFrames.push(dataUrl);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Export turntable as GIF
|
|
649
|
+
*/
|
|
650
|
+
exportTurntable() {
|
|
651
|
+
if (this.turntableFrames.length === 0) {
|
|
652
|
+
console.log('No frames to export');
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Simplified: export as image sequence
|
|
657
|
+
for (let i = 0; i < this.turntableFrames.length; i++) {
|
|
658
|
+
const link = document.createElement('a');
|
|
659
|
+
link.href = this.turntableFrames[i];
|
|
660
|
+
link.download = `turntable_frame_${String(i).padStart(4, '0')}.png`;
|
|
661
|
+
// Don't actually download in this demo, just log
|
|
662
|
+
console.log(`Frame ${i} ready for download`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Export turntable frames as PNG sequence
|
|
668
|
+
*/
|
|
669
|
+
exportFrames() {
|
|
670
|
+
console.log(`Exporting ${this.turntableFrames.length} frames...`);
|
|
671
|
+
for (let i = 0; i < this.turntableFrames.length; i++) {
|
|
672
|
+
const link = document.createElement('a');
|
|
673
|
+
link.href = this.turntableFrames[i];
|
|
674
|
+
link.download = `frame_${String(i).padStart(4, '0')}.png`;
|
|
675
|
+
// Optionally download: link.click();
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Set screenshot resolution
|
|
681
|
+
*/
|
|
682
|
+
setScreenshotResolution(resolution) {
|
|
683
|
+
const [w, h] = resolution.split('x').map(Number);
|
|
684
|
+
this.screenshotSettings.width = w;
|
|
685
|
+
this.screenshotSettings.height = h;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Toggle transparent background
|
|
690
|
+
*/
|
|
691
|
+
toggleTransparentBG() {
|
|
692
|
+
this.screenshotSettings.transparent = document.getElementById('renderScreenshotTransp')?.checked || false;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Take high-resolution screenshot
|
|
697
|
+
*/
|
|
698
|
+
takeScreenshot() {
|
|
699
|
+
const originalSize = this.renderer.getSize(new THREE.Vector2());
|
|
700
|
+
const originalPixelRatio = this.renderer.getPixelRatio();
|
|
701
|
+
|
|
702
|
+
// Set high resolution
|
|
703
|
+
this.renderer.setSize(this.screenshotSettings.width, this.screenshotSettings.height);
|
|
704
|
+
this.renderer.setPixelRatio(1);
|
|
705
|
+
|
|
706
|
+
// Set transparent background if requested
|
|
707
|
+
if (this.screenshotSettings.transparent) {
|
|
708
|
+
this.renderer.setClearColor(0x000000, 0);
|
|
709
|
+
} else {
|
|
710
|
+
this.renderer.setClearColor(this.scene.background || 0xfafafa);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Render and capture
|
|
714
|
+
this.renderer.render(this.scene, this.camera);
|
|
715
|
+
const canvas = this.renderer.domElement;
|
|
716
|
+
const dataUrl = canvas.toDataURL('image/png');
|
|
717
|
+
|
|
718
|
+
// Download
|
|
719
|
+
const link = document.createElement('a');
|
|
720
|
+
link.href = dataUrl;
|
|
721
|
+
link.download = `render_${Date.now()}.png`;
|
|
722
|
+
link.click();
|
|
723
|
+
|
|
724
|
+
// Restore original size
|
|
725
|
+
this.renderer.setSize(originalSize.x, originalSize.y);
|
|
726
|
+
this.renderer.setPixelRatio(originalPixelRatio);
|
|
727
|
+
this.renderer.setClearColor(this.scene.background || 0xfafafa);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Setup environments
|
|
732
|
+
*/
|
|
733
|
+
setupEnvironments() {
|
|
734
|
+
// Load HDRI maps if available
|
|
735
|
+
// For now, use solid colors
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Setup event listeners
|
|
740
|
+
*/
|
|
741
|
+
setupEventListeners() {
|
|
742
|
+
// Listen for animation timeline scrubbing
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Execute command from agent API
|
|
747
|
+
*/
|
|
748
|
+
execute(command, params) {
|
|
749
|
+
switch (command) {
|
|
750
|
+
case 'applyMaterial':
|
|
751
|
+
return this.applyMaterial(params.materialId);
|
|
752
|
+
case 'setEnvironment':
|
|
753
|
+
return this.setEnvironment(params.environmentId);
|
|
754
|
+
case 'setQuality':
|
|
755
|
+
return this.setQuality(params.quality);
|
|
756
|
+
case 'addKeyframe':
|
|
757
|
+
return this.addKeyframe();
|
|
758
|
+
case 'playAnimation':
|
|
759
|
+
return this.playAnimation();
|
|
760
|
+
case 'pauseAnimation':
|
|
761
|
+
return this.pauseAnimation();
|
|
762
|
+
case 'startTurntable':
|
|
763
|
+
return this.startTurntable();
|
|
764
|
+
case 'takeScreenshot':
|
|
765
|
+
return this.takeScreenshot();
|
|
766
|
+
case 'addDecal':
|
|
767
|
+
return this.addDecal();
|
|
768
|
+
default:
|
|
769
|
+
console.warn(`Unknown render command: ${command}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export default FusionRenderModule;
|