forked from 1b-agent/opencadstudio-upstream
Integrate upstream constraint editing fixes with viewport dimensions
This commit is contained in:
commit
71aae72219
9 files changed
+485
-93
No files matched your search
+47
-5
@@ -1685,6 +1685,10 @@ pub struct Scene {
|
||||
pub hover_highlight: Option<Handle>,
|
||||
/// All entities related to the constraint indicator currently under the cursor.
|
||||
constraint_hover_highlights: HashSet<Handle>,
|
||||
/// Sub-entity references related to that indicator. Polyline segments are
|
||||
/// drawn separately so hovering one constraint does not light the whole chain.
|
||||
constraint_hover_refs: Vec<crate::scene::parametric_constraints::ParametricRef>,
|
||||
constraint_hover_wires: Vec<WireModel>,
|
||||
/// Color used to draw the selection highlight overlay wires (SELECTIONEFFECTCOLOR).
|
||||
pub selection_color: [f32; 4],
|
||||
/// Whether selection visual effect (glow/highlight) is enabled (SELECTIONEFFECT).
|
||||
@@ -2215,6 +2219,8 @@ impl Scene {
|
||||
refedit_keep: None,
|
||||
hover_highlight: None,
|
||||
constraint_hover_highlights: HashSet::default(),
|
||||
constraint_hover_refs: Vec::new(),
|
||||
constraint_hover_wires: Vec::new(),
|
||||
selection_color: crate::scene::model::wire_model::WireModel::SELECTED,
|
||||
selection_effect: true,
|
||||
transparency_display: true,
|
||||
@@ -5126,12 +5132,48 @@ impl Scene {
|
||||
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();
|
||||
pub fn set_constraint_hover_highlights(
|
||||
&mut self,
|
||||
references: &[crate::scene::parametric_constraints::ParametricRef],
|
||||
) {
|
||||
let mut references = references.to_vec();
|
||||
references.dedup();
|
||||
if references == self.constraint_hover_refs {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut handles = HashSet::default();
|
||||
let mut wires = Vec::new();
|
||||
for reference in &references {
|
||||
let Some(segment) = reference.segment_index() else {
|
||||
handles.insert(reference.entity);
|
||||
continue;
|
||||
};
|
||||
let Some(entity) = self.document.get_entity(reference.entity) else {
|
||||
continue;
|
||||
};
|
||||
let Some(mut curve) = crate::entities::curve::entity_curve(entity) else {
|
||||
handles.insert(reference.entity);
|
||||
continue;
|
||||
};
|
||||
let Some(segment_curve) = curve.curve.segments().into_iter().nth(segment) else {
|
||||
continue;
|
||||
};
|
||||
curve.curve = segment_curve;
|
||||
let mut wire = WireModel::solid_f64(
|
||||
format!("constraint-segment:{}:{segment}", reference.entity.value()),
|
||||
curve.tessellate(8.0),
|
||||
WireModel::HOVER,
|
||||
false,
|
||||
);
|
||||
wire.set_fixed_screen_width(2.0);
|
||||
wires.push(wire);
|
||||
}
|
||||
|
||||
self.constraint_hover_refs = references;
|
||||
self.constraint_hover_highlights = handles;
|
||||
self.constraint_hover_wires = wires;
|
||||
self.bump_selection();
|
||||
}
|
||||
|
||||
/// Keep the current selection visible and temporarily filter every other
|
||||
|
||||
@@ -83,17 +83,17 @@ 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(
|
||||
/// Point reference temporarily pinned while solving a native grip edit.
|
||||
/// A line endpoint anchors its opposite endpoint so the grabbed endpoint is
|
||||
/// projected onto the constraint solution instead of moving the whole line.
|
||||
pub(crate) fn grip_solve_anchor_ref(
|
||||
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))
|
||||
Some(ParametricRef::point(handle, 1 - grip_id as i32))
|
||||
}
|
||||
acadrust::EntityType::LwPolyline(polyline) if grip_id < polyline.vertices.len() => {
|
||||
Some(ParametricRef::point(handle, grip_id as i32))
|
||||
@@ -575,6 +575,37 @@ pub(crate) fn parametric_curve_ref_for_pick(
|
||||
}
|
||||
|
||||
impl ConstraintKind {
|
||||
pub const fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Coincident => "Coincident",
|
||||
Self::Horizontal => "Horizontal",
|
||||
Self::Vertical => "Vertical",
|
||||
Self::Parallel => "Parallel",
|
||||
Self::Perpendicular => "Perpendicular",
|
||||
Self::Equal => "Equal",
|
||||
Self::Distance => "Distance",
|
||||
Self::Angle => "Angle",
|
||||
Self::Angle3Point => "3-point angle",
|
||||
Self::Radius => "Radius",
|
||||
Self::Tangent => "Tangent",
|
||||
Self::Smooth => "Smooth",
|
||||
Self::Concentric => "Concentric",
|
||||
Self::CenterPoint => "Center point",
|
||||
Self::Colinear => "Collinear",
|
||||
Self::Midpoint => "Midpoint",
|
||||
Self::Fixed => "Fixed",
|
||||
Self::PointOnCurve => "Point on curve",
|
||||
Self::EqualDistance => "Equal distance",
|
||||
Self::Symmetric => "Symmetric",
|
||||
Self::Diameter => "Diameter",
|
||||
Self::DistanceX => "Horizontal distance",
|
||||
Self::DistanceY => "Vertical distance",
|
||||
Self::DistanceDirected => "Directed distance",
|
||||
Self::Normal => "Normal",
|
||||
Self::RigidSet => "Rigid set",
|
||||
}
|
||||
}
|
||||
|
||||
/// The short symbol a constraint glyph shows — matches the existing
|
||||
/// ribbon icons (`crate::modules::parametric::{tools,value}`) for
|
||||
/// the kinds that have a one-click button, so the same glyph means the
|
||||
@@ -640,7 +671,13 @@ pub(crate) fn glyph_placement(
|
||||
document: &acadrust::CadDocument,
|
||||
constraint: &ParametricConstraint,
|
||||
) -> Option<(Vector3, Vector3)> {
|
||||
let r = constraint.refs.first()?;
|
||||
glyph_placements(document, constraint).into_iter().next()
|
||||
}
|
||||
|
||||
fn glyph_placement_for_reference(
|
||||
document: &acadrust::CadDocument,
|
||||
r: ParametricRef,
|
||||
) -> Option<(Vector3, Vector3)> {
|
||||
let entity = document.get_entity(r.entity)?;
|
||||
let line_midpoint = |line: &acadrust::entities::Line| {
|
||||
Vector3::new(
|
||||
@@ -661,7 +698,7 @@ pub(crate) fn glyph_placement(
|
||||
entity,
|
||||
POLYLINE_SEGMENT_MIDPOINT_MARKER_BASE - segment as i32,
|
||||
)?;
|
||||
let [start, end] = constraint_segment_endpoints(document, *r)?;
|
||||
let [start, end] = constraint_segment_endpoints(document, r)?;
|
||||
return Some((anchor, segment_normal(start, end)));
|
||||
}
|
||||
match (entity, r.marker) {
|
||||
@@ -704,6 +741,52 @@ pub(crate) fn glyph_placement(
|
||||
}
|
||||
}
|
||||
|
||||
fn glyph_placements(
|
||||
document: &acadrust::CadDocument,
|
||||
constraint: &ParametricConstraint,
|
||||
) -> Vec<(Vector3, Vector3)> {
|
||||
if constraint.kind == ConstraintKind::Parallel {
|
||||
return constraint
|
||||
.refs
|
||||
.iter()
|
||||
.filter_map(|reference| glyph_placement_for_reference(document, *reference))
|
||||
.collect();
|
||||
}
|
||||
|
||||
let Some(first) = constraint.refs.first().copied() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let fallback = glyph_placement_for_reference(document, first);
|
||||
if matches!(
|
||||
constraint.kind,
|
||||
ConstraintKind::Perpendicular | ConstraintKind::Tangent
|
||||
) {
|
||||
if let [first, second, ..] = constraint.refs.as_slice() {
|
||||
if let (Some(first_curve), Some(second_curve), Some((anchor, outward))) = (
|
||||
constraint_reference_curve_xy(document, *first),
|
||||
constraint_reference_curve_xy(document, *second),
|
||||
fallback,
|
||||
) {
|
||||
if let Some(crossing) = cadkernel::geom2d::intersect(
|
||||
&first_curve,
|
||||
&second_curve,
|
||||
cadkernel::geom2d::Tolerance::default(),
|
||||
)
|
||||
.into_iter()
|
||||
.next()
|
||||
{
|
||||
return vec![(
|
||||
Vector3::new(crossing.point[0], crossing.point[1], anchor.z),
|
||||
outward,
|
||||
)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fallback.into_iter().collect()
|
||||
}
|
||||
|
||||
/// World-space locations that explain what a hovered constraint acts on.
|
||||
/// Point constraints expose their referenced point directly; curve relations
|
||||
/// expose the contact or intersection that makes the relation visible.
|
||||
@@ -1334,33 +1417,14 @@ impl super::Scene {
|
||||
.any(|reference| self.selected.contains(&reference.entity));
|
||||
self.should_display_parametric_constraint(scope, c.id, selected, display_mode)
|
||||
})
|
||||
.filter_map(|c| {
|
||||
let (anchor, outward) = glyph_placement(&self.document, c)?;
|
||||
let screen = crate::scene::pick::grip::project_rte(
|
||||
glam::DVec3::new(anchor.x, anchor.y, anchor.z),
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
)?;
|
||||
let outward_screen = crate::scene::pick::grip::project_rte(
|
||||
glam::DVec3::new(
|
||||
anchor.x + outward.x,
|
||||
anchor.y + outward.y,
|
||||
anchor.z + outward.z,
|
||||
),
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
)?;
|
||||
let direction = (outward_screen - screen).normalize_or(glam::Vec2::NEG_Y);
|
||||
let point = iced::Point::new(bounds.x + screen.x, bounds.y + screen.y);
|
||||
.flat_map(|c| {
|
||||
let is_conflicting = set.conflicts.iter().any(|(id, _)| *id == c.id);
|
||||
let label = if show_values {
|
||||
glyph_label(c)
|
||||
} else {
|
||||
c.kind.glyph_symbol().to_string()
|
||||
};
|
||||
let hover_points = constraint_hover_points(&self.document, c)
|
||||
let hover_points: Vec<iced::Point> = constraint_hover_points(&self.document, c)
|
||||
.into_iter()
|
||||
.filter_map(|hover_point| {
|
||||
let projected = crate::scene::pick::grip::project_rte(
|
||||
@@ -1376,14 +1440,38 @@ impl super::Scene {
|
||||
(point.x.is_finite() && point.y.is_finite()).then_some(point)
|
||||
})
|
||||
.collect();
|
||||
point.x.is_finite().then_some((
|
||||
c.id,
|
||||
point,
|
||||
direction.to_array(),
|
||||
label,
|
||||
is_conflicting,
|
||||
hover_points,
|
||||
))
|
||||
glyph_placements(&self.document, c)
|
||||
.into_iter()
|
||||
.filter_map(|(anchor, outward)| {
|
||||
let screen = crate::scene::pick::grip::project_rte(
|
||||
glam::DVec3::new(anchor.x, anchor.y, anchor.z),
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
)?;
|
||||
let outward_screen = crate::scene::pick::grip::project_rte(
|
||||
glam::DVec3::new(
|
||||
anchor.x + outward.x,
|
||||
anchor.y + outward.y,
|
||||
anchor.z + outward.z,
|
||||
),
|
||||
view_rot,
|
||||
eye,
|
||||
bounds,
|
||||
)?;
|
||||
let direction =
|
||||
(outward_screen - screen).normalize_or(glam::Vec2::NEG_Y);
|
||||
let point = iced::Point::new(bounds.x + screen.x, bounds.y + screen.y);
|
||||
point.x.is_finite().then(|| (
|
||||
c.id,
|
||||
point,
|
||||
direction.to_array(),
|
||||
label.clone(),
|
||||
is_conflicting,
|
||||
hover_points.clone(),
|
||||
))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -1580,6 +1668,88 @@ mod tests {
|
||||
glyph_placement(&document, &constraint(ParametricRef::segment(h(3), 0))).unwrap();
|
||||
assert_eq!(anchor, Vector3::new(5.0, 0.0, 0.0));
|
||||
assert_eq!(direction, Vector3::new(0.0, 10.0, 0.0));
|
||||
|
||||
let mut tangent_line = acadrust::entities::Line::from_points(
|
||||
Vector3::new(-10.0, 5.0, 0.0),
|
||||
Vector3::new(10.0, 5.0, 0.0),
|
||||
);
|
||||
tangent_line.common.handle = h(4);
|
||||
document.add_entity(acadrust::EntityType::Line(tangent_line)).unwrap();
|
||||
let relation = |id, kind, refs| ParametricConstraint {
|
||||
id,
|
||||
kind,
|
||||
refs,
|
||||
driving_param: None,
|
||||
enabled: true,
|
||||
native_origin: None,
|
||||
rigid_points: Vec::new(),
|
||||
distance_direction_type: 0,
|
||||
distance_direction: None,
|
||||
angle_sector: angle_sector::PARALLEL_COUNTERCLOCKWISE,
|
||||
};
|
||||
let tangent = relation(
|
||||
1,
|
||||
ConstraintKind::Tangent,
|
||||
vec![ParametricRef::whole(h(4)), ParametricRef::whole(h(2))],
|
||||
);
|
||||
assert_eq!(
|
||||
glyph_placement(&document, &tangent).unwrap().0,
|
||||
Vector3::new(0.0, 5.0, 0.0)
|
||||
);
|
||||
|
||||
let mut rectangle = acadrust::entities::LwPolyline::from_points(vec![
|
||||
acadrust::types::Vector2::new(0.0, 0.0),
|
||||
acadrust::types::Vector2::new(4.0, 0.0),
|
||||
acadrust::types::Vector2::new(4.0, 2.0),
|
||||
acadrust::types::Vector2::new(0.0, 2.0),
|
||||
]);
|
||||
rectangle.common.handle = h(5);
|
||||
rectangle.is_closed = true;
|
||||
document.add_entity(acadrust::EntityType::LwPolyline(rectangle)).unwrap();
|
||||
let parallel = glyph_placements(
|
||||
&document,
|
||||
&relation(
|
||||
2,
|
||||
ConstraintKind::Parallel,
|
||||
vec![ParametricRef::segment(h(5), 0), ParametricRef::segment(h(5), 2)],
|
||||
),
|
||||
);
|
||||
assert_eq!(parallel.len(), 2);
|
||||
assert_eq!(parallel[0].0, Vector3::new(2.0, 0.0, 0.0));
|
||||
assert_eq!(parallel[1].0, Vector3::new(2.0, 2.0, 0.0));
|
||||
let perpendicular = relation(
|
||||
3,
|
||||
ConstraintKind::Perpendicular,
|
||||
vec![ParametricRef::segment(h(5), 3), ParametricRef::segment(h(5), 2)],
|
||||
);
|
||||
assert_eq!(
|
||||
glyph_placement(&document, &perpendicular).unwrap().0,
|
||||
Vector3::new(0.0, 2.0, 0.0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn polyline_constraint_hover_builds_only_the_referenced_segment() {
|
||||
let mut scene = super::super::Scene::new();
|
||||
let handle = scene.add_entity(acadrust::EntityType::LwPolyline(
|
||||
acadrust::entities::LwPolyline::from_points(vec![
|
||||
acadrust::types::Vector2::new(0.0, 0.0),
|
||||
acadrust::types::Vector2::new(4.0, 0.0),
|
||||
acadrust::types::Vector2::new(4.0, 2.0),
|
||||
]),
|
||||
));
|
||||
|
||||
scene.set_constraint_hover_highlights(&[ParametricRef::segment(handle, 1)]);
|
||||
|
||||
assert!(scene.constraint_hover_highlights.is_empty());
|
||||
assert_eq!(scene.constraint_hover_wires.len(), 1);
|
||||
let wire = &scene.constraint_hover_wires[0];
|
||||
let points: Vec<_> = wire.points.iter().zip(&wire.points_low).map(|(high, low)| [
|
||||
high[0] as f64 + low[0] as f64,
|
||||
high[1] as f64 + low[1] as f64,
|
||||
high[2] as f64 + low[2] as f64,
|
||||
]).collect();
|
||||
assert_eq!(points, vec![[4.0, 0.0, 0.0], [4.0, 2.0, 0.0]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1718,18 +1888,18 @@ mod tests {
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
driven_ref_for_grip(&arc, handle, 0),
|
||||
grip_solve_anchor_ref(&arc, handle, 0),
|
||||
Some(ParametricRef::center(handle))
|
||||
);
|
||||
assert_eq!(
|
||||
driven_ref_for_grip(&arc, handle, 1),
|
||||
grip_solve_anchor_ref(&arc, handle, 1),
|
||||
Some(ParametricRef::point(handle, 0))
|
||||
);
|
||||
assert_eq!(
|
||||
driven_ref_for_grip(&arc, handle, 2),
|
||||
grip_solve_anchor_ref(&arc, handle, 2),
|
||||
Some(ParametricRef::point(handle, 1))
|
||||
);
|
||||
assert_eq!(driven_ref_for_grip(&arc, handle, 3), None);
|
||||
assert_eq!(grip_solve_anchor_ref(&arc, handle, 3), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1942,10 +1942,10 @@ fn solve_scope(
|
||||
sys.add_constraint(Rc::new(Difference::new(el.center.y, el.focus1.y, dy)));
|
||||
}
|
||||
|
||||
// The first ordered Coincident selection is the anchor for this solve.
|
||||
// Pin either its addressed point or every defining parameter of its whole
|
||||
// curve temporarily; the relation remains persistent, while these pins do
|
||||
// not enter the drawing's constraint graph.
|
||||
// Temporarily make each requested anchor a fixed kernel input. Marking the
|
||||
// geometry parameters as driven keeps them exact and out of the solver's
|
||||
// free-variable list; an approximate equality equation would still allow
|
||||
// visible drift after repeated grip frames.
|
||||
for reference in driven_refs {
|
||||
if !set
|
||||
.constraints
|
||||
@@ -1996,9 +1996,7 @@ fn solve_scope(
|
||||
}
|
||||
};
|
||||
for parameter in params_to_pin {
|
||||
let value = sys.store().get(parameter);
|
||||
let target = sys.add_param(value, true);
|
||||
sys.add_constraint(Rc::new(Equal::new(parameter, target, 1.0)));
|
||||
sys.store_mut().set_driven(parameter, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2638,11 +2636,24 @@ impl Scene {
|
||||
if !is_touched {
|
||||
continue;
|
||||
}
|
||||
let mut anchors = driven_refs.to_vec();
|
||||
for reference in set
|
||||
.constraints
|
||||
.iter()
|
||||
.filter(|constraint| constraint.enabled)
|
||||
.flat_map(|constraint| &constraint.refs)
|
||||
{
|
||||
if !touched.contains(&reference.entity)
|
||||
&& !anchors.iter().any(|anchor| anchor.entity == reference.entity)
|
||||
{
|
||||
anchors.push(ParametricRef::whole(reference.entity));
|
||||
}
|
||||
}
|
||||
let Some((solved, _dof, _conflicts)) = solve_scope(
|
||||
&self.document,
|
||||
&self.named_parameters,
|
||||
set,
|
||||
driven_refs,
|
||||
&anchors,
|
||||
retain_size,
|
||||
&retained_before,
|
||||
)
|
||||
@@ -2914,11 +2925,11 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// `solve_parametric_constraints_preview` reports the constrained
|
||||
/// neighbor's solved position without writing anything into the
|
||||
/// document itself — the grip-drag caller owns applying it.
|
||||
/// A grip preview treats geometry outside the edit as a fixed reference,
|
||||
/// so a parallel constraint projects the dragged endpoint without moving
|
||||
/// the other line or changing their separation.
|
||||
#[test]
|
||||
fn preview_solve_reports_the_neighbors_new_state_without_mutating_the_document() {
|
||||
fn preview_solve_keeps_untouched_parallel_geometry_fixed() {
|
||||
let mut scene = Scene::new();
|
||||
let a = scene.add_entity(EntityType::Line(acadrust::entities::Line::from_points(
|
||||
Vector3::new(0.0, 0.0, 0.0),
|
||||
@@ -2943,40 +2954,39 @@ mod tests {
|
||||
}
|
||||
let b_before = scene.document.get_entity(b).cloned();
|
||||
|
||||
let solved = scene.solve_parametric_constraints_preview(&[a], &[], false, &[]);
|
||||
let solved = scene.solve_parametric_constraints_preview(
|
||||
&[a],
|
||||
&[ParametricRef::point(a, 0)],
|
||||
false,
|
||||
&[],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
scene.document.get_entity(b),
|
||||
b_before.as_ref(),
|
||||
"preview must not mutate the document"
|
||||
);
|
||||
// The whole scope solves together (every registered entity's params
|
||||
// are free, not just `b`'s — see `register_entity`'s doc comment),
|
||||
// so `a` itself may also have shifted slightly to reach the nearest
|
||||
// mutually-parallel configuration; read whichever position `a` ends
|
||||
// up at from `solved` too, rather than assuming it stayed exactly
|
||||
// where the simulated drag put it.
|
||||
let line_dir = |entity: &EntityType| {
|
||||
let EntityType::Line(l) = entity else {
|
||||
panic!("expected a Line")
|
||||
};
|
||||
(l.end.x - l.start.x, l.end.y - l.start.y)
|
||||
};
|
||||
let dir_a = solved
|
||||
let moved_a = solved
|
||||
.iter()
|
||||
.find(|(h, _)| *h == a)
|
||||
.map(|(_, e)| line_dir(e))
|
||||
.unwrap_or((10.0, 6.0));
|
||||
let (_, moved_entity) = solved
|
||||
.iter()
|
||||
.find(|(h, _)| *h == b)
|
||||
.expect("b should be reported as moved");
|
||||
let dir_b = line_dir(moved_entity);
|
||||
let cross = dir_a.0 * dir_b.1 - dir_a.1 * dir_b.0;
|
||||
.map(|(_, entity)| entity)
|
||||
.expect("the dragged line should be projected");
|
||||
assert!(solved.iter().all(|(handle, _)| *handle != b));
|
||||
let EntityType::Line(moved_a_line) = moved_a else {
|
||||
panic!("expected a Line")
|
||||
};
|
||||
assert_eq!(moved_a_line.start, Vector3::new(0.0, 0.0, 0.0));
|
||||
let EntityType::Line(fixed_b) = b_before.unwrap() else {
|
||||
panic!("expected a Line")
|
||||
};
|
||||
let dir_a = moved_a_line.end - moved_a_line.start;
|
||||
let dir_b = fixed_b.end - fixed_b.start;
|
||||
let cross = dir_a.x * dir_b.y - dir_a.y * dir_b.x;
|
||||
assert!(
|
||||
cross.abs() < 1e-6,
|
||||
"the previewed positions should be mutually parallel: dir_a={dir_a:?} dir_b={dir_b:?}"
|
||||
);
|
||||
assert_eq!(fixed_b.start.y - moved_a_line.start.y, 5.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1008,6 +1008,16 @@ impl Scene {
|
||||
hover_changed = true;
|
||||
}
|
||||
hover_changed |= self.constraint_hover_highlights.remove(&h);
|
||||
if self
|
||||
.constraint_hover_refs
|
||||
.iter()
|
||||
.any(|reference| reference.entity == h)
|
||||
{
|
||||
self.constraint_hover_refs.clear();
|
||||
self.constraint_hover_wires.clear();
|
||||
self.constraint_hover_highlights.clear();
|
||||
hover_changed = true;
|
||||
}
|
||||
self.hatches.remove(&h);
|
||||
self.images.remove(&h);
|
||||
self.meshes.remove(&h);
|
||||
|
||||
@@ -4254,15 +4254,20 @@ impl Scene {
|
||||
Arc::new(Vec::new())
|
||||
};
|
||||
let preview_wires = if !show_live_overlay
|
||||
|| (self.interim_wire.is_none() && self.preview_wires.is_empty())
|
||||
|| (self.interim_wire.is_none()
|
||||
&& self.preview_wires.is_empty()
|
||||
&& self.constraint_hover_wires.is_empty())
|
||||
{
|
||||
Arc::new(Vec::new())
|
||||
} else {
|
||||
let mut v: Vec<WireModel> = Vec::with_capacity(self.preview_wires.len() + 1);
|
||||
let mut v: Vec<WireModel> = Vec::with_capacity(
|
||||
self.preview_wires.len() + self.constraint_hover_wires.len() + 1,
|
||||
);
|
||||
if let Some(iw) = &self.interim_wire {
|
||||
v.push(iw.clone());
|
||||
}
|
||||
v.extend(self.preview_wires.iter().cloned());
|
||||
v.extend(self.constraint_hover_wires.iter().cloned());
|
||||
Arc::new(v)
|
||||
};
|
||||
let preview_hatches = if show_live_overlay {
|
||||
|
||||
Reference in new issue
Block a user