Skip to content

Commit d566036

Browse files
committed
Add documentation catalog
1 parent 70db7ce commit d566036

23 files changed

+978
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Alignment and Stacking
2+
3+
Position geometry relative to the origin and arrange shapes along an axis.
4+
5+
## Overview
6+
7+
In Cadova, alignment is a way to control how geometry is positioned relative to the coordinate system's origin. Since different shapes are defined differently, some centered, some corner-based, aligning them explicitly is often the easiest way to place them where you want.
8+
9+
For example, a `Circle(radius: 10)` is centered at the origin by default, but a `Rectangle([20, 10])` extends from the origin toward the top right. Using alignment helps you standardize and simplify placement:
10+
11+
```swift
12+
Rectangle([20, 10])
13+
.aligned(at: .centerX, .bottom)
14+
```
15+
16+
This centers the rectangle along the X-axis and aligns its bottom edge with Y = 0.
17+
18+
## How `.aligned(at:)` works
19+
20+
The `.aligned(at:)` method repositions geometry by translating it so that parts of its *bounding box* align to the coordinate system origin, based on the criteria you provide. The geometry isn't clipped, resized, or modified, just moved so it aligns as requested. You can align any geometry, including complex compositions, boolean operations, or custom components.
21+
22+
You align along one or more axes:
23+
24+
```swift
25+
Box([30, 40, 50])
26+
.aligned(at: .centerX, .maxY, .bottom)
27+
```
28+
29+
This centers the box in X, moves the back to Y = 0, and aligns the bottom of the Z axis to Z = 0. If you provide multiple alignments for the same axis, the last one wins.
30+
31+
## Alignment Presets
32+
33+
Cadova provides a set of readable alignment constants so you don't need to manually calculate anything. These include:
34+
35+
- `.minX`, `.centerX`, `.maxX`, `.left`, `.right`
36+
- `.minY`, `.centerY`, `.maxY`
37+
- `.minZ`, `.centerZ`, `.maxZ`
38+
- `.top`, `.bottom` (Y axis in 2D, Z axis in 3D)
39+
- `.center` (all axes), `.centerXY`
40+
41+
## Practical Examples
42+
43+
### Center a rectangle
44+
45+
```swift
46+
Rectangle([20, 10])
47+
.aligned(at: .center)
48+
```
49+
50+
### Align the bottom (min Y) of a circle to the origin
51+
52+
```swift
53+
Circle(radius: 10)
54+
.aligned(at: .bottom)
55+
```
56+
57+
### Center a shape horizontally and align to the top
58+
59+
```swift
60+
Rectangle([50, 20])
61+
.aligned(at: .centerX, .top)
62+
```
63+
64+
## Stack
65+
66+
``Stack`` is a container that arranges its contents one after another along a given axis like `.x`, `.y`, or `.z`. It avoids overlap by using bounding boxes, and positions items relative to the origin using alignment on the *non-stacking* axes.
67+
68+
### Basic Usage
69+
70+
```swift
71+
Stack(.z, spacing: 2, alignment: .centerX) {
72+
Cylinder(radius: 5, height: 1)
73+
Box([10, 10, 3])
74+
}
75+
```
76+
77+
This stacks a cylinder and a box vertically. Each item is spaced 2 mm apart, centered in X, and Y positioning is left unchanged.
78+
79+
### Axis and Alignment
80+
81+
- `axis` determines the direction of stacking.
82+
- `spacing` is `0` by default, meaning items touch edge-to-edge. Positive values add space between them.
83+
- `alignment` applies only to *non-stacking axes you explicitly specify*. Unspecified axes are left unchanged and the stacking axis is ignored. You can use `.center`, `.left`, `.bottom`, etc., just like with `.aligned(at:)`.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# ``Cadova``
2+
3+
A Swift DSL for creating precise, parametric 3D models for 3D printing.
4+
5+
@Metadata {
6+
@DisplayName("Cadova")
7+
}
8+
9+
## Overview
10+
11+
Cadova is a Swift library for constructing 3D models programmatically — with a focus on 3D printing. It builds on the ideas of OpenSCAD, but replaces its limited language with the power and elegance of Swift. Inspired by SwiftUI and designed for developers who want a better way to build models through code, Cadova is cross-platform and works on macOS, Linux, and Windows.
12+
13+
![A 3D model created with Cadova](home-hero)
14+
15+
## Topics
16+
17+
### Essentials
18+
19+
- <doc:GettingStarted>
20+
- <doc:WhatIsCadova>
21+
22+
### Core Concepts
23+
24+
- <doc:Geometry>
25+
- <doc:VectorsAndAngles>
26+
- <doc:AlignmentAndStacking>
27+
- <doc:Environment>
28+
- <doc:ModelAndProject>
29+
30+
### Guides
31+
32+
- <doc:WorkingWithParts>
33+
- <doc:Examples>
34+
35+
### Under the Hood
36+
37+
- <doc:Internals>
38+
- <doc:Troubleshooting>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Environment
2+
3+
Use environment values to control modeling behavior across geometry trees.
4+
5+
## Overview
6+
7+
Cadova's ``EnvironmentValues`` system provides a clean, declarative way to control modeling behavior across entire geometry trees, much like SwiftUI. This allows settings like resolution, tolerance, and material to apply consistently and implicitly, reducing the need for repetitive parameters in your modeling code. Environment values wrap around geometry and propagate through the tree unless explicitly overridden.
8+
9+
Cadova has several built-in environment settings. For example, segmentation controls the number of straight segments used for curved surfaces like circles and curves. Other settings include the fill rule for polygons, the miter limit for offsets, and the maximum twist rate for sweeps, among others.
10+
11+
## What Is the Environment For?
12+
13+
The environment injects shared configuration into a subtree of your geometry. It flows down the tree — or rather *wraps around* the geometry it's attached to. Any geometry inside will receive those values, unless they're overridden further in.
14+
15+
```swift
16+
Sphere(radius: 3)
17+
.adding {
18+
Cylinder(diameter: 2, height: 1)
19+
}
20+
.withSegmentation(minAngle: 1°, minSize: 0.5)
21+
.adding {
22+
Circle(radius: 2).revolved()
23+
}
24+
```
25+
26+
In this example, the segmentation settings apply to the sphere and the cylinder, but not the circle — because the `.withSegmentation(...)` is only applied to the subtree above it.
27+
28+
This system makes it easy to apply shared settings without passing explicit parameters to every single node.
29+
30+
## Reading Environment Values
31+
32+
There are two primary ways to read values from the environment:
33+
34+
### Using `.readingEnvironment(...)`
35+
36+
This modifier reads a value from the environment and passes it into a closure:
37+
38+
```swift
39+
Box(2)
40+
.readingEnvironment(\.tolerance) { box, tolerance in
41+
box.resized(x: 1 + tolerance)
42+
}
43+
// ...
44+
.withTolerance(0.5)
45+
```
46+
47+
Use this when you want to adjust an existing geometry based on the environment context.
48+
49+
### Using the `@Environment` Property Wrapper
50+
51+
If you're defining your own ``Shape2D`` or ``Shape3D``, you can use the `@Environment` property wrapper to access values directly:
52+
53+
```swift
54+
struct MyShape: Shape3D {
55+
@Environment(\.tolerance) var tolerance
56+
57+
var body: any Geometry3D {
58+
Box(x: 10.0 + tolerance, y: 12.0 + tolerance, z: 4)
59+
}
60+
}
61+
62+
await Model("shape") {
63+
MyShape()
64+
.withTolerance(0.3)
65+
}
66+
```
67+
68+
This works much like SwiftUI's `@Environment` and is ideal for defining reusable parametric shapes that adapt to configuration. You can also use it inside geometry builders:
69+
70+
```swift
71+
Box(10)
72+
.aligned(at: .centerXY)
73+
.subtracting {
74+
@Environment(\.tolerance) var tolerance
75+
Cylinder(diameter: 5.0 + tolerance, height: 10)
76+
}
77+
```
78+
79+
## Custom Values
80+
81+
You can define your own environment values. This is useful for advanced users and custom geometry behavior.
82+
83+
```swift
84+
extension EnvironmentValues {
85+
private static let key = Key("MyName.MyCustomValue")
86+
87+
var myCustomValue: Double? {
88+
get { self[Self.key] as? Double }
89+
set { self[Self.key] = newValue }
90+
}
91+
}
92+
93+
extension Geometry {
94+
func withMyCustomValue(_ value: Double) -> D.Geometry {
95+
withEnvironment { $0.myCustomValue = value }
96+
}
97+
}
98+
```
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Examples
2+
3+
A collection of example models demonstrating Cadova's features.
4+
5+
## Chamfer
6+
7+
![A chamfered shape created from two circles](example-chamfer)
8+
9+
```swift
10+
await Model("chamfer") {
11+
Circle(diameter: 12)
12+
.clonedAt(x: 8)
13+
.rounded(insideRadius: 2)
14+
.extruded(height: 7, topEdge: .chamfer(depth: 2))
15+
}
16+
```
17+
18+
## Colors and materials
19+
20+
![Three shapes with different colors and materials](example-colors-materials)
21+
22+
```swift
23+
await Model("stack-materials") {
24+
Stack(.x, spacing: 2, alignment: .center, .bottom) {
25+
Sphere(diameter: 10)
26+
.colored(.darkOrange)
27+
Cylinder(diameter: 8, height: 12)
28+
.withMaterial(.glossyPlastic(.mediumSeaGreen))
29+
Box(x: 12, y: 8, z: 15)
30+
.withMaterial(.steel)
31+
}
32+
}
33+
```
34+
35+
## Swept text
36+
37+
![Text swept along a bezier curve](example-swept-text)
38+
39+
```swift
40+
await Model("swept-text") {
41+
Text("Cadova")
42+
.withFont("Futura", style: "Condensed Medium", size: 10)
43+
.wrappedAroundCircle(spanning: 230°..<310°)
44+
.aligned(at: .centerX, .bottom)
45+
.swept(along: BezierPath {
46+
curve(
47+
controlX: 20, controlY: 10, controlZ: 3,
48+
endX: 20, endY: 30, endZ: 12
49+
)
50+
})
51+
}
52+
```
53+
54+
## Loft
55+
56+
![A lofted shape transitioning between profiles](example-loft)
57+
58+
```swift
59+
await Model("loft") {
60+
Loft(interpolation: .easeInOut) {
61+
layer(z: 0) {
62+
Ring(outerDiameter: 20, innerDiameter: 12)
63+
}
64+
layer(z: 30) {
65+
Rectangle(x: 25, y: 6)
66+
.aligned(at: .center)
67+
.cloned { $0.rotated(90°) }
68+
.subtracting {
69+
RegularPolygon(sideCount: 8, circumradius: 2)
70+
}
71+
}
72+
layer(z: 35) {
73+
Ring(outerDiameter: 12, innerDiameter: 10)
74+
}
75+
}
76+
}
77+
```
78+
79+
## Circular overhang
80+
81+
![Overhang-safe circles for FDM printing](example-circular-overhang)
82+
83+
```swift
84+
await Model("circular-overhang") {
85+
Stack(.x, spacing: 5) {
86+
Box(20)
87+
.aligned(at: .centerXY)
88+
.subtracting {
89+
Cylinder(diameter: 10, height: 20)
90+
.overhangSafe(.teardrop)
91+
}
92+
.rotated(x: -90°)
93+
94+
Circle(diameter: 20)
95+
.overhangSafe(.bridge)
96+
.extruded(height: 20)
97+
.rotated(x: -90°)
98+
}
99+
.aligned(at: .minZ)
100+
}
101+
```
102+
103+
Using `overhangSafe(_:)` makes a circle or cylinder extend its shape to work better with FDM 3D printing. The shapes are extended in the right direction automatically, using the geometry's world transform, pointing downward for additive geometry and upward for subtractive geometry (holes).
104+
105+
## Table
106+
107+
![A table with lofted legs](example-table)
108+
109+
```swift
110+
await Model("table") {
111+
let height = 7.0
112+
let footDiameter = 2.0
113+
let footHeight = 2.0
114+
let legDiameter = 1.0
115+
let topThickness = 1.0
116+
let size = Vector2D(5, 7)
117+
118+
Loft(interpolation: .smootherstep) {
119+
layer(z: 0) { Circle(diameter: footDiameter) }
120+
layer(z: footHeight) { Circle(diameter: legDiameter) }
121+
layer(z: height) { Circle(diameter: legDiameter) }
122+
}
123+
.translated(size / 2, z: 0)
124+
.symmetry(over: .xy)
125+
.sliced(atZ: height - 1e-6) { base, slice in
126+
base
127+
slice.extruded(height: topThickness)
128+
.convexHull()
129+
.translated(z: height)
130+
}
131+
}
132+
```

0 commit comments

Comments
 (0)