feat(model): add exact solid modelling tools

This commit is contained in:
Hakan Seven committed 2026-08-23 22:53:31 +03:00
1 parent fb2ea1e831
commit 06cd0ec6a9
51 files changed
+1487 -574

No files matched your search

+38
View File
@@ -1,5 +1,15 @@
use super::*;
fn selected_solid(app: &OpenCADStudio, tab: usize) -> Option<acadrust::Handle> {
let selected = app.tabs[tab].scene.selected_handles_in_order();
(selected.len() == 1
&& matches!(
app.tabs[tab].scene.document.get_entity(selected[0]),
Some(acadrust::EntityType::Solid3D(_))
))
.then_some(selected[0])
}
impl OpenCADStudio {
pub(super) fn dispatch_dim(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
match cmd {
@@ -950,6 +960,20 @@ impl OpenCADStudio {
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
"SOLIDFILLET" => {
use crate::modules::model::edge_cmd::{EdgeOperation, SolidEdgeCommand};
let command = SolidEdgeCommand::new(EdgeOperation::Fillet, selected_solid(self, i));
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
}
"FILLET" if selected_solid(self, i).is_some() => {
use crate::modules::model::edge_cmd::{EdgeOperation, SolidEdgeCommand};
let command = SolidEdgeCommand::new(EdgeOperation::Fillet, selected_solid(self, i));
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
}
"FILLET" => {
use crate::modules::draw::modify::fillet::FilletCommand;
let entities: Vec<_> = self.tabs[i]
@@ -1083,6 +1107,20 @@ impl OpenCADStudio {
}
}
"SOLIDCHAMFER" => {
use crate::modules::model::edge_cmd::{EdgeOperation, SolidEdgeCommand};
let command = SolidEdgeCommand::new(EdgeOperation::Chamfer, selected_solid(self, i));
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
}
"CHAMFER" if selected_solid(self, i).is_some() => {
use crate::modules::model::edge_cmd::{EdgeOperation, SolidEdgeCommand};
let command = SolidEdgeCommand::new(EdgeOperation::Chamfer, selected_solid(self, i));
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
}
"CHAMFER" => {
use crate::modules::draw::modify::fillet::ChamferCommand;
let entities: Vec<_> = self.tabs[i]
+34 -10
View File
@@ -466,31 +466,55 @@ impl OpenCADStudio {
}
}
// BOX / SPHERE / CYLINDER / CONE / WEDGE / TORUS are handled by the
// Model-tab primitive command above (with the kernel boolean caching).
// ── EXTRUDE ────────────────────────────────────────────────────
// PRESSPULL on a closed boundary creates a solid by extruding it to a
// height — the same operation as EXTRUDE. THICKEN turns a closed planar
// profile into a solid of the given thickness, which is also an extrude.
"EXTRUDE" | "PRESSPULL" | "THICKEN" => {
"EXTRUDE" | "THICKEN" => {
use crate::modules::insert::solid3d_cmds::ExtrudeCommand;
// If a single entity is already selected, skip the pick step.
let selected: Vec<_> = self.tabs[i].scene.selected_entities().into_iter().collect();
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
if selected.len() == 1 {
let handle = selected[0].0;
let mut cmd = ExtrudeCommand::new(color);
cmd.on_entity_pick(handle, glam::DVec3::ZERO);
let mut cmd = ExtrudeCommand::new_named(cmd, color);
if let Some(curve) = crate::entities::curve::entity_curve(selected[0].1) {
cmd.set_entity_pick_direction(
curve.plane.normal().map(glam::DVec3::from_array),
);
cmd.on_entity_pick(
handle,
glam::DVec3::from_array(curve.plane.origin),
);
}
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
} else {
let cmd = ExtrudeCommand::new(color);
let cmd = ExtrudeCommand::new_named(cmd, color);
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
}
"PRESSPULL" => {
use crate::modules::insert::solid3d_cmds::PresspullCommand;
let selected: Vec<_> = self.tabs[i].scene.selected_entities().into_iter().collect();
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
let mut command = PresspullCommand::new(color);
if selected.len() == 1
&& !matches!(selected[0].1, acadrust::EntityType::Solid3D(_))
{
if let Some(curve) = crate::entities::curve::entity_curve(selected[0].1) {
command.set_entity_pick_direction(
curve.plane.normal().map(glam::DVec3::from_array),
);
command.on_entity_pick(
selected[0].0,
glam::DVec3::from_array(curve.plane.origin),
);
}
}
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
}
// ── REVOLVE ────────────────────────────────────────────────────
"REVOLVE" => {
use crate::modules::insert::solid3d_cmds::RevolveCommand;
+4 -13
View File
@@ -925,14 +925,15 @@ impl OpenCADStudio {
}
// ── Model commands (3D primitives) ─────────────────────────────
"BOX" | "WEDGE" | "CYLINDER" | "CONE" | "SPHERE" | "TORUS" => {
"BOX" | "WEDGE" | "CYLINDER" | "CONE" | "SPHERE" | "PYRAMID" | "PYR"
| "TORUS" => {
use crate::modules::model::primitive_cmd::PrimitiveCommand;
let new_cmd = PrimitiveCommand::new(cmd);
self.command_line.push_info(&new_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
}
// ── Design commands (solid booleans) ───────────────────────────
// ── Solid booleans ─────────────────────────────────────────────
"UNION" | "SUBTRACT" | "INTERSECT" => {
use crate::modules::model::boolean_cmd::BoolOp;
if let Some(op) = BoolOp::from_id(cmd) {
@@ -1033,17 +1034,7 @@ impl OpenCADStudio {
}
}
// PYRAMID <radius> <height> [sides] — create an n-sided pyramid mesh.
"PYRAMID" | "PYR" => {
use crate::command::TwoValuePromptCommand;
let c = TwoValuePromptCommand::new(
"PYRAMID",
"PYRAMID base radius:",
"PYRAMID height (add sides by typing a 3rd number):",
);
self.command_line.push_info(&c.prompt());
self.tabs[i].active_cmd = Some(Box::new(c));
}
// PYRAMID <radius> <height> [sides] keeps the direct numeric form.
cmd if cmd.starts_with("PYRAMID ") || cmd.starts_with("PYR ") => {
let nums: Vec<f64> = cmd
.split_whitespace()