@cadit-app/image-extrude 0.1.1 → 0.3.2
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/README.md +18 -0
- package/dist/bundle.js +61754 -0
- package/dist/src/params.js +5 -5
- package/dist/src/resvg.d.ts +3 -0
- package/dist/src/resvg.d.ts.map +1 -1
- package/dist/src/resvg.js +33 -16
- package/dist/src/tracing.d.ts +4 -0
- package/dist/src/tracing.d.ts.map +1 -1
- package/dist/src/tracing.js +5 -1
- package/package.json +28 -13
- package/src/crossSectionUtils.ts +0 -30
- package/src/main.ts +0 -77
- package/src/makeCrossSection.ts +0 -57
- package/src/manifoldUtils.ts +0 -13
- package/src/params.ts +0 -70
- package/src/resvg.ts +0 -63
- package/src/threeMfExport.ts +0 -67
- package/src/tracing.ts +0 -94
- package/src/types.d.ts +0 -40
- package/src/utils.ts +0 -55
package/dist/src/params.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Parameter schema for the Image Extrude generator.
|
|
3
3
|
*/
|
|
4
|
-
// Default
|
|
5
|
-
const
|
|
4
|
+
// Default image - CookieCAD logo from GitHub via jsdelivr CDN
|
|
5
|
+
const defaultImageUrl = 'https://cdn.jsdelivr.net/gh/CADit-app/image-extrude@master/images/cookiecad-logo-dark.svg';
|
|
6
6
|
export const imageExtrudeParamsSchema = {
|
|
7
7
|
mode: {
|
|
8
8
|
type: 'choice',
|
|
@@ -17,10 +17,10 @@ export const imageExtrudeParamsSchema = {
|
|
|
17
17
|
type: 'image',
|
|
18
18
|
label: 'Image File',
|
|
19
19
|
default: {
|
|
20
|
-
imageUrl:
|
|
21
|
-
dataUrl:
|
|
20
|
+
imageUrl: defaultImageUrl,
|
|
21
|
+
dataUrl: '',
|
|
22
22
|
fileType: 'image/svg+xml',
|
|
23
|
-
fileName: '
|
|
23
|
+
fileName: 'cookiecad-logo-dark.svg'
|
|
24
24
|
},
|
|
25
25
|
},
|
|
26
26
|
height: {
|
package/dist/src/resvg.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SVG to bitmap rendering using resvg-wasm
|
|
3
|
+
*
|
|
4
|
+
* Note: When running as a dynamically loaded script in CADit, the WASM module
|
|
5
|
+
* is loaded from esm.sh CDN since Vite's ?url import syntax isn't available.
|
|
3
6
|
*/
|
|
4
7
|
/**
|
|
5
8
|
* Renders an SVG Data URL to a PNG Data URL using resvg-wasm.
|
package/dist/src/resvg.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resvg.d.ts","sourceRoot":"","sources":["../../src/resvg.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"resvg.d.ts","sourceRoot":"","sources":["../../src/resvg.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAsCH;;GAEG;AACH,eAAO,MAAM,wBAAwB,GACnC,YAAY,MAAM,EAClB,UAAU;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,KAC9B,OAAO,CAAC,MAAM,CA6BhB,CAAC"}
|
package/dist/src/resvg.js
CHANGED
|
@@ -1,26 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SVG to bitmap rendering using resvg-wasm
|
|
3
|
+
*
|
|
4
|
+
* Note: When running as a dynamically loaded script in CADit, the WASM module
|
|
5
|
+
* is loaded from esm.sh CDN since Vite's ?url import syntax isn't available.
|
|
3
6
|
*/
|
|
4
7
|
import * as resvg from '@resvg/resvg-wasm';
|
|
5
8
|
import { svgDataUrlToString } from './utils';
|
|
9
|
+
// Hardcoded version for WASM URL - update when upgrading @resvg/resvg-wasm dependency
|
|
10
|
+
const RESVG_VERSION = '2.6.2';
|
|
6
11
|
let wasmInitialized = false;
|
|
12
|
+
let wasmInitPromise = null;
|
|
7
13
|
/**
|
|
8
|
-
* Initialize resvg WASM module
|
|
9
|
-
*
|
|
10
|
-
*
|
|
14
|
+
* Initialize resvg WASM module.
|
|
15
|
+
* Fetches the WASM binary from esm.sh CDN to work in dynamically loaded scripts.
|
|
16
|
+
*
|
|
17
|
+
* Safe to call multiple times - subsequent calls return immediately.
|
|
18
|
+
* If initialization fails, subsequent calls will retry.
|
|
11
19
|
*/
|
|
12
20
|
async function initializeResvgWasm() {
|
|
13
21
|
if (wasmInitialized)
|
|
14
22
|
return;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
if (wasmInitPromise)
|
|
24
|
+
return wasmInitPromise;
|
|
25
|
+
wasmInitPromise = (async () => {
|
|
26
|
+
try {
|
|
27
|
+
// Fetch WASM from esm.sh CDN
|
|
28
|
+
const wasmUrl = `https://esm.sh/@resvg/resvg-wasm@${RESVG_VERSION}/index_bg.wasm`;
|
|
29
|
+
await resvg.initWasm(fetch(wasmUrl));
|
|
30
|
+
wasmInitialized = true;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
// Reset promise so retry is possible
|
|
34
|
+
wasmInitPromise = null;
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
})();
|
|
38
|
+
return wasmInitPromise;
|
|
24
39
|
}
|
|
25
40
|
/**
|
|
26
41
|
* Renders an SVG Data URL to a PNG Data URL using resvg-wasm.
|
|
@@ -40,8 +55,10 @@ export const renderSvgToBitmapDataUrl = async (svgDataUrl, options) => {
|
|
|
40
55
|
}
|
|
41
56
|
const resvgInstance = new resvg.Resvg(svgString, opts);
|
|
42
57
|
const pngData = resvgInstance.render();
|
|
43
|
-
const pngBuffer = pngData.asPng();
|
|
44
|
-
// Convert to
|
|
45
|
-
const
|
|
46
|
-
|
|
58
|
+
const pngBuffer = pngData.asPng(); // Uint8Array
|
|
59
|
+
// Convert Uint8Array to proper ArrayBuffer for Blob
|
|
60
|
+
const arrayBuffer = pngBuffer.buffer.slice(pngBuffer.byteOffset, pngBuffer.byteOffset + pngBuffer.byteLength);
|
|
61
|
+
// Return blob URL (browser-compatible and more efficient)
|
|
62
|
+
const blob = new Blob([arrayBuffer], { type: 'image/png' });
|
|
63
|
+
return URL.createObjectURL(blob);
|
|
47
64
|
};
|
package/dist/src/tracing.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Image tracing and SVG sampling utilities
|
|
3
|
+
*
|
|
4
|
+
* Uses potrace for bitmap tracing. When running in CADit, potrace is exposed
|
|
5
|
+
* as an importable library by the code.worker, which handles the jimp dependency.
|
|
3
6
|
*/
|
|
4
7
|
import { CrossSection } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
5
8
|
/**
|
|
@@ -16,6 +19,7 @@ export declare const svgStringToCrossSection: (svgContent: string, maxWidth?: nu
|
|
|
16
19
|
export declare const sampleSvg: (svgDataUrl: string, maxWidth?: number) => Promise<CrossSection>;
|
|
17
20
|
/**
|
|
18
21
|
* Traces a bitmap image and returns a centered CrossSection
|
|
22
|
+
* Uses potrace with jimp for image loading (works in web workers).
|
|
19
23
|
*/
|
|
20
24
|
export declare const traceImage: (imageDataUrl: string, options: {
|
|
21
25
|
maxWidth?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../../src/tracing.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAKlE;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAC/B,YAAY,MAAM,EAClB,UAAU,MAAM,kCAWjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAClC,YAAY,MAAM,EAClB,WAAW,MAAM,EACjB,WAAU,MAAa,KACtB,OAAO,CAAC,YAAY,CAgBtB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,GAAU,YAAY,MAAM,EAAE,WAAW,MAAM,KAAG,OAAO,CAAC,YAAY,CAO3F,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,GACrB,cAAc,MAAM,EACpB,SAAS;IACP,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,KACA,OAAO,CAAC,YAAY,CAmBtB,CAAC"}
|
package/dist/src/tracing.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Image tracing and SVG sampling utilities
|
|
3
|
+
*
|
|
4
|
+
* Uses potrace for bitmap tracing. When running in CADit, potrace is exposed
|
|
5
|
+
* as an importable library by the code.worker, which handles the jimp dependency.
|
|
3
6
|
*/
|
|
4
7
|
import { svgToPolygons } from '@cadit-app/svg-sampler';
|
|
5
8
|
import { CrossSection } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
@@ -49,6 +52,7 @@ export const sampleSvg = async (svgDataUrl, maxWidth) => {
|
|
|
49
52
|
};
|
|
50
53
|
/**
|
|
51
54
|
* Traces a bitmap image and returns a centered CrossSection
|
|
55
|
+
* Uses potrace with jimp for image loading (works in web workers).
|
|
52
56
|
*/
|
|
53
57
|
export const traceImage = async (imageDataUrl, options) => {
|
|
54
58
|
const tracer = new Potrace({
|
|
@@ -56,7 +60,7 @@ export const traceImage = async (imageDataUrl, options) => {
|
|
|
56
60
|
});
|
|
57
61
|
// Promisify tracer.loadImage
|
|
58
62
|
await new Promise((resolve, reject) => {
|
|
59
|
-
tracer.loadImage(imageDataUrl, (err) => {
|
|
63
|
+
tracer.loadImage(imageDataUrl, (_potrace, err) => {
|
|
60
64
|
if (err) {
|
|
61
65
|
reject(err);
|
|
62
66
|
}
|
package/package.json
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cadit-app/image-extrude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Image Extrude for CADit - Extrude shapes from SVG or bitmap images",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/
|
|
6
|
+
"main": "dist/bundle.js",
|
|
7
7
|
"types": "dist/src/main.d.ts",
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/bundle.js",
|
|
11
|
+
"types": "./dist/src/main.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./unbundled": {
|
|
14
|
+
"import": "./dist/src/main.js",
|
|
15
|
+
"types": "./dist/src/main.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"cadit": {
|
|
19
|
+
"browserBundle": "./dist/bundle.js"
|
|
15
20
|
},
|
|
16
21
|
"dependencies": {
|
|
17
22
|
"@cadit-app/script-params": "0.4.1",
|
|
18
23
|
"@cadit-app/svg-sampler": "^0.1.0",
|
|
19
24
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
20
|
-
"@types/potrace": "^2.1.5",
|
|
21
25
|
"potrace": "^2.1.8"
|
|
22
26
|
},
|
|
23
27
|
"peerDependencies": {
|
|
@@ -28,9 +32,12 @@
|
|
|
28
32
|
"@gltf-transform/core": "^4.1.1",
|
|
29
33
|
"@jscadui/3mf-export": "^0.5.0",
|
|
30
34
|
"@types/node": "^25.0.3",
|
|
35
|
+
"@types/potrace": "^2.1.5",
|
|
36
|
+
"esbuild": "^0.24.0",
|
|
31
37
|
"fflate": "^0.8.2",
|
|
32
38
|
"tsx": "^4.0.0",
|
|
33
|
-
"typescript": "^5.0.0"
|
|
39
|
+
"typescript": "^5.0.0",
|
|
40
|
+
"vite": "^7.3.1"
|
|
34
41
|
},
|
|
35
42
|
"keywords": [
|
|
36
43
|
"cadit",
|
|
@@ -45,12 +52,20 @@
|
|
|
45
52
|
"license": "MIT",
|
|
46
53
|
"files": [
|
|
47
54
|
"dist/**/*",
|
|
48
|
-
"src/**/*",
|
|
49
55
|
"images/**/*",
|
|
50
56
|
"cadit.json",
|
|
51
57
|
"README.md"
|
|
52
58
|
],
|
|
53
59
|
"publishConfig": {
|
|
54
60
|
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
64
|
+
"build:types": "tsc",
|
|
65
|
+
"build:bundle": "vite build",
|
|
66
|
+
"build": "npm run build:types && npm run build:bundle",
|
|
67
|
+
"generate": "npx tsx cli.ts",
|
|
68
|
+
"build:glb": "npx tsx cli.ts output.glb",
|
|
69
|
+
"build:3mf": "npx tsx cli.ts output.3mf"
|
|
55
70
|
}
|
|
56
|
-
}
|
|
71
|
+
}
|
package/src/crossSectionUtils.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CrossSection utility functions
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { CrossSection } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Centers a CrossSection at the origin based on its bounding box
|
|
9
|
-
*/
|
|
10
|
-
export function centerCrossSection(crossSection: CrossSection): CrossSection {
|
|
11
|
-
const bounds = crossSection.bounds();
|
|
12
|
-
const centerX = (bounds.min[0] + bounds.max[0]) / 2;
|
|
13
|
-
const centerY = (bounds.min[1] + bounds.max[1]) / 2;
|
|
14
|
-
return crossSection.translate([-centerX, -centerY]);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Scales a CrossSection to fit within a maximum size while maintaining aspect ratio
|
|
19
|
-
*/
|
|
20
|
-
export function scaleToMaxSize(crossSection: CrossSection, maxSize: number): CrossSection {
|
|
21
|
-
const bounds = crossSection.bounds();
|
|
22
|
-
const width = bounds.max[0] - bounds.min[0];
|
|
23
|
-
const height = bounds.max[1] - bounds.min[1];
|
|
24
|
-
const maxDim = Math.max(width, height);
|
|
25
|
-
|
|
26
|
-
if (maxDim <= 0) return crossSection;
|
|
27
|
-
|
|
28
|
-
const scale = maxSize / maxDim;
|
|
29
|
-
return crossSection.scale([scale, scale]);
|
|
30
|
-
}
|
package/src/main.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @cadit-app/image-extrude
|
|
3
|
-
*
|
|
4
|
-
* Extrude 3D shapes from SVG or bitmap images.
|
|
5
|
-
* Uses the defineParams API from @cadit-app/script-params.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { defineParams } from '@cadit-app/script-params';
|
|
9
|
-
import type { Manifold, CrossSection } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
10
|
-
import { imageExtrudeParamsSchema, ImageExtrudeParams, ImageFileValue } from './params';
|
|
11
|
-
import { sampleSvg, traceImage } from './tracing';
|
|
12
|
-
import { renderSvgToBitmapDataUrl } from './resvg';
|
|
13
|
-
import { fetchImageAsDataUrl } from './utils';
|
|
14
|
-
import { createEmptyManifold } from './manifoldUtils';
|
|
15
|
-
|
|
16
|
-
// Re-export for external use
|
|
17
|
-
export { sampleSvg, traceImage } from './tracing';
|
|
18
|
-
export { renderSvgToBitmapDataUrl } from './resvg';
|
|
19
|
-
export { makeCrossSection } from './makeCrossSection';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Main entry point using defineParams
|
|
23
|
-
*/
|
|
24
|
-
export default defineParams({
|
|
25
|
-
params: imageExtrudeParamsSchema as any,
|
|
26
|
-
main: async (params): Promise<Manifold> => {
|
|
27
|
-
const typedParams = params as unknown as ImageExtrudeParams;
|
|
28
|
-
let { mode, height } = typedParams;
|
|
29
|
-
let imageFile: ImageFileValue | undefined = typedParams.imageFile;
|
|
30
|
-
|
|
31
|
-
// If imageFile has imageUrl but not dataUrl, fetch and convert to dataUrl
|
|
32
|
-
if (imageFile && !imageFile.dataUrl && imageFile.imageUrl) {
|
|
33
|
-
try {
|
|
34
|
-
imageFile = {
|
|
35
|
-
...imageFile,
|
|
36
|
-
dataUrl: await fetchImageAsDataUrl(imageFile.imageUrl)
|
|
37
|
-
};
|
|
38
|
-
} catch (err) {
|
|
39
|
-
console.warn('Failed to fetch imageUrl:', err);
|
|
40
|
-
return createEmptyManifold();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (!imageFile || !imageFile.dataUrl) {
|
|
45
|
-
console.warn('No valid image file provided.');
|
|
46
|
-
return createEmptyManifold();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Adjust mode if sample is selected for non-SVG
|
|
50
|
-
if (mode === 'sample' && !imageFile.fileType?.includes('svg')) {
|
|
51
|
-
console.warn('Sample mode selected for non-SVG file. Defaulting to Trace mode.');
|
|
52
|
-
mode = 'trace';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let crossSection: CrossSection;
|
|
56
|
-
try {
|
|
57
|
-
if (mode === 'trace') {
|
|
58
|
-
// if svg, render svg to bitmap and then trace
|
|
59
|
-
const isSvg = imageFile.fileType?.includes('svg');
|
|
60
|
-
const dataUrl = isSvg ? await renderSvgToBitmapDataUrl(imageFile.dataUrl) : imageFile.dataUrl;
|
|
61
|
-
|
|
62
|
-
crossSection = await traceImage(dataUrl, {
|
|
63
|
-
maxWidth: typedParams.maxWidth,
|
|
64
|
-
despeckleSize: typedParams.despeckleSize
|
|
65
|
-
});
|
|
66
|
-
} else {
|
|
67
|
-
// mode is 'sample', and fileType is guaranteed to be svg+xml
|
|
68
|
-
crossSection = await sampleSvg(imageFile.dataUrl, typedParams.maxWidth);
|
|
69
|
-
}
|
|
70
|
-
} catch (error) {
|
|
71
|
-
console.error(`Error during image processing (mode: ${mode}):`, error);
|
|
72
|
-
return createEmptyManifold();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return crossSection.extrude(height);
|
|
76
|
-
},
|
|
77
|
-
});
|
package/src/makeCrossSection.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Create a CrossSection from image parameters
|
|
3
|
-
* Exported for external use (e.g., embedding in other makers)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { CrossSection } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
7
|
-
import { ImageExtrudeParams } from './params';
|
|
8
|
-
import { sampleSvg, traceImage } from './tracing';
|
|
9
|
-
import { renderSvgToBitmapDataUrl } from './resvg';
|
|
10
|
-
import { fetchImageAsDataUrl } from './utils';
|
|
11
|
-
|
|
12
|
-
export type MakeCrossSectionOptions = {
|
|
13
|
-
imageFile: ImageExtrudeParams['imageFile'];
|
|
14
|
-
mode: 'trace' | 'sample';
|
|
15
|
-
maxWidth?: number;
|
|
16
|
-
despeckleSize?: number;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Creates a CrossSection from image data
|
|
21
|
-
* This is the main function for embedding in other makers
|
|
22
|
-
*/
|
|
23
|
-
export async function makeCrossSection(options: MakeCrossSectionOptions): Promise<CrossSection> {
|
|
24
|
-
let { imageFile, mode, maxWidth, despeckleSize } = options;
|
|
25
|
-
|
|
26
|
-
// If imageFile has imageUrl but not dataUrl, fetch and convert
|
|
27
|
-
if (imageFile && !imageFile.dataUrl && imageFile.imageUrl) {
|
|
28
|
-
imageFile = {
|
|
29
|
-
...imageFile,
|
|
30
|
-
dataUrl: await fetchImageAsDataUrl(imageFile.imageUrl)
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (!imageFile?.dataUrl) {
|
|
35
|
-
throw new Error('No valid image file provided');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Adjust mode if sample is selected for non-SVG
|
|
39
|
-
if (mode === 'sample' && !imageFile.fileType?.includes('svg')) {
|
|
40
|
-
console.warn('Sample mode selected for non-SVG file. Defaulting to Trace mode.');
|
|
41
|
-
mode = 'trace';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (mode === 'trace') {
|
|
45
|
-
// if svg, render svg to bitmap and then trace
|
|
46
|
-
const isSvg = imageFile.fileType?.includes('svg');
|
|
47
|
-
const dataUrl = isSvg ? await renderSvgToBitmapDataUrl(imageFile.dataUrl) : imageFile.dataUrl;
|
|
48
|
-
|
|
49
|
-
return traceImage(dataUrl, {
|
|
50
|
-
maxWidth,
|
|
51
|
-
despeckleSize
|
|
52
|
-
});
|
|
53
|
-
} else {
|
|
54
|
-
// mode is 'sample', and fileType is guaranteed to be svg+xml
|
|
55
|
-
return sampleSvg(imageFile.dataUrl, maxWidth);
|
|
56
|
-
}
|
|
57
|
-
}
|
package/src/manifoldUtils.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Manifold utility functions
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Manifold, CrossSection } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Creates an empty manifold (a very small cube that will be invisible)
|
|
9
|
-
*/
|
|
10
|
-
export function createEmptyManifold(): Manifold {
|
|
11
|
-
// Create a tiny box that's essentially invisible
|
|
12
|
-
return CrossSection.square([0.001, 0.001], true).extrude(0.001);
|
|
13
|
-
}
|
package/src/params.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parameter schema for the Image Extrude generator.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Value type for image parameters.
|
|
7
|
-
* Matches the ImageFileValue type from @cadit-app/script-params.
|
|
8
|
-
* TODO: Import from script-params once version with ImageFileValue is published.
|
|
9
|
-
*/
|
|
10
|
-
export interface ImageFileValue {
|
|
11
|
-
/** Remote URL to fetch the image from (e.g., HTTP URL or relative path). */
|
|
12
|
-
imageUrl?: string;
|
|
13
|
-
/** Base64-encoded data URL of the image content. */
|
|
14
|
-
dataUrl?: string;
|
|
15
|
-
/** MIME type of the image (e.g., 'image/svg+xml', 'image/png'). */
|
|
16
|
-
fileType?: string;
|
|
17
|
-
/** Original filename of the image. */
|
|
18
|
-
fileName?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Default SVG - a simple star shape
|
|
22
|
-
const defaultSvgDataUrl = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cG9seWdvbiBwb2ludHM9IjUwLDUgNjEsMzkgOTcsMzkgNjgsMjIgNzksOTUgNTAsNzAgMjEsOTUgMzIsNjIgMywzOSAzOSwzOSIgZmlsbD0iYmxhY2siLz48L3N2Zz4=';
|
|
23
|
-
|
|
24
|
-
export const imageExtrudeParamsSchema = {
|
|
25
|
-
mode: {
|
|
26
|
-
type: 'choice' as const,
|
|
27
|
-
label: 'Mode',
|
|
28
|
-
options: [
|
|
29
|
-
{ value: 'trace', label: 'Trace' },
|
|
30
|
-
{ value: 'sample', label: 'Sample (SVG only)' },
|
|
31
|
-
],
|
|
32
|
-
default: 'trace',
|
|
33
|
-
},
|
|
34
|
-
imageFile: {
|
|
35
|
-
type: 'image' as const,
|
|
36
|
-
label: 'Image File',
|
|
37
|
-
default: {
|
|
38
|
-
imageUrl: '',
|
|
39
|
-
dataUrl: defaultSvgDataUrl,
|
|
40
|
-
fileType: 'image/svg+xml',
|
|
41
|
-
fileName: 'star.svg'
|
|
42
|
-
} as ImageFileValue,
|
|
43
|
-
},
|
|
44
|
-
height: {
|
|
45
|
-
type: 'number' as const,
|
|
46
|
-
label: 'Extrusion Height (mm)',
|
|
47
|
-
default: 1,
|
|
48
|
-
min: 0.1,
|
|
49
|
-
},
|
|
50
|
-
maxWidth: {
|
|
51
|
-
type: 'number' as const,
|
|
52
|
-
label: 'Maximum Width (mm)',
|
|
53
|
-
default: 50,
|
|
54
|
-
min: 0.1,
|
|
55
|
-
},
|
|
56
|
-
despeckleSize: {
|
|
57
|
-
type: 'number' as const,
|
|
58
|
-
label: 'Despeckle Size (Tracing only)',
|
|
59
|
-
default: 2,
|
|
60
|
-
min: 0.1,
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export type ImageExtrudeParams = {
|
|
65
|
-
mode: 'trace' | 'sample';
|
|
66
|
-
imageFile: ImageFileValue;
|
|
67
|
-
height: number;
|
|
68
|
-
maxWidth: number;
|
|
69
|
-
despeckleSize: number;
|
|
70
|
-
};
|
package/src/resvg.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SVG to bitmap rendering using resvg-wasm
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as resvg from '@resvg/resvg-wasm';
|
|
6
|
-
import { svgDataUrlToString } from './utils';
|
|
7
|
-
|
|
8
|
-
let wasmInitialized = false;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Initialize resvg WASM module
|
|
12
|
-
* In CLI context, we need to load from file system
|
|
13
|
-
* In browser context (via bundler), the WASM is loaded differently
|
|
14
|
-
*/
|
|
15
|
-
async function initializeResvgWasm() {
|
|
16
|
-
if (wasmInitialized) return;
|
|
17
|
-
|
|
18
|
-
// For CLI usage, load WASM from node_modules
|
|
19
|
-
const { readFile } = await import('fs/promises');
|
|
20
|
-
const { resolve, dirname } = await import('path');
|
|
21
|
-
const { fileURLToPath } = await import('url');
|
|
22
|
-
|
|
23
|
-
// Find the wasm file in node_modules
|
|
24
|
-
const wasmPath = resolve(
|
|
25
|
-
dirname(fileURLToPath(import.meta.url)),
|
|
26
|
-
'../node_modules/@resvg/resvg-wasm/index_bg.wasm'
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const wasmBuffer = await readFile(wasmPath);
|
|
30
|
-
await resvg.initWasm(wasmBuffer);
|
|
31
|
-
wasmInitialized = true;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Renders an SVG Data URL to a PNG Data URL using resvg-wasm.
|
|
36
|
-
*/
|
|
37
|
-
export const renderSvgToBitmapDataUrl = async (
|
|
38
|
-
svgDataUrl: string,
|
|
39
|
-
options?: { maxWidth?: number }
|
|
40
|
-
): Promise<string> => {
|
|
41
|
-
await initializeResvgWasm();
|
|
42
|
-
|
|
43
|
-
const svgString = svgDataUrlToString(svgDataUrl);
|
|
44
|
-
if (!svgString) {
|
|
45
|
-
throw new Error("Could not decode SVG Data URL");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const opts: Record<string, unknown> = {};
|
|
49
|
-
if (options?.maxWidth && options.maxWidth > 0 && isFinite(options.maxWidth)) {
|
|
50
|
-
opts.fitTo = {
|
|
51
|
-
mode: 'width',
|
|
52
|
-
value: options.maxWidth
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const resvgInstance = new resvg.Resvg(svgString, opts);
|
|
57
|
-
const pngData = resvgInstance.render();
|
|
58
|
-
const pngBuffer = pngData.asPng();
|
|
59
|
-
|
|
60
|
-
// Convert to data URL
|
|
61
|
-
const base64 = Buffer.from(pngBuffer).toString('base64');
|
|
62
|
-
return `data:image/png;base64,${base64}`;
|
|
63
|
-
};
|
package/src/threeMfExport.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 3MF Export for image-extrude
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { Manifold } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
6
|
-
// @ts-ignore - No type declarations available
|
|
7
|
-
import { to3dmodel, fileForContentTypes, FileForRelThumbnail } from '@jscadui/3mf-export';
|
|
8
|
-
import { strToU8, zipSync, Zippable } from 'fflate';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Creates a 3MF ArrayBuffer from manifolds
|
|
12
|
-
*/
|
|
13
|
-
export async function create3mfArrayBuffer(manifolds: Manifold[]): Promise<ArrayBuffer> {
|
|
14
|
-
const meshes = manifolds.map((m, index) => {
|
|
15
|
-
const mesh = m.getMesh();
|
|
16
|
-
return {
|
|
17
|
-
id: String(index + 1),
|
|
18
|
-
vertices: mesh.vertProperties,
|
|
19
|
-
indices: mesh.triVerts,
|
|
20
|
-
name: `Part-${index + 1}`,
|
|
21
|
-
};
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
// Generate a single component with all meshes as children
|
|
25
|
-
const components = [
|
|
26
|
-
{
|
|
27
|
-
id: meshes.length + 1,
|
|
28
|
-
children: meshes.map((mesh) => ({ objectID: mesh.id })),
|
|
29
|
-
name: 'ImageExtrude-Assembly',
|
|
30
|
-
},
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
// The main item should reference the component
|
|
34
|
-
const items = components.map((component) => ({
|
|
35
|
-
objectID: component.id,
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
const header = {
|
|
39
|
-
unit: 'millimeter',
|
|
40
|
-
title: 'CADit Image Extrude',
|
|
41
|
-
description: 'Image Extrude 3MF export',
|
|
42
|
-
application: 'CADit',
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const to3mf = {
|
|
46
|
-
meshes,
|
|
47
|
-
components,
|
|
48
|
-
items,
|
|
49
|
-
precision: 7,
|
|
50
|
-
header,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// Generate the 3D model XML
|
|
54
|
-
const model = to3dmodel(to3mf as any);
|
|
55
|
-
|
|
56
|
-
// Package the 3MF file using fflate
|
|
57
|
-
const fileForRelThumbnail = new FileForRelThumbnail();
|
|
58
|
-
fileForRelThumbnail.add3dModel('3D/3dmodel.model');
|
|
59
|
-
|
|
60
|
-
const files: Zippable = {};
|
|
61
|
-
files['3D/3dmodel.model'] = strToU8(model);
|
|
62
|
-
files[fileForContentTypes.name] = strToU8(fileForContentTypes.content);
|
|
63
|
-
files[fileForRelThumbnail.name] = strToU8(fileForRelThumbnail.content);
|
|
64
|
-
|
|
65
|
-
const zipData = zipSync(files);
|
|
66
|
-
return zipData.buffer as ArrayBuffer;
|
|
67
|
-
}
|