Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions examples/01_hello_part/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Example 01 — Hello Part

Demonstrates the **headless fluent API** (`Part` and `Sketch`) that runs
entirely in-process — no HTTP services needed.

## What this example shows

- Creating primitive shapes (`box`, `cylinder`)
- Performing a boolean cut to subtract one shape from another
- Applying edge fillets
- Inspecting the resulting feature tree
- Exporting the part to a STEP file

## Run

```bash
# From the repository root (after pip install -e ".[full]")
python examples/01_hello_part/hello_part.py
```

## Expected output

```
✅ Created box: feat-0001 shape_id=box-0001
✅ Created cylinder: feat-0002 shape_id=cyl-0001
✅ Boolean cut: feat-0003
✅ Fillet: feat-0004
Feature tree has 5 nodes (including root)
✅ Exported to /tmp/hello_part.step
```
69 changes: 69 additions & 0 deletions examples/01_hello_part/hello_part.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
Example 01 — Hello Part
=======================
A "hello world" for the OpenCAD headless fluent API.

Demonstrates:
- Creating primitive shapes (box, cylinder)
- Boolean cut (subtract one shape from another)
- Edge fillet
- Feature-tree inspection
- STEP export

No HTTP services are required; everything runs in a single Python process.
"""

from __future__ import annotations

import json
import tempfile
from pathlib import Path

from opencad import Part, Sketch, get_default_context, reset_default_context


def main() -> None:
# Always reset the default context so each run starts from a clean state.
reset_default_context()

# ── 1. Create a rectangular base block ──────────────────────────────
base = Part(name="Base")
base.box(length=80, width=60, height=20)
print(f"✅ Created box: {base.feature_id} shape_id={base.shape_id}")

# ── 2. Create a cylinder to use as a hole ───────────────────────────
hole = Part(name="Hole")
hole.cylinder(radius=10, height=25)
print(f"✅ Created cylinder: {hole.feature_id} shape_id={hole.shape_id}")

# ── 3. Subtract the cylinder from the base block ────────────────────
base.cut(hole, name="CutHole")
print(f"✅ Boolean cut: {base.feature_id}")

# ── 4. Round off some edges ──────────────────────────────────────────
base.fillet(edges="top", radius=3, name="TopFillet")
print(f"✅ Fillet: {base.feature_id}")

# ── 5. Inspect the feature tree ─────────────────────────────────────
ctx = get_default_context()
node_count = len(ctx.tree.nodes)
print(f"Feature tree has {node_count} nodes (including root)")

# Pretty-print the feature node names and operations for clarity.
for node_id, node in ctx.tree.nodes.items():
print(f" [{node.status:>10}] {node_id:12} op={node.operation} name={node.name!r}")

# ── 6. Export to STEP ────────────────────────────────────────────────
output_path = Path(tempfile.gettempdir()) / "hello_part.step"
base.export(str(output_path))
print(f"✅ Exported to {output_path}")

# ── 7. (Optional) Dump the tree as JSON for inspection ──────────────
tree_json = ctx.tree.model_dump()
json_path = Path(tempfile.gettempdir()) / "hello_part_tree.json"
json_path.write_text(json.dumps(tree_json, indent=2))
print(f"✅ Tree JSON written to {json_path}")


if __name__ == "__main__":
main()
39 changes: 39 additions & 0 deletions examples/02_parametric_bracket/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Example 02 — Parametric Bracket

A more complete **headless scripting** example that builds a mechanical
mounting bracket entirely in-process using the `Part` and `Sketch` fluent APIs.

## What this example shows

- Using `Sketch` to draw a 2-D profile (rectangle)
- Extruding the sketch into a 3-D solid
- Adding chamfer/fillet finishing operations
- Using `linear_pattern` to create an evenly spaced bolt-hole array
- Serialising the feature tree to JSON (for reloading or CI inspection)

## Design

```
┌─────────────────────────────────────────┐
│ ○ ○ ○ ○ ○ ○ ○ ○ │ ← 8 bolt holes, linear pattern
│ │
│ │
└─────────────────────────────────────────┘
120 mm × 40 mm × 8 mm base plate
```

## Run

```bash
python examples/02_parametric_bracket/bracket.py
```

## Expected output

```
✅ Base plate extruded feat-0002
✅ First hole cut feat-0005
✅ Bolt hole pattern feat-0006
Feature tree: 7 nodes
✅ Tree JSON → /tmp/bracket_tree.json
```
94 changes: 94 additions & 0 deletions examples/02_parametric_bracket/bracket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""
Example 02 — Parametric Bracket
================================
Builds a flat mounting bracket with evenly spaced bolt holes using the
OpenCAD headless fluent API.

Design parameters (edit freely):
PLATE_LENGTH — overall length of the bracket in mm
PLATE_WIDTH — overall width of the bracket in mm
PLATE_THICK — thickness of the bracket in mm
HOLE_RADIUS — radius of each bolt hole in mm
HOLE_COUNT — number of bolt holes along the length
FILLET_R — edge fillet radius in mm

No HTTP services are required.
"""

