@cadit-app/image-extrude 0.1.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/LICENSE +21 -0
- package/README.md +99 -0
- package/cadit.json +20 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +160 -0
- package/dist/src/crossSectionUtils.d.ts +13 -0
- package/dist/src/crossSectionUtils.d.ts.map +1 -0
- package/dist/src/crossSectionUtils.js +25 -0
- package/dist/src/main.d.ts +16 -0
- package/dist/src/main.d.ts.map +1 -0
- package/dist/src/main.js +70 -0
- package/dist/src/makeCrossSection.d.ts +18 -0
- package/dist/src/makeCrossSection.d.ts.map +1 -0
- package/dist/src/makeCrossSection.js +42 -0
- package/dist/src/manifoldUtils.d.ts +9 -0
- package/dist/src/manifoldUtils.d.ts.map +1 -0
- package/dist/src/manifoldUtils.js +11 -0
- package/dist/src/params.d.ts +60 -0
- package/dist/src/params.d.ts.map +1 -0
- package/dist/src/params.js +44 -0
- package/dist/src/resvg.d.ts +10 -0
- package/dist/src/resvg.d.ts.map +1 -0
- package/dist/src/resvg.js +47 -0
- package/dist/src/threeMfExport.d.ts +9 -0
- package/dist/src/threeMfExport.d.ts.map +1 -0
- package/dist/src/threeMfExport.js +56 -0
- package/dist/src/tracing.d.ts +24 -0
- package/dist/src/tracing.d.ts.map +1 -0
- package/dist/src/tracing.js +71 -0
- package/dist/src/utils.d.ts +13 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +53 -0
- package/images/cookiecad-logo-dark.svg +73 -0
- package/images/preview.png +0 -0
- package/package.json +56 -0
- package/src/crossSectionUtils.ts +30 -0
- package/src/main.ts +77 -0
- package/src/makeCrossSection.ts +57 -0
- package/src/manifoldUtils.ts +13 -0
- package/src/params.ts +70 -0
- package/src/resvg.ts +63 -0
- package/src/threeMfExport.ts +67 -0
- package/src/tracing.ts +94 -0
- package/src/types.d.ts +40 -0
- package/src/utils.ts +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 CADit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# @cadit-app/image-extrude
|
|
2
|
+
|
|
3
|
+
Extrude 3D shapes from SVG or bitmap images. Supports both tracing (for raster images) and sampling (for SVGs).
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Trace mode**: Convert any bitmap image (PNG, JPG) to 3D by tracing edges (browser only)
|
|
10
|
+
- **Sample mode**: High-fidelity conversion of SVG files to 3D
|
|
11
|
+
- **Configurable size**: Set maximum width to control output dimensions
|
|
12
|
+
- **Despeckle**: Remove small artifacts during tracing
|
|
13
|
+
- **CLI support**: Generate GLB and 3MF files from command line (SVG sample mode)
|
|
14
|
+
|
|
15
|
+
> **Note:** Trace mode for bitmap images currently only works in the browser (CADit).
|
|
16
|
+
> The CLI only supports SVG files with sample mode.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @cadit-app/image-extrude
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### As a CADit Script
|
|
27
|
+
|
|
28
|
+
Import this script in [CADit](https://cadit.app) by adding the GitHub repository URL.
|
|
29
|
+
|
|
30
|
+
### CLI Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Generate GLB from default star shape
|
|
34
|
+
npx tsx cli.ts output.glb
|
|
35
|
+
|
|
36
|
+
# Generate from a specific image
|
|
37
|
+
npx tsx cli.ts output.glb --image=logo.svg --height=2 --maxWidth=50
|
|
38
|
+
|
|
39
|
+
# Generate 3MF
|
|
40
|
+
npx tsx cli.ts output.3mf --image=photo.png --mode=trace
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### CLI Options
|
|
44
|
+
|
|
45
|
+
| Option | Description | Default |
|
|
46
|
+
|--------|-------------|---------|
|
|
47
|
+
| `--image=<path>` | Path to image file (SVG, PNG, JPG) | Built-in star |
|
|
48
|
+
| `--height=<mm>` | Extrusion height in mm | 1 |
|
|
49
|
+
| `--maxWidth=<mm>` | Maximum width in mm | 50 |
|
|
50
|
+
| `--mode=<trace\|sample>` | Processing mode | trace |
|
|
51
|
+
| `--despeckle=<size>` | Despeckle size for tracing | 2 |
|
|
52
|
+
|
|
53
|
+
### Programmatic Usage
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import imageExtrude from '@cadit-app/image-extrude';
|
|
57
|
+
|
|
58
|
+
// Use the defineParams API
|
|
59
|
+
const result = await imageExtrude.main({
|
|
60
|
+
mode: 'trace',
|
|
61
|
+
imageFile: {
|
|
62
|
+
dataUrl: 'data:image/png;base64,...',
|
|
63
|
+
fileType: 'image/png',
|
|
64
|
+
fileName: 'logo.png'
|
|
65
|
+
},
|
|
66
|
+
height: 2,
|
|
67
|
+
maxWidth: 50,
|
|
68
|
+
despeckleSize: 2
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Creating CrossSections for Embedding
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { makeCrossSection } from '@cadit-app/image-extrude';
|
|
76
|
+
|
|
77
|
+
const crossSection = await makeCrossSection({
|
|
78
|
+
imageFile: { dataUrl: '...', fileType: 'image/svg+xml' },
|
|
79
|
+
mode: 'sample',
|
|
80
|
+
maxWidth: 30
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Use in your own maker
|
|
84
|
+
const manifold = crossSection.extrude(5);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Parameters
|
|
88
|
+
|
|
89
|
+
| Parameter | Type | Description |
|
|
90
|
+
|-----------|------|-------------|
|
|
91
|
+
| `mode` | `'trace' \| 'sample'` | Trace for bitmaps, Sample for SVGs |
|
|
92
|
+
| `imageFile` | `object` | Image data with dataUrl, fileType, fileName |
|
|
93
|
+
| `height` | `number` | Extrusion height in mm |
|
|
94
|
+
| `maxWidth` | `number` | Maximum width in mm |
|
|
95
|
+
| `despeckleSize` | `number` | Remove spots smaller than this (trace only) |
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
package/cadit.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Image Extrude",
|
|
3
|
+
"description": "Extrude 3D shapes from SVG or bitmap images. Supports both tracing (for raster images) and sampling (for SVGs). Perfect for creating custom 3D prints from logos, icons, or artwork.",
|
|
4
|
+
"entryPoint": "src/main.ts",
|
|
5
|
+
"tags": ["image", "svg", "trace", "extrude", "3d-printing", "logo"],
|
|
6
|
+
"category": "Tools",
|
|
7
|
+
"license": {
|
|
8
|
+
"type": "MIT"
|
|
9
|
+
},
|
|
10
|
+
"author": {
|
|
11
|
+
"name": "CADit"
|
|
12
|
+
},
|
|
13
|
+
"images": [
|
|
14
|
+
{
|
|
15
|
+
"path": "images/preview.png",
|
|
16
|
+
"alt": "Image Extrude Preview",
|
|
17
|
+
"isPrimary": true
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* CLI for @cadit-app/image-extrude
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx tsx cli.ts [output.glb|output.3mf] [options]
|
|
7
|
+
*
|
|
8
|
+
* Examples:
|
|
9
|
+
* npx tsx cli.ts output.glb
|
|
10
|
+
* npx tsx cli.ts output.3mf --image=logo.svg --height=2
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* CLI for @cadit-app/image-extrude
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx tsx cli.ts [output.glb|output.3mf] [options]
|
|
7
|
+
*
|
|
8
|
+
* Examples:
|
|
9
|
+
* npx tsx cli.ts output.glb
|
|
10
|
+
* npx tsx cli.ts output.3mf --image=logo.svg --height=2
|
|
11
|
+
*/
|
|
12
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
13
|
+
import { resolve, extname, basename } from 'path';
|
|
14
|
+
import { parseArgs } from 'util';
|
|
15
|
+
const SUPPORTED_FORMATS = ['.glb', '.3mf'];
|
|
16
|
+
// Parse command line arguments
|
|
17
|
+
const { values, positionals } = parseArgs({
|
|
18
|
+
allowPositionals: true,
|
|
19
|
+
options: {
|
|
20
|
+
image: { type: 'string', short: 'i' },
|
|
21
|
+
height: { type: 'string', short: 'h' },
|
|
22
|
+
maxWidth: { type: 'string', short: 'w' },
|
|
23
|
+
mode: { type: 'string', short: 'm' },
|
|
24
|
+
despeckle: { type: 'string', short: 'd' },
|
|
25
|
+
help: { type: 'boolean' },
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
if (values.help || positionals.length === 0) {
|
|
29
|
+
console.log(`
|
|
30
|
+
@cadit-app/image-extrude CLI
|
|
31
|
+
|
|
32
|
+
Usage: npx tsx cli.ts <output.[glb|3mf]> [options]
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
-i, --image <path> Path to image file (SVG, PNG, JPG)
|
|
36
|
+
-h, --height <mm> Extrusion height in mm (default: 1)
|
|
37
|
+
-w, --maxWidth <mm> Maximum width in mm (default: 50)
|
|
38
|
+
-m, --mode <trace|sample> Processing mode (default: trace)
|
|
39
|
+
-d, --despeckle <size> Despeckle size for tracing (default: 2)
|
|
40
|
+
--help Show this help
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
npx tsx cli.ts output.glb
|
|
44
|
+
npx tsx cli.ts output.glb --image logo.svg --height 2
|
|
45
|
+
npx tsx cli.ts output.3mf --image photo.png --mode trace
|
|
46
|
+
`);
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
const outputFile = positionals[0];
|
|
50
|
+
const ext = extname(outputFile).toLowerCase();
|
|
51
|
+
if (!SUPPORTED_FORMATS.includes(ext)) {
|
|
52
|
+
console.error(`Error: Output file must have one of these extensions: ${SUPPORTED_FORMATS.join(', ')}`);
|
|
53
|
+
console.error(`Got: ${ext}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
async function main() {
|
|
57
|
+
console.log('Initializing manifold...');
|
|
58
|
+
// Initialize manifold-3d
|
|
59
|
+
const manifold = await import('@cadit-app/manifold-3d');
|
|
60
|
+
await manifold.default();
|
|
61
|
+
// Now import the maker
|
|
62
|
+
const { default: imageExtrudeMaker } = await import('./src/main');
|
|
63
|
+
const { imageExtrudeParamsSchema } = await import('./src/params');
|
|
64
|
+
console.log('Generating image extrusion...');
|
|
65
|
+
// Build params from defaults and CLI options
|
|
66
|
+
const defaultParams = {};
|
|
67
|
+
for (const [key, param] of Object.entries(imageExtrudeParamsSchema)) {
|
|
68
|
+
defaultParams[key] = param.default;
|
|
69
|
+
}
|
|
70
|
+
// Load default image from images folder if no image specified
|
|
71
|
+
const defaultImagePath = resolve(import.meta.dirname, 'images', 'cookiecad-logo-dark.svg');
|
|
72
|
+
const imagePath = values.image && existsSync(values.image) ? values.image : defaultImagePath;
|
|
73
|
+
// Override with CLI options
|
|
74
|
+
if (values.height) {
|
|
75
|
+
defaultParams.height = parseFloat(values.height);
|
|
76
|
+
}
|
|
77
|
+
if (values.maxWidth) {
|
|
78
|
+
defaultParams.maxWidth = parseFloat(values.maxWidth);
|
|
79
|
+
}
|
|
80
|
+
// Default to 'sample' mode for CLI (trace requires additional WASM setup for non-SVG)
|
|
81
|
+
if (values.mode && (values.mode === 'trace' || values.mode === 'sample')) {
|
|
82
|
+
defaultParams.mode = values.mode;
|
|
83
|
+
}
|
|
84
|
+
else if (!values.mode && imagePath.endsWith('.svg')) {
|
|
85
|
+
defaultParams.mode = 'sample'; // Default to sample for SVGs in CLI
|
|
86
|
+
}
|
|
87
|
+
if (values.despeckle) {
|
|
88
|
+
defaultParams.despeckleSize = parseFloat(values.despeckle);
|
|
89
|
+
}
|
|
90
|
+
if (existsSync(imagePath)) {
|
|
91
|
+
const imageData = readFileSync(imagePath);
|
|
92
|
+
const base64 = imageData.toString('base64');
|
|
93
|
+
const imageExt = extname(imagePath).toLowerCase();
|
|
94
|
+
const mimeType = imageExt === '.svg' ? 'image/svg+xml' :
|
|
95
|
+
imageExt === '.png' ? 'image/png' :
|
|
96
|
+
imageExt === '.jpg' || imageExt === '.jpeg' ? 'image/jpeg' :
|
|
97
|
+
'application/octet-stream';
|
|
98
|
+
defaultParams.imageFile = {
|
|
99
|
+
dataUrl: `data:${mimeType};base64,${base64}`,
|
|
100
|
+
fileType: mimeType,
|
|
101
|
+
fileName: basename(imagePath)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
console.log('Parameters:', {
|
|
105
|
+
mode: defaultParams.mode,
|
|
106
|
+
height: defaultParams.height,
|
|
107
|
+
maxWidth: defaultParams.maxWidth,
|
|
108
|
+
despeckleSize: defaultParams.despeckleSize,
|
|
109
|
+
hasImage: !!defaultParams.imageFile?.dataUrl
|
|
110
|
+
});
|
|
111
|
+
// Generate the model
|
|
112
|
+
const result = await imageExtrudeMaker(defaultParams);
|
|
113
|
+
if (ext === '.glb') {
|
|
114
|
+
// Export as GLB
|
|
115
|
+
const mesh = result.getMesh();
|
|
116
|
+
const { Document, NodeIO } = await import('@gltf-transform/core');
|
|
117
|
+
const document = new Document();
|
|
118
|
+
const buffer = document.createBuffer();
|
|
119
|
+
const scene = document.createScene();
|
|
120
|
+
const node = document.createNode();
|
|
121
|
+
scene.addChild(node);
|
|
122
|
+
// Create mesh
|
|
123
|
+
const primitive = document.createPrimitive();
|
|
124
|
+
// Position accessor
|
|
125
|
+
const positions = new Float32Array(mesh.vertProperties);
|
|
126
|
+
const positionAccessor = document.createAccessor()
|
|
127
|
+
.setType('VEC3')
|
|
128
|
+
.setBuffer(buffer)
|
|
129
|
+
.setArray(positions);
|
|
130
|
+
primitive.setAttribute('POSITION', positionAccessor);
|
|
131
|
+
// Index accessor
|
|
132
|
+
const indices = new Uint32Array(mesh.triVerts);
|
|
133
|
+
const indexAccessor = document.createAccessor()
|
|
134
|
+
.setType('SCALAR')
|
|
135
|
+
.setBuffer(buffer)
|
|
136
|
+
.setArray(indices);
|
|
137
|
+
primitive.setIndices(indexAccessor);
|
|
138
|
+
// Material
|
|
139
|
+
const material = document.createMaterial()
|
|
140
|
+
.setBaseColorFactor([0.2, 0.8, 0.6, 1.0]);
|
|
141
|
+
primitive.setMaterial(material);
|
|
142
|
+
const gltfMesh = document.createMesh().addPrimitive(primitive);
|
|
143
|
+
node.setMesh(gltfMesh);
|
|
144
|
+
const io = new NodeIO();
|
|
145
|
+
const glb = await io.writeBinary(document);
|
|
146
|
+
writeFileSync(resolve(outputFile), glb);
|
|
147
|
+
console.log(`Wrote ${outputFile}`);
|
|
148
|
+
}
|
|
149
|
+
else if (ext === '.3mf') {
|
|
150
|
+
// Export as 3MF
|
|
151
|
+
const { create3mfArrayBuffer } = await import('./src/threeMfExport');
|
|
152
|
+
const buffer = await create3mfArrayBuffer([result]);
|
|
153
|
+
writeFileSync(resolve(outputFile), Buffer.from(buffer));
|
|
154
|
+
console.log(`Wrote ${outputFile}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
main().catch(err => {
|
|
158
|
+
console.error('Error:', err);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CrossSection utility functions
|
|
3
|
+
*/
|
|
4
|
+
import { CrossSection } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
5
|
+
/**
|
|
6
|
+
* Centers a CrossSection at the origin based on its bounding box
|
|
7
|
+
*/
|
|
8
|
+
export declare function centerCrossSection(crossSection: CrossSection): CrossSection;
|
|
9
|
+
/**
|
|
10
|
+
* Scales a CrossSection to fit within a maximum size while maintaining aspect ratio
|
|
11
|
+
*/
|
|
12
|
+
export declare function scaleToMaxSize(crossSection: CrossSection, maxSize: number): CrossSection;
|
|
13
|
+
//# sourceMappingURL=crossSectionUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crossSectionUtils.d.ts","sourceRoot":"","sources":["../../src/crossSectionUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAElE;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,YAAY,GAAG,YAAY,CAK3E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAUxF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CrossSection utility functions
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Centers a CrossSection at the origin based on its bounding box
|
|
6
|
+
*/
|
|
7
|
+
export function centerCrossSection(crossSection) {
|
|
8
|
+
const bounds = crossSection.bounds();
|
|
9
|
+
const centerX = (bounds.min[0] + bounds.max[0]) / 2;
|
|
10
|
+
const centerY = (bounds.min[1] + bounds.max[1]) / 2;
|
|
11
|
+
return crossSection.translate([-centerX, -centerY]);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Scales a CrossSection to fit within a maximum size while maintaining aspect ratio
|
|
15
|
+
*/
|
|
16
|
+
export function scaleToMaxSize(crossSection, maxSize) {
|
|
17
|
+
const bounds = crossSection.bounds();
|
|
18
|
+
const width = bounds.max[0] - bounds.min[0];
|
|
19
|
+
const height = bounds.max[1] - bounds.min[1];
|
|
20
|
+
const maxDim = Math.max(width, height);
|
|
21
|
+
if (maxDim <= 0)
|
|
22
|
+
return crossSection;
|
|
23
|
+
const scale = maxSize / maxDim;
|
|
24
|
+
return crossSection.scale([scale, scale]);
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
import type { Manifold } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
8
|
+
export { sampleSvg, traceImage } from './tracing';
|
|
9
|
+
export { renderSvgToBitmapDataUrl } from './resvg';
|
|
10
|
+
export { makeCrossSection } from './makeCrossSection';
|
|
11
|
+
/**
|
|
12
|
+
* Main entry point using defineParams
|
|
13
|
+
*/
|
|
14
|
+
declare const _default: import("@cadit-app/script-params").ScriptModule<any, Promise<Manifold>>;
|
|
15
|
+
export default _default;
|
|
16
|
+
//# sourceMappingURL=main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAgB,MAAM,oCAAoC,CAAC;AAQjF,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;GAEG;;AACH,wBAqDG"}
|
package/dist/src/main.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
import { defineParams } from '@cadit-app/script-params';
|
|
8
|
+
import { imageExtrudeParamsSchema } from './params';
|
|
9
|
+
import { sampleSvg, traceImage } from './tracing';
|
|
10
|
+
import { renderSvgToBitmapDataUrl } from './resvg';
|
|
11
|
+
import { fetchImageAsDataUrl } from './utils';
|
|
12
|
+
import { createEmptyManifold } from './manifoldUtils';
|
|
13
|
+
// Re-export for external use
|
|
14
|
+
export { sampleSvg, traceImage } from './tracing';
|
|
15
|
+
export { renderSvgToBitmapDataUrl } from './resvg';
|
|
16
|
+
export { makeCrossSection } from './makeCrossSection';
|
|
17
|
+
/**
|
|
18
|
+
* Main entry point using defineParams
|
|
19
|
+
*/
|
|
20
|
+
export default defineParams({
|
|
21
|
+
params: imageExtrudeParamsSchema,
|
|
22
|
+
main: async (params) => {
|
|
23
|
+
const typedParams = params;
|
|
24
|
+
let { mode, height } = typedParams;
|
|
25
|
+
let imageFile = typedParams.imageFile;
|
|
26
|
+
// If imageFile has imageUrl but not dataUrl, fetch and convert to dataUrl
|
|
27
|
+
if (imageFile && !imageFile.dataUrl && imageFile.imageUrl) {
|
|
28
|
+
try {
|
|
29
|
+
imageFile = {
|
|
30
|
+
...imageFile,
|
|
31
|
+
dataUrl: await fetchImageAsDataUrl(imageFile.imageUrl)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
console.warn('Failed to fetch imageUrl:', err);
|
|
36
|
+
return createEmptyManifold();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!imageFile || !imageFile.dataUrl) {
|
|
40
|
+
console.warn('No valid image file provided.');
|
|
41
|
+
return createEmptyManifold();
|
|
42
|
+
}
|
|
43
|
+
// Adjust mode if sample is selected for non-SVG
|
|
44
|
+
if (mode === 'sample' && !imageFile.fileType?.includes('svg')) {
|
|
45
|
+
console.warn('Sample mode selected for non-SVG file. Defaulting to Trace mode.');
|
|
46
|
+
mode = 'trace';
|
|
47
|
+
}
|
|
48
|
+
let crossSection;
|
|
49
|
+
try {
|
|
50
|
+
if (mode === 'trace') {
|
|
51
|
+
// if svg, render svg to bitmap and then trace
|
|
52
|
+
const isSvg = imageFile.fileType?.includes('svg');
|
|
53
|
+
const dataUrl = isSvg ? await renderSvgToBitmapDataUrl(imageFile.dataUrl) : imageFile.dataUrl;
|
|
54
|
+
crossSection = await traceImage(dataUrl, {
|
|
55
|
+
maxWidth: typedParams.maxWidth,
|
|
56
|
+
despeckleSize: typedParams.despeckleSize
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// mode is 'sample', and fileType is guaranteed to be svg+xml
|
|
61
|
+
crossSection = await sampleSvg(imageFile.dataUrl, typedParams.maxWidth);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(`Error during image processing (mode: ${mode}):`, error);
|
|
66
|
+
return createEmptyManifold();
|
|
67
|
+
}
|
|
68
|
+
return crossSection.extrude(height);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a CrossSection from image parameters
|
|
3
|
+
* Exported for external use (e.g., embedding in other makers)
|
|
4
|
+
*/
|
|
5
|
+
import type { CrossSection } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
6
|
+
import { ImageExtrudeParams } from './params';
|
|
7
|
+
export type MakeCrossSectionOptions = {
|
|
8
|
+
imageFile: ImageExtrudeParams['imageFile'];
|
|
9
|
+
mode: 'trace' | 'sample';
|
|
10
|
+
maxWidth?: number;
|
|
11
|
+
despeckleSize?: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Creates a CrossSection from image data
|
|
15
|
+
* This is the main function for embedding in other makers
|
|
16
|
+
*/
|
|
17
|
+
export declare function makeCrossSection(options: MakeCrossSectionOptions): Promise<CrossSection>;
|
|
18
|
+
//# sourceMappingURL=makeCrossSection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"makeCrossSection.d.ts","sourceRoot":"","sources":["../../src/makeCrossSection.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAK9C,MAAM,MAAM,uBAAuB,GAAG;IACpC,SAAS,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAC3C,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkC9F"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a CrossSection from image parameters
|
|
3
|
+
* Exported for external use (e.g., embedding in other makers)
|
|
4
|
+
*/
|
|
5
|
+
import { sampleSvg, traceImage } from './tracing';
|
|
6
|
+
import { renderSvgToBitmapDataUrl } from './resvg';
|
|
7
|
+
import { fetchImageAsDataUrl } from './utils';
|
|
8
|
+
/**
|
|
9
|
+
* Creates a CrossSection from image data
|
|
10
|
+
* This is the main function for embedding in other makers
|
|
11
|
+
*/
|
|
12
|
+
export async function makeCrossSection(options) {
|
|
13
|
+
let { imageFile, mode, maxWidth, despeckleSize } = options;
|
|
14
|
+
// If imageFile has imageUrl but not dataUrl, fetch and convert
|
|
15
|
+
if (imageFile && !imageFile.dataUrl && imageFile.imageUrl) {
|
|
16
|
+
imageFile = {
|
|
17
|
+
...imageFile,
|
|
18
|
+
dataUrl: await fetchImageAsDataUrl(imageFile.imageUrl)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (!imageFile?.dataUrl) {
|
|
22
|
+
throw new Error('No valid image file provided');
|
|
23
|
+
}
|
|
24
|
+
// Adjust mode if sample is selected for non-SVG
|
|
25
|
+
if (mode === 'sample' && !imageFile.fileType?.includes('svg')) {
|
|
26
|
+
console.warn('Sample mode selected for non-SVG file. Defaulting to Trace mode.');
|
|
27
|
+
mode = 'trace';
|
|
28
|
+
}
|
|
29
|
+
if (mode === 'trace') {
|
|
30
|
+
// if svg, render svg to bitmap and then trace
|
|
31
|
+
const isSvg = imageFile.fileType?.includes('svg');
|
|
32
|
+
const dataUrl = isSvg ? await renderSvgToBitmapDataUrl(imageFile.dataUrl) : imageFile.dataUrl;
|
|
33
|
+
return traceImage(dataUrl, {
|
|
34
|
+
maxWidth,
|
|
35
|
+
despeckleSize
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// mode is 'sample', and fileType is guaranteed to be svg+xml
|
|
40
|
+
return sampleSvg(imageFile.dataUrl, maxWidth);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifold utility functions
|
|
3
|
+
*/
|
|
4
|
+
import { Manifold } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
5
|
+
/**
|
|
6
|
+
* Creates an empty manifold (a very small cube that will be invisible)
|
|
7
|
+
*/
|
|
8
|
+
export declare function createEmptyManifold(): Manifold;
|
|
9
|
+
//# sourceMappingURL=manifoldUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifoldUtils.d.ts","sourceRoot":"","sources":["../../src/manifoldUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAgB,MAAM,oCAAoC,CAAC;AAE5E;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,QAAQ,CAG9C"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifold utility functions
|
|
3
|
+
*/
|
|
4
|
+
import { CrossSection } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
5
|
+
/**
|
|
6
|
+
* Creates an empty manifold (a very small cube that will be invisible)
|
|
7
|
+
*/
|
|
8
|
+
export function createEmptyManifold() {
|
|
9
|
+
// Create a tiny box that's essentially invisible
|
|
10
|
+
return CrossSection.square([0.001, 0.001], true).extrude(0.001);
|
|
11
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parameter schema for the Image Extrude generator.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Value type for image parameters.
|
|
6
|
+
* Matches the ImageFileValue type from @cadit-app/script-params.
|
|
7
|
+
* TODO: Import from script-params once version with ImageFileValue is published.
|
|
8
|
+
*/
|
|
9
|
+
export interface ImageFileValue {
|
|
10
|
+
/** Remote URL to fetch the image from (e.g., HTTP URL or relative path). */
|
|
11
|
+
imageUrl?: string;
|
|
12
|
+
/** Base64-encoded data URL of the image content. */
|
|
13
|
+
dataUrl?: string;
|
|
14
|
+
/** MIME type of the image (e.g., 'image/svg+xml', 'image/png'). */
|
|
15
|
+
fileType?: string;
|
|
16
|
+
/** Original filename of the image. */
|
|
17
|
+
fileName?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const imageExtrudeParamsSchema: {
|
|
20
|
+
mode: {
|
|
21
|
+
type: "choice";
|
|
22
|
+
label: string;
|
|
23
|
+
options: {
|
|
24
|
+
value: string;
|
|
25
|
+
label: string;
|
|
26
|
+
}[];
|
|
27
|
+
default: string;
|
|
28
|
+
};
|
|
29
|
+
imageFile: {
|
|
30
|
+
type: "image";
|
|
31
|
+
label: string;
|
|
32
|
+
default: ImageFileValue;
|
|
33
|
+
};
|
|
34
|
+
height: {
|
|
35
|
+
type: "number";
|
|
36
|
+
label: string;
|
|
37
|
+
default: number;
|
|
38
|
+
min: number;
|
|
39
|
+
};
|
|
40
|
+
maxWidth: {
|
|
41
|
+
type: "number";
|
|
42
|
+
label: string;
|
|
43
|
+
default: number;
|
|
44
|
+
min: number;
|
|
45
|
+
};
|
|
46
|
+
despeckleSize: {
|
|
47
|
+
type: "number";
|
|
48
|
+
label: string;
|
|
49
|
+
default: number;
|
|
50
|
+
min: number;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export type ImageExtrudeParams = {
|
|
54
|
+
mode: 'trace' | 'sample';
|
|
55
|
+
imageFile: ImageFileValue;
|
|
56
|
+
height: number;
|
|
57
|
+
maxWidth: number;
|
|
58
|
+
despeckleSize: number;
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=params.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../../src/params.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;iBAkB5B,cAAc;;;;;;;;;;;;;;;;;;;;CAoBtB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;IACzB,SAAS,EAAE,cAAc,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parameter schema for the Image Extrude generator.
|
|
3
|
+
*/
|
|
4
|
+
// Default SVG - a simple star shape
|
|
5
|
+
const defaultSvgDataUrl = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48cG9seWdvbiBwb2ludHM9IjUwLDUgNjEsMzkgOTcsMzkgNjgsMjIgNzksOTUgNTAsNzAgMjEsOTUgMzIsNjIgMywzOSAzOSwzOSIgZmlsbD0iYmxhY2siLz48L3N2Zz4=';
|
|
6
|
+
export const imageExtrudeParamsSchema = {
|
|
7
|
+
mode: {
|
|
8
|
+
type: 'choice',
|
|
9
|
+
label: 'Mode',
|
|
10
|
+
options: [
|
|
11
|
+
{ value: 'trace', label: 'Trace' },
|
|
12
|
+
{ value: 'sample', label: 'Sample (SVG only)' },
|
|
13
|
+
],
|
|
14
|
+
default: 'trace',
|
|
15
|
+
},
|
|
16
|
+
imageFile: {
|
|
17
|
+
type: 'image',
|
|
18
|
+
label: 'Image File',
|
|
19
|
+
default: {
|
|
20
|
+
imageUrl: '',
|
|
21
|
+
dataUrl: defaultSvgDataUrl,
|
|
22
|
+
fileType: 'image/svg+xml',
|
|
23
|
+
fileName: 'star.svg'
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
height: {
|
|
27
|
+
type: 'number',
|
|
28
|
+
label: 'Extrusion Height (mm)',
|
|
29
|
+
default: 1,
|
|
30
|
+
min: 0.1,
|
|
31
|
+
},
|
|
32
|
+
maxWidth: {
|
|
33
|
+
type: 'number',
|
|
34
|
+
label: 'Maximum Width (mm)',
|
|
35
|
+
default: 50,
|
|
36
|
+
min: 0.1,
|
|
37
|
+
},
|
|
38
|
+
despeckleSize: {
|
|
39
|
+
type: 'number',
|
|
40
|
+
label: 'Despeckle Size (Tracing only)',
|
|
41
|
+
default: 2,
|
|
42
|
+
min: 0.1,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG to bitmap rendering using resvg-wasm
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Renders an SVG Data URL to a PNG Data URL using resvg-wasm.
|
|
6
|
+
*/
|
|
7
|
+
export declare const renderSvgToBitmapDataUrl: (svgDataUrl: string, options?: {
|
|
8
|
+
maxWidth?: number;
|
|
9
|
+
}) => Promise<string>;
|
|
10
|
+
//# sourceMappingURL=resvg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resvg.d.ts","sourceRoot":"","sources":["../../src/resvg.ts"],"names":[],"mappings":"AAAA;;GAEG;AA+BH;;GAEG;AACH,eAAO,MAAM,wBAAwB,GACnC,YAAY,MAAM,EAClB,UAAU;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,KAC9B,OAAO,CAAC,MAAM,CAuBhB,CAAC"}
|