freeform-modeling-mcp 1.0.26

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.
Files changed (81) hide show
  1. package/README.md +61 -0
  2. package/index.cjs +21 -0
  3. package/package.json +48 -0
  4. package/src/cli.ts +47 -0
  5. package/src/commands/start.ts +88 -0
  6. package/src/commands/status.ts +81 -0
  7. package/src/dev.ts +24 -0
  8. package/src/lib/fileproxy/index.ts +505 -0
  9. package/src/lib/fileproxy/proxy-server.js +347 -0
  10. package/src/lib/gltf2obj/compress.js +33 -0
  11. package/src/lib/gltf2obj/engine/core/core.js +34 -0
  12. package/src/lib/gltf2obj/engine/core/eventnotifier.js +39 -0
  13. package/src/lib/gltf2obj/engine/core/taskrunner.js +88 -0
  14. package/src/lib/gltf2obj/engine/export/exporter.js +38 -0
  15. package/src/lib/gltf2obj/engine/export/exporterbase.js +86 -0
  16. package/src/lib/gltf2obj/engine/export/exportermodel.js +117 -0
  17. package/src/lib/gltf2obj/engine/export/exporterobj.js +147 -0
  18. package/src/lib/gltf2obj/engine/geometry/box3d.js +60 -0
  19. package/src/lib/gltf2obj/engine/geometry/coord2d.js +35 -0
  20. package/src/lib/gltf2obj/engine/geometry/coord3d.js +126 -0
  21. package/src/lib/gltf2obj/engine/geometry/coord4d.js +15 -0
  22. package/src/lib/gltf2obj/engine/geometry/geometry.js +56 -0
  23. package/src/lib/gltf2obj/engine/geometry/matrix.js +440 -0
  24. package/src/lib/gltf2obj/engine/geometry/octree.js +160 -0
  25. package/src/lib/gltf2obj/engine/geometry/quaternion.js +83 -0
  26. package/src/lib/gltf2obj/engine/geometry/transformation.js +63 -0
  27. package/src/lib/gltf2obj/engine/geometry/tween.js +31 -0
  28. package/src/lib/gltf2obj/engine/import/importer.js +270 -0
  29. package/src/lib/gltf2obj/engine/import/importerbase.js +115 -0
  30. package/src/lib/gltf2obj/engine/import/importerfiles.js +139 -0
  31. package/src/lib/gltf2obj/engine/import/importergltf.js +1045 -0
  32. package/src/lib/gltf2obj/engine/import/importerutils.js +102 -0
  33. package/src/lib/gltf2obj/engine/io/binaryreader.js +93 -0
  34. package/src/lib/gltf2obj/engine/io/binarywriter.js +92 -0
  35. package/src/lib/gltf2obj/engine/io/bufferutils.js +85 -0
  36. package/src/lib/gltf2obj/engine/io/externallibs.js +42 -0
  37. package/src/lib/gltf2obj/engine/io/fileutils.js +120 -0
  38. package/src/lib/gltf2obj/engine/io/textwriter.js +41 -0
  39. package/src/lib/gltf2obj/engine/main.js +19 -0
  40. package/src/lib/gltf2obj/engine/model/color.js +130 -0
  41. package/src/lib/gltf2obj/engine/model/generator.js +433 -0
  42. package/src/lib/gltf2obj/engine/model/material.js +243 -0
  43. package/src/lib/gltf2obj/engine/model/mesh.js +173 -0
  44. package/src/lib/gltf2obj/engine/model/meshbuffer.js +233 -0
  45. package/src/lib/gltf2obj/engine/model/meshinstance.js +128 -0
  46. package/src/lib/gltf2obj/engine/model/meshutils.js +64 -0
  47. package/src/lib/gltf2obj/engine/model/model.js +195 -0
  48. package/src/lib/gltf2obj/engine/model/modelfinalization.js +377 -0
  49. package/src/lib/gltf2obj/engine/model/modelutils.js +117 -0
  50. package/src/lib/gltf2obj/engine/model/node.js +178 -0
  51. package/src/lib/gltf2obj/engine/model/object.js +90 -0
  52. package/src/lib/gltf2obj/engine/model/property.js +86 -0
  53. package/src/lib/gltf2obj/engine/model/quantities.js +46 -0
  54. package/src/lib/gltf2obj/engine/model/topology.js +139 -0
  55. package/src/lib/gltf2obj/engine/model/triangle.js +99 -0
  56. package/src/lib/gltf2obj/engine/threejs/threemodelloader.js +85 -0
  57. package/src/lib/gltf2obj/handler.js +63 -0
  58. package/src/lib/gltf2obj/index.js +47 -0
  59. package/src/lib/logger.ts +304 -0
  60. package/src/lib/print.ts +37 -0
  61. package/src/lib/utils.ts +34 -0
  62. package/src/lib/wsbridge/bridge.ts +639 -0
  63. package/src/lib/wsbridge/lock.ts +258 -0
  64. package/src/llmClient.ts +245 -0
  65. package/src/prompt.ts +187 -0
  66. package/src/server.ts +125 -0
  67. package/src/tools/assets-tool.ts +314 -0
  68. package/src/tools/basic-tools.ts +531 -0
  69. package/src/tools/index.ts +2 -0
  70. package/src/tools/material-tools.ts +88 -0
  71. package/src/tools/modeling/auxiliary-curve-tool.ts +44 -0
  72. package/src/tools/modeling/find-face.ts +38 -0
  73. package/src/tools/modeling/sweep-tool.ts +60 -0
  74. package/src/tools/modeling/utils.ts +181 -0
  75. package/src/tools/modeling-tools.ts +135 -0
  76. package/src/tools/screenshot-tools.ts +107 -0
  77. package/src/tools/tools-info.ts +107 -0
  78. package/src/tools/tripo3d-tools.ts +496 -0
  79. package/src/types/index.ts +25 -0
  80. package/src/types/tripo3d.ts +38 -0
  81. package/tsconfig.json +22 -0
