forked from 1b-agent/opencadstudio-upstream
perf: keep heavy work off UI thread
This commit is contained in:
1 parent
12bf126646
commit
deec48ad99
75 files changed
+11702
-518
No files matched your search
@@ -0,0 +1,99 @@
|
||||
//! Shared broad phase for interactive modify commands.
|
||||
//!
|
||||
//! The scene already narrows the cursor pick. Commands still need fast access
|
||||
//! to the picked analytic entity and, for intersection-heavy operations such
|
||||
//! as TRIM, to nearby boundary entities. Keeping this compact command-local
|
||||
//! index avoids cloning/scanning the complete drawing on every mouse move.
|
||||
|
||||
use acadrust::{EntityType, Handle};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::scene::convert::tess::entity_world_aabb_f64;
|
||||
use crate::scene::pick::quadtree::QuadTree;
|
||||
|
||||
pub(super) struct ModifyEntityIndex {
|
||||
by_handle: FxHashMap<Handle, usize>,
|
||||
tree: Option<QuadTree>,
|
||||
unbounded: Vec<Handle>,
|
||||
}
|
||||
|
||||
impl ModifyEntityIndex {
|
||||
pub(super) fn build(entities: &[EntityType]) -> Self {
|
||||
let mut by_handle = FxHashMap::default();
|
||||
let mut bounded = Vec::new();
|
||||
let mut unbounded = Vec::new();
|
||||
let mut world = [
|
||||
f64::INFINITY,
|
||||
f64::INFINITY,
|
||||
f64::NEG_INFINITY,
|
||||
f64::NEG_INFINITY,
|
||||
];
|
||||
|
||||
for (index, entity) in entities.iter().enumerate() {
|
||||
let handle = entity.common().handle;
|
||||
by_handle.insert(handle, index);
|
||||
if let Some(aabb) = entity_world_aabb_f64(entity) {
|
||||
world[0] = world[0].min(aabb[0]);
|
||||
world[1] = world[1].min(aabb[1]);
|
||||
world[2] = world[2].max(aabb[2]);
|
||||
world[3] = world[3].max(aabb[3]);
|
||||
bounded.push((handle, aabb));
|
||||
} else {
|
||||
unbounded.push(handle);
|
||||
}
|
||||
}
|
||||
|
||||
let tree = if bounded.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let span = (world[2] - world[0]).max(world[3] - world[1]).max(1.0);
|
||||
let pad = span * 1.0e-9 + 1.0e-6;
|
||||
let mut tree = QuadTree::new([
|
||||
world[0] - pad,
|
||||
world[1] - pad,
|
||||
world[2] + pad,
|
||||
world[3] + pad,
|
||||
]);
|
||||
for (handle, aabb) in bounded {
|
||||
tree.insert(handle, aabb);
|
||||
}
|
||||
Some(tree)
|
||||
};
|
||||
|
||||
Self {
|
||||
by_handle,
|
||||
tree,
|
||||
unbounded,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn get<'a>(
|
||||
&self,
|
||||
entities: &'a [EntityType],
|
||||
handle: Handle,
|
||||
) -> Option<&'a EntityType> {
|
||||
self.by_handle
|
||||
.get(&handle)
|
||||
.and_then(|index| entities.get(*index))
|
||||
}
|
||||
|
||||
pub(super) fn nearby_handles(
|
||||
&self,
|
||||
entities: &[EntityType],
|
||||
handle: Handle,
|
||||
) -> Option<FxHashSet<Handle>> {
|
||||
let entity = self.get(entities, handle)?;
|
||||
let aabb = entity_world_aabb_f64(entity)?;
|
||||
let span = (aabb[2] - aabb[0]).max(aabb[3] - aabb[1]).max(1.0);
|
||||
let pad = span * 1.0e-10 + 1.0e-7;
|
||||
let query = [aabb[0] - pad, aabb[1] - pad, aabb[2] + pad, aabb[3] + pad];
|
||||
let mut handles: FxHashSet<Handle> = self
|
||||
.tree
|
||||
.as_ref()
|
||||
.map(|tree| tree.query_rect(query).into_iter().collect())
|
||||
.unwrap_or_default();
|
||||
handles.extend(self.unbounded.iter().copied());
|
||||
Some(handles)
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ use crate::modules::draw::defaults;
|
||||
use crate::modules::IconKind;
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
|
||||
use super::entity_index::ModifyEntityIndex;
|
||||
|
||||
// ── Dropdown constants ─────────────────────────────────────────────────────
|
||||
|
||||
pub const DROPDOWN_ID: &str = "fillet_chamfer";
|
||||
@@ -1204,6 +1206,7 @@ pub struct FilletCommand {
|
||||
radius: f64,
|
||||
step: FilletStep,
|
||||
all_entities: Vec<EntityType>,
|
||||
entity_index: ModifyEntityIndex,
|
||||
/// First-object pick to restore after a radius entry made mid-selection
|
||||
/// (i.e. "R" pressed after the first object was already picked), so the
|
||||
/// command resumes at the second pick instead of restarting selection.
|
||||
@@ -1212,10 +1215,12 @@ pub struct FilletCommand {
|
||||
|
||||
impl FilletCommand {
|
||||
pub fn new(radius: f64, all_entities: Vec<EntityType>) -> Self {
|
||||
let entity_index = ModifyEntityIndex::build(&all_entities);
|
||||
Self {
|
||||
radius: radius as f64,
|
||||
step: FilletStep::First,
|
||||
all_entities,
|
||||
entity_index,
|
||||
resume_second: None,
|
||||
}
|
||||
}
|
||||
@@ -1348,9 +1353,8 @@ impl CadCommand for FilletCommand {
|
||||
FilletStep::WaitingForRadius => return CmdResult::NeedPoint,
|
||||
FilletStep::First => {
|
||||
let e1 = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.entity_index
|
||||
.get(&self.all_entities, handle)
|
||||
.and_then(|entity| match entity {
|
||||
EntityType::LwPolyline(p) => {
|
||||
Some(FilletEntity::from_lwpoly(p, handle, click))
|
||||
@@ -1380,9 +1384,8 @@ impl CadCommand for FilletCommand {
|
||||
}
|
||||
|
||||
let e2 = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.entity_index
|
||||
.get(&self.all_entities, handle)
|
||||
.and_then(|entity| match entity {
|
||||
EntityType::LwPolyline(p) => {
|
||||
Some(FilletEntity::from_lwpoly(p, handle, click))
|
||||
@@ -1426,9 +1429,8 @@ impl CadCommand for FilletCommand {
|
||||
FilletStep::WaitingForRadius => vec![],
|
||||
FilletStep::First => {
|
||||
let pts = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.entity_index
|
||||
.get(&self.all_entities, handle)
|
||||
.and_then(|e| match e {
|
||||
EntityType::LwPolyline(p) => Some(lwpoly_seg_hover_pts(p, click)),
|
||||
_ => FilletEntity::from_entity(e).map(|fe| entity_pts(&fe.to_entity_type())),
|
||||
@@ -1449,9 +1451,8 @@ impl CadCommand for FilletCommand {
|
||||
let e1 = e1.clone();
|
||||
let click1 = *click1;
|
||||
let e2 = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.entity_index
|
||||
.get(&self.all_entities, handle)
|
||||
.and_then(|entity| match entity {
|
||||
EntityType::LwPolyline(p) => {
|
||||
Some(FilletEntity::from_lwpoly(p, handle, click))
|
||||
@@ -1583,6 +1584,7 @@ pub struct ChamferCommand {
|
||||
dist2: f64,
|
||||
step: ChamferStep,
|
||||
all_entities: Vec<EntityType>,
|
||||
entity_index: ModifyEntityIndex,
|
||||
/// First-object pick (line or polyline segment) to restore after a
|
||||
/// distance entry made mid-selection, so the command resumes at the
|
||||
/// second pick instead of restarting selection.
|
||||
@@ -1591,11 +1593,13 @@ pub struct ChamferCommand {
|
||||
|
||||
impl ChamferCommand {
|
||||
pub fn new(dist: f64, all_entities: Vec<EntityType>) -> Self {
|
||||
let entity_index = ModifyEntityIndex::build(&all_entities);
|
||||
Self {
|
||||
dist1: dist as f64,
|
||||
dist2: defaults::get_chamfer_dist2(),
|
||||
step: ChamferStep::First,
|
||||
all_entities,
|
||||
entity_index,
|
||||
resume_pick: None,
|
||||
}
|
||||
}
|
||||
@@ -1777,9 +1781,7 @@ impl CadCommand for ChamferCommand {
|
||||
}
|
||||
ChamferStep::First => {
|
||||
match self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.entity_index.get(&self.all_entities, handle)
|
||||
{
|
||||
Some(EntityType::Line(l)) => {
|
||||
self.step = ChamferStep::Second {
|
||||
@@ -1821,9 +1823,8 @@ impl CadCommand for ChamferCommand {
|
||||
}
|
||||
|
||||
let l2 = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.entity_index
|
||||
.get(&self.all_entities, handle)
|
||||
.and_then(|e| {
|
||||
if let EntityType::Line(l) = e {
|
||||
Some(l.clone())
|
||||
@@ -1857,9 +1858,8 @@ impl CadCommand for ChamferCommand {
|
||||
ChamferStep::WaitingForDist1 | ChamferStep::WaitingForDist2 => return vec![],
|
||||
ChamferStep::First => {
|
||||
let pts = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.entity_index
|
||||
.get(&self.all_entities, handle)
|
||||
.and_then(|e| match e {
|
||||
EntityType::Line(l) => Some(line_pts(l)),
|
||||
EntityType::LwPolyline(p) => Some(lwpoly_seg_hover_pts(p, click)),
|
||||
@@ -1880,9 +1880,8 @@ impl CadCommand for ChamferCommand {
|
||||
let l1 = l1.clone();
|
||||
let click1 = *click1;
|
||||
let l2 = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.entity_index
|
||||
.get(&self.all_entities, handle)
|
||||
.and_then(|e| {
|
||||
if let EntityType::Line(l) = e {
|
||||
Some(l.clone())
|
||||
|
||||
@@ -4,6 +4,7 @@ pub mod attedit;
|
||||
pub mod break_cmd;
|
||||
pub mod copy;
|
||||
pub mod delete;
|
||||
mod entity_index;
|
||||
pub mod explode;
|
||||
pub mod fillet;
|
||||
pub mod join;
|
||||
|
||||
@@ -25,6 +25,8 @@ use crate::modules::draw::defaults;
|
||||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
|
||||
use super::entity_index::ModifyEntityIndex;
|
||||
|
||||
// ── Ribbon definition ──────────────────────────────────────────────────────
|
||||
|
||||
pub fn tool() -> ToolDef {
|
||||
@@ -691,6 +693,7 @@ enum Step {
|
||||
pub struct OffsetCommand {
|
||||
step: Step,
|
||||
all_entities: Vec<EntityType>,
|
||||
entity_index: ModifyEntityIndex,
|
||||
/// Pre-selected offsettable objects (pick-first, #422); consumed when the
|
||||
/// distance step resolves.
|
||||
preselected: Vec<EntityType>,
|
||||
@@ -712,9 +715,11 @@ pub fn is_offsettable(e: &EntityType) -> bool {
|
||||
|
||||
impl OffsetCommand {
|
||||
pub fn new(all_entities: Vec<EntityType>) -> Self {
|
||||
let entity_index = ModifyEntityIndex::build(&all_entities);
|
||||
Self {
|
||||
step: Step::Distance,
|
||||
all_entities,
|
||||
entity_index,
|
||||
preselected: Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -722,9 +727,11 @@ impl OffsetCommand {
|
||||
/// Pick-first flow (#422): the distance step still comes first, then the
|
||||
/// pre-selected objects go straight to the side step.
|
||||
pub fn with_selection(all_entities: Vec<EntityType>, targets: Vec<EntityType>) -> Self {
|
||||
let entity_index = ModifyEntityIndex::build(&all_entities);
|
||||
Self {
|
||||
step: Step::Distance,
|
||||
all_entities,
|
||||
entity_index,
|
||||
preselected: targets,
|
||||
}
|
||||
}
|
||||
@@ -801,9 +808,7 @@ impl CadCommand for OffsetCommand {
|
||||
}
|
||||
|
||||
let entity = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.entity_index.get(&self.all_entities, handle)
|
||||
.cloned();
|
||||
|
||||
// Accept every type compute_offset can offset — including XLine (#296),
|
||||
@@ -881,9 +886,7 @@ impl CadCommand for OffsetCommand {
|
||||
return vec![];
|
||||
}
|
||||
if let Some(entity) = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.entity_index.get(&self.all_entities, handle)
|
||||
{
|
||||
let pts = entity_wire_pts(entity);
|
||||
if !pts.is_empty() {
|
||||
|
||||
@@ -27,6 +27,8 @@ use crate::modules::draw::modify::spline_ops::{
|
||||
use crate::modules::IconKind;
|
||||
use crate::scene::model::wire_model::WireModel;
|
||||
|
||||
use super::entity_index::ModifyEntityIndex;
|
||||
|
||||
// ── Dropdown constants ─────────────────────────────────────────────────────
|
||||
|
||||
pub const DROPDOWN_ID: &str = "trim_extend";
|
||||
@@ -269,6 +271,20 @@ enum Geo {
|
||||
},
|
||||
}
|
||||
|
||||
impl Geo {
|
||||
fn handle(&self) -> Handle {
|
||||
match self {
|
||||
Self::Line { handle, .. }
|
||||
| Self::Arc { handle, .. }
|
||||
| Self::Circle { handle, .. }
|
||||
| Self::Ray { handle, .. }
|
||||
| Self::InfLine { handle, .. }
|
||||
| Self::Ellipse { handle, .. }
|
||||
| Self::Spline { handle, .. } => *handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_geos(entities: &[EntityType]) -> Vec<Geo> {
|
||||
let mut out = Vec::new();
|
||||
for e in entities {
|
||||
@@ -2797,6 +2813,7 @@ fn crossing_preview_wire(p1: [f64; 2], cursor: [f64; 2], name: &str) -> WireMode
|
||||
|
||||
pub struct TrimCommand {
|
||||
all_entities: Vec<EntityType>,
|
||||
entity_index: ModifyEntityIndex,
|
||||
geos: Vec<Geo>,
|
||||
mode: TrimMode,
|
||||
/// Cutting-edge selection; empty = every object cuts (quick mode).
|
||||
@@ -2809,9 +2826,11 @@ pub struct TrimCommand {
|
||||
|
||||
impl TrimCommand {
|
||||
pub fn new(all_entities: Vec<EntityType>) -> Self {
|
||||
let entity_index = ModifyEntityIndex::build(&all_entities);
|
||||
let geos = build_geos(&all_entities);
|
||||
Self {
|
||||
all_entities,
|
||||
entity_index,
|
||||
geos,
|
||||
mode: TrimMode::Pick,
|
||||
edge_set: Vec::new(),
|
||||
@@ -2823,6 +2842,7 @@ impl TrimCommand {
|
||||
/// Boundary geometry from the edge selection (or everything when none),
|
||||
/// with the Edge option's implied extrapolation applied on top.
|
||||
fn rebuild_geos(&mut self) {
|
||||
self.entity_index = ModifyEntityIndex::build(&self.all_entities);
|
||||
self.geos = if self.edge_set.is_empty() {
|
||||
build_geos(&self.all_entities)
|
||||
} else {
|
||||
@@ -2839,6 +2859,23 @@ impl TrimCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// Exact analytic boundaries whose boxes overlap the picked entity. The
|
||||
/// host already narrowed the cursor pick; this second broad phase keeps
|
||||
/// TRIM preview/intersection work local on dense drawings.
|
||||
fn nearby_geos(&self, handle: Handle) -> Vec<Geo> {
|
||||
if self.implied_edges {
|
||||
return self.geos.clone();
|
||||
}
|
||||
let Some(handles) = self.entity_index.nearby_handles(&self.all_entities, handle) else {
|
||||
return self.geos.clone();
|
||||
};
|
||||
self.geos
|
||||
.iter()
|
||||
.filter(|geo| handles.contains(&geo.handle()))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn fence_run(
|
||||
&mut self,
|
||||
fence: &[[f64; 2]],
|
||||
@@ -2974,7 +3011,8 @@ impl CadCommand for TrimCommand {
|
||||
pick_extend_at(&self.all_entities, &self.geos, handle, px, py)
|
||||
.map(|e| vec![e])
|
||||
} else {
|
||||
pick_trim_at(&self.all_entities, &self.geos, handle, px, py)
|
||||
let geos = self.nearby_geos(handle);
|
||||
pick_trim_at(&self.all_entities, &geos, handle, px, py)
|
||||
};
|
||||
if let Some(new_entities) = new_entities {
|
||||
// Snapshot is updated in on_entity_replaced once we know
|
||||
@@ -3108,10 +3146,10 @@ impl CadCommand for TrimCommand {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let nearby_geos = self.nearby_geos(handle);
|
||||
let geos = nearby_geos.as_slice();
|
||||
let entity = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle);
|
||||
.entity_index.get(&self.all_entities, handle);
|
||||
|
||||
let mut hover_wires = match entity {
|
||||
Some(EntityType::Line(l)) => {
|
||||
@@ -3119,7 +3157,7 @@ impl CadCommand for TrimCommand {
|
||||
let ay = l.start.y;
|
||||
let bx = l.end.x;
|
||||
let by = l.end.y;
|
||||
let ts = line_seg_ts(ax, ay, bx, by, handle, &self.geos);
|
||||
let ts = line_seg_ts(ax, ay, bx, by, handle, geos);
|
||||
if ts.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
@@ -3152,7 +3190,7 @@ impl CadCommand for TrimCommand {
|
||||
let cy = a.center.y;
|
||||
let a0 = a.start_angle;
|
||||
let a1 = a.end_angle;
|
||||
let ts = arc_seg_ts(cx, cy, a.radius, a0, a1, handle, &self.geos);
|
||||
let ts = arc_seg_ts(cx, cy, a.radius, a0, a1, handle, geos);
|
||||
if ts.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
@@ -3176,7 +3214,7 @@ impl CadCommand for TrimCommand {
|
||||
Some(EntityType::Circle(c)) => {
|
||||
let cx = c.center.x;
|
||||
let cy = c.center.y;
|
||||
let ts = arc_seg_ts(cx, cy, c.radius, 0.0, TAU, handle, &self.geos);
|
||||
let ts = arc_seg_ts(cx, cy, c.radius, 0.0, TAU, handle, geos);
|
||||
if ts.len() < 2 {
|
||||
return vec![];
|
||||
}
|
||||
@@ -3205,7 +3243,7 @@ impl CadCommand for TrimCommand {
|
||||
let by = r.base_point.y;
|
||||
let ex = bx + r.direction.x * TRIM_EXTENT;
|
||||
let ey = by + r.direction.y * TRIM_EXTENT;
|
||||
let ts = line_seg_ts(bx, by, ex, ey, handle, &self.geos);
|
||||
let ts = line_seg_ts(bx, by, ex, ey, handle, geos);
|
||||
if ts.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
@@ -3245,7 +3283,7 @@ impl CadCommand for TrimCommand {
|
||||
let ey_start = by - x.direction.y * TRIM_EXTENT;
|
||||
let ex_end = bx + x.direction.x * TRIM_EXTENT;
|
||||
let ey_end = by + x.direction.y * TRIM_EXTENT;
|
||||
let ts = line_seg_ts(ex_start, ey_start, ex_end, ey_end, handle, &self.geos);
|
||||
let ts = line_seg_ts(ex_start, ey_start, ex_end, ey_end, handle, geos);
|
||||
if ts.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
@@ -3294,7 +3332,7 @@ impl CadCommand for TrimCommand {
|
||||
t1 += TAU;
|
||||
}
|
||||
let ts = ellipse_seg_ts(
|
||||
e.center.x, e.center.y, a, b, nx, ny, t0, t1, handle, &self.geos,
|
||||
e.center.x, e.center.y, a, b, nx, ny, t0, t1, handle, geos,
|
||||
);
|
||||
if ts.is_empty() {
|
||||
return vec![];
|
||||
@@ -3321,7 +3359,7 @@ impl CadCommand for TrimCommand {
|
||||
out
|
||||
}
|
||||
Some(EntityType::Spline(s)) => {
|
||||
let ts = spline_seg_ts(s, handle, &self.geos);
|
||||
let ts = spline_seg_ts(s, handle, geos);
|
||||
if ts.is_empty() {
|
||||
return vec![];
|
||||
}
|
||||
@@ -3348,7 +3386,7 @@ impl CadCommand for TrimCommand {
|
||||
out
|
||||
}
|
||||
Some(EntityType::LwPolyline(p)) => {
|
||||
let Some(survivors) = trim_lwpolyline(p, pt.x as f64, pt.y as f64, &self.geos)
|
||||
let Some(survivors) = trim_lwpolyline(p, pt.x as f64, pt.y as f64, geos)
|
||||
else {
|
||||
return vec![];
|
||||
};
|
||||
@@ -3371,7 +3409,7 @@ impl CadCommand for TrimCommand {
|
||||
if self.implied_edges && !hover_wires.is_empty() {
|
||||
if let (Some(orig), Some(pieces)) = (
|
||||
entity,
|
||||
pick_trim_at(&self.all_entities, &self.geos, handle, pt.x, pt.y),
|
||||
pick_trim_at(&self.all_entities, geos, handle, pt.x, pt.y),
|
||||
) {
|
||||
let cuts = piece_cut_points(orig, &pieces);
|
||||
hover_wires.extend(implied_cut_guides(&self.all_entities, handle, &cuts));
|
||||
@@ -3477,6 +3515,7 @@ impl CadCommand for TrimCommand {
|
||||
|
||||
pub struct ExtendCommand {
|
||||
all_entities: Vec<EntityType>,
|
||||
entity_index: ModifyEntityIndex,
|
||||
geos: Vec<Geo>,
|
||||
mode: TrimMode,
|
||||
/// Boundary-edge selection; empty = every object is a boundary.
|
||||
@@ -3489,9 +3528,11 @@ pub struct ExtendCommand {
|
||||
|
||||
impl ExtendCommand {
|
||||
pub fn new(all_entities: Vec<EntityType>) -> Self {
|
||||
let entity_index = ModifyEntityIndex::build(&all_entities);
|
||||
let geos = build_geos(&all_entities);
|
||||
Self {
|
||||
all_entities,
|
||||
entity_index,
|
||||
geos,
|
||||
mode: TrimMode::Pick,
|
||||
edge_set: Vec::new(),
|
||||
@@ -3501,6 +3542,7 @@ impl ExtendCommand {
|
||||
}
|
||||
|
||||
fn rebuild_geos(&mut self) {
|
||||
self.entity_index = ModifyEntityIndex::build(&self.all_entities);
|
||||
self.geos = if self.edge_set.is_empty() {
|
||||
build_geos(&self.all_entities)
|
||||
} else {
|
||||
@@ -3681,18 +3723,14 @@ impl CadCommand for ExtendCommand {
|
||||
let mut out: Vec<WireModel> = self
|
||||
.edge_set
|
||||
.iter()
|
||||
.filter_map(|h| {
|
||||
self.all_entities.iter().find(|e| e.common().handle == *h)
|
||||
})
|
||||
.filter_map(|h| self.entity_index.get(&self.all_entities, *h))
|
||||
.map(|e| {
|
||||
WireModel::solid("edge_sel".into(), entity_pts(e), OPT_YELLOW, false)
|
||||
})
|
||||
.collect();
|
||||
if !handle.is_null() && !self.edge_set.contains(&handle) {
|
||||
if let Some(e) = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle)
|
||||
.entity_index.get(&self.all_entities, handle)
|
||||
{
|
||||
let mut c = OPT_YELLOW;
|
||||
c[3] = 0.45;
|
||||
@@ -3732,9 +3770,7 @@ impl CadCommand for ExtendCommand {
|
||||
}
|
||||
|
||||
let entity = self
|
||||
.all_entities
|
||||
.iter()
|
||||
.find(|e| e.common().handle == handle);
|
||||
.entity_index.get(&self.all_entities, handle);
|
||||
match entity {
|
||||
Some(EntityType::Line(l)) => {
|
||||
let ax = l.start.x;
|
||||
|
||||
Reference in new issue
Block a user