@react-scad/core 0.1.8 → 0.1.10

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 CHANGED
@@ -1,26 +1,61 @@
1
- # react-scad
1
+ # [react-scad](https://github.com/react-scad/react-scad) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/react-scad/react-scad/blob/main/LICENSE) [![npm version](https://img.shields.io/npm/v/@react-scad/core.svg?style=flat)](https://www.npmjs.com/package/@react-scad/core) [![Publish](https://github.com/react-scad/react-scad/actions/workflows/cicd.yml/badge.svg)](https://github.com/react-scad/react-scad/actions/workflows/cicd.yml) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/react-scad/react-scad#contributing)
2
2
 
3
- Render JSX to **OpenSCAD** models using the [React reconciler](https://github.com/facebook/react/tree/main/packages/react-reconciler).
3
+ Render JSX to **SCAD** models using the [React reconciler](https://github.com/facebook/react/tree/main/packages/react-reconciler).
4
4
 
5
- - Write declarative 3D with React components; no imperative OpenSCAD scripting
6
- - Compose shapes with familiar JSX; get `.scad` source for OpenSCAD or 3D printing
7
- - Use [@react-scad/cli](../cli) via npx to build and run
5
+ - Describe models as a tree of components instead of imperative SCAD; avoids nested modules and parameter threading.
6
+ - Same React/JSX mental model (components, props, composition), output is 3D.
7
+ - Writes plain `.scad` files for [OpenSCAD](https://openscad.org/) or any slicer.
8
8
 
9
- ![Example](https://github.com/react-scad/react-scad/raw/main/assets/example.gif)
9
+ ---
10
+
11
+ ## Preview
12
+
13
+ ![Example](./assets/example.gif)
10
14
 
11
15
  *Rocket example with animated rotation.*
12
16
 
13
- Install the library and React:
17
+ ---
18
+
19
+ ## Why react-scad?
20
+
21
+ SCAD is good for parametric 3D but scripts are imperative and nesting gets heavy; composing modules and passing parameters is tedious.
22
+
23
+ A lot of people already think in components and JSX from building UIs. **react-scad** aims to facilitate that same way of thinking for parametric 3D.
24
+
25
+ ---
26
+
27
+ ## Getting Started
28
+
29
+ ### Prerequisites
30
+
31
+ - **Node.js** 18+
32
+ - **React** 18 or later (peer dependency)
33
+
34
+ ### Install
14
35
 
15
36
  ```bash
16
37
  npm install react @react-scad/core
17
38
  ```
18
39
 
40
+ <details>
41
+ <summary>pnpm or yarn</summary>
42
+
43
+ ```bash
44
+ pnpm add react @react-scad/core
45
+ # or
46
+ yarn add react @react-scad/core
47
+ ```
48
+
49
+ </details>
50
+
19
51
  ### Minimal example
20
52
 
53
+ Create a file `main.tsx` (or `main.jsx`):
54
+
21
55
  ```jsx
22
56
  import { createRoot, Cube, Sphere, Union } from "@react-scad/core";
23
57
 
58
+ // Output path: the .scad file that will be created
24
59
  const root = createRoot("model.scad");
25
60
 
26
61
  root.render(
@@ -31,18 +66,68 @@ root.render(
31
66
  );
32
67
  ```
33
68
 
34
- Save as `main.tsx`, then run it:
69
+ | Piece | Meaning |
70
+ | ----- | ------- |
71
+ | `createRoot("model.scad")` | Root that writes to `model.scad` |
72
+ | `Union` | CSG union of all children (like `union()` in SCAD) |
73
+ | `Cube` / `Sphere` | Props match SCAD: `size`, `center`, `r`, `$fn`, etc. |
74
+
75
+ ### Run and write the `.scad` file
76
+
77
+ Run your entry file with [tsx](https://github.com/privatenumber/tsx) so Node can execute the `.tsx`. The `.scad` file is written to the **current working directory** when you call `root.render()`.
78
+
79
+ > **Note:** Node doesn’t run `.tsx` by itself. Use **tsx** or bundle with esbuild (see [Advanced](#advanced) for alternatives).
80
+
81
+ ```bash
82
+ npx tsx main.tsx
83
+ ```
84
+
85
+ Watch mode (re-run on save):
35
86
 
36
87
  ```bash
37
- npx @react-scad/cli run main.tsx
38
- npx @react-scad/cli run main.tsx --watch
88
+ npx tsx watch main.tsx
39
89
  ```
40
90
 
41
- ## Primitives (OpenSCAD coverage)
91
+ The path you pass to `createRoot()` is relative to the current working directory.
92
+
93
+ ### View the result
42
94
 
43
- All listed OpenSCAD primitives and operations are implemented. Prop names follow OpenSCAD where it makes sense (`r`, `h`, `size`, `center`, `$fn`, etc.).
95
+ - Open the generated `.scad` file in [OpenSCAD](https://openscad.org/) to preview, export STL, or tweak.
96
+ - Or import the `.scad` (or an exported STL) into your slicer for 3D printing.
44
97
 
45
- | OpenSCAD | react-scad | Implemented |
98
+ ---
99
+
100
+ ## Advanced
101
+
102
+ To write to a custom path or get the SCAD string in memory instead of using `createRoot(path)`, use `createContainer()`, `render()`, and `toScad()`:
103
+
104
+ ```jsx
105
+ import { createContainer, render, toScad, Cube, Sphere, Union } from "@react-scad/core";
106
+ import { writeFileSync } from "fs";
107
+
108
+ const container = createContainer();
109
+ render(
110
+ <Union>
111
+ <Cube size={[10, 10, 10]} center />
112
+ <Sphere r={6} />
113
+ </Union>,
114
+ container
115
+ );
116
+
117
+ const scadCode = toScad(container);
118
+ writeFileSync("out/model.scad", scadCode);
119
+ // or use scadCode however you like
120
+ ```
121
+
122
+ Then run with `npx tsx main.tsx` or bundle with esbuild and run with Node.
123
+
124
+ ---
125
+
126
+ ## Primitives (SCAD coverage)
127
+
128
+ All listed SCAD primitives and operations are implemented. Prop names follow SCAD where it makes sense (`r`, `h`, `size`, `center`, `$fn`, etc.).
129
+
130
+ | SCAD | react-scad | Implemented |
46
131
  | -------- | ---------- | :---------: |
47
132
  | **3D primitives** | | |
48
133
  | `cube()` | `Cube` | ✓ |
@@ -72,22 +157,51 @@ All listed OpenSCAD primitives and operations are implemented. Prop names follow
72
157
  | `surface()` | `Surface` | ✓ |
73
158
  | `import()` | `Import` | ✓ |
74
159
 
75
- ## Why react-scad?
160
+ ### Missing anything?
161
+
162
+ > If you need a SCAD primitive or feature that isn’t listed here, open an [issue](https://github.com/react-scad/react-scad/issues/new) or a PR.
76
163
 
77
- OpenSCAD is great for parametric 3D, but code is imperative and nesting gets messy. Composing modules and passing parameters can be tedious.
164
+ ---
78
165
 
79
- With react-scad you build a tree of SCAD primitives using React components. The reconciler runs with a custom host config that builds an internal SCAD tree (no DOM). Serialization turns that tree into OpenSCAD source.
166
+ ## Contributing
80
167
 
81
- **JSX React tree host config → SCAD tree → OpenSCAD source.**
168
+ 1. **Fork and clone** the repo, then install dependencies:
169
+ ```bash
170
+ git clone https://github.com/YOUR_USER/react-scad.git
171
+ cd react-scad
172
+ pnpm install
173
+ ```
82
174
 
83
- You get declarative composition, reuse via components, and the same `.scad` output you’d use in OpenSCAD or slicers.
175
+ 2. **Create a branch** for your change:
176
+ ```bash
177
+ git checkout -b fix/your-change
178
+ ```
179
+
180
+ 3. **Build and test** before committing:
181
+ ```bash
182
+ pnpm run build
183
+ pnpm run dev # optional: smoke-test the example
184
+ ```
185
+
186
+ 4. **Format and lint** (all code in the repo):
187
+ ```bash
188
+ pnpm run format
189
+ pnpm run lint
190
+ ```
191
+
192
+ 5. **Open a PR** against `main` with a short description of the change. For bugs, reference the issue if one exists.
193
+
194
+ 6. **Publishing** is done via GitHub Actions on push to `main`; no need to publish from a PR.
195
+
196
+ ---
84
197
 
85
198
  ## Acknowledgements
86
199
 
87
200
  - [React](https://react.dev/) and the [React reconciler](https://github.com/facebook/react/tree/main/packages/react-reconciler) for the rendering model that makes this approach possible.
88
- - [OpenSCAD](https://openscad.org/) for the script-based CAD language and documentation.
201
+ - [OpenSCAD](https://openscad.org/) for the SCAD language and documentation.
202
+
203
+ ---
89
204
 
90
205
  ## License
91
206
 
92
- React SCAD is MIT-licensed open-source software by Leon Meka and
93
- contributors.
207
+ MIT
package/dist/log.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export declare const c: {
2
+ dim: (s: string) => string;
3
+ cyan: (s: string) => string;
4
+ green: (s: string) => string;
5
+ red: (s: string) => string;
6
+ yellow: (s: string) => string;
7
+ bold: (s: string) => string;
8
+ };
9
+ export declare const log: {
10
+ buildOk(ms: number): void;
11
+ wrote(path: string, ms: number): void;
12
+ watchCycle(ms: number, code: number | null): void;
13
+ };
14
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,CAAC;aACJ,MAAM;cACL,MAAM;eACL,MAAM;aACR,MAAM;gBACH,MAAM;cACR,MAAM;CAChB,CAAC;AAMF,eAAO,MAAM,GAAG;gBACH,MAAM,GAAG,IAAI;gBAGb,MAAM,MAAM,MAAM,GAAG,IAAI;mBAGtB,MAAM,QAAQ,MAAM,GAAG,IAAI,GAAG,IAAI;CAKjD,CAAC"}
package/dist/log.js ADDED
@@ -0,0 +1,25 @@
1
+ const noColor = process.env.NO_COLOR != null || !process.stdout.isTTY;
2
+ export const c = {
3
+ dim: (s) => (noColor ? s : `\x1b[2m${s}\x1b[0m`),
4
+ cyan: (s) => (noColor ? s : `\x1b[36m${s}\x1b[0m`),
5
+ green: (s) => (noColor ? s : `\x1b[32m${s}\x1b[0m`),
6
+ red: (s) => (noColor ? s : `\x1b[31m${s}\x1b[0m`),
7
+ yellow: (s) => (noColor ? s : `\x1b[33m${s}\x1b[0m`),
8
+ bold: (s) => (noColor ? s : `\x1b[1m${s}\x1b[0m`),
9
+ };
10
+ function time() {
11
+ return new Date().toLocaleTimeString("en-US", { hour12: false });
12
+ }
13
+ export const log = {
14
+ buildOk(ms) {
15
+ console.log(c.dim(`[${time()}] `) + c.green("✓ Built") + c.dim(` in ${ms}ms`));
16
+ },
17
+ wrote(path, ms) {
18
+ console.log(c.dim(`[${time()}] `) + c.green("✓ Wrote") + c.dim(` ${path} in ${ms}ms`));
19
+ },
20
+ watchCycle(ms, code) {
21
+ const ok = code === 0;
22
+ const status = ok ? c.green("done") : c.red(`exit ${code}`);
23
+ console.log(` ${ok ? c.green("✓") : c.red("✗")}${c.dim(` ${ms}ms `)}${status}`);
24
+ },
25
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../src/runtime/root.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B,MAAM,WAAW,QAAQ;IACxB,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;IAC1C,MAAM,IAAI,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC;AAE1B,wBAAgB,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,QAAQ,CAchD;AAED,wBAAgB,eAAe,IAAI,aAAa,CAE/C;AAED,wBAAgB,MAAM,CACrB,OAAO,EAAE,KAAK,CAAC,YAAY,EAC3B,SAAS,EAAE,aAAa,EACxB,QAAQ,CAAC,EAAE,MAAM,IAAI,GACnB,IAAI,CAGN;AAED,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../src/runtime/root.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,YAAY,EAAE,aAAa,EAAE,CAAC;AAE9B,MAAM,WAAW,QAAQ;IACxB,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;IAC1C,MAAM,IAAI,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC;AAE1B,wBAAgB,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,QAAQ,CAgBhD;AAED,wBAAgB,eAAe,IAAI,aAAa,CAE/C;AAED,wBAAgB,MAAM,CACrB,OAAO,EAAE,KAAK,CAAC,YAAY,EAC3B,SAAS,EAAE,aAAa,EACxB,QAAQ,CAAC,EAAE,MAAM,IAAI,GACnB,IAAI,CAGN;AAED,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { log } from "../log.js";
1
2
  import { toScad } from "../serialize/index.js";
2
3
  import { createScadContainer } from "./node-ops.js";
3
4
  import { createFiberRoot, updateContainer } from "./reconciler.js";
@@ -9,9 +10,11 @@ export function createRoot(path) {
9
10
  registerWriteOnCommit(container, path);
10
11
  return {
11
12
  render(element) {
13
+ const start = Date.now();
12
14
  updateContainer(element, fiberRoot, null, null);
13
15
  if (path)
14
16
  writeAfterCommit(container);
17
+ log.watchCycle(Date.now() - start, 0);
15
18
  },
16
19
  toScad() {
17
20
  return toScad(container);
@@ -1 +1 @@
1
- {"version":3,"file":"write-on-commit.d.ts","sourceRoot":"","sources":["../../src/runtime/write-on-commit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAElF;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAG/D"}
1
+ {"version":3,"file":"write-on-commit.d.ts","sourceRoot":"","sources":["../../src/runtime/write-on-commit.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD,wBAAgB,qBAAqB,CACpC,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,MAAM,GACV,IAAI,CAEN;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAU/D"}
@@ -1,4 +1,6 @@
1
- import { writeFileSync } from "node:fs";
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { log } from "../log.js";
2
4
  import { toScad } from "../serialize/index.js";
3
5
  const writeOnCommitPaths = new WeakMap();
4
6
  export function registerWriteOnCommit(container, path) {
@@ -6,6 +8,13 @@ export function registerWriteOnCommit(container, path) {
6
8
  }
7
9
  export function writeAfterCommit(container) {
8
10
  const path = writeOnCommitPaths.get(container);
9
- if (path)
11
+ if (path) {
12
+ const start = Date.now();
13
+ const dir = dirname(path);
14
+ if (dir !== ".")
15
+ mkdirSync(dir, { recursive: true });
10
16
  writeFileSync(path, toScad(container), "utf8");
17
+ const ms = Date.now() - start;
18
+ log.wrote(path, ms);
19
+ }
11
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-scad/core",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Render JSX to OpenSCAD models using the React reconciler",
5
5
  "keywords": [
6
6
  "react",