forked from 1b-agent/opencadstudio-upstream
Fix perpendicular constraint behavior
This commit is contained in:
1 parent
a27545c379
commit
cbf1e76c69
8 files changed
+216
-16
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||||
<line x1="4" y1="17" x2="20" y2="17" stroke="#B4B6B9" stroke-width="2.25" stroke-linecap="round"/>
|
<line x1="4" y1="18.5" x2="20" y2="18.5" stroke="#B4B6B9" stroke-width="2.25" stroke-linecap="round"/>
|
||||||
<line x1="12" y1="4" x2="12" y2="17" stroke="#6DB7ED" stroke-width="2.25" stroke-linecap="round"/>
|
<line x1="12" y1="5.5" x2="12" y2="18.5" stroke="#6DB7ED" stroke-width="2.25" stroke-linecap="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 293 B After Width: | Height: | Size: 301 B |
@@ -3225,6 +3225,62 @@ impl OpenCADStudio {
|
|||||||
self.tabs[i]
|
self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
.record_undo_parametric_constraints_before(scope, constraints_before);
|
.record_undo_parametric_constraints_before(scope, constraints_before);
|
||||||
|
if first.entity != second.entity {
|
||||||
|
use crate::scene::parametric_constraints::directional_reference_endpoints;
|
||||||
|
|
||||||
|
let fixed_axis = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity(first.entity)
|
||||||
|
.and_then(|entity| directional_reference_endpoints(entity, first));
|
||||||
|
let moving_axis = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity(second.entity)
|
||||||
|
.and_then(|entity| directional_reference_endpoints(entity, second));
|
||||||
|
if let (Some([fixed_start, fixed_end]), Some([moving_start, moving_end])) =
|
||||||
|
(fixed_axis, moving_axis)
|
||||||
|
{
|
||||||
|
let fixed_direction = fixed_end - fixed_start;
|
||||||
|
let moving_direction = moving_end - moving_start;
|
||||||
|
if fixed_direction.length_squared() > 1.0e-24
|
||||||
|
&& moving_direction.length_squared() > 1.0e-24
|
||||||
|
{
|
||||||
|
let fixed_angle = fixed_direction.y.atan2(fixed_direction.x);
|
||||||
|
let moving_angle = moving_direction.y.atan2(moving_direction.x);
|
||||||
|
let normalize = |angle: f64| {
|
||||||
|
(angle + std::f64::consts::PI)
|
||||||
|
.rem_euclid(std::f64::consts::TAU)
|
||||||
|
- std::f64::consts::PI
|
||||||
|
};
|
||||||
|
let positive = normalize(
|
||||||
|
fixed_angle + std::f64::consts::FRAC_PI_2 - moving_angle,
|
||||||
|
);
|
||||||
|
let negative = normalize(
|
||||||
|
fixed_angle - std::f64::consts::FRAC_PI_2 - moving_angle,
|
||||||
|
);
|
||||||
|
let angle_rad = if positive.abs() <= negative.abs() {
|
||||||
|
positive
|
||||||
|
} else {
|
||||||
|
negative
|
||||||
|
};
|
||||||
|
if angle_rad.abs() > 1.0e-12 {
|
||||||
|
self.tabs[i].scene.transform_entities(
|
||||||
|
&[second.entity],
|
||||||
|
&crate::command::EntityTransform::Rotate {
|
||||||
|
center: glam::DVec3::new(
|
||||||
|
moving_start.x,
|
||||||
|
moving_start.y,
|
||||||
|
moving_start.z,
|
||||||
|
),
|
||||||
|
axis: glam::DVec3::Z,
|
||||||
|
angle_rad,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let id = self.tabs[i].scene.parametric_constraint_set_mut(scope).add(
|
let id = self.tabs[i].scene.parametric_constraint_set_mut(scope).add(
|
||||||
ConstraintKind::Perpendicular,
|
ConstraintKind::Perpendicular,
|
||||||
refs,
|
refs,
|
||||||
|
|||||||
@@ -220,6 +220,25 @@ fn command_category(name: &str) -> &'static str {
|
|||||||
"view"
|
"view"
|
||||||
} else if name.starts_with("DIM") || ["LEADER", "MLEADER", "TOLERANCE"].contains(&name) {
|
} else if name.starts_with("DIM") || ["LEADER", "MLEADER", "TOLERANCE"].contains(&name) {
|
||||||
"annotate"
|
"annotate"
|
||||||
|
} else if [
|
||||||
|
"GEOMCONSTRAINT",
|
||||||
|
"GCCOINCIDENT",
|
||||||
|
"GCCOLLINEAR",
|
||||||
|
"GCPERPENDICULAR",
|
||||||
|
"QCONSTRAINT",
|
||||||
|
"PCONSTRAINT",
|
||||||
|
"HCONSTRAINT",
|
||||||
|
"VCONSTRAINT",
|
||||||
|
"ECONSTRAINT",
|
||||||
|
"TCONSTRAINT",
|
||||||
|
"NCONSTRAINT",
|
||||||
|
"NRCONSTRAINT",
|
||||||
|
"LCONSTRAINT",
|
||||||
|
"DELCONSTRAINT",
|
||||||
|
]
|
||||||
|
.contains(&name)
|
||||||
|
{
|
||||||
|
"parametric"
|
||||||
} else if [
|
} else if [
|
||||||
"BOX",
|
"BOX",
|
||||||
"CYLINDER",
|
"CYLINDER",
|
||||||
|
|||||||
@@ -101,12 +101,13 @@ impl PerpendicularConstraintCommand {
|
|||||||
} else {
|
} else {
|
||||||
polyline.vertices.len().saturating_sub(1)
|
polyline.vertices.len().saturating_sub(1)
|
||||||
};
|
};
|
||||||
(segment_count == 1 && polyline.vertices.first()?.bulge.abs() <= 1.0e-12)
|
let index = (0..segment_count)
|
||||||
.then_some(PerpendicularPick {
|
.find(|index| polyline.vertices[*index].bulge.abs() <= 1.0e-12)?;
|
||||||
reference: ParametricRef::segment(handle, 0),
|
Some(PerpendicularPick {
|
||||||
fixed_reference: ParametricRef::segment(handle, 0),
|
reference: ParametricRef::segment(handle, index),
|
||||||
start_reference: ParametricRef::point(handle, 0),
|
fixed_reference: ParametricRef::segment(handle, index),
|
||||||
})
|
start_reference: ParametricRef::point(handle, index as i32),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
EntityType::Polyline2D(polyline) => {
|
EntityType::Polyline2D(polyline) => {
|
||||||
let segment_count = if polyline.is_closed() {
|
let segment_count = if polyline.is_closed() {
|
||||||
@@ -114,12 +115,13 @@ impl PerpendicularConstraintCommand {
|
|||||||
} else {
|
} else {
|
||||||
polyline.vertices.len().saturating_sub(1)
|
polyline.vertices.len().saturating_sub(1)
|
||||||
};
|
};
|
||||||
(segment_count == 1 && polyline.vertices.first()?.bulge.abs() <= 1.0e-12)
|
let index = (0..segment_count)
|
||||||
.then_some(PerpendicularPick {
|
.find(|index| polyline.vertices[*index].bulge.abs() <= 1.0e-12)?;
|
||||||
reference: ParametricRef::segment(handle, 0),
|
Some(PerpendicularPick {
|
||||||
fixed_reference: ParametricRef::segment(handle, 0),
|
reference: ParametricRef::segment(handle, index),
|
||||||
start_reference: ParametricRef::point(handle, 0),
|
fixed_reference: ParametricRef::segment(handle, index),
|
||||||
})
|
start_reference: ParametricRef::point(handle, index as i32),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3245,6 +3245,23 @@ impl Scene {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bump_entities_with_parametric_transform_policy(
|
||||||
|
&mut self,
|
||||||
|
changes: &[(Handle, ChangeKind)],
|
||||||
|
driven_refs: &[parametric_constraints::ParametricRef],
|
||||||
|
transformed_refs: &[parametric_constraints::ParametricRef],
|
||||||
|
retained_originals: &[(Handle, EntityType)],
|
||||||
|
) {
|
||||||
|
self.bump_entities_with_solve_policy(
|
||||||
|
changes,
|
||||||
|
driven_refs,
|
||||||
|
true,
|
||||||
|
retained_originals,
|
||||||
|
transformed_refs,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn bump_entities_with_solve_policy(
|
fn bump_entities_with_solve_policy(
|
||||||
&mut self,
|
&mut self,
|
||||||
changes: &[(Handle, ChangeKind)],
|
changes: &[(Handle, ChangeKind)],
|
||||||
|
|||||||
+80
-1
@@ -161,6 +161,80 @@ impl Scene {
|
|||||||
.filter(|&h| !self.is_layer_locked(h))
|
.filter(|&h| !self.is_layer_locked(h))
|
||||||
.collect();
|
.collect();
|
||||||
let handles = &handles[..];
|
let handles = &handles[..];
|
||||||
|
let retained_originals: Vec<(Handle, EntityType)> = handles
|
||||||
|
.iter()
|
||||||
|
.filter_map(|handle| {
|
||||||
|
self.document
|
||||||
|
.get_entity(*handle)
|
||||||
|
.cloned()
|
||||||
|
.map(|entity| (*handle, entity))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let transformed_refs: Vec<_> = handles
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(parametric_constraints::ParametricRef::whole)
|
||||||
|
.collect();
|
||||||
|
let mut driven_refs = Vec::new();
|
||||||
|
if !matches!(t, EntityTransform::Translate(_)) {
|
||||||
|
for set in &self.parametric_constraints {
|
||||||
|
for constraint in &set.constraints {
|
||||||
|
if !constraint.enabled
|
||||||
|
|| !matches!(
|
||||||
|
constraint.kind,
|
||||||
|
parametric_constraints::ConstraintKind::Parallel
|
||||||
|
| parametric_constraints::ConstraintKind::Perpendicular
|
||||||
|
| parametric_constraints::ConstraintKind::Colinear
|
||||||
|
)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let selected_refs = constraint
|
||||||
|
.refs
|
||||||
|
.iter()
|
||||||
|
.filter(|reference| handles.contains(&reference.entity))
|
||||||
|
.count();
|
||||||
|
if selected_refs == 0 || selected_refs == constraint.refs.len() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for reference in constraint
|
||||||
|
.refs
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|reference| !handles.contains(&reference.entity))
|
||||||
|
{
|
||||||
|
let anchor = if let Some(index) = reference.segment_index() {
|
||||||
|
parametric_constraints::ParametricRef::point(
|
||||||
|
reference.entity,
|
||||||
|
index as i32,
|
||||||
|
)
|
||||||
|
} else if reference.directional_axis().is_some()
|
||||||
|
&& matches!(
|
||||||
|
self.document.get_entity(reference.entity),
|
||||||
|
Some(EntityType::Ellipse(_))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
parametric_constraints::ParametricRef::center(reference.entity)
|
||||||
|
} else {
|
||||||
|
parametric_constraints::ParametricRef::point(reference.entity, 0)
|
||||||
|
};
|
||||||
|
let follows_selected_point = set.constraints.iter().any(|link| {
|
||||||
|
link.enabled
|
||||||
|
&& link.kind
|
||||||
|
== parametric_constraints::ConstraintKind::Coincident
|
||||||
|
&& link.refs.contains(&anchor)
|
||||||
|
&& link
|
||||||
|
.refs
|
||||||
|
.iter()
|
||||||
|
.any(|item| handles.contains(&item.entity))
|
||||||
|
});
|
||||||
|
if !follows_selected_point && !driven_refs.contains(&anchor) {
|
||||||
|
driven_refs.push(anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Preserve readable text orientation when MIRRTEXT is disabled.
|
// Preserve readable text orientation when MIRRTEXT is disabled.
|
||||||
let preserve_text_orientation =
|
let preserve_text_orientation =
|
||||||
matches!(t, EntityTransform::Mirror { .. }) && !self.document.header.mirror_text;
|
matches!(t, EntityTransform::Mirror { .. }) && !self.document.header.mirror_text;
|
||||||
@@ -290,7 +364,12 @@ impl Scene {
|
|||||||
// per-handle, keeping the block cache + all other memoized wires.
|
// per-handle, keeping the block cache + all other memoized wires.
|
||||||
let changes: Vec<(Handle, ChangeKind)> =
|
let changes: Vec<(Handle, ChangeKind)> =
|
||||||
handles.iter().map(|&h| (h, ChangeKind::Modified)).collect();
|
handles.iter().map(|&h| (h, ChangeKind::Modified)).collect();
|
||||||
self.bump_entities(&changes);
|
self.bump_entities_with_parametric_transform_policy(
|
||||||
|
&changes,
|
||||||
|
&driven_refs,
|
||||||
|
&transformed_refs,
|
||||||
|
&retained_originals,
|
||||||
|
);
|
||||||
self.refresh_meshes_for_handles(&refresh_solid_handles);
|
self.refresh_meshes_for_handles(&refresh_solid_handles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,6 +186,33 @@ pub(crate) fn directional_axis_endpoints(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Finite endpoints used to orient a line-like constraint reference.
|
||||||
|
pub(crate) fn directional_reference_endpoints(
|
||||||
|
entity: &acadrust::EntityType,
|
||||||
|
reference: ParametricRef,
|
||||||
|
) -> Option<[Vector3; 2]> {
|
||||||
|
if reference.directional_axis().is_some() {
|
||||||
|
return directional_axis_endpoints(entity, reference);
|
||||||
|
}
|
||||||
|
match entity {
|
||||||
|
acadrust::EntityType::Line(line) if reference.marker.is_none() => {
|
||||||
|
Some([line.start, line.end])
|
||||||
|
}
|
||||||
|
acadrust::EntityType::LwPolyline(_) | acadrust::EntityType::Polyline2D(_) => {
|
||||||
|
let index = reference.segment_index()?;
|
||||||
|
let points = super::dimension_assoc::source_points(entity);
|
||||||
|
let start = *points.get(index)?;
|
||||||
|
let end = if let Some(end) = points.get(index + 1) {
|
||||||
|
*end
|
||||||
|
} else {
|
||||||
|
*points.first()?
|
||||||
|
};
|
||||||
|
Some([start, end])
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Grabbed points are exact kernel inputs; the solver anchors the remaining
|
/// Grabbed points are exact kernel inputs; the solver anchors the remaining
|
||||||
/// endpoint coordinates according to the line's directional constraints.
|
/// endpoint coordinates according to the line's directional constraints.
|
||||||
pub(crate) fn grip_solve_anchor_refs(
|
pub(crate) fn grip_solve_anchor_refs(
|
||||||
|
|||||||
@@ -2123,7 +2123,7 @@ fn solve_scope(
|
|||||||
for (handle, geom) in &cache {
|
for (handle, geom) in &cache {
|
||||||
if initial_fixed_refs
|
if initial_fixed_refs
|
||||||
.iter()
|
.iter()
|
||||||
.any(|reference| reference.entity == *handle)
|
.any(|reference| reference.entity == *handle && reference.marker.is_none())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user