Skip to content

v0.12.0

Choose a tag to compare

@knipknap knipknap released this 23 Jun 01:27
· 50 commits to main since this release

v0.12.0

Highlights

  • Layered architecture: geoopscnc layering established with HSM motion assembly moved from geo to ops::assembly.
  • CNC terminology: Laser-centric naming replaced with CNC-milling terminology (cut_speedfeed_rate, travel_speedrapid_rate, air_assistCoolantMode, etc.).
  • 2D/3D naming convention: All functions now follow bare = 2D, _3d = 3D.
  • Polygon/Polyline module split: Polyline functions moved from polygon to dedicated polyline module.
  • New geometry primitives: Medial axis routing, A* pathfinding, FEM meshing, spiral/helix/ramp/trochoid primitives, polylabel, offset improvements.
  • New CNC module: raygeo.cnc with Tool, StateStrategy, EntryStrategy, and adaptive_clear_hsm orchestrator.

Breaking changes

See the full migration guide below.

CNC terminology rename

  • State.cut_speedState.feed_rate
  • State.travel_speedState.rapid_rate
  • State.active_laser_uidState.active_head_uid
  • State.spindle_speedState.spindle_rpm
  • Ops.set_cut_speed()Ops.set_feed_rate()
  • Ops.set_travel_speed()Ops.set_rapid_rate()
  • Ops.set_laser()Ops.set_head()
  • Ops.set_spindle_speed()Ops.set_spindle_rpm()
  • ops.travel_distance()ops.distance()
  • enable_air_assist() removed → use set_coolant()
  • estimate_time/estimate_command_times parameter rename: default_cut_speeddefault_feed_rate, default_travel_speeddefault_rapid_rate
  • get_frame(speed=)get_frame(feed_rate=)
  • CommandType.SetCutSpeedSetFeedRate, SetTravelSpeedSetRapidRate, SetLaserSetHead, SetSpindleSpeedSetSpindleRpm
  • CommandType.EnableAirAssist/DisableAirAssist removed → SetCoolant

Coolant mode enum

  • State.air_assist: boolState.coolant: Option<CoolantMode>
  • CoolantMode values: Off, Flood, Mist, Air

ScanMode uppercase

  • ScanMode.SegmentedScanMode.SEGMENTED
  • ScanMode.FullSweepScanMode.FULL_SWEEP

JoinStyle enum

  • offset_polygon(..., join_style="round")offset_polygon(..., join_style=JoinStyle.Round)
  • offset_contour_group(..., join_style="miter")offset_contour_group(..., join_style=JoinStyle.Miter)
  • Rust: offset_polygon_with_style renamed back to offset_polygon

Rect/Rect3D struct change

  • Rect(x1, y1, x2, y2)Rect { min: Point, max: Point }
  • Rect.0/.1/.2/.3Rect.min.x/.y, Rect.max.x/.y
  • Rect3D.x_min/.x_max/.y_min/.y_max/.z_min/.z_maxRect3D.min/.max

2D/3D naming

  • circumcenter is now 2D; circumcenter_3d is 3D (swap)
  • simplify_polylinesimplify_polyline_3d (2D wrapper removed)
  • midpointmidpoint_3d, transform_pointtransform_point_3d
  • normal_from_clockwisenormal_from_clockwise_3d
  • generate_helixgenerate_helix_3d, generate_spiralgenerate_spiral_3d, generate_rampgenerate_ramp_3d, trochoid_alongtrochoid_along_3d
  • smooth_polylinesmooth_polyline_3d
  • flatten_to_pointsflatten_to_points_3d
  • resample_polyline (3D) moved from geo.algo.smooth to geo.shape.polygon3d

Module reorganization

  • ops.lead_in_outops.assembly.lead_in_out (re-exported)
  • ops.overscanops.assembly.overscan (re-exported)
  • ops.tabsops.assembly.tabs (re-exported)
  • ops.polylineops.assembly.polyline (re-exported)
  • ops.rasterops.assembly.raster (re-exported)
  • Polygon module split: polyline functions moved to geo.shape.polyline

New features

CNC orchestration (raygeo.cnc)

  • Tool, ToolShape, ToolMaterial classes
  • StateStrategy with constant() factory
  • EntryStrategy with helix/ramp support
  • adaptive_clear_hsm — full HSM clearing orchestrator

