@react-scad/core 0.1.8 → 0.1.9

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
@@ -2,25 +2,51 @@
2
2
 
3
3
  Render JSX to **OpenSCAD** 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
+ ## Preview
10
+
11
+ ![Example](./assets/example.gif)
10
12
 
11
13
  *Rocket example with animated rotation.*
12
14
 
13
- Install the library and React:
15
+ ## Why react-scad?
16
+
17
+ SCAD is good for parametric 3D but scripts are imperative and nesting gets heavy; composing modules and passing parameters is tedious.
18
+
19
+ 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.
20
+
21
+ ## Getting Started
22
+
23
+ ### Prerequisites
24
+
25
+ - **Node.js** 18+
26
+ - **React** 18 or later (peer dependency)
27
+
28
+ ### Install
14
29
 
15
30
  ```bash
16
31
  npm install react @react-scad/core
17
32
  ```
18
33
 
34
+ With pnpm or yarn:
35
+
36
+ ```bash
37
+ pnpm add react @react-scad/core
38
+ # or
39
+ yarn add react @react-scad/core
40
+ ```
41
+
19
42
  ### Minimal example
20
43
 
44
+ Create a file `main.tsx` (or `main.jsx`):
45
+
21
46
  ```jsx
22
47
  import { createRoot, Cube, Sphere, Union } from "@react-scad/core";
23
48
 
49
+ // Output path: the .scad file that will be created
24
50
  const root = createRoot("model.scad");
25
51
 
26
52
  root.render(
@@ -31,18 +57,58 @@ root.render(
31
57
  );
32
58
  ```
33
59
 
34
- Save as `main.tsx`, then run it:
60
+ - `createRoot("model.scad")`: creates a root that writes to `model.scad`.
61
+ - `Union`: CSG union of all children (like `union()` in SCAD).
62
+ - `Cube` / `Sphere`: props match SCAD: `size`, `center`, `r`, `$fn`, etc.
63
+
64
+ ### Run and write the `.scad` file
65
+
66
+ 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()`.
67
+
68
+ ```bash
69
+ npx tsx main.tsx
70
+ ```
71
+
72
+ Watch mode (re-run on save):
35
73
 
36
74
  ```bash
37
- npx @react-scad/cli run main.tsx
38
- npx @react-scad/cli run main.tsx --watch
75
+ npx tsx watch main.tsx
76
+ ```
77
+
78
+ Run from the directory that contains `main.tsx` (or use the path to it).
79
+
80
+ ### View the result
81
+
82
+ - Open the generated `.scad` file in [OpenSCAD](https://openscad.org/) to preview, export STL, or tweak.
83
+ - Or import the `.scad` (or an exported STL) into your slicer for 3D printing.
84
+
85
+ ## Advanced
86
+
87
+ To write to a custom path or get the SCAD string in memory instead of using `createRoot(path)`, use a container and `toScad`:
88
+
89
+ ```jsx
90
+ import { createContainer, render, toScad, Cube, Sphere, Union } from "@react-scad/core";
91
+ import { writeFileSync } from "fs";
92
+
93
+ const container = createContainer();
94
+ render(
95
+ <Union>
96
+ <Cube size={[10, 10, 10]} center />
97
+ <Sphere r={6} />
98
+ </Union>,
99
+ container
100
+ );
101
+
102
+ const scadCode = toScad(container);
103
+ writeFileSync("out/model.scad", scadCode);
104
+ // or use scadCode however you like
39
105
  ```
40
106
 
41
- ## Primitives (OpenSCAD coverage)
107
+ ## Primitives (SCAD coverage)
42
108
 
43
- All listed OpenSCAD primitives and operations are implemented. Prop names follow OpenSCAD where it makes sense (`r`, `h`, `size`, `center`, `$fn`, etc.).
109
+ All listed SCAD primitives and operations are implemented. Prop names follow SCAD where it makes sense (`r`, `h`, `size`, `center`, `$fn`, etc.).
44
110
 
45
- | OpenSCAD | react-scad | Implemented |
111
+ | SCAD | react-scad | Implemented |
46
112
  | -------- | ---------- | :---------: |
47
113
  | **3D primitives** | | |
48
114
  | `cube()` | `Cube` | ✓ |
@@ -72,22 +138,41 @@ All listed OpenSCAD primitives and operations are implemented. Prop names follow
72
138
  | `surface()` | `Surface` | ✓ |
73
139
  | `import()` | `Import` | ✓ |
74
140
 
75
- ## Why react-scad?
141
+ ## Contributing
142
+
143
+ 1. **Fork and clone** the repo, then install dependencies:
144
+ ```bash
145
+ git clone https://github.com/YOUR_USER/react-scad.git
146
+ cd react-scad
147
+ pnpm install
148
+ ```
149
+
150
+ 2. **Create a branch** for your change:
151
+ ```bash
152
+ git checkout -b fix/your-change
153
+ ```
76
154
 
77
- OpenSCAD is great for parametric 3D, but code is imperative and nesting gets messy. Composing modules and passing parameters can be tedious.
155
+ 3. **Build and test** before committing:
156
+ ```bash
157
+ pnpm run build
158
+ pnpm run dev # optional: smoke-test the example
159
+ ```
78
160
 
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.
161
+ 4. **Format and lint** (all code in the repo):
162
+ ```bash
163
+ pnpm run format
164
+ pnpm run lint
165
+ ```
80
166
 
81
- **JSX React tree host config SCAD tree OpenSCAD source.**
167
+ 5. **Open a PR** against `main` with a short description of the change. For bugs, reference the issue if one exists.
82
168
 
83
- You get declarative composition, reuse via components, and the same `.scad` output you’d use in OpenSCAD or slicers.
169
+ 6. **Publishing** is done via GitHub Actions on push to `main`; no need to publish from a PR.
84
170
 
85
171
  ## Acknowledgements
86
172
 
87
173
  - [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.
174
+ - [OpenSCAD](https://openscad.org/) for the SCAD language and documentation.
89
175
 
90
176
  ## License
91
177
 
92
- React SCAD is MIT-licensed open-source software by Leon Meka and
93
- contributors.
178
+ 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.9",
4
4
  "description": "Render JSX to OpenSCAD models using the React reconciler",
5
5
  "keywords": [
6
6
  "react",