from __future__ import annotations

import json
import tempfile
from pathlib import Path

from opencad import Part, Sketch, get_default_context, reset_default_context

# ── Design parameters ────────────────────────────────────────────────────────
PLATE_LENGTH: float = 120.0 # mm
PLATE_WIDTH: float = 40.0 # mm
PLATE_THICK: float = 8.0 # mm
HOLE_RADIUS: float = 4.0 # mm
HOLE_COUNT: int = 8 # number of bolt holes
FILLET_R: float = 2.0 # edge fillet radius


def main() -> None:
reset_default_context()

# ── 1. Draw the base-plate profile and extrude ───────────────────────
# A simple rectangle profile — no cut-out in the sketch because we
# subtract the bolt holes as separate boolean operations later.
plate_sketch = Sketch(name="PlateProfile", plane="XY")
plate_sketch.rect(PLATE_LENGTH, PLATE_WIDTH)

plate = Part(name="BracketBase")
plate.extrude(plate_sketch, depth=PLATE_THICK, name="BasePlate")
print(f"✅ Base plate extruded {plate.feature_id}")

# ── 2. Create a single bolt-hole cylinder ────────────────────────────
# The hole is taller than the plate so the boolean cut goes all the way
# through regardless of floating-point edge cases.
bolt_hole = Part(name="BoltHole")
bolt_hole.cylinder(radius=HOLE_RADIUS, height=PLATE_THICK + 2)
print(f"✅ Bolt hole cylinder {bolt_hole.feature_id}")

# ── 3. Subtract the single hole from the plate ───────────────────────
plate.cut(bolt_hole, name="FirstHoleCut")
print(f"✅ First hole cut {plate.feature_id}")

# ── 4. Repeat the hole along the plate length ────────────────────────
# Spacing = total available span divided equally between holes.
spacing = PLATE_LENGTH / HOLE_COUNT
plate.linear_pattern(
direction=(1.0, 0.0, 0.0),
count=HOLE_COUNT,
spacing=spacing,
name="BoltHolePattern",
)
print(f"✅ Bolt hole pattern {plate.feature_id}")

# ── 5. Round the long edges of the plate ────────────────────────────
plate.fillet(edges="top", radius=FILLET_R, name="EdgeFillet")
print(f"✅ Edge fillet {plate.feature_id}")

# ── 6. Inspect the feature tree ─────────────────────────────────────
ctx = get_default_context()
node_count = len(ctx.tree.nodes)
print(f"\nFeature tree: {node_count} nodes")
for node in ctx.tree.nodes.values():
deps = ", ".join(node.depends_on) if node.depends_on else "—"
print(f" [{node.status:>10}] {node.id:12} {node.operation:20} deps=[{deps}]")

# ── 7. Write tree JSON for inspection / replay ────────────────────────
json_path = Path(tempfile.gettempdir()) / "bracket_tree.json"
json_path.write_text(json.dumps(ctx.tree.model_dump(), indent=2))
print(f"\n✅ Tree JSON → {json_path}")

# ── 8. Export the finished part to STEP ──────────────────────────────
step_path = Path(tempfile.gettempdir()) / "bracket.step"
plate.export(str(step_path))
print(f"✅ STEP export → {step_path}")


if __name__ == "__main__":
main()
42 changes: 42 additions & 0 deletions examples/03_rest_api_client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Example 03 — REST API Client

Demonstrates how to talk to the **OpenCAD Kernel REST API** directly from Python
using the standard `urllib` library (no third-party dependencies beyond the
installed package).

## What this example shows

- Health-checking all four backend services
- Listing available operations
- Creating shapes via `POST /operations/{name}`
- Fetching the mesh for a shape
- Retrieving topology (face/edge references)
- Running an operation replay

## Requirements

All four backend services must be running. Start them with:

```bash
python -m uvicorn opencad_kernel.api:app --reload --port 8000
python -m uvicorn opencad_solver.api:app --reload --port 8001
python -m uvicorn opencad_tree.api:app --reload --port 8002
python -m uvicorn opencad_agent.api:app --reload --port 8003
```

## Run

```bash
python examples/03_rest_api_client/client.py
```

## Configuration

Override the default service URLs with environment variables:

| Variable | Default |
|----------|---------|
| `KERNEL_URL` | `http://127.0.0.1:8000` |
| `SOLVER_URL` | `http://127.0.0.1:8001` |
| `TREE_URL` | `http://127.0.0.1:8002` |
| `AGENT_URL` | `http://127.0.0.1:8003` |
Loading