Releases: tomasf/Cadova
Cadova 0.7.0
Anchors and Tags
- Anchor APIs generalized to 2D, with bridging between 2D and 3D anchors.
- New
Anchor.readingTransformsfor reading every transform recorded for an anchor. - Transforms chained directly onto a tag reference now move the reference in its local frame.
- New APIs for removing anchor and tag definitions.
Additions
- New
readingSurfacesfor querying where a line or line segment crosses a geometry's surface, with newLineSegmentandSurfaceCrossingvalue types. - New range-based
splitmethods, plus a 2Dsplitvariant that takes a mask. - New
modifyingBodyAndPartsto apply an operation to a geometry's body and its parts in one call. - New
readSamples(at:)for arc-length resampling of curves, plus a newtransformproperty onCurveSample.
Improvements
hidden()now preserves geometry dimensionality and result elements.- Items in exported 3MF files are sorted by their part names.
- Several named color constants whose values didn't match their names were changed to match common perception:
.lavender,.azure,.violet,.brown,.chocolate,.khaki,.crimson,.gold,.lime,.plum,.sandyBrown.
Fixes
- Edge-profile slicing and plane-local projection now correctly inherit the surrounding transform and environment, so profiles produced inside transformed scopes behave the same as profiles produced at the top level.
- Natural up direction no longer produces incorrect results when defined inside a transformed scope.
naturalUpDirectionXYAngleis now guarded against floating-point noise.
Cadova 0.6.1
New features
- Model filtering: a
Projectbuild can now be restricted to a subset of its models from the command line. Passing one or more--model NAME(or--model=NAME) flags causes aProjectto build only the named models; with no flags, every model is built as before. The flag may be repeated to include several (--model A --model B).
Names are matched against each model's full output-relative path, joined with /. For example, given:
Project {
Group("Brackets") {
Model("Left") { ... }
Model("Right") { ... }
}
Model("Base") { ... }
}use --model Brackets/Left to build just the left bracket, or --model Base for the top-level model. This means identically named models in different groups can be targeted independently.
- Tag introspection:
Tag.readingMembers(_:)andTag.map(_:)give access to individual tagged geometries instead of their union, with each member's captured world-space transform preserved. - 2D chamfering:
chamfered(insideDepth:outsideDepth:)andchamfered(depth:)onGeometry2D, similar to the existingroundedoperations.
Improvements
- Validation: vector components and color components (0.0–1.0) are now validated at the call site instead of propagating silently.
- Log formatting: error and warning log output now has emoji prefixes.
separated()dimensionality: the closure can now return geometry of a different dimensionality than the input.
Bug fixes
- Nested tag references: tag references defined deep in the tree are now merged correctly with same-named tags higher up, instead of the upstream definition silently overwriting the deeper one.
- Antipodal rotation reflection:
Transform3D.rotation(from:to:)no longer produces a reflection when the two directions are antipodal. - Edge profile orientation: an asymmetric 2D shape passed to
cuttingEdgeProfile(_:on:using:)/formingEdgeProfile(_:on:using:)now lands with consistent orientation across all six box sides. - Exponential shaping function: was inverted; now produces the correct curve.
- Loft islands: loft now properly validates that all layers have the same number of islands, instead of producing broken geometry.
Cadova 0.6.0
3D Smoothing
New smoothed(strength:) operation softens hard edges and corners. Lower values produce subtle softening; higher values give visibly rounder shapes.
Loft Improvements
Relative layers: Layers can now be placed relative to the previous layer using layer(zOffset:), in addition to absolute positions.
Better polygon lofting: Lofting between shapes with sharp corners (triangles, rectangles, etc.) now preserves corner vertices exactly during resampling. Previously this caused visible anomalies, as reported in #21.
Bug Fixes
- Fixed two bugs in
Trianglethat caused incorrect angle calculations and vertex placement for non-isosceles triangles. If you useTriangleas geometry in your models, retest them. - Fixed SVG import silently ignoring open paths.
Dependencies
Updated to Manifold-Swift 1.0, which includes Manifold 3.4.0.
Cadova 0.5.2
New Features
- Added
.easeInCubicand.easeOutCubicshaping functions
Bug Fixes
- Fixed twist extrusion producing one fewer segment than requested
- Fixed helix sweep segments not aligning evenly across revolutions
Cadova 0.5.1
ModelFileGenerator
Cadova 0.5.1 adds ModelFileGenerator, a new API for building models programmatically. While Model is designed for executable Swift packages for development of models, ModelFileGenerator is intended for embedding Cadova into other codebases such as GUI apps, server code, or any context where you need more control over how and where files are generated.
Use ModelFileGenerator.build(named:options:content:) to render geometry into a ModelFile. From there, you can access the file contents as Data via .data(), get a suggested filename via .suggestedFileName, or write directly to disk with .write(to:). If you're generating multiple files, you can reuse a ModelFileGenerator instance to benefit from caching.
let modelFile = try await ModelFileGenerator.build(named: "my-model") {
Box(x: 10, y: 10, z: 5)
}
let fileData = try await modelFile.data()Thanks to @iKenndac for contributing this feature.
3MF colors
Geometry without explicit colors no longer defaults to white in 3MF files. Bambu Studio now supports mapping 3MF colors to filaments, but prompts for this even when the entire model is a single color. With this change, uncolored geometry stays uncolored, which is more correct and avoids unnecessary prompts in Bambu Studio.
Cadova 0.5.0
SVG Import
Cadova 0.5 adds support for importing SVG as 2D geometry via Pelagos. Import is now generic over Dimensionality. Importing 3D models works as before, but new initializers for SVG have been added. These take the path or URL to an SVG file and two options:
scale:
.physical: The default. SVGs are imported according to standard SVG conversions. Physical units are preserved, so "1mm" in SVG becomes an actual millimeter in your model. Pixel units are scaled correspondingly: 1 pixel becomes ~0.265 mm (96 pixels per inch)..pixels: Pixels are converted to millimeters and physical units are scaled correspondingly. This can be more convenient when dealing with graphics designed for screens.
Regardless of the scale option, you can scale the result however you'd like after import.
origin:
.flipped: The default. The SVG is flipped along the Y axis so it appears the same way it normally would. This is needed because SVG starts Y at the top and Cadova starts it at the bottom..native: Preserves SVG coordinates as-is, which can look incorrect but can be easier to deal with in some cases.
The easiest way to embed an SVG into your Swift package is to create a resource directory and reference it in your target in Package.swift:
resources: [.embedInCode("resources")],You will get generated code in a struct called PackageResources. Pass these data arrays to Import:
Import(svg: PackageResources.snake_svg)A corresponding DataProtocol initializer has been added for Import<D3> so you can use the same approach for embedded 3D models as well.
Bug Fixes
- Fixed
wrappedAroundCirclecalculating incorrect radius when inferring from geometry bounds, causing geometry to only wrap halfway around the circle
Cadova 0.4.3
New Features
- 2D split and trimmed operations for cutting 2D geometry
offset()method onParametricCurvefilled()method onParametricCurvewhileAlignedoperation
Bug Fixes
- Fixed division by zero bugs in repeat operations
API Changes
Geometry2D.filled()has been renamed tofillingHoles()to avoid confusion with the newParametricCurve.filled(). The old name remains available but is deprecated.
Cadova 0.4.2
- Convert parametric curves into stroked 2D geometry with
curve.stroked(width:alignment:style:). Supports left/right/centered alignment, all line join styles (miter, bevel, square, round), and line cap styles (butt, round, square) - New
.withLineCapStyle()environment modifier for controlling stroke end caps - New
lofted()overload that accepts aLayerTransition - Added
inverted(reflects about center, swapping ease-in ↔ ease-out) andmirrored(geometric reflection, inverse function) properties
Cadova 0.4.1
New Features
Text Rendering Enhancements
Text rendering now uses Apus, bringing variable font support and improved typography controls.
Variable Fonts - Control font axes for precise typographic styling. Requires a variable font that supports the specified axes (e.g., Inter, Roboto Flex, SF Pro):
// Common axis modifiers
Text("Bold Condensed")
.withFontVariations(weight: 700, width: 75)
// Full control with FontVariation array
Text("Custom Style")
.withFontVariations([.weight(600), .width(85), .slant(-6)])
// Custom axes
Text("Graded")
.withFontVariations([FontVariation(tag: "GRAD", value: 50)])Tracking - Adjust uniform spacing between characters:
Text("SPACED OUT")
.withTracking(1) // Add 1mm between characters
Text("TIGHT")
.withTracking(-0.5) // Reduce spacingStroke Modifier
Convert 2D shapes to outlined strokes:
// Basic stroke
Circle(radius: 10)
.stroked(width: 2)
// Control alignment: .inside, .centered, or .outside
Rectangle([20, 10])
.stroked(width: 1, alignment: .inside)Debug Isolation
Temporarily isolate geometry during debugging:
Union {
complexShape1
complexShape2.only() // Only this shape will render
complexShape3
}Project Organization
Groups let you organize related models within a Project into a hierarchy. When exporting, groups create subdirectories to keep your output organized:
let project = Project {
Group("Enclosure") {
Model("Top") { topGeometry }
Model("Bottom") { bottomGeometry }
}
Group("Hardware") {
Model("Bracket") { bracketGeometry }
}
}
// Exports to:
// output/Enclosure/Top.3mf
// output/Enclosure/Bottom.3mf
// output/Hardware/Bracket.3mfCadova 0.4.0
New Part API
Breaking change: Parts are now created as reusable instances instead of using string names:
// Before
Box(10).inPart(named: "bracket", type: .solid)
// After
let bracket = Part("Bracket")
Box(10).inPart(bracket)The same Part instance used multiple times collects geometry into a single part. Different instances with the same name are treated as separate parts.
Parts can specify their semantic role and a default material applied to geometry that doesn't specify its own:
let bracket = Part("Bracket", semantic: .solid, color: .gray)
let insert = Part("Insert", color: .silver, metallicness: 0.9, roughness: 0.3)Ruler Visualization
New Ruler type for measuring distances:
Ruler(length: 100, interval: 10)