Skip to content

Instantly share code, notes, and snippets.

@ochafik
Created February 18, 2022 00:02
Show Gist options
  • Select an option

  • Save ochafik/cf84c971339736b4aa2a84a0c6c86f18 to your computer and use it in GitHub Desktop.

Select an option

Save ochafik/cf84c971339736b4aa2a84a0c6c86f18 to your computer and use it in GitHub Desktop.
OpenSCAD.js TS definitions brainstorming
/*
- JS/TS library
- VS Code to the rescue!
- Mechanical, (mostly) bidirectional source code converter (OpenSCAD -> OpenSCAD.js guaranteed)
- Node.js native extension: run OpenSCAD in Node.js at full native speed !!!
- Emscripten: run OpenSCAD in the browser (or Node.js) at reduced speed.
-
- [HARD] Future extension: include OpenCSG w/ WebGL
*/
type Point2d = [number, number];
type Point3d = [number, number, number];
type Transform3d = [
number, number, number, number,
number, number, number, number,
number, number, number, number,
number, number, number, number
];
const enum CsgOperation {
Union = 1,
Intersection = 2,
Minkowski = 3,
Difference = 4,
}
// Easily convertible to/from a Surface_mesh
type Poly = {
dimension: 2 | 3,
points: Float64Array,
components: Int32Array,
convexity: number
};
type FeatureName =
'fast-csg' | 'lazy-union';
type FeatureSet = FeatureName[];//{[name: FeatureName]: bool};
// NodeHandle is the index in an unordered_map<NodeHandle, std::shared_ptr<AbstractNode>>
type NodeHandle = number;
type ExecutionContext = {
// ~ExecutionContext will delete all the nodes.
createColorNode(color: string): NodeHandle;
createImportNode(file: string): NodeHandle;
createPolyNode(poly: Poly): NodeHandle;
createCsgOperationNode(op: CsgOperation): NodeHandle;
createMultmatrixNode(matrix: Transform3d): NodeHandle;
deleteNode(node: NodeHandle): void;
addChild(parent: NodeHandle, child: NodeHandle): void;
// Creates a root, attaches to it and renders.
renderGeometry(node: NodeHandle, opts?: {experimentalFeatures?: FeatureSet}): Poly;
// getCurrentParent(): NodeHandle;
// pushCurrentParent(node: NodeHandle): void;
// popCurrentParent(): void;
};
// declare let runtime: ExecutionContext;
type SceneDeclaration = () => void;
type RenderFormat = "csg" | "stl" | "nef";
class OpenSCADImpl {
// Runtime is implemented in C++ and bound by a native extension in Node.js or through Emscripten FFI.
private runtime: ExecutionContext
private currentStack: NodeHandle[] = [];
private withPushedNode(newParent: NodeHandle, ...children: SceneDeclaration[]) {
this.currentStack.push(newParent);
try {
for (const child of children) {
child();
}
} finally {
if (this.currentStack[this.currentStack.length] === newParent) throw 'Invalid state';
this.currentStack.pop();
}
}
private addChild(child: NodeHandle) {
if (!child) throw "No child!";
if (this.currentStack.length == 0) throw "Now parent!";
const parent = this.currentStack[this.currentStack.length - 1];
if (!parent) throw "No parent!";
this.runtime.addChild(parent, child);
}
run(scene: SceneDeclaration) {
const globals = ['cube', 'color', 'union', 'intersection', 'difference', 'translate', 'scale', 'polyhedron'];
const oldValues = {};
for (const name of globals) {
oldValues[name] = window[name];
window[name] = this[name].bind(this);
}
try {
scene();
} finally {
for (const name of globals) {
window[name] = oldValues[name]
}
}
}
render(format: RenderFormat, opts?: {experimentalFeatures?: FeatureSet}) {
}
cube(size: number) {
this.polyhedron([[0, 0, 0]], [[0]], 1);
}
color(color: string, ...children: SceneDeclaration[]) {
this.withPushedNode(this.runtime.createColorNode(color), ...children);
}
union(...children: SceneDeclaration[]) {
this.withPushedNode(this.runtime.createCsgOperationNode(CsgOperation.Union), ...children);
}
intersection(...children: SceneDeclaration[]) {
this.withPushedNode(this.runtime.createCsgOperationNode(CsgOperation.Intersection), ...children);
}
difference(...children: SceneDeclaration[]) {
this.withPushedNode(this.runtime.createCsgOperationNode(CsgOperation.Difference), ...children);
}
translate([tx, ty, tz]: [number, number, number], ...children: SceneDeclaration[]) {
this.withPushedNode(this.runtime.createMultmatrixNode([1, 0, 0, tx, 0, 1, 0, ty, 0, 0, 1, tz, 0, 0, 0, 1]), ...children);
}
scale([sx, sy, sz]: [number, number, number], ...children: SceneDeclaration[]) {
this.withPushedNode(this.runtime.createMultmatrixNode([sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1]), ...children);
}
polyhedron(...args) {
let points: Point3d[], faces: number[], convexity: number;
if (args.length == 1) {
[{points, faces, convexity}] = args;
} else {
[points, faces, convexity] = args;
}
if (convexity == null) {
convexity = 1;
}
let pointArray = new Float64Array(points.length * 3);
for (let i = 0, n = points.length; i < n; i++) {
const point = points[i];
if (point.length != 3) throw `Not a 3D point: ${JSON.stringify(point)}`;
pointArray.set(point, i * 3);
}
let components = new Int32Array(faces);
this.addChild(this.runtime.createPolyNode({dimension: 2, points: pointArray, components, convexity}));
}
}
type RemoveMembersWithNames<Source, NameUnion> =
{[K in keyof Source]: K extends NameUnion ? never : Source[K]};
type OpenSCAD =
{
new(): OpenSCAD;
polyhedron(points: Point3d[], faces: number[], convexity?: number): void;
polyhedron({points, faces, convexity}: {points: Point3d[], faces: number[], convexity?: number}): void;
polygon(points: Point3d[], paths: number[], convexity?: number): void;
polygon({points, paths, convexity}: {points: Point3d[], paths: number[], convexity?: number}): void;
} |
RemoveMembersWithNames<OpenSCADImpl, 'polyhedron' | 'polygon'>[keyof OpenSCADImpl] ;
export const OpenSCAD: OpenSCAD = OpenSCADImpl as any;
var openscad = new OpenSCAD();
openscad.polyhedron({points: [], faces: []});
openscad.cube(1);
/*
module A() scale(10) cube(1);
difference() {
union() {
A();
translate([1, 0, 0]) A();
}
}
*/
function A() { openscad.scale(10); openscad.cube(1); }
openscad.difference(() => {
openscad.union(() => {
A();
openscad.translate([1, 0, 0], A);
})
})
// declare interface Window { difference: any }
declare global {
interface Window { difference: any; }
}
window.difference = window.difference || {};
var openscad = new OpenSCAD();
openscad.run(() => {
function A() { scale(10); cube(1); }
difference(() => {
union(() => {
A();
translate([1, 0, 0], A);
})
})
})
console.log(openscad.render());
// let openscad: OpenSCAD = new OpenSCADImpl();
//
// Declarative API
//
// type SceneDeclaration = (context: ExecutionContext) => void;
// //
// // Object API: weird as we won't have access to the geometry.
// //
// type SceneDeclarationCtx = (ctx: SceneContext) => void;
// class SceneContext {
// cube(size: number) {
// returnpolyhedron([[0, 0, 0]], [[0]], 1);
// }
// color(color: string, children: SceneDeclaration) {
// children();
// }
// union(...children: SceneDeclaration[]) {
// withPushedNode(runtime.createCsgOperation(CsgOperation.Union), ...children);
// }
// intersection(...children: SceneDeclaration[]) {
// withPushedNode(runtime.createCsgOperation(CsgOperation.Intersection), ...children);
// }
// difference(...children: SceneDeclaration[]) {
// withPushedNode(runtime.createCsgOperation(CsgOperation.Difference), ...children);
// }
// translate([tx, ty, tz]: [number, number, number], children: SceneDeclaration) {
// withPushedNode(runtime.createMultmatrix([1, 0, 0, tx, 0, 1, 0, ty, 0, 0, 1, tz, 0, 0, 0, 1]), ...children);
// }
// scale([sx, sy, sz]: [number, number, number], children: SceneDeclaration) {
// withPushedNode(runtime.createMultmatrix([sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1]), ...children);
// }
// declare module OpenSCAD {
// function polyhedron(points: Point3d[], faces: number[], convexity?: number): void;
// function polyhedron(args: {points: Point3d[], faces: number[], convexity: number}): void;
// function polygon(points: Point3d[], paths: number[], convexity?: number): void;
// function polygon(args: {points: Point3d[], paths: number[], convexity: number}): void;
// }
// }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment