From 9da2a36efa849cb866fa3636f0bfdc0a438d7b14 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 11 Sep 2026 10:22:20 +0300 Subject: [PATCH 1/2] Create regions from exact closed profiles and connected selected edges --- src/app/commands/draw.rs | 44 ++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/app/commands/draw.rs b/src/app/commands/draw.rs index cf848ff7b..d3f73afb9 100644 --- a/src/app/commands/draw.rs +++ b/src/app/commands/draw.rs @@ -1114,8 +1114,7 @@ impl OpenCADStudio { return Some(self.fit_spline()); } - // REGION — convert selected closed boundaries (closed polylines / - // circles) into Region entities (one wire loop each). + // REGION — convert exact closed planar profiles into regions. "REGION" | "REG" => { if self.tabs[i].scene.selected_entities().is_empty() { use crate::modules::draw::select::SelectObjectsCommand; @@ -1129,12 +1128,7 @@ impl OpenCADStudio { let mut regions = Vec::new(); let mut sources = Vec::new(); for (handle, e) in self.tabs[i].scene.selected_entities().iter() { - let supported = matches!( - e, - acadrust::EntityType::LwPolyline(pl) - if pl.is_closed && pl.vertices.len() >= 3 - ) || matches!(e, acadrust::EntityType::Circle(_)); - if supported { + if !matches!(e, acadrust::EntityType::Region(_)) { let Some((plane, loops, true)) = crate::scene::model::presspull_model::profile_geometry(e) else { @@ -1154,6 +1148,40 @@ impl OpenCADStudio { sources.push(*handle); } } + // Assemble remaining selected open edges on the active plane. + // Face discovery and exact curve reconstruction reuse the + // same kernel-backed boundary path used by area selection. + let working_plane = self.tabs[i].ucs_xform().working_plane(); + let selected_handles: rustc_hash::FxHashSet<_> = self.tabs[i].scene + .selected_entities().iter().map(|(handle, _)| *handle).collect(); + let mut boundary_sources = self.tabs[i].scene + .boundary_sources_on_plane(working_plane, 1.0e-6); + boundary_sources.retain(|handle, _| { + selected_handles.contains(handle) && !sources.contains(handle) + }); + let plane = cadkernel::space::Plane::from_axes( + working_plane.origin.to_array(), + working_plane.x.to_array(), + working_plane.y.to_array(), + ); + for ring in crate::scene::boundary_faces(&boundary_sources, 1.0e-6) { + let paths = crate::scene::exact_hatch_paths( + std::slice::from_ref(&ring), &[true], &boundary_sources, 1.0e-6, + ); + let Some(path) = paths.first() else { continue; }; + let Some(curves) = path.edges.iter() + .map(crate::entities::hatch::edge_curve).collect::>>() + else { continue; }; + let Some(body) = cadkernel::brep::planar_region(plane, &[curves]) + else { continue; }; + let mut region = Region::new(); + region.point_of_reference = Vector3::new( + plane.origin[0], plane.origin[1], plane.origin[2], + ); + region.common.layer = self.tabs[i].active_layer.clone(); + regions.push((region, body)); + sources.extend(crate::scene::ring_source_handles(&ring, &boundary_sources)); + } if regions.is_empty() { self.command_line .push_error(crate::t!("REGION: select closed polylines or circles.").as_ref()); From 2486adbb6775e7dc825ee630bff1e7c5c7014f7a Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 11 Sep 2026 11:47:41 +0300 Subject: [PATCH 2/2] Create regions on the shared plane of selected open curves --- src/app/commands/draw.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/app/commands/draw.rs b/src/app/commands/draw.rs index d3f73afb9..fdce2b7d4 100644 --- a/src/app/commands/draw.rs +++ b/src/app/commands/draw.rs @@ -1148,10 +1148,17 @@ impl OpenCADStudio { sources.push(*handle); } } - // Assemble remaining selected open edges on the active plane. - // Face discovery and exact curve reconstruction reuse the - // same kernel-backed boundary path used by area selection. - let working_plane = self.tabs[i].ucs_xform().working_plane(); + // Open inputs must share one geometric plane, independently of the UCS. + let open_curves: Vec<_> = self.tabs[i].scene.selected_entities().iter() + .filter(|(handle, _)| !sources.contains(handle)) + .filter_map(|(_, entity)| crate::entities::curve::entity_curve(entity)) + .collect(); + if let Some(plane) = cadkernel::space::common_curve_plane(&open_curves, 1.0e-6) { + let working_plane = crate::command::WorkingPlane::new( + glam::DVec3::from_array(plane.origin), + glam::DVec3::from_array(plane.x_axis), + glam::DVec3::from_array(plane.y_axis), + ); let selected_handles: rustc_hash::FxHashSet<_> = self.tabs[i].scene .selected_entities().iter().map(|(handle, _)| *handle).collect(); let mut boundary_sources = self.tabs[i].scene @@ -1159,11 +1166,6 @@ impl OpenCADStudio { boundary_sources.retain(|handle, _| { selected_handles.contains(handle) && !sources.contains(handle) }); - let plane = cadkernel::space::Plane::from_axes( - working_plane.origin.to_array(), - working_plane.x.to_array(), - working_plane.y.to_array(), - ); for ring in crate::scene::boundary_faces(&boundary_sources, 1.0e-6) { let paths = crate::scene::exact_hatch_paths( std::slice::from_ref(&ring), &[true], &boundary_sources, 1.0e-6, @@ -1182,6 +1184,9 @@ impl OpenCADStudio { regions.push((region, body)); sources.extend(crate::scene::ring_source_handles(&ring, &boundary_sources)); } + } else if !open_curves.is_empty() { + self.command_line.push_error("REGION: open objects must form coplanar, noncollinear boundaries."); + } if regions.is_empty() { self.command_line .push_error(crate::t!("REGION: select closed polylines or circles.").as_ref());