Geometry additions

  • Medial axis computation and MAT path routing
  • A* pathfinding with rasterized grid
  • FEM PDE meshing with Laplace solver
  • Flat Archimedean spiral primitive
  • ClearedArea with fragments(), bites(), bite_in_direction(), incorporate(), all_bites(), remaining_in_inset(), add_cleared_polygons()
  • Adaptive entry (helix/spiral + zigzag ramp) and adaptive wavefronts
  • Polylabel: find_largest_circle, pole-of-inaccessibility algorithm
  • Offset improvements: find_deepest_cores, compute_inset_region, apply_minimum_curvature
  • JoinStyle enum (Miter/Round/Square) for offset operations
  • Fillet arc ends, safe sweep end detection
  • walk_along_polyline_3d, walk_along_polygon_3d, fillet_polyline_3d
  • polyline_to_ops, link_passes, find_pass_entry/find_pass_exit
  • apply_lead_in_out, apply_overscan, apply_tab_gaps, apply_tab_power
  • get_ray_polygon_intersection, does_line_cross_polygon, get_segment_segment_distance
  • get_polyline_closest_point, trim_polyline_at, trim_polyline_angular_ends
  • split_polyline_at_v_junctions, resample_polygon
  • get_polygon_boundary_distance, get_polygon_vertex_centroid
  • shortcut_path smoothing, shortcut + Gaussian relaxation
  • deduplicate_polyline_3d, get_polyline_end_tangent_3d
  • fit_cubic_bezier, nearest_tangent_circle_on_bezier
  • arc_through_point, generate_arc_between_two_points, generate_linking_arc
  • interpolated_segment_3d, circumcenter_2d
  • get_polygon_area_3d, get_polygon_signed_area_3d
  • order_nearest_neighbor, blend_tangent, get_polyline_turn_sign
  • compute_valid_tool_area

Ops additions

  • Ops::apply_state(&State) for domain-neutral state emission
  • Ops.distance(), Ops.cut_distance()
  • CoolantMode enum

Migration Guide

Migration Guide

1. 2D/3D Naming Convention

Function names now consistently follow the rule: bare = 2D, _3d = 3D.

Tier 1 — Pair inversions

circumcenter / circumcenter_3d swap

The 3D circumcenter (takes Point3D) was renamed from circumcenter to circumcenter_3d. The 2D circumcenter (takes Point) was renamed from circumcenter_2d to circumcenter.

 // Rust
-use raygeo::geo::shape::point::circumcenter;     // was 3D, now 2D
+use raygeo::geo::shape::point::circumcenter_3d;  // 3D circumcenter (returns Option<Point3D>)
+use raygeo::geo::shape::point::circumcenter;      // 2D circumcenter (returns (Point, f64))
 # Python
-from raygeo.geo.shape.point import circumcenter        # was 3D
-from raygeo.geo.shape.point import circumcenter_2d      # was 2D
+from raygeo.geo.shape.point import circumcenter_3d     # 3D circumcenter
+from raygeo.geo.shape.point import circumcenter         # 2D circumcenter

simplify_polylinesimplify_polyline_3d

 // Rust
-use crate::geo::algo::simplify::simplify_polyline;
+use crate::geo::algo::simplify::simplify_polyline_3d;

The Python 2D wrapper was removed entirely — only simplify_polyline_3d exists:

 # Python
-from raygeo.geo.algo.simplify import simplify_polyline  # removed
-from raygeo.geo.algo.simplify import simplify_polyline_3d
+from raygeo.geo.algo.simplify import simplify_polyline_3d  # only name

Convert 2D calls by adding z=0:

-result = simplify_polyline([(0, 0), (5, 5), (10, 0)], tolerance=0.5)
+result = simplify_polyline_3d([(0, 0, 0), (5, 5, 0), (10, 0, 0)], tolerance=0.5)

Tier 3 — Misnamed _2d helpers → _xy

Private functions that accept Point3D but only measure XY:

File Old name New name
src/ops/assembly/tabs.rs bezier_arc_length_2d bezier_arc_length_xy
src/ops/assembly/tabs.rs distance_2d distance_xy
src/ops/optimize.rs dist_2d dist_xy

No Python impact (all private).

Tier 4 — 3D orphans → _3d suffix

Functions that return 3D types and have no 2D counterpart:

Point operations

 // Rust
-use crate::geo::shape::point::midpoint;
-use crate::geo::shape::point::transform_point;
+use crate::geo::shape::point::midpoint_3d;
+use crate::geo::shape::point::transform_point_3d;
 # Python
-from raygeo.geo.shape.point import midpoint
-from raygeo.geo.shape.point import transform_point
+from raygeo.geo.shape.point import midpoint_3d
+from raygeo.geo.shape.point import transform_point_3d

Arc operations

 // Rust
-use crate::geo::shape::arc::normal_from_clockwise;
+use crate::geo::shape::arc::normal_from_clockwise_3d;

No Python impact (no direct binding).

