forked from 1b-agent/opencadstudio-upstream
Complete automatic constraint behavior
This commit is contained in:
1 parent
d738ebfcc6
commit
66be530655
25 files changed
+1494
-102
No files matched your search
+55
-3
@@ -1676,6 +1676,8 @@ pub struct Scene {
|
||||
/// Entity drawn with the selection-highlight colour without being part
|
||||
/// of the real selection — used to preview a row in the cycling list box.
|
||||
pub hover_highlight: Option<Handle>,
|
||||
/// All entities related to the constraint indicator currently under the cursor.
|
||||
constraint_hover_highlights: HashSet<Handle>,
|
||||
/// Color used to draw the selection highlight overlay wires (SELECTIONEFFECTCOLOR).
|
||||
pub selection_color: [f32; 4],
|
||||
/// Whether selection visual effect (glow/highlight) is enabled (SELECTIONEFFECT).
|
||||
@@ -1956,6 +1958,11 @@ pub struct Scene {
|
||||
parametric_constraints::ParametricScope,
|
||||
parametric_constraints::ConstraintId,
|
||||
)>,
|
||||
/// Constraint glyphs explicitly shown through the constraint-bar commands.
|
||||
shown_parametric_constraints: HashSet<(
|
||||
parametric_constraints::ParametricScope,
|
||||
parametric_constraints::ConstraintId,
|
||||
)>,
|
||||
/// Document-wide named-parameter and expression table decoded from
|
||||
/// standard associative variables.
|
||||
pub(crate) named_parameters: named_parameters::ParameterTable,
|
||||
@@ -2199,6 +2206,7 @@ impl Scene {
|
||||
command_preview_hidden: HashSet::default(),
|
||||
refedit_keep: None,
|
||||
hover_highlight: None,
|
||||
constraint_hover_highlights: HashSet::default(),
|
||||
selection_color: crate::scene::model::wire_model::WireModel::SELECTED,
|
||||
selection_effect: true,
|
||||
transparency_display: true,
|
||||
@@ -2273,6 +2281,7 @@ impl Scene {
|
||||
associative_hatch_source_cache: RefCell::new(None),
|
||||
parametric_constraints: Vec::new(),
|
||||
hidden_parametric_constraints: HashSet::default(),
|
||||
shown_parametric_constraints: HashSet::default(),
|
||||
named_parameters: named_parameters::ParameterTable::new(),
|
||||
has_associative_centers: std::cell::Cell::new(None),
|
||||
block_defn_cache: RefCell::new(HashMap::default()),
|
||||
@@ -2832,6 +2841,33 @@ impl Scene {
|
||||
}
|
||||
|
||||
pub fn bump_entities(&mut self, changes: &[(Handle, ChangeKind)]) {
|
||||
self.bump_entities_with_parametric_policy(changes, &[], false);
|
||||
}
|
||||
|
||||
pub fn bump_entities_with_parametric_driven(
|
||||
&mut self,
|
||||
changes: &[(Handle, ChangeKind)],
|
||||
driven_refs: &[parametric_constraints::ParametricRef],
|
||||
) {
|
||||
self.bump_entities_with_parametric_policy(changes, driven_refs, false);
|
||||
}
|
||||
|
||||
pub fn bump_entities_with_parametric_policy(
|
||||
&mut self,
|
||||
changes: &[(Handle, ChangeKind)],
|
||||
driven_refs: &[parametric_constraints::ParametricRef],
|
||||
retain_size: bool,
|
||||
) {
|
||||
self.bump_entities_with_parametric_originals(changes, driven_refs, retain_size, &[]);
|
||||
}
|
||||
|
||||
pub fn bump_entities_with_parametric_originals(
|
||||
&mut self,
|
||||
changes: &[(Handle, ChangeKind)],
|
||||
driven_refs: &[parametric_constraints::ParametricRef],
|
||||
retain_size: bool,
|
||||
retained_originals: &[(Handle, EntityType)],
|
||||
) {
|
||||
if changes.iter().any(|(handle, kind)| {
|
||||
matches!(kind, ChangeKind::Removed)
|
||||
|| self
|
||||
@@ -2865,7 +2901,12 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
if !self.parametric_constraints.is_empty() {
|
||||
for change in self.refresh_parametric_constraints(&changes) {
|
||||
for change in self.refresh_parametric_constraints_with_originals(
|
||||
&changes,
|
||||
driven_refs,
|
||||
retain_size,
|
||||
retained_originals,
|
||||
) {
|
||||
if !changes.iter().any(|(handle, _)| *handle == change.0) {
|
||||
changes.push(change);
|
||||
}
|
||||
@@ -5069,9 +5110,20 @@ impl Scene {
|
||||
/// remains the picked member for hit-test/UI bookkeeping; rendering expands
|
||||
/// it to the same selectable group that a click would select.
|
||||
pub fn hover_highlight_handles(&self) -> HashSet<Handle> {
|
||||
self.hover_highlight
|
||||
let mut handles = self
|
||||
.hover_highlight
|
||||
.map(|handle| self.handles_expanded_for_selectable_groups(&[handle]))
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_default();
|
||||
handles.extend(self.constraint_hover_highlights.iter().copied());
|
||||
handles
|
||||
}
|
||||
|
||||
pub fn set_constraint_hover_highlights(&mut self, handles: &[Handle]) {
|
||||
let desired: HashSet<_> = handles.iter().copied().collect();
|
||||
if desired != self.constraint_hover_highlights {
|
||||
self.constraint_hover_highlights = desired;
|
||||
self.bump_selection();
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep the current selection visible and temporarily filter every other
|
||||
|
||||
@@ -83,6 +83,45 @@ impl ParametricRef {
|
||||
}
|
||||
}
|
||||
|
||||
/// Point reference driven by a native entity grip. Segment midpoint and
|
||||
/// scalar radius grips deliberately return None because they do not identify
|
||||
/// one stored point parameter.
|
||||
pub(crate) fn driven_ref_for_grip(
|
||||
entity: &acadrust::EntityType,
|
||||
handle: Handle,
|
||||
grip_id: usize,
|
||||
) -> Option<ParametricRef> {
|
||||
match entity {
|
||||
acadrust::EntityType::Line(_) if grip_id <= 1 => {
|
||||
Some(ParametricRef::point(handle, grip_id as i32))
|
||||
}
|
||||
acadrust::EntityType::LwPolyline(polyline) if grip_id < polyline.vertices.len() => {
|
||||
Some(ParametricRef::point(handle, grip_id as i32))
|
||||
}
|
||||
acadrust::EntityType::Polyline2D(polyline) if grip_id < polyline.vertices.len() => {
|
||||
Some(ParametricRef::point(handle, grip_id as i32))
|
||||
}
|
||||
acadrust::EntityType::Arc(_) => match grip_id {
|
||||
0 => Some(ParametricRef::center(handle)),
|
||||
1 => Some(ParametricRef::point(handle, 0)),
|
||||
3 => Some(ParametricRef::point(handle, 1)),
|
||||
_ => None,
|
||||
},
|
||||
acadrust::EntityType::Circle(_) if grip_id == 0 => {
|
||||
Some(ParametricRef::center(handle))
|
||||
}
|
||||
acadrust::EntityType::Point(_)
|
||||
| acadrust::EntityType::Insert(_)
|
||||
| acadrust::EntityType::Text(_)
|
||||
| acadrust::EntityType::MText(_)
|
||||
if grip_id == 0 =>
|
||||
{
|
||||
Some(ParametricRef::point(handle, 0))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The friendly, user-facing constraint types — the "what button did they
|
||||
/// click" vocabulary, one layer above the `cadkernel_constraints` primitives each maps
|
||||
/// onto (that mapping is `constraint_map`, a later stage; see the design
|
||||
@@ -576,6 +615,18 @@ impl super::Scene {
|
||||
!self.hidden_parametric_constraints.contains(&(scope, id))
|
||||
}
|
||||
|
||||
pub fn should_display_parametric_constraint(
|
||||
&self,
|
||||
scope: ParametricScope,
|
||||
id: ConstraintId,
|
||||
related_entity_selected: bool,
|
||||
display_mode: i16,
|
||||
) -> bool {
|
||||
self.shown_parametric_constraints.contains(&(scope, id))
|
||||
|| (display_mode & 1 != 0 && self.is_parametric_constraint_visible(scope, id))
|
||||
|| (display_mode & 2 != 0 && related_entity_selected)
|
||||
}
|
||||
|
||||
pub fn set_parametric_constraint_visibility(
|
||||
&mut self,
|
||||
scope: ParametricScope,
|
||||
@@ -601,8 +652,10 @@ impl super::Scene {
|
||||
for id in &ids {
|
||||
if visible {
|
||||
self.hidden_parametric_constraints.remove(&(scope, *id));
|
||||
self.shown_parametric_constraints.insert((scope, *id));
|
||||
} else {
|
||||
self.hidden_parametric_constraints.insert((scope, *id));
|
||||
self.shown_parametric_constraints.remove(&(scope, *id));
|
||||
}
|
||||
}
|
||||
ids.len()
|
||||
@@ -613,40 +666,155 @@ impl super::Scene {
|
||||
&self,
|
||||
scope: ParametricScope,
|
||||
handles: &[Handle],
|
||||
settings: &crate::app::settings::AutoConstrainSettings,
|
||||
) -> Vec<(ConstraintKind, Vec<ParametricRef>)> {
|
||||
use cadkernel::geom2d::{
|
||||
infer_constraints, Arc, Circle, ConstraintEndpoint, InferredConstraint, Line,
|
||||
ParametricPrimitive, Tolerance,
|
||||
infer_constraints_with_settings, Arc, Circle, ConstraintEndpoint, InferenceKind,
|
||||
InferenceSettings, InferredConstraint, Line, ParametricPrimitive,
|
||||
};
|
||||
struct Source {
|
||||
handle: Handle,
|
||||
primitive: ParametricPrimitive,
|
||||
whole: ParametricRef,
|
||||
endpoints: [ParametricRef; 2],
|
||||
}
|
||||
let mut sources = Vec::new();
|
||||
for handle in handles {
|
||||
let primitive = match self.document.get_entity(*handle) {
|
||||
Some(acadrust::EntityType::Line(line)) => ParametricPrimitive::Line(Line {
|
||||
start: [line.start.x, line.start.y],
|
||||
end: [line.end.x, line.end.y],
|
||||
match self.document.get_entity(*handle) {
|
||||
Some(acadrust::EntityType::Line(line)) => sources.push(Source {
|
||||
handle: *handle,
|
||||
primitive: ParametricPrimitive::Line(Line {
|
||||
start: [line.start.x, line.start.y],
|
||||
end: [line.end.x, line.end.y],
|
||||
}),
|
||||
whole: ParametricRef::whole(*handle),
|
||||
endpoints: [
|
||||
ParametricRef::point(*handle, 0),
|
||||
ParametricRef::point(*handle, 1),
|
||||
],
|
||||
}),
|
||||
Some(acadrust::EntityType::Circle(circle)) => ParametricPrimitive::Circle(Circle {
|
||||
centre: [circle.center.x, circle.center.y],
|
||||
radius: circle.radius,
|
||||
Some(acadrust::EntityType::Circle(circle)) => sources.push(Source {
|
||||
handle: *handle,
|
||||
primitive: ParametricPrimitive::Circle(Circle {
|
||||
centre: [circle.center.x, circle.center.y],
|
||||
radius: circle.radius,
|
||||
}),
|
||||
whole: ParametricRef::whole(*handle),
|
||||
endpoints: [
|
||||
ParametricRef::center(*handle),
|
||||
ParametricRef::center(*handle),
|
||||
],
|
||||
}),
|
||||
Some(acadrust::EntityType::Arc(arc)) => ParametricPrimitive::Arc(Arc {
|
||||
centre: [arc.center.x, arc.center.y],
|
||||
radius: arc.radius,
|
||||
start_angle: arc.start_angle,
|
||||
end_angle: arc.end_angle,
|
||||
Some(acadrust::EntityType::Arc(arc)) => sources.push(Source {
|
||||
handle: *handle,
|
||||
primitive: ParametricPrimitive::Arc(Arc {
|
||||
centre: [arc.center.x, arc.center.y],
|
||||
radius: arc.radius,
|
||||
start_angle: arc.start_angle,
|
||||
end_angle: arc.end_angle,
|
||||
}),
|
||||
whole: ParametricRef::whole(*handle),
|
||||
endpoints: [
|
||||
ParametricRef::point(*handle, 0),
|
||||
ParametricRef::point(*handle, 1),
|
||||
],
|
||||
}),
|
||||
Some(acadrust::EntityType::LwPolyline(polyline)) => {
|
||||
let Some(world) = crate::entities::curve::lwpolyline_world_xy(polyline) else {
|
||||
continue;
|
||||
};
|
||||
let count = world.vertices.len();
|
||||
for index in 0..count {
|
||||
let next = index + 1;
|
||||
if next >= count && !world.is_closed {
|
||||
break;
|
||||
}
|
||||
if world.vertices[index].bulge.abs() > 1e-9 {
|
||||
continue;
|
||||
}
|
||||
let a = world.vertices[index].location;
|
||||
let b = world.vertices[next % count].location;
|
||||
sources.push(Source {
|
||||
handle: *handle,
|
||||
primitive: ParametricPrimitive::Line(Line {
|
||||
start: [a.x, a.y],
|
||||
end: [b.x, b.y],
|
||||
}),
|
||||
whole: ParametricRef::segment(*handle, index),
|
||||
endpoints: [
|
||||
ParametricRef::point(*handle, index as i32),
|
||||
ParametricRef::point(*handle, (next % count) as i32),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(acadrust::EntityType::Polyline2D(polyline)) => {
|
||||
let count = polyline.vertices.len();
|
||||
for index in 0..count {
|
||||
let next = index + 1;
|
||||
if next >= count && !polyline.is_closed() {
|
||||
break;
|
||||
}
|
||||
if polyline.vertices[index].bulge.abs() > 1e-9 {
|
||||
continue;
|
||||
}
|
||||
let a = polyline.vertices[index].location;
|
||||
let b = polyline.vertices[next % count].location;
|
||||
sources.push(Source {
|
||||
handle: *handle,
|
||||
primitive: ParametricPrimitive::Line(Line {
|
||||
start: [a.x, a.y],
|
||||
end: [b.x, b.y],
|
||||
}),
|
||||
whole: ParametricRef::segment(*handle, index),
|
||||
endpoints: [
|
||||
ParametricRef::point(*handle, index as i32),
|
||||
ParametricRef::point(*handle, (next % count) as i32),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
sources.push((*handle, primitive));
|
||||
}
|
||||
}
|
||||
let primitives: Vec<_> = sources.iter().map(|(_, primitive)| *primitive).collect();
|
||||
let primitives: Vec<_> = sources.iter().map(|source| source.primitive).collect();
|
||||
let marker = |endpoint| match endpoint {
|
||||
ConstraintEndpoint::Start => 0,
|
||||
ConstraintEndpoint::End => 1,
|
||||
ConstraintEndpoint::Start => 0usize,
|
||||
ConstraintEndpoint::End => 1usize,
|
||||
};
|
||||
let kind = |kind| match kind {
|
||||
crate::app::settings::AutoConstraintKind::Coincident => InferenceKind::Coincident,
|
||||
crate::app::settings::AutoConstraintKind::Collinear => InferenceKind::Collinear,
|
||||
crate::app::settings::AutoConstraintKind::Parallel => InferenceKind::Parallel,
|
||||
crate::app::settings::AutoConstraintKind::Perpendicular => {
|
||||
InferenceKind::Perpendicular
|
||||
}
|
||||
crate::app::settings::AutoConstraintKind::Tangent => InferenceKind::Tangent,
|
||||
crate::app::settings::AutoConstraintKind::Concentric => InferenceKind::Concentric,
|
||||
crate::app::settings::AutoConstraintKind::Horizontal => InferenceKind::Horizontal,
|
||||
crate::app::settings::AutoConstraintKind::Vertical => InferenceKind::Vertical,
|
||||
crate::app::settings::AutoConstraintKind::Equal => InferenceKind::Equal,
|
||||
};
|
||||
let inference_settings = InferenceSettings {
|
||||
priority: settings
|
||||
.priority
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|candidate| settings.enabled.contains(candidate))
|
||||
.map(kind)
|
||||
.collect(),
|
||||
distance_tolerance: settings.distance_tolerance,
|
||||
angle_tolerance_radians: settings.angle_tolerance_deg.to_radians(),
|
||||
tangent_must_share_point: settings.tangent_must_share_point,
|
||||
perpendicular_must_intersect: settings.perpendicular_must_intersect,
|
||||
};
|
||||
let mut mapped: Vec<_> =
|
||||
infer_constraints(&primitives, Tolerance::new(1e-6), 0.5_f64.to_radians())
|
||||
infer_constraints_with_settings(&primitives, &inference_settings)
|
||||
.into_iter()
|
||||
.filter(|relation| {
|
||||
!matches!(relation, InferredConstraint::Coincident { first, second, .. }
|
||||
if sources[*first].handle == sources[*second].handle)
|
||||
})
|
||||
.map(|relation| match relation {
|
||||
InferredConstraint::Coincident {
|
||||
first,
|
||||
@@ -656,53 +824,57 @@ impl super::Scene {
|
||||
} => (
|
||||
ConstraintKind::Coincident,
|
||||
vec![
|
||||
ParametricRef::point(sources[first].0, marker(first_endpoint)),
|
||||
ParametricRef::point(sources[second].0, marker(second_endpoint)),
|
||||
sources[first].endpoints[marker(first_endpoint)],
|
||||
sources[second].endpoints[marker(second_endpoint)],
|
||||
],
|
||||
),
|
||||
InferredConstraint::Collinear { first, second } => (
|
||||
ConstraintKind::Colinear,
|
||||
vec![
|
||||
ParametricRef::whole(sources[first].0),
|
||||
ParametricRef::whole(sources[second].0),
|
||||
sources[first].whole,
|
||||
sources[second].whole,
|
||||
],
|
||||
),
|
||||
InferredConstraint::Concentric { first, second } => (
|
||||
ConstraintKind::Concentric,
|
||||
vec![
|
||||
ParametricRef::center(sources[first].0),
|
||||
ParametricRef::center(sources[second].0),
|
||||
ParametricRef::center(sources[first].handle),
|
||||
ParametricRef::center(sources[second].handle),
|
||||
],
|
||||
),
|
||||
InferredConstraint::Parallel { first, second } => (
|
||||
ConstraintKind::Parallel,
|
||||
vec![
|
||||
ParametricRef::whole(sources[first].0),
|
||||
ParametricRef::whole(sources[second].0),
|
||||
sources[first].whole,
|
||||
sources[second].whole,
|
||||
],
|
||||
),
|
||||
InferredConstraint::Perpendicular { first, second } => (
|
||||
ConstraintKind::Perpendicular,
|
||||
vec![
|
||||
ParametricRef::whole(sources[first].0),
|
||||
ParametricRef::whole(sources[second].0),
|
||||
sources[first].whole,
|
||||
sources[second].whole,
|
||||
],
|
||||
),
|
||||
InferredConstraint::Horizontal { entity } => (
|
||||
ConstraintKind::Horizontal,
|
||||
vec![ParametricRef::whole(sources[entity].0)],
|
||||
vec![sources[entity].whole],
|
||||
),
|
||||
InferredConstraint::Vertical { entity } => (
|
||||
ConstraintKind::Vertical,
|
||||
vec![ParametricRef::whole(sources[entity].0)],
|
||||
vec![sources[entity].whole],
|
||||
),
|
||||
InferredConstraint::Tangent { first, second } => (
|
||||
ConstraintKind::Tangent,
|
||||
vec![
|
||||
ParametricRef::whole(sources[first].0),
|
||||
ParametricRef::whole(sources[second].0),
|
||||
sources[first].whole,
|
||||
sources[second].whole,
|
||||
],
|
||||
),
|
||||
InferredConstraint::Equal { first, second } => (
|
||||
ConstraintKind::Equal,
|
||||
vec![sources[first].whole, sources[second].whole],
|
||||
),
|
||||
})
|
||||
.collect();
|
||||
if let Some(existing) = self.parametric_constraint_set(scope) {
|
||||
@@ -1174,7 +1346,11 @@ mod tests {
|
||||
);
|
||||
|
||||
let inferred =
|
||||
scene.inferred_parametric_constraints(ParametricScope::ModelSpace, &[first, second]);
|
||||
scene.inferred_parametric_constraints(
|
||||
ParametricScope::ModelSpace,
|
||||
&[first, second],
|
||||
&crate::app::settings::AutoConstrainSettings::default(),
|
||||
);
|
||||
|
||||
assert!(!inferred.iter().any(|(kind, refs)| {
|
||||
*kind == ConstraintKind::Horizontal && *refs == [ParametricRef::whole(first)]
|
||||
|
||||
@@ -1549,6 +1549,44 @@ type SolveResult = (
|
||||
)>,
|
||||
);
|
||||
|
||||
fn retained_line_length(entity: &EntityType, segment: Option<usize>) -> Option<f64> {
|
||||
match (entity, segment) {
|
||||
(EntityType::Line(line), None) => Some((line.end - line.start).length()),
|
||||
(EntityType::LwPolyline(polyline), Some(index)) => {
|
||||
let world = crate::entities::curve::lwpolyline_world_xy(polyline)?;
|
||||
let a = world.vertices.get(index)?.location;
|
||||
let b = if let Some(vertex) = world.vertices.get(index + 1) {
|
||||
vertex.location
|
||||
} else if world.is_closed {
|
||||
world.vertices.first()?.location
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some((b - a).length())
|
||||
}
|
||||
(EntityType::Polyline2D(polyline), Some(index)) => {
|
||||
let a = polyline.vertices.get(index)?.location;
|
||||
let b = if let Some(vertex) = polyline.vertices.get(index + 1) {
|
||||
vertex.location
|
||||
} else if polyline.is_closed() {
|
||||
polyline.vertices.first()?.location
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some((b - a).length())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn retained_radius(entity: &EntityType) -> Option<f64> {
|
||||
match entity {
|
||||
EntityType::Circle(circle) => Some(circle.radius),
|
||||
EntityType::Arc(arc) => Some(arc.radius),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Rebuilds `set`'s entire `cadkernel_constraints::System` from current document
|
||||
/// geometry, solves it, and returns the resulting entity states for every
|
||||
/// handle whose registered coordinates actually moved beyond floating-point
|
||||
@@ -1564,6 +1602,9 @@ fn solve_scope(
|
||||
document: &acadrust::CadDocument,
|
||||
drawing_params: &ParameterTable,
|
||||
set: &ParametricConstraintSet,
|
||||
driven_refs: &[ParametricRef],
|
||||
retain_size: bool,
|
||||
retained_before: &HashMap<Handle, std::sync::Arc<EntityType>>,
|
||||
) -> Option<SolveResult> {
|
||||
let params = if set.local_parameters.is_empty() {
|
||||
drawing_params
|
||||
@@ -1597,6 +1638,81 @@ fn solve_scope(
|
||||
return None;
|
||||
}
|
||||
|
||||
// Preserve current line lengths and radii for this solve when
|
||||
// CONSTRAINTSOLVEMODE requests it. These temporary equations guide the
|
||||
// solution without becoming persistent dimensional constraints.
|
||||
if retain_size {
|
||||
for (handle, geom) in &cache {
|
||||
match geom {
|
||||
EntityGeom::Line(line) => {
|
||||
let current_length = {
|
||||
let store = sys.store();
|
||||
let dx = store.get(line.p2.x) - store.get(line.p1.x);
|
||||
let dy = store.get(line.p2.y) - store.get(line.p1.y);
|
||||
(dx * dx + dy * dy).sqrt()
|
||||
};
|
||||
let length = retained_before
|
||||
.get(handle)
|
||||
.and_then(|entity| retained_line_length(entity, None))
|
||||
.unwrap_or(current_length);
|
||||
let target = sys.add_param(length, true);
|
||||
sys.add_constraint(Rc::new(P2PDistance::new(line.p1, line.p2, target)));
|
||||
}
|
||||
EntityGeom::Polyline {
|
||||
points,
|
||||
straight,
|
||||
closed,
|
||||
} => {
|
||||
for index in 0..straight.len() {
|
||||
if !straight[index] {
|
||||
continue;
|
||||
}
|
||||
let Some(a) = points.get(index).copied() else {
|
||||
continue;
|
||||
};
|
||||
let b = if let Some(point) = points.get(index + 1).copied() {
|
||||
Some(point)
|
||||
} else if *closed {
|
||||
points.first().copied()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let Some(b) = b else { continue };
|
||||
let current_length = {
|
||||
let store = sys.store();
|
||||
let dx = store.get(b.x) - store.get(a.x);
|
||||
let dy = store.get(b.y) - store.get(a.y);
|
||||
(dx * dx + dy * dy).sqrt()
|
||||
};
|
||||
let length = retained_before
|
||||
.get(handle)
|
||||
.and_then(|entity| retained_line_length(entity, Some(index)))
|
||||
.unwrap_or(current_length);
|
||||
let target = sys.add_param(length, true);
|
||||
sys.add_constraint(Rc::new(P2PDistance::new(a, b, target)));
|
||||
}
|
||||
}
|
||||
EntityGeom::Circle(circle) => {
|
||||
let radius = retained_before
|
||||
.get(handle)
|
||||
.and_then(|entity| retained_radius(entity))
|
||||
.unwrap_or_else(|| sys.store().get(circle.rad));
|
||||
let target = sys.add_param(radius, true);
|
||||
sys.add_constraint(Rc::new(Equal::new(circle.rad, target, 1.0)));
|
||||
}
|
||||
EntityGeom::Arc(arc) => {
|
||||
let radius = retained_before
|
||||
.get(handle)
|
||||
.and_then(|entity| retained_radius(entity))
|
||||
.unwrap_or_else(|| sys.store().get(arc.circle.rad));
|
||||
let target = sys.add_param(radius, true);
|
||||
sys.add_constraint(Rc::new(Equal::new(arc.circle.rad, target, 1.0)));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Arc rules (this module's doc comment): keep every registered arc's
|
||||
// `start`/`end` points consistent with its center/radius/angle,
|
||||
// unconditionally — the kernel adds these the moment an arc
|
||||
@@ -1633,6 +1749,33 @@ fn solve_scope(
|
||||
)));
|
||||
}
|
||||
|
||||
// A point explicitly edited by a grip, STRETCH, or Properties is the
|
||||
// driver's target for this solve. Pin its current coordinates for this
|
||||
// solve only so constrained neighbours respond while the chosen point
|
||||
// stays at the exact cursor or typed coordinate.
|
||||
for reference in driven_refs {
|
||||
if !set
|
||||
.constraints
|
||||
.iter()
|
||||
.any(|constraint| constraint.refs.iter().any(|r| r.entity == reference.entity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Some(point) =
|
||||
resolve_constraint_point(document, &mut sys, &mut cache, *reference)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let (x, y) = {
|
||||
let store = sys.store();
|
||||
(store.get(point.x), store.get(point.y))
|
||||
};
|
||||
let target_x = sys.add_param(x, true);
|
||||
let target_y = sys.add_param(y, true);
|
||||
sys.add_constraint(Rc::new(Equal::new(point.x, target_x, 1.0)));
|
||||
sys.add_constraint(Rc::new(Equal::new(point.y, target_y, 1.0)));
|
||||
}
|
||||
|
||||
// Ellipse rules (same motivation as arc rules, different mechanism):
|
||||
// `cadkernel_constraints::geo::Ellipse` stores `focus1` as an absolute point, not an
|
||||
// offset from `center`. If some constraint (e.g. Concentric) pulls only
|
||||
@@ -2151,9 +2294,43 @@ impl Scene {
|
||||
/// the resulting `(Handle, ChangeKind::Modified)` entries the same way
|
||||
/// `refresh_associative_dimensions`/`_hatches` do, for `bump_entities`
|
||||
/// to fold into its own `changes` vec.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn refresh_parametric_constraints(
|
||||
&mut self,
|
||||
changes: &[(Handle, ChangeKind)],
|
||||
) -> Vec<(Handle, ChangeKind)> {
|
||||
self.refresh_parametric_constraints_with_policy(changes, &[], false)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn refresh_parametric_constraints_with_driven(
|
||||
&mut self,
|
||||
changes: &[(Handle, ChangeKind)],
|
||||
driven_refs: &[ParametricRef],
|
||||
) -> Vec<(Handle, ChangeKind)> {
|
||||
self.refresh_parametric_constraints_with_policy(changes, driven_refs, false)
|
||||
}
|
||||
|
||||
pub(crate) fn refresh_parametric_constraints_with_policy(
|
||||
&mut self,
|
||||
changes: &[(Handle, ChangeKind)],
|
||||
driven_refs: &[ParametricRef],
|
||||
retain_size: bool,
|
||||
) -> Vec<(Handle, ChangeKind)> {
|
||||
self.refresh_parametric_constraints_with_originals(
|
||||
changes,
|
||||
driven_refs,
|
||||
retain_size,
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn refresh_parametric_constraints_with_originals(
|
||||
&mut self,
|
||||
changes: &[(Handle, ChangeKind)],
|
||||
driven_refs: &[ParametricRef],
|
||||
retain_size: bool,
|
||||
retained_originals: &[(Handle, EntityType)],
|
||||
) -> Vec<(Handle, ChangeKind)> {
|
||||
// An erased entity takes its constraints with it instead of leaving
|
||||
// dangling references. Done first,
|
||||
@@ -2185,6 +2362,25 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
let retained_before: HashMap<Handle, std::sync::Arc<EntityType>> = if retain_size {
|
||||
let mut retained: HashMap<Handle, std::sync::Arc<EntityType>> = self
|
||||
.undo_recording
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.flat_map(|recording| recording.before.iter())
|
||||
.filter_map(|(handle, entity)| {
|
||||
entity
|
||||
.as_ref()
|
||||
.map(|entity| (*handle, std::sync::Arc::clone(entity)))
|
||||
})
|
||||
.collect();
|
||||
for (handle, entity) in retained_originals {
|
||||
retained.insert(*handle, std::sync::Arc::new(entity.clone()));
|
||||
}
|
||||
retained
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
let mut result = Vec::new();
|
||||
for i in 0..self.parametric_constraints.len() {
|
||||
let touched = changes.iter().any(|(handle, _)| {
|
||||
@@ -2200,6 +2396,9 @@ impl Scene {
|
||||
&self.document,
|
||||
&self.named_parameters,
|
||||
&self.parametric_constraints[i],
|
||||
driven_refs,
|
||||
retain_size,
|
||||
&retained_before,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
@@ -2224,10 +2423,17 @@ impl Scene {
|
||||
pub(crate) fn solve_parametric_constraints_preview(
|
||||
&self,
|
||||
touched: &[Handle],
|
||||
driven_refs: &[ParametricRef],
|
||||
retain_size: bool,
|
||||
retained_originals: &[(Handle, EntityType)],
|
||||
) -> Vec<(Handle, EntityType)> {
|
||||
if self.parametric_constraints.is_empty() || touched.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let retained_before: HashMap<Handle, std::sync::Arc<EntityType>> = retained_originals
|
||||
.iter()
|
||||
.map(|(handle, entity)| (*handle, std::sync::Arc::new(entity.clone())))
|
||||
.collect();
|
||||
let mut result = Vec::new();
|
||||
for set in &self.parametric_constraints {
|
||||
let is_touched = touched
|
||||
@@ -2236,8 +2442,14 @@ impl Scene {
|
||||
if !is_touched {
|
||||
continue;
|
||||
}
|
||||
let Some((solved, _dof, _conflicts)) =
|
||||
solve_scope(&self.document, &self.named_parameters, set)
|
||||
let Some((solved, _dof, _conflicts)) = solve_scope(
|
||||
&self.document,
|
||||
&self.named_parameters,
|
||||
set,
|
||||
driven_refs,
|
||||
retain_size,
|
||||
&retained_before,
|
||||
)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
@@ -2515,7 +2727,7 @@ mod tests {
|
||||
}
|
||||
let b_before = scene.document.get_entity(b).cloned();
|
||||
|
||||
let solved = scene.solve_parametric_constraints_preview(&[a]);
|
||||
let solved = scene.solve_parametric_constraints_preview(&[a], &[], false, &[]);
|
||||
|
||||
assert_eq!(
|
||||
scene.document.get_entity(b),
|
||||
|
||||
@@ -1001,6 +1001,7 @@ impl Scene {
|
||||
self.hover_highlight = None;
|
||||
hover_changed = true;
|
||||
}
|
||||
hover_changed |= self.constraint_hover_highlights.remove(&h);
|
||||
self.hatches.remove(&h);
|
||||
self.images.remove(&h);
|
||||
self.meshes.remove(&h);
|
||||
|
||||
@@ -3435,7 +3435,10 @@ impl Scene {
|
||||
&self,
|
||||
inst: &ViewportInstance,
|
||||
) -> Arc<Vec<WireModel>> {
|
||||
if self.selected.is_empty() && self.hover_highlight.is_none() {
|
||||
if self.selected.is_empty()
|
||||
&& self.hover_highlight.is_none()
|
||||
&& self.constraint_hover_highlights.is_empty()
|
||||
{
|
||||
return Arc::new(Vec::new());
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user