@@ -0,0 +1,347 @@
1
+ import express from 'express';
2
+ import axios from 'axios';
3
+ import cors from 'cors';
4
+ import { URL, fileURLToPath } from 'url';
5
+ import path from 'path';
6
+ import fs from 'fs';
7
+ import crypto from 'crypto';
8
+ import { gltf2obj } from '../gltf2obj/index.js'; // 导入GLB转OBJ的方法
9
+
10
+ // 在ES模块中获取__dirname的等效方法
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ const app = express();
15
+ const PORT = process.env.PORT || 3000;
16
+
17
+ // 启用CORS
18
+ app.use(cors());
19
+
20
+ // 设置缓存目录
21
+ const cacheDir = path.join(__dirname, 'cache');
22
+ if (!fs.existsSync(cacheDir)) {
23
+ fs.mkdirSync(cacheDir);
24
+ }
25
+
26
+ // 设置上传目录
27
+ const uploadsDir = path.join(__dirname, 'uploads');
28
+ if (!fs.existsSync(uploadsDir)) {
29
+ fs.mkdirSync(uploadsDir);
30
+ }
31
+
32
+ // 创建安全的缓存文件名 - 使用哈希而非Base64编码
33
+ const createSafeCacheFilename = (url) => {
34
+ // 使用MD5哈希创建固定长度的文件名
35
+ const hash = crypto.createHash('md5').update(url).digest('hex');
36
+ // 添加URL的扩展名以便更容易识别文件类型
37
+ try {
38
+ const extension = path.extname(new URL(url).pathname).toLowerCase();
39
+ // 如果有合法扩展名,则添加,否则使用.bin
40
+ return extension && extension.length > 0 && extension.length < 10
41
+ ? `${hash}${extension}`
42
+ : `${hash}.bin`;
43
+ } catch (e) {
44
+ // 如果URL解析失败,直接返回哈希
45
+ return `${hash}.bin`;
46
+ }
47
+ };
48
+
49
+ // 根据URL获取内容类型
50
+ const getContentTypeFromURL = (sourceUrl) => {
51
+ try {
52
+ const extension = path.extname(new URL(sourceUrl).pathname).toLowerCase();
53
+ const mimeTypes = {
54
+ '.jpg': 'image/jpeg',
55
+ '.jpeg': 'image/jpeg',
56
+ '.png': 'image/png',
57
+ '.gif': 'image/gif',
58
+ '.webp': 'image/webp',
59
+ '.svg': 'image/svg+xml',
60
+ '.pdf': 'application/pdf',
61
+ '.json': 'application/json',
62
+ '.txt': 'text/plain',
63
+ '.html': 'text/html',
64
+ '.css': 'text/css',
65
+ '.js': 'application/javascript',
66
+ '.mp3': 'audio/mpeg',
67
+ '.mp4': 'video/mp4',
68
+ '.webm': 'video/webm',
69
+ '.glb': 'model/gltf-binary',
70
+ '.gltf': 'model/gltf+json',
71
+ '.obj': 'application/octet-stream',
72
+ '.fbx': 'application/octet-stream'
73
+ };
74
+ return mimeTypes[extension] || null;
75
+ } catch (e) {
76
+ return null;
77
+ }
78
+ };
79
+
80
+ // 主要代理端点
81
+ app.get('/proxy', async (req, res) => {
82
+ try {
83
+ // 获取源文件URL参数
84
+ let { url: sourceUrl } = req.query;
85
+ try {
86
+ // 尝试去掉双引号
87
+ sourceUrl = JSON.parse(sourceUrl);
88
+ } catch (e) { }
89
+ if (!sourceUrl) {
90
+ return res.status(400).json({ error: '缺少url参数' });
91
+ }
92
+ // 验证URL
93
+ try {
94
+ new URL(sourceUrl);
95
+ } catch (e) {
96
+ return res.status(400).json({ error: '无效的URL' });
97
+ }
98
+ // 为缓存创建安全的文件名
99
+ const cacheFilename = createSafeCacheFilename(sourceUrl);
100
+ const cachePath = path.join(cacheDir, cacheFilename);
101
+ // 记录URL和缓存文件的映射(调试用)
102
+ // console.log(`URL: ${sourceUrl}`);
103
+ // console.log(`缓存文件: ${cacheFilename}`);
104
+ // 检查缓存
105
+ if (fs.existsSync(cachePath)) {
106
+ const stats = fs.statSync(cachePath);
107
+ const fileAge = Date.now() - stats.mtime.getTime();
108
+ // 如果缓存小于1小时,直接使用缓存
109
+ if (fileAge < 3600000) {
110
+ // console.log(`使用缓存: ${sourceUrl}`);
111
+ const contentType = getContentTypeFromURL(sourceUrl);
112
+ if (contentType) {
113
+ res.setHeader('Content-Type', contentType);
114
+ }
115
+ return fs.createReadStream(cachePath).pipe(res);
116
+ }
117
+ }
118
+ // 获取远程文件
119
+ // console.log(`获取文件: ${sourceUrl}`);
120
+ const response = await axios({
121
+ method: 'get',
122
+ url: sourceUrl,
123
+ responseType: 'stream',
124
+ timeout: 30000,
125
+ headers: {
126
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
127
+ }
128
+ });
129
+ // 设置响应头
130
+ const { headers } = response;
131
+ const contentType = headers['content-type'];
132
+ if (contentType) {
133
+ res.setHeader('Content-Type', contentType);
134
+ }
135
+ // 设置其他响应头
136
+ Object.entries(headers)
137
+ .filter(([key]) => !['transfer-encoding', 'connection'].includes(key.toLowerCase()))
138
+ .forEach(([key, value]) => {
139
+ res.setHeader(key, value);
140
+ });
141
+ // 将文件流同时写入缓存和响应
142
+ const cacheStream = fs.createWriteStream(cachePath);
143
+ response.data.pipe(cacheStream);
144
+ response.data.pipe(res);
145
+ // 错误处理
146
+ cacheStream.on('error', (err) => {
147
+ // console.error(`缓存写入错误: ${err.message}`);
148
+ if (!res.headersSent) {
149
+ res.status(500).json({ error: '服务器缓存错误' });
150
+ }
151
+ });
152
+ } catch (error) {
153
+ // console.error('代理请求错误:', error.message);
154
+ // 检查请求是否已经发送响应
155
+ if (!res.headersSent) {
156
+ res.status(500).json({
157
+ error: '无法获取请求的文件',
158
+ details: error.message,
159
+ url: req.query.url
160
+ });
161
+ }
162
+ }
163
+ });
164
+
165
+ // ====== 新增: GLB到OBJ的转换接口 ======
166
+
167
+ /**
168
+ * GLB到OBJ转换接口 - 通过URL
169
+ * 通过提供GLB文件的URL将其转换为OBJ
170
+ */
171
+ app.get('/convert/glb-to-obj', async (req, res) => {
172
+ try {
173
+ // 获取GLB文件URL
174
+ let { url: glbUrl } = req.query;
175
+ try {
176
+ // 尝试去掉双引号
177
+ glbUrl = JSON.parse(glbUrl);
178
+ } catch (e) { }
179
+
180
+ if (!glbUrl) {
181
+ return res.status(400).json({ error: '缺少GLB文件URL' });
182
+ }
183
+
184
+ // 验证URL
185
+ try {
186
+ new URL(glbUrl);
187
+ } catch (e) {
188
+ return res.status(400).json({ error: '无效的URL' });
189
+ }
190
+
191
+ // 验证是否为GLB文件
192
+ const urlExt = path.extname(new URL(glbUrl).pathname).toLowerCase();
193
+ if (urlExt !== '.glb') {
194
+ return res.status(400).json({ error: '提供的URL必须是GLB文件' });
195
+ }
196
+
197
+ // console.log("download file:", urlExt);
198
+
199
+ // 使用gltf2obj转换文件
200
+ const { zipBuffer, boundingBox } = await gltf2obj(glbUrl);
201
+ // console.log("boundingBox", JSON.stringify(boundingBox));
202
+
203
+ // 从URL获取文件名用于下载
204
+ const urlFileName = path.basename(new URL(glbUrl).pathname);
205
+ const downloadName = path.basename(urlFileName, '.glb') + '.zip';
206
+
207
+ res.setHeader('Content-Type', 'application/zip');
208
+ // 设置响应头,以便浏览器下载文件
209
+ res.setHeader('Content-Disposition', `attachment; filename="${downloadName}"`);
210
+ res.setHeader("Access-Control-Expose-Headers", "X-Bounding-Box")
211
+ res.setHeader('X-Bounding-Box', JSON.stringify(boundingBox));
212
+ res.set('Cache-Control', 'no-store');
213
+ // console.log("send", zipBuffer.length);
214
+ // 发送转换后的ZIP文件
215
+ res.send(zipBuffer);
216
+ } catch (error) {
217
+ // console.error('GLB转OBJ处理出错:', error);
218
+ res.status(500).json({
219
+ error: '转换GLB到OBJ时出错',
220
+ details: error.message
221
+ });
222
+ }
223
+ });
224
+
225
+ // 清理缓存的路由
226
+ app.get('/clear-cache', (req, res) => {
227
+ const { key: apiKey } = req.query;
228
+ // 简单的API密钥验证
229
+ if (apiKey !== process.env.ADMIN_API_KEY) {
230
+ return res.status(403).json({ error: '未授权' });
231
+ }
232
+ try {
233
+ const files = fs.readdirSync(cacheDir);
234
+ let deletedCount = 0;
235
+ files.forEach(file => {
236
+ fs.unlinkSync(path.join(cacheDir, file));
237
+ deletedCount++;
238
+ });
239
+ res.json({ success: `已清除 ${deletedCount} 个缓存文件` });
240
+ } catch (error) {
241
+ res.status(500).json({ error: `清除缓存时出错: ${error.message}` });
242
+ }
243
+ });
244
+
245
+ // 缓存信息路由
246
+ app.get('/cache-info', (req, res) => {
247
+ try {
248
+ const files = fs.readdirSync(cacheDir);
249
+ let totalSize = 0;
250
+ const fileDetails = [];
251
+ files.forEach(file => {
252
+ const filePath = path.join(cacheDir, file);
253
+ const stats = fs.statSync(filePath);
254
+ totalSize += stats.size;
255
+ fileDetails.push({
256
+ name: file,
257
+ size: (stats.size / 1024).toFixed(2) + ' KB',
258
+ created: stats.birthtime.toISOString()
259
+ });
260
+ });
261
+ res.json({
262
+ totalFiles: files.length,
263
+ totalSize: (totalSize / (1024 * 1024)).toFixed(2) + ' MB',
264
+ details: fileDetails.slice(0, 50) // 只返回前50个文件信息以避免响应过大
265
+ });
266
+ } catch (error) {
267
+ res.status(500).json({ error: `获取缓存信息时出错: ${error.message}` });
268
+ }
269
+ });
270
+ // 服务器状态检查,修改添加新功能描述(续)
271
+ app.get('/', (req, res) => {
272
+ res.send(`
273
+ <html>
274
+ <head><title>文件转发与转换服务</title>
275
+ <style>
276
+ body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
277
+ h1 { color: #333; }
278
+ h2 { color: #555; margin-top: 20px; }
279
+ pre { background: #f4f4f4; padding: 10px; border-radius: 5px; }
280
+ code { font-family: monospace; }
281
+ .endpoint { margin-bottom: 20px; }
282
+ </style>
283
+ </head>
284
+ <body>
285
+ <h1>文件转发与转换服务</h1>
286
+ <p>这是一个文件代理和转换服务,用于解决跨域资源访问问题并提供文件格式转换功能。</p>
287
+
288
+ <h2>文件代理:</h2>
289
+ <div class="endpoint">
290
+ <pre><code>GET /proxy?url="https://example.com/path/to/file.ext"</code></pre>
291
+ <p>上面的请求将获取指定URL的文件内容并返回,无需担心跨域限制。</p>
292
+ </div>
293
+
294
+ <h2>GLB转OBJ转换:</h2>
295
+ <div class="endpoint">
296
+ <h3>通过URL转换:</h3>
297
+ <pre><code>GET /convert/glb-to-obj?url="https://example.com/path/to/model.glb"</code></pre>
298
+ <p>使用指定URL的GLB文件,转换为OBJ格式并以ZIP压缩包返回。</p>
299
+
300
+ <h3>通过文件上传转换:</h3>
301
+ <pre><code>POST /convert/glb-to-obj</code></pre>
302
+ <p>上传一个GLB文件,表单字段名为'file',服务器将转换为OBJ格式并以ZIP压缩包返回。</p>
303
+ <p>示例HTML表单:</p>
304
+ <pre><code>&lt;form action="/convert/glb-to-obj" method="post" enctype="multipart/form-data"&gt;
305
+ &lt;input type="file" name="file" accept=".glb"&gt;
306
+ &lt;button type="submit"&gt;转换为OBJ&lt;/button&gt;
307
+ &lt;/form&gt;</code></pre>
308
+ </div>
309
+
310
+ <p>服务状态: 正常运行</p>
311
+ </body>
312
+ </html>
313
+ `);
314
+ });
315
+
316
+ // 启动服务器
317
+ const server = app.listen(PORT, () => {
318
+ // console.log(`文件转发与转换服务运行在 http://localhost:${PORT}`);
319
+ });
320
+
321
+ // 处理终止信号
322
+ process.on('SIGTERM', () => {
323
+ // console.log('收到终止信号,服务器正在优雅关闭...');
324
+ server.close(() => {
325
+ // console.log('HTTP服务器已关闭,正在完成剩余请求');
326
+
327
+ // 清理上传文件夹中的临时文件
328
+ try {
329
+ const files = fs.readdirSync(uploadsDir);
330
+ files.forEach(file => {
331
+ fs.unlinkSync(path.join(uploadsDir, file));
332
+ });
333
+ // console.log('已清理临时文件');
334
+ } catch (err) {
335
+ // console.error('清理临时文件时出错:', err);
336
+ }
337
+
338
+ // console.log('资源清理完成,进程将退出');
339
+ process.exit(0);
340
+ });
341
+
342
+ // 设置强制退出超时
343
+ setTimeout(() => {
344
+ // console.log('关闭超时,强制退出');
345
+ process.exit(1);
346
+ }, 3000);
347
+ });
@@ -0,0 +1,33 @@
1
+ import JSZip from 'jszip';
2
+
3
+ /**
4
+ * 将多个已读取为ArrayBuffer的文件压缩成ZIP
5
+ * @param {Array<{name: string, content: ArrayBuffer}>} files - 包含文件信息的数组
6
+ * @returns {Promise<Buffer>} 压缩后的Buffer
7
+ */
8
+ export async function compressArrayBufferFiles(files) {
9
+ try {
10
+ // 创建新的ZIP实例
11
+ const zip = new JSZip();
12
+
13
+ // 添加文件到ZIP
14
+ for (const fileInfo of files) {
15
+ // JSZip可以直接接受ArrayBuffer作为内容
16
+ zip.file(fileInfo.name, fileInfo.content);
17
+ }
18
+
19
+ // 生成ZIP文件
20
+ const zipContent = await zip.generateAsync({
21
+ type: 'nodebuffer', // Node.js中使用nodebuffer
22
+ compression: 'DEFLATE',
23
+ compressionOptions: {
24
+ level: 9 // 最高压缩级别
25
+ }
26
+ });
27
+
28
+ return zipContent;
29
+ } catch (error) {
30
+ console.error('创建ZIP文件时出错:', error instanceof Error ? error.message : String(error));
31
+ throw error;
32
+ }
33
+ }
@@ -0,0 +1,34 @@
1
+ export function IsDefined (val)
2
+ {
3
+ return val !== undefined && val !== null;
4
+ }
5
+
6
+ export function ValueOrDefault (val, def)
7
+ {
8
+ if (val === undefined || val === null) {
9
+ return def;
10
+ }
11
+ return val;
12
+ }
13
+
14
+ export function CopyObjectAttributes (src, dest)
15
+ {
16
+ if (!IsDefined (src)) {
17
+ return;
18
+ }
19
+ for (let attribute of Object.keys (src)) {
20
+ if (IsDefined (src[attribute])) {
21
+ dest[attribute] = src[attribute];
22
+ }
23
+ }
24
+ }
25
+
26
+ export function IsObjectEmpty (obj)
27
+ {
28
+ return Object.keys (obj).length === 0;
29
+ }
30
+
31
+ export function EscapeHtmlChars (str)
32
+ {
33
+ return str.replace (/</g, '&lt;').replace (/>/g, '&gt;');
34
+ }
@@ -0,0 +1,39 @@
1
+ export class EventNotifier
2
+ {
3
+ constructor ()
4
+ {
5
+ this.eventListeners = new Map ();
6
+ }
7
+
8
+ AddEventListener (eventId, listener)
9
+ {
10
+ if (!this.eventListeners.has (eventId)) {
11
+ this.eventListeners.set (eventId, []);
12
+ }
13
+ let listeners = this.eventListeners.get (eventId);
14
+ listeners.push (listener);
15
+ }
16
+
17
+ HasEventListener (eventId)
18
+ {
19
+ return this.eventListeners.has (eventId);
20
+ }
21
+
22
+ GetEventNotifier (eventId)
23
+ {
24
+ return () => {
25
+ this.NotifyEventListeners (eventId);
26
+ };
27
+ }
28
+
29
+ NotifyEventListeners (eventId, ...args)
30
+ {
31
+ if (!this.eventListeners.has (eventId)) {
32
+ return;
33
+ }
34
+ let listeners = this.eventListeners.get (eventId);
35
+ for (let listener of listeners) {
36
+ listener (...args);
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,88 @@
1
+ export class TaskRunner
2
+ {
3
+ constructor ()
4
+ {
5
+ this.count = null;
6
+ this.current = null;
7
+ this.callbacks = null;
8
+ }
9
+
10
+ Run (count, callbacks)
11
+ {
12
+ this.count = count;
13
+ this.current = 0;
14
+ this.callbacks = callbacks;
15
+ if (count === 0) {
16
+ this.TaskReady ();
17
+ } else {
18
+ this.RunOnce ();
19
+ }
20
+ }
21
+
22
+ RunBatch (count, batchCount, callbacks)
23
+ {
24
+ let stepCount = 0;
25
+ if (count > 0) {
26
+ stepCount = parseInt ((count - 1) / batchCount, 10) + 1;
27
+ }
28
+ this.Run (stepCount, {
29
+ runTask : (index, ready) => {
30
+ const firstIndex = index * batchCount;
31
+ const lastIndex = Math.min ((index + 1) * batchCount, count) - 1;
32
+ callbacks.runTask (firstIndex, lastIndex, ready);
33
+ },
34
+ onReady : callbacks.onReady
35
+ });
36
+ }
37
+
38
+ RunOnce ()
39
+ {
40
+ setTimeout (() => {
41
+ this.callbacks.runTask (this.current, this.TaskReady.bind (this));
42
+ }, 0);
43
+ }
44
+
45
+ TaskReady ()
46
+ {
47
+ this.current += 1;
48
+ if (this.current < this.count) {
49
+ this.RunOnce ();
50
+ } else {
51
+ if (this.callbacks.onReady) {
52
+ this.callbacks.onReady ();
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ export function RunTaskAsync (task)
59
+ {
60
+ setTimeout (() => {
61
+ task ();
62
+ }, 10);
63
+ }
64
+
65
+ export function RunTasks (count, callbacks)
66
+ {
67
+ let taskRunner = new TaskRunner ();
68
+ taskRunner.Run (count, callbacks);
69
+ }
70
+
71
+ export function RunTasksBatch (count, batchCount, callbacks)
72
+ {
73
+ let taskRunner = new TaskRunner ();
74
+ taskRunner.RunBatch (count, batchCount, callbacks);
75
+ }
76
+
77
+ export function WaitWhile (expression)
78
+ {
79
+ function Waiter (expression)
80
+ {
81
+ if (expression ()) {
82
+ setTimeout (() => {
83
+ Waiter (expression);
84
+ }, 10);
85
+ }
86
+ }
87
+ Waiter (expression);
88
+ }
@@ -0,0 +1,38 @@
1
+ import { ExporterModel } from './exportermodel.js';
2
+ import { ExporterObj } from './exporterobj.js';
3
+
4
+ export class Exporter {
5
+ constructor() {
6
+ this.exporters = [
7
+ new ExporterObj(),
8
+ ];
9
+ }
10
+
11
+ AddExporter(exporter) {
12
+ this.exporters.push(exporter);
13
+ }
14
+
15
+ Export(model, settings, format, extension, callbacks) {
16
+ let exporter = null;
17
+ for (let i = 0; i < this.exporters.length; i++) {
18
+ let currentExporter = this.exporters[i];
19
+ if (currentExporter.CanExport(format, extension)) {
20
+ exporter = currentExporter;
21
+ break;
22
+ }
23
+ }
24
+ if (exporter === null) {
25
+ callbacks.onError();
26
+ return;
27
+ }
28
+
29
+ let exporterModel = new ExporterModel(model, settings);
30
+ exporter.Export(exporterModel, format, (files) => {
31
+ if (files.length === 0) {
32
+ callbacks.onError();
33
+ } else {
34
+ callbacks.onSuccess(files, exporterModel.boundingBox);
35
+ }
36
+ });
37
+ }
38
+ }
@@ -0,0 +1,86 @@
1
+ import { ArrayBufferToUtf8String, Utf8StringToArrayBuffer } from '../io/bufferutils.js';
2
+
3
+ export class ExportedFile
4
+ {
5
+ constructor (name)
6
+ {
7
+ this.name = name;
8
+ this.content = null;
9
+ }
10
+
11
+ GetName ()
12
+ {
13
+ return this.name;
14
+ }
15
+
16
+ SetName (name)
17
+ {
18
+ this.name = name;
19
+ }
20
+
21
+ GetTextContent ()
22
+ {
23
+ let text = ArrayBufferToUtf8String (this.content);
24
+ return text;
25
+ }
26
+
27
+ GetBufferContent ()
28
+ {
29
+ return this.content;
30
+ }
31
+
32
+ SetTextContent (content)
33
+ {
34
+ let buffer = Utf8StringToArrayBuffer (content);
35
+ this.content = buffer;
36
+ }
37
+
38
+ SetBufferContent (content)
39
+ {
40
+ this.content = content;
41
+ }
42
+ }
43
+
44
+ export class ExporterBase
45
+ {
46
+ constructor ()
47
+ {
48
+
49
+ }
50
+
51
+ CanExport (format, extension)
52
+ {
53
+ return false;
54
+ }
55
+
56
+ Export (exporterModel, format, onFinish)
57
+ {
58
+ let files = [];
59
+ this.ExportContent (exporterModel, format, files, () => {
60
+ onFinish (files);
61
+ });
62
+ }
63
+
64
+ ExportContent (exporterModel, format, files, onFinish)
65
+ {
66
+
67
+ }
68
+
69
+ GetExportedMaterialName (originalName)
70
+ {
71
+ return this.GetExportedName (originalName, 'Material');
72
+ }
73
+
74
+ GetExportedMeshName (originalName)
75
+ {
76
+ return this.GetExportedName (originalName, 'Mesh');
77
+ }
78
+
79
+ GetExportedName (originalName, defaultName)
80
+ {
81
+ if (originalName.length === 0) {
82
+ return defaultName;
83
+ }
84
+ return originalName;
85
+ }
86
+ }