forked from 1b-agent/opencadstudio-upstream
Picking the cloud opens the Extract Section Lines from Point Cloud dialog with the reference's settings and defaults: entire cross section or perimeter only, maximum points to process (slider and field, with the estimated time), output layer (Use Current or a layer), colour, lines or 2D polylines with their width, minimum line length and connect lines tolerance (each measurable by two points on screen), collinear angle tolerance (0-10) and Preview result. Create extracts; with the preview it asks [Accept/Settings/Undo] and Settings reopens the dialog. The settings are kept for the session. Automation action pc_section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
200 lines
8.8 KiB
Rust
200 lines
8.8 KiB
Rust
//! PCEXTRACTEDGE, PCEXTRACTCORNER, PCEXTRACTCENTERLINE and
|
|
//! (-)PCEXTRACTSECTION: the commands get the drawing's shown cloud points
|
|
//! and its section objects when they start.
|
|
|
|
use super::*;
|
|
use crate::ui::window::pdf_dialogs::PdfDialogMsg;
|
|
use codec::entities::ExtendedEntityData;
|
|
use glam::DVec3;
|
|
|
|
use crate::modules::insert::pc_extract::{
|
|
CenterlineCommand, Clouds, PlanesCommand, Section, SectionCommand,
|
|
};
|
|
|
|
fn dvec(v: codec::types::Vector3) -> DVec3 {
|
|
DVec3::new(v.x, v.y, v.z)
|
|
}
|
|
|
|
impl OpenCADStudio {
|
|
/// The points each point cloud shows (the renderer's placed set).
|
|
fn extraction_clouds(&self, i: usize) -> Clouds {
|
|
let document = &self.tabs[i].scene.document;
|
|
let mut clouds = Vec::new();
|
|
for entity in document.entities() {
|
|
let codec::EntityType::Extended(extended) = entity else {
|
|
continue;
|
|
};
|
|
let ExtendedEntityData::PointCloudEx(data) = &extended.data else {
|
|
continue;
|
|
};
|
|
let common = entity.common();
|
|
// The colour the renderer places with, so its cached points are reused.
|
|
let color = if matches!(common.color, codec::types::Color::ByLayer) {
|
|
document.layers.get(&common.layer).map(|layer| layer.color.clone()).unwrap_or(common.color.clone())
|
|
} else {
|
|
common.color.clone()
|
|
};
|
|
let color = color.rgb().map_or([255; 3], |(r, g, b)| [r, g, b]);
|
|
if let Some(placed) = crate::scene::model::point_cloud::placed(
|
|
document,
|
|
data,
|
|
color,
|
|
None,
|
|
&crate::scene::model::point_cloud::hidden(data),
|
|
) {
|
|
clouds.push((common.handle, placed));
|
|
}
|
|
}
|
|
Clouds(clouds)
|
|
}
|
|
|
|
fn extraction_sections(&self, i: usize) -> Vec<Section> {
|
|
self.tabs[i]
|
|
.scene
|
|
.document
|
|
.entities()
|
|
.filter_map(|entity| {
|
|
let codec::EntityType::Extended(extended) = entity else {
|
|
return None;
|
|
};
|
|
let ExtendedEntityData::SectionObject(data) = &extended.data else {
|
|
return None;
|
|
};
|
|
let (first, last) = (dvec(*data.vertices.first()?), dvec(*data.vertices.last()?));
|
|
let tangent = (last - first).normalize_or(DVec3::X);
|
|
let vertical = dvec(data.vertical_direction).normalize_or(DVec3::Z);
|
|
let viewing = vertical.cross(tangent).normalize_or(-DVec3::Z)
|
|
* if data.flags & 4 != 0 { 1.0 } else { -1.0 };
|
|
Some(Section { origin: first, viewing, tangent, live: data.flags & 1 != 0 })
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub(super) fn dispatch_pc_extract(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
|
use crate::command::CadCommand;
|
|
let command: Box<dyn CadCommand> = match cmd {
|
|
"PCEXTRACTEDGE" => Box::new(PlanesCommand::new(false, self.extraction_clouds(i))),
|
|
"PCEXTRACTCORNER" => Box::new(PlanesCommand::new(true, self.extraction_clouds(i))),
|
|
"PCEXTRACTCENTERLINE" => Box::new(CenterlineCommand::new(self.extraction_clouds(i))),
|
|
"PCEXTRACTSECTION" | "-PCEXTRACTSECTION" => Box::new(SectionCommand::new(
|
|
cmd == "PCEXTRACTSECTION",
|
|
self.extraction_clouds(i),
|
|
self.extraction_sections(i),
|
|
)),
|
|
"_PCSECTIONDLG" => {
|
|
self.open_pc_section_dialog(i);
|
|
return Some(self.finish_dispatch(cmd));
|
|
}
|
|
_ => return None,
|
|
};
|
|
self.command_line.push_info(&command.prompt());
|
|
self.tabs[i].active_cmd = Some(command);
|
|
Some(self.finish_dispatch(cmd))
|
|
}
|
|
}
|
|
|
|
impl OpenCADStudio {
|
|
/// PCEXTRACTSECTION's settings dialog, filled from the session settings.
|
|
fn open_pc_section_dialog(&mut self, i: usize) {
|
|
use crate::modules::insert::pc_extract::{has_job, settings};
|
|
use crate::ui::window::pdf_dialogs::{LineColor, PcSectionState};
|
|
if !has_job() {
|
|
return;
|
|
}
|
|
let s = settings();
|
|
let current = crate::t!("Use Current").into_owned();
|
|
let mut layers = vec![current.clone()];
|
|
let mut names: Vec<String> = self.tabs[i].scene.document.layers.iter().map(|l| l.name.clone()).collect();
|
|
names.sort_by_key(|n| n.to_lowercase());
|
|
layers.extend(names);
|
|
let short = |v: f64| {
|
|
let t = format!("{v:.4}");
|
|
t.trim_end_matches('0').trim_end_matches('.').to_string()
|
|
};
|
|
self.pc_section = Some(PcSectionState {
|
|
perimeter: s.perimeter,
|
|
max_points: s.max_points.to_string(),
|
|
layers,
|
|
layer: s.layer.clone().unwrap_or(current),
|
|
color: LineColor(s.color),
|
|
polylines: s.polylines,
|
|
width: short(s.width),
|
|
min_length: short(s.min_length),
|
|
connect: short(s.connect),
|
|
angle: short(s.angle),
|
|
preview: s.preview,
|
|
});
|
|
self.active_modal = Some(crate::app::ModalKind::PcSection);
|
|
}
|
|
|
|
pub(in crate::app) fn update_pc_section(&mut self, message: PdfDialogMsg) -> Task<Message> {
|
|
use crate::modules::insert::pc_extract::{set_settings, settings, SectionCommand, SectionDistanceCommand};
|
|
use crate::command::CadCommand;
|
|
let i = self.active_tab;
|
|
let Some(state) = self.pc_section.as_mut() else {
|
|
return Task::none();
|
|
};
|
|
match message {
|
|
PdfDialogMsg::SecPerimeter(on) => state.perimeter = on,
|
|
PdfDialogMsg::SecMaxPoints(v) => state.max_points = v,
|
|
PdfDialogMsg::SecLayer(v) => state.layer = v,
|
|
PdfDialogMsg::SecColor(c) => state.color = c,
|
|
PdfDialogMsg::SecPolylines(on) => state.polylines = on,
|
|
PdfDialogMsg::SecWidth(v) => state.width = v,
|
|
PdfDialogMsg::SecMinLength(v) => state.min_length = v,
|
|
PdfDialogMsg::SecConnect(v) => state.connect = v,
|
|
PdfDialogMsg::SecAngle(v) => state.angle = v,
|
|
PdfDialogMsg::SecPreview(on) => state.preview = on,
|
|
// Measure on screen, then back to the dialog (the extraction waits).
|
|
PdfDialogMsg::SecPick(connect) => {
|
|
self.pc_section = None;
|
|
self.active_modal = None;
|
|
let command = SectionDistanceCommand::new(connect);
|
|
self.command_line.push_info(&command.prompt());
|
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
|
}
|
|
PdfDialogMsg::SecCreate => {
|
|
let number = |t: &str| crate::entities::common::parse_f64(t.trim());
|
|
let (min_length, connect, width) = (number(&state.min_length), number(&state.connect), number(&state.width));
|
|
let (Some(min_length), Some(connect)) = (min_length.filter(|v| *v > 0.0), connect.filter(|v| *v > 0.0)) else {
|
|
self.command_line.push_error(crate::t!("Requires a positive number.").as_ref());
|
|
return Task::none();
|
|
};
|
|
let Some(angle) = state.angle.trim().parse::<i64>().ok().filter(|v| (0..=10).contains(v)) else {
|
|
self.command_line
|
|
.push_error("Colinear angle tolerance only accepts integer from 0 to 10.");
|
|
return Task::none();
|
|
};
|
|
let Some(max_points) = state.max_points.trim().parse::<usize>().ok().filter(|v| *v > 0) else {
|
|
self.command_line.push_error(crate::t!("Requires a positive integer.").as_ref());
|
|
return Task::none();
|
|
};
|
|
let current = crate::t!("Use Current").into_owned();
|
|
let mut s = settings();
|
|
s.perimeter = state.perimeter;
|
|
s.max_points = max_points;
|
|
s.layer = (state.layer != current).then(|| state.layer.clone());
|
|
s.color = state.color.0;
|
|
s.polylines = state.polylines;
|
|
s.width = width.filter(|v| *v >= 0.0).unwrap_or(0.0);
|
|
s.min_length = min_length;
|
|
s.connect = connect;
|
|
s.angle = angle as f64;
|
|
s.preview = state.preview;
|
|
set_settings(s);
|
|
self.pc_section = None;
|
|
self.active_modal = None;
|
|
let Some(mut command) = SectionCommand::resume() else {
|
|
return Task::none();
|
|
};
|
|
let result = command.run();
|
|
self.command_line.push_info(&command.prompt());
|
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
|
return self.apply_cmd_result(result);
|
|
}
|
|
_ => {}
|
|
}
|
|
Task::none()
|
|
}
|
|
}
|