Helix / Spiral / Ramp / Trochoid

 // Rust
-use crate::geo::algo::helix::generate_helix;
-use crate::geo::algo::spiral::generate_spiral;
-use crate::geo::algo::ramp::generate_ramp;
-use crate::geo::algo::trochoid::trochoid_along;
+use crate::geo::algo::helix::generate_helix_3d;
+use crate::geo::algo::spiral::generate_spiral_3d;
+use crate::geo::algo::ramp::generate_ramp_3d;
+use crate::geo::algo::trochoid::trochoid_along_3d;
 # Python
-from raygeo.geo.algo.helix import generate_helix
-from raygeo.geo.algo.spiral import generate_spiral
-from raygeo.geo.algo.ramp import generate_ramp
-from raygeo.geo.algo.trochoid import trochoid_along
+from raygeo.geo.algo.helix import generate_helix_3d
+from raygeo.geo.algo.spiral import generate_spiral_3d
+from raygeo.geo.algo.ramp import generate_ramp_3d
+from raygeo.geo.algo.trochoid import trochoid_along_3d

Smoothing

 // Rust
-use crate::geo::algo::smooth::smooth_polyline;
+use crate::geo::algo::smooth::smooth_polyline_3d;
 # Python
-from raygeo.geo.algo.smooth import smooth_polyline
+from raygeo.geo.algo.smooth import smooth_polyline_3d

Fitting

 // Rust
-use crate::geo::algo::fitting::flatten_to_points;
+use crate::geo::algo::fitting::flatten_to_points_3d;
 # Python
-from raygeo.geo.algo.fitting import flatten_to_points
+from raygeo.geo.algo.fitting import flatten_to_points_3d

Tier 5 — Rect re-export

Rect is now exported from the crate root alongside Rect3D:

 // Rust
-use raygeo::Rect3D;
+use raygeo::{Rect, Rect3D};

2. Polygon / Polyline module split

polygon.rs was split into separate polygon and polyline modules. If you import polyline functions from the polygon module, update your imports.

Rust imports

-use crate::geo::shape::polygon::get_polyline_bounds;
-use crate::geo::shape::polygon::get_polyline_closest_point;
-use crate::geo::shape::polygon::resample_polyline;
-use crate::geo::shape::polygon::split_polyline_at_v_junctions;
-use crate::geo::shape::polygon::trim_polyline_angular_ends;
-use crate::geo::shape::polygon::trim_polyline_at;
+use crate::geo::shape::polyline::get_polyline_bounds;
+use crate::geo::shape::polyline::get_polyline_closest_point;
+use crate::geo::shape::polyline::resample_polyline;
+use crate::geo::shape::polyline::split_polyline_at_v_junctions;
+use crate::geo::shape::polyline::trim_polyline_angular_ends;
+use crate::geo::shape::polyline::trim_polyline_at;

Python imports

-from raygeo.geo.shape.polygon import get_polyline_bounds
-from raygeo.geo.shape.polygon import get_polyline_closest_point
-from raygeo.geo.shape.polygon import resample_polyline
-from raygeo.geo.shape.polygon import split_polyline_at_v_junctions
-from raygeo.geo.shape.polygon import trim_polyline_angular_ends
-from raygeo.geo.shape.polygon import trim_polyline_at
+from raygeo.geo.shape.polyline import get_polyline_bounds
+from raygeo.geo.shape.polyline import get_polyline_closest_point
+from raygeo.geo.shape.polyline import resample_polyline
+from raygeo.geo.shape.polyline import split_polyline_at_v_junctions
+from raygeo.geo.shape.polyline import trim_polyline_angular_ends
+from raygeo.geo.shape.polyline import trim_polyline_at

3. resample_polyline_3d relocation

The 3D resample_polyline was moved from smooth.rs to polygon3d.rs and renamed to resample_polyline_3d for consistency.

Rust

-use crate::geo::algo::smooth::resample_polyline;
+use crate::geo::shape::polygon3d::resample_polyline_3d;

Python

-from raygeo.geo.algo.smooth import resample_polyline
+from raygeo.geo.shape.polygon3d import resample_polyline_3d

The 2D resample_polyline in raygeo.geo.shape.polyline is unchanged.


4. CNC terminology rename

Laser-specific terminology was replaced with CNC-milling terminology across the API.

Python State

 from raygeo.ops.state import State

-s = State(power=1.0, air_assist=False, cut_speed=1200, travel_speed=8000, active_laser_uid="co2", spindle_speed=18000)
+s = State(power=1.0, feed_rate=1200, rapid_rate=8000, active_head_uid="spindle", spindle_rpm=18000)

Field renames:

Old name New name
air_assist removed — use coolant instead
cut_speed feed_rate
travel_speed rapid_rate
active_laser_uid active_head_uid
spindle_speed spindle_rpm

Rust State

 let s = State {
     power: 1.0,
-    air_assist: false,
-    cut_speed: Some(1200),
-    travel_speed: Some(8000),
-    active_laser_uid: Some("co2".into()),
-    spindle_speed: Some(18000),
+    coolant: Some(CoolantMode::Flood),
+    feed_rate: Some(1200),
+    rapid_rate: Some(8000),
+    active_head_uid: Some("spindle".into()),
+    spindle_rpm: Some(18000),
     ..Default::default()
 };

Ops methods

-ops.set_cut_speed(1200)
-ops.set_travel_speed(8000)
-ops.set_laser("co2")
-ops.set_spindle_speed(18000)
-ops.enable_air_assist(True)
+ops.set_feed_rate(1200)
+ops.set_rapid_rate(8000)
+ops.set_head("spindle")
+ops.set_spindle_rpm(18000)
+ops.set_coolant(CoolantMode.Flood)

estimate_time / estimate_command_times

-ops.estimate_time(default_cut_speed=1200.0, default_travel_speed=8000.0, acceleration=500.0)
+ops.estimate_time(default_feed_rate=1200.0, default_rapid_rate=8000.0, acceleration=500.0)

get_frame

-ops.get_frame(power=1.0, speed=1200.0)
+ops.get_frame(power=1.0, feed_rate=1200.0)

travel_distancedistance

-ops.travel_distance()
+ops.distance()

CommandType enum (Python)

-CommandType.SetCutSpeed
-CommandType.SetTravelSpeed
-CommandType.SetLaser
-CommandType.SetSpindleSpeed
-CommandType.EnableAirAssist
-CommandType.DisableAirAssist
+CommandType.SetFeedRate
+CommandType.SetRapidRate
+CommandType.SetHead
+CommandType.SetSpindleRpm
+CommandType.SetCoolant

5. Coolant mode replaces air assist

The boolean air_assist field was replaced by a CoolantMode enum with four values.

Python

from raygeo.ops.state import CoolantMode

# Old
s = State(power=1.0, air_assist=True)
ops.enable_air_assist(True)

# New
s = State(power=1.0, coolant=CoolantMode.AIR)
ops.set_coolant(CoolantMode.AIR)

Rust

use crate::ops::state::CoolantMode;

// CoolantMode values: Off, Flood, Mist, Air
state.coolant = Some(CoolantMode::Air);
ops.set_coolant(CoolantMode::Air);

Serialization

The serialized dict key "air_assist" was removed. "coolant" is used instead:

# Old
cmd_dict["air_assist"]  # True / False

# New
cmd_dict["coolant"]  # "Off" | "Flood" | "Mist" | "Air"

6. ScanMode values are now uppercase

-ScanMode.Segmented
-ScanMode.FullSweep
+ScanMode.SEGMENTED
+ScanMode.FULL_SWEEP

This affects rasterize_power_modulation, rasterize_mask_scan, rasterize_mask_lines, and extract_zero_power_segments.


7. JoinStyle enum replaces string parameter

offset_polygon and offset_contour_group now take a JoinStyle enum instead of a string.

Python

from raygeo.geo.shape.polygon import JoinStyle

# Old
offset_polygon(poly, 5.0, join_style="round")
offset_contour_group(solid, holes, 5.0, join_style="miter")

# New
offset_polygon(poly, 5.0, join_style=JoinStyle.Round)
offset_contour_group(solid, holes, 5.0, join_style=JoinStyle.Miter)

Rust

use crate::geo::shape::polygon::JoinStyle;

// Old
offset_polygon_with_style(&poly, 5.0, JoinStyle::Round)

// New (function renamed back to offset_polygon)
offset_polygon(&poly, 5.0, JoinStyle::Round)

8. Rect / Rect3D field access

Rect and Rect3D were converted from tuple/flat-field structs to min/max structs using glam vector types.

Rust

 let r = compute_rect();
 // Old
-r.0  // x_min
-r.1  // y_min
-r.2  // x_max
-r.3  // y_max
+ r.min.x
+ r.min.y
+ r.max.x
+ r.max.y

 let r3 = compute_rect_3d();
 // Old
-r3.x_min
-r3.x_max
-r3.y_min
-r3.y_max
-r3.z_min
-r3.z_max
+ r3.min.x
+ r3.min.y
+ r3.max.z

Construction:

 // Old
-Rect(0.0, 0.0, 100.0, 100.0)
+Rect::new(0.0, 0.0, 100.0, 100.0)