@bitbybit-dev/jscad
Version:
Bit By Bit Developers JSCAD based CAD Library to Program Geometry
203 lines (202 loc) • 9.09 kB
JavaScript
import { GeometryHelper, Lists, Point, Transforms, Vector } from "@bitbybit-dev/base";
import { MathBitByBit } from "@bitbybit-dev/base";
import { JSCADExpansions } from "./services/jscad-expansions";
import { JSCADBooleans } from "./services/jscad-booleans";
import { JSCADExtrusions } from "./services/jscad-extrusions";
import { JSCADPath } from "./services/jscad-path";
import { JSCADPolygon } from "./services/jscad-polygon";
import { JSCADShapes } from "./services/jscad-shapes";
import { JSCADText } from "./services/jscad-text";
import { JSCADHulls } from "./services/jscad-hulls";
import { JSCADColors } from "./services/jscad-colors";
// Worker make an instance of this class itself
export class Jscad {
constructor(jscad) {
this.getArrayDepth = (value) => {
return Array.isArray(value) ?
1 + Math.max(...value.map(this.getArrayDepth)) :
0;
};
const geometryHelper = new GeometryHelper();
const math = new MathBitByBit();
const vector = new Vector(math, geometryHelper);
const transforms = new Transforms(vector, math);
const lists = new Lists();
this.point = new Point(geometryHelper, transforms, vector, lists);
this.booleans = new JSCADBooleans(jscad);
this.expansions = new JSCADExpansions(jscad);
this.extrusions = new JSCADExtrusions(jscad, geometryHelper, math);
this.hulls = new JSCADHulls(jscad);
this.path = new JSCADPath(jscad, geometryHelper, math);
this.polygon = new JSCADPolygon(jscad, geometryHelper, math);
this.shapes = new JSCADShapes(jscad, math);
this.text = new JSCADText(jscad);
this.colors = new JSCADColors(jscad);
this.jscad = jscad;
}
toPolygonPoints(inputs) {
const meshData = this.shapeToMesh({ mesh: inputs.mesh });
if (!meshData || !meshData.positions || !meshData.indices) {
throw new Error("Invalid input: 'data', 'data.positions', and 'data.indices' must be provided.");
}
const { positions, indices } = meshData;
if (positions.length % 3 !== 0) {
throw new Error(`Invalid input: 'positions' array length (${positions.length}) must be a multiple of 3.`);
}
if (indices.length % 3 !== 0) {
throw new Error(`Invalid input: 'indices' array length (${indices.length}) must be a multiple of 3.`);
}
if (positions.length === 0) {
return [];
}
if (indices.length === 0) {
return [];
}
const polygons = [];
const numVertices = positions.length / 3;
// --- Triangle Reconstruction ---
for (let i = 0; i < indices.length; i += 3) {
const index1 = indices[i];
const index2 = indices[i + 1];
const index3 = indices[i + 2];
if (index1 >= numVertices || index2 >= numVertices || index3 >= numVertices ||
index1 < 0 || index2 < 0 || index3 < 0) {
console.error(`Invalid vertex index found in 'indices' array at triangle starting at index ${i}. Max vertex index is ${numVertices - 1}. Indices: ${index1}, ${index2}, ${index3}. Skipping triangle.`);
continue;
}
const offset1 = index1 * 3;
const offset2 = index2 * 3;
const offset3 = index3 * 3;
const point1 = [positions[offset1], positions[offset1 + 1], positions[offset1 + 2]];
const point2 = [positions[offset2], positions[offset2 + 1], positions[offset2 + 2]];
const point3 = [positions[offset3], positions[offset3 + 1], positions[offset3 + 2]];
// We must bake the transformations as JSCAD uses those extensively
const transformation = inputs.mesh.transforms;
let transformedPoints = [point1, point2, point3];
if (this.getArrayDepth(transformation) === 2) {
transformation.forEach(transform => {
transformedPoints = this.point.transformPoints({ points: transformedPoints, transformation: [transform] });
});
}
else if (this.getArrayDepth(transformation) === 3) {
transformation.forEach(transforms => {
transforms.forEach(mat => {
transformedPoints = this.point.transformPoints({ points: transformedPoints, transformation: [mat] });
});
});
}
else {
transformedPoints = this.point.transformPoints({ points: transformedPoints, transformation: [transformation] });
}
const triangle = transformedPoints;
polygons.push(triangle);
}
return polygons;
}
shapesToMeshes(inputs) {
return inputs.meshes.map(mesh => {
return this.shapeToMesh(Object.assign(Object.assign({}, inputs), { mesh }));
});
}
shapeToMesh(inputs) {
let polygons = [];
if (inputs.mesh.toPolygons) {
polygons = inputs.mesh.toPolygons();
}
else if (inputs.mesh.polygons) {
polygons = inputs.mesh.polygons;
}
else if (inputs.mesh.sides || inputs.mesh.vertices) {
const extrusion = this.extrusions.extrudeLinear({ height: 0.001, twistAngle: 0, twistSteps: 1, geometry: inputs.mesh });
if (extrusion.toPolygons) {
polygons = extrusion.toPolygons();
}
else if (extrusion.polygons) {
polygons = extrusion.polygons;
}
}
const positions = [];
const normals = [];
const indices = [];
let countIndices = 0;
for (const polygon of polygons) {
if (polygon.vertices.length === 3) {
polygon.vertices.forEach(vert => {
positions.push(vert[0], vert[1], vert[2]);
indices.push(countIndices);
countIndices++;
});
}
else {
const triangles = [];
const reversedVertices = polygon.vertices;
const firstVertex = reversedVertices[0];
for (let i = reversedVertices.length - 3; i >= 0; i--) {
triangles.push([
firstVertex,
reversedVertices[i + 1],
reversedVertices[i + 2],
]);
}
triangles.forEach((triangle, index) => {
triangle.forEach(vert => {
positions.push(vert[0], vert[1], vert[2]);
indices.push(countIndices);
countIndices++;
});
});
}
}
return {
positions, normals, indices, transforms: inputs.mesh.transforms,
};
}
transformSolids(inputs) {
const solidsToTransform = inputs.meshes;
return solidsToTransform.map(mesh => {
return this.transformSolid({ mesh, transformation: inputs.transformation });
});
}
transformSolid(inputs) {
const transformation = inputs.transformation;
let transformedMesh = this.jscad.geometries.geom3.clone(inputs.mesh);
if (this.getArrayDepth(transformation) === 2) {
transformation.forEach(transform => {
transformedMesh = this.jscad.transforms.transform(transform, transformedMesh);
});
}
else if (this.getArrayDepth(transformation) === 3) {
transformation.forEach(transforms => {
transforms.forEach(mat => {
transformedMesh = this.jscad.transforms.transform(mat, transformedMesh);
});
});
}
else {
transformedMesh = this.jscad.transforms.transform(transformation, transformedMesh);
}
return transformedMesh;
}
downloadSolidSTL(inputs) {
const rawData = this.jscad.STLSERIALIZER.serialize({ binary: true }, inputs.mesh);
const madeBlob = new Blob(rawData, { type: "application/sla" });
return { blob: madeBlob };
}
downloadGeometryDxf(inputs) {
const options = inputs.options ? inputs.options : {};
const rawData = this.jscad.DXFSERIALIZER.serialize(options, inputs.geometry);
const madeBlob = new Blob(rawData);
return { blob: madeBlob };
}
downloadGeometry3MF(inputs) {
const options = inputs.options ? inputs.options : {};
const rawData = this.jscad.THREEMFSERIALIZER.serialize(options, inputs.geometry);
const madeBlob = new Blob(rawData);
return { blob: madeBlob };
}
downloadSolidsSTL(inputs) {
const rawData = this.jscad.STLSERIALIZER.serialize({ binary: true }, ...inputs.meshes);
const madeBlob = new Blob(rawData, { type: "application/sla" });
return { blob: madeBlob };
}
}