@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/src/tracing.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image tracing and SVG sampling utilities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { svgToPolygons } from '@cadit-app/svg-sampler';
|
|
6
|
-
import { CrossSection } from '@cadit-app/manifold-3d/manifoldCAD';
|
|
7
|
-
import { Potrace } from 'potrace';
|
|
8
|
-
import { svgDataUrlToString } from './utils';
|
|
9
|
-
import { centerCrossSection } from './crossSectionUtils';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Converts SVG content to polygons
|
|
13
|
-
*/
|
|
14
|
-
export const svgContentToPolygons = async (
|
|
15
|
-
svgContent: string,
|
|
16
|
-
maxError: number
|
|
17
|
-
) => {
|
|
18
|
-
// Sample the SVG into polygons
|
|
19
|
-
const polygons = await svgToPolygons(svgContent, { maxError });
|
|
20
|
-
|
|
21
|
-
// Flip the Y-axis for SVG paths (SVG uses Y-down, but 3D modeling uses Y-up)
|
|
22
|
-
const flippedPolygons = polygons.map((polygon) => {
|
|
23
|
-
return polygon.points.map(([x, y]) => [x, -y]) as [number, number][];
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
return flippedPolygons;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Converts an SVG string to a CrossSection with optional scaling
|
|
31
|
-
*/
|
|
32
|
-
export const svgStringToCrossSection = async (
|
|
33
|
-
svgContent: string,
|
|
34
|
-
maxWidth?: number,
|
|
35
|
-
maxError: number = 0.01
|
|
36
|
-
): Promise<CrossSection> => {
|
|
37
|
-
const polygons = await svgContentToPolygons(svgContent, maxError);
|
|
38
|
-
|
|
39
|
-
const crossSection = new CrossSection(polygons, 'EvenOdd').simplify(maxError);
|
|
40
|
-
if (!maxWidth) return crossSection;
|
|
41
|
-
|
|
42
|
-
// Check the width of the resulting CrossSection
|
|
43
|
-
const boundingBox = crossSection.bounds();
|
|
44
|
-
const width = boundingBox.max[0] - boundingBox.min[0];
|
|
45
|
-
const scaleFactor = maxWidth / width;
|
|
46
|
-
const scaledError = maxError / scaleFactor;
|
|
47
|
-
|
|
48
|
-
// Sample again with new error
|
|
49
|
-
const newPolygons = await svgContentToPolygons(svgContent, scaledError);
|
|
50
|
-
const newCrossSection = new CrossSection(newPolygons, 'EvenOdd').simplify(scaledError);
|
|
51
|
-
return newCrossSection.scale([scaleFactor, scaleFactor]);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Samples an SVG data URL and returns a centered CrossSection
|
|
56
|
-
*/
|
|
57
|
-
export const sampleSvg = async (svgDataUrl: string, maxWidth?: number): Promise<CrossSection> => {
|
|
58
|
-
const svgContent = svgDataUrlToString(svgDataUrl);
|
|
59
|
-
if (!svgContent) {
|
|
60
|
-
throw new Error('Failed to parse SVG data URL');
|
|
61
|
-
}
|
|
62
|
-
const crossSection = await svgStringToCrossSection(svgContent, maxWidth);
|
|
63
|
-
return centerCrossSection(crossSection);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Traces a bitmap image and returns a centered CrossSection
|
|
68
|
-
*/
|
|
69
|
-
export const traceImage = async (
|
|
70
|
-
imageDataUrl: string,
|
|
71
|
-
options: {
|
|
72
|
-
maxWidth?: number;
|
|
73
|
-
despeckleSize?: number;
|
|
74
|
-
}
|
|
75
|
-
): Promise<CrossSection> => {
|
|
76
|
-
const tracer = new Potrace({
|
|
77
|
-
turdSize: options.despeckleSize || 2,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Promisify tracer.loadImage
|
|
81
|
-
await new Promise<void>((resolve, reject) => {
|
|
82
|
-
tracer.loadImage(imageDataUrl, (err: Error | null) => {
|
|
83
|
-
if (err) {
|
|
84
|
-
reject(err);
|
|
85
|
-
} else {
|
|
86
|
-
resolve();
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const svgContent = tracer.getSVG();
|
|
92
|
-
const crossSection = await svgStringToCrossSection(svgContent, options.maxWidth);
|
|
93
|
-
return centerCrossSection(crossSection);
|
|
94
|
-
};
|
package/src/types.d.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
declare module 'potrace' {
|
|
2
|
-
interface PotraceOptions {
|
|
3
|
-
turdSize?: number;
|
|
4
|
-
turnPolicy?: string;
|
|
5
|
-
alphaMax?: number;
|
|
6
|
-
optCurve?: boolean;
|
|
7
|
-
optTolerance?: number;
|
|
8
|
-
threshold?: number;
|
|
9
|
-
blackOnWhite?: boolean;
|
|
10
|
-
color?: string;
|
|
11
|
-
background?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
class Potrace {
|
|
15
|
-
constructor(options?: PotraceOptions);
|
|
16
|
-
loadImage(source: string | Buffer, callback: (err: Error | null) => void): void;
|
|
17
|
-
getSVG(): string;
|
|
18
|
-
getPathTag(): string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function trace(source: string | Buffer, options?: PotraceOptions, callback?: (err: Error | null, svg: string) => void): void;
|
|
22
|
-
function posterize(source: string | Buffer, options?: PotraceOptions, callback?: (err: Error | null, svg: string) => void): void;
|
|
23
|
-
|
|
24
|
-
export { Potrace, trace, posterize, PotraceOptions };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
declare module '@jscadui/3mf-export' {
|
|
28
|
-
export interface ZipWriter {
|
|
29
|
-
files: Record<string, Uint8Array>;
|
|
30
|
-
add(path: string, data: string): void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface Mesh3MF {
|
|
34
|
-
id: string;
|
|
35
|
-
vertices: number[];
|
|
36
|
-
indices: number[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function create3mf(zipWriter: ZipWriter, meshes: Mesh3MF[]): void;
|
|
40
|
-
}
|
package/src/utils.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utility functions for image processing
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Extracts the plain SVG XML string from a Data URL.
|
|
7
|
-
* Handles both Base64 encoded and URL-encoded SVG data URLs.
|
|
8
|
-
*/
|
|
9
|
-
export function svgDataUrlToString(dataUrl: string): string | null {
|
|
10
|
-
if (!dataUrl || !dataUrl.startsWith('data:image/svg+xml')) {
|
|
11
|
-
console.error("Invalid SVG Data URL format.");
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const commaIndex = dataUrl.indexOf(',');
|
|
16
|
-
if (commaIndex === -1) {
|
|
17
|
-
console.error("Invalid Data URL: Missing comma separator.");
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const header = dataUrl.substring(0, commaIndex);
|
|
22
|
-
const encodedData = dataUrl.substring(commaIndex + 1);
|
|
23
|
-
|
|
24
|
-
if (header.includes(';base64')) {
|
|
25
|
-
try {
|
|
26
|
-
// Decode Base64
|
|
27
|
-
return atob(encodedData);
|
|
28
|
-
} catch (e) {
|
|
29
|
-
console.error("Error decoding Base64 SVG data:", e);
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
try {
|
|
34
|
-
// Decode URL-encoded string
|
|
35
|
-
return decodeURIComponent(encodedData);
|
|
36
|
-
} catch (e) {
|
|
37
|
-
console.error("Error decoding URL-encoded SVG data:", e);
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Fetches an image URL and converts it to a data URL
|
|
45
|
-
*/
|
|
46
|
-
export async function fetchImageAsDataUrl(imageUrl: string): Promise<string> {
|
|
47
|
-
const response = await fetch(imageUrl);
|
|
48
|
-
const blob = await response.blob();
|
|
49
|
-
return new Promise<string>((resolve, reject) => {
|
|
50
|
-
const reader = new FileReader();
|
|
51
|
-
reader.onloadend = () => resolve(reader.result as string);
|
|
52
|
-
reader.onerror = reject;
|
|
53
|
-
reader.readAsDataURL(blob);
|
|
54
|
-
});
|
|
55
|
-
}
|