forked from 1b-agent/opencadstudio-upstream
CLIP on raster images and viewports, IMAGECLIP, VPCLIP, clip depth rules
- CLIP continues as IMAGECLIP on a raster image and as VPCLIP on a layout viewport, with the reference prompts and stored data. - Image clip boundaries sit on pixel centres, OFF only clears the display flag, and an inverted boundary cuts the image. - VPCLIP polygon (Arc/Length/Close/Undo), clipping object and Delete; the viewport shrinks to the boundary's exact extents, view kept. - XCLIP Clipdepth: Remove clears only the plane asked, a front plane behind the back one is rejected with the reference message. - Attached and imported images carry the reference display flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
857f2f2e58
commit
bd17e7e0d4
12 files changed
+569
-112
No files matched your search
@@ -471,8 +471,11 @@ impl OpenCADStudio {
|
||||
CmdResult::MviewCreateClipped {
|
||||
boundary,
|
||||
boundary_handle,
|
||||
target,
|
||||
} => {
|
||||
if let Some(task) = self.handle_mview_create_clipped(boundary, boundary_handle) {
|
||||
if !target.is_null() {
|
||||
self.handle_vpclip(target, boundary, boundary_handle);
|
||||
} else if let Some(task) = self.handle_mview_create_clipped(boundary, boundary_handle) {
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,101 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
/// VPCLIP: clip `viewport` to a new polygon (`boundary`) or a picked closed
|
||||
/// paper-space object (`boundary_handle`), or — with neither — delete its
|
||||
/// clip. The viewport shrinks to the boundary's extents and its view
|
||||
/// moves with it, so the model stays where it was on paper; the old
|
||||
/// boundary is erased. One undo step.
|
||||
pub(super) fn handle_vpclip(
|
||||
&mut self,
|
||||
viewport: Handle,
|
||||
boundary: Option<codec::EntityType>,
|
||||
boundary_handle: Handle,
|
||||
) {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
let Some(codec::EntityType::Viewport(vp)) = self.tabs[i].scene.document.get_entity(viewport).cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if boundary.is_none() && !boundary_handle.is_null() {
|
||||
let scene = &self.tabs[i].scene;
|
||||
let closed = scene.entity_belongs_to_current_layout(boundary_handle)
|
||||
&& scene.document.get_entity(boundary_handle).is_some_and(|entity| match entity {
|
||||
codec::EntityType::Circle(_) => true,
|
||||
codec::EntityType::Ellipse(ellipse) => ellipse.is_full(),
|
||||
codec::EntityType::LwPolyline(polyline) => polyline.is_closed,
|
||||
codec::EntityType::Polyline(polyline) => polyline.is_closed(),
|
||||
codec::EntityType::Polyline2D(polyline) => polyline.is_closed(),
|
||||
_ => false,
|
||||
});
|
||||
if !closed {
|
||||
self.command_line.push_error("Object is not a closed curve.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.push_undo_snapshot(i, "VPCLIP");
|
||||
let old = vp.clip_boundary_handle;
|
||||
let scene = &mut self.tabs[i].scene;
|
||||
let clip = match boundary {
|
||||
Some(mut entity) => {
|
||||
entity.common_mut().invisible = true;
|
||||
scene.add_entity(entity)
|
||||
}
|
||||
None => boundary_handle,
|
||||
};
|
||||
let mut vp = vp;
|
||||
if clip.is_null() {
|
||||
vp.clip_boundary_handle = Handle::NULL;
|
||||
} else {
|
||||
let (lo, hi) = clip_boundary_extents(scene, clip)
|
||||
.map(|(lo, hi)| (glam::DVec2::from(lo), glam::DVec2::from(hi)))
|
||||
.unwrap_or((glam::DVec2::ZERO, glam::DVec2::ZERO));
|
||||
if hi.x - lo.x < 1e-6 || hi.y - lo.y < 1e-6 {
|
||||
self.command_line
|
||||
.push_error(crate::t!("MVIEW: the clipping boundary has no usable area.").as_ref());
|
||||
return;
|
||||
}
|
||||
// Model units per paper unit; the view centre follows the paper
|
||||
// centre so the model does not move on the sheet.
|
||||
let scale = if vp.height.abs() > 1e-12 { vp.view_height / vp.height } else { 1.0 };
|
||||
let center = (lo + hi) * 0.5;
|
||||
let (dx, dy) = (center.x - vp.center.x, center.y - vp.center.y);
|
||||
let (s, c) = (-vp.twist_angle).sin_cos();
|
||||
vp.view_center.x += (dx * c - dy * s) * scale;
|
||||
vp.view_center.y += (dx * s + dy * c) * scale;
|
||||
vp.center.x = center.x;
|
||||
vp.center.y = center.y;
|
||||
vp.width = hi.x - lo.x;
|
||||
vp.height = hi.y - lo.y;
|
||||
vp.view_height = vp.height * scale;
|
||||
vp.clip_boundary_handle = clip;
|
||||
if let Some(entity) = scene.document.get_entity_mut(clip) {
|
||||
let common = entity.common_mut();
|
||||
common.invisible = true;
|
||||
if !common.reactors.contains(&viewport) {
|
||||
common.reactors.push(viewport);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(entity) = scene.document.get_entity_mut(viewport) {
|
||||
*entity = codec::EntityType::Viewport(vp);
|
||||
}
|
||||
if !old.is_null() && old != clip {
|
||||
scene.erase_entities(&[old]);
|
||||
}
|
||||
let mut changes = vec![(viewport, crate::scene::ChangeKind::Modified)];
|
||||
if !clip.is_null() {
|
||||
changes.push((clip, crate::scene::ChangeKind::Modified));
|
||||
}
|
||||
scene.bump_entities(&changes);
|
||||
scene.camera_generation += 1;
|
||||
self.tabs[i].dirty = true;
|
||||
self.restore_pre_cmd_tangent();
|
||||
}
|
||||
|
||||
pub(super) fn handle_mview_create_clipped(
|
||||
&mut self,
|
||||
boundary: Option<codec::EntityType>,
|
||||
@@ -107,27 +202,8 @@ impl OpenCADStudio {
|
||||
}
|
||||
None => boundary_handle,
|
||||
};
|
||||
let polygon = self.tabs[i].scene.clip_boundary_polygon(clip_handle, 0.0);
|
||||
let bounds: Option<(f64, f64, f64, f64)> = polygon.iter().fold(None, |bounds, point| {
|
||||
if !point[0].is_finite() || !point[1].is_finite() {
|
||||
return bounds;
|
||||
}
|
||||
Some(match bounds {
|
||||
Some((min_x, min_y, max_x, max_y)) => (
|
||||
min_x.min(point[0] as f64),
|
||||
min_y.min(point[1] as f64),
|
||||
max_x.max(point[0] as f64),
|
||||
max_y.max(point[1] as f64),
|
||||
),
|
||||
None => (
|
||||
point[0] as f64,
|
||||
point[1] as f64,
|
||||
point[0] as f64,
|
||||
point[1] as f64,
|
||||
),
|
||||
})
|
||||
});
|
||||
let Some((min_x, min_y, max_x, max_y)) = bounds else {
|
||||
let bounds = clip_boundary_extents(&self.tabs[i].scene, clip_handle);
|
||||
let Some(([min_x, min_y], [max_x, max_y])) = bounds else {
|
||||
self.command_line
|
||||
.push_error(crate::t!("MVIEW: the clipping boundary has no usable area.").as_ref());
|
||||
self.tabs[i].active_cmd = None;
|
||||
@@ -412,3 +488,27 @@ impl OpenCADStudio {
|
||||
self.on_quick_print_handles(handles)
|
||||
}
|
||||
}
|
||||
|
||||
/// Paper-space extents of a viewport clip boundary: exact for lines, arcs
|
||||
/// and circles (bulges included), else from its outline.
|
||||
fn clip_boundary_extents(scene: &crate::scene::Scene, clip: Handle) -> Option<([f64; 2], [f64; 2])> {
|
||||
let exact = scene
|
||||
.document
|
||||
.get_entity(clip)
|
||||
.and_then(crate::entities::curve::entity_curve_xy)
|
||||
.and_then(|curve| kernel::geom2d::analytic_curve_bounds(&[curve]));
|
||||
if exact.is_some() {
|
||||
return exact;
|
||||
}
|
||||
let polygon = scene.clip_boundary_polygon(clip, 0.0);
|
||||
polygon
|
||||
.iter()
|
||||
.filter(|p| p[0].is_finite() && p[1].is_finite())
|
||||
.fold(None, |bounds: Option<([f64; 2], [f64; 2])>, p| {
|
||||
let (x, y) = (p[0] as f64, p[1] as f64);
|
||||
Some(match bounds {
|
||||
Some((lo, hi)) => ([lo[0].min(x), lo[1].min(y)], [hi[0].max(x), hi[1].max(y)]),
|
||||
None => ([x, y], [x, y]),
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user