diff --git a/ROADMAP.md b/ROADMAP.md index 726f8b735..eec809d31 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -126,7 +126,7 @@ Underlay (PDF/DWF/DGN) | PEDIT (PE) | βœ… | β€” | | STRETCH (SS) | πŸ”§ | Crossing-window seΓ§ici yok (MOVE gibi Γ§alışıyor) | | SPLINEDIT | ⬜ | β€” | -| HATCHEDIT | ⬜ | Var olan hatch'i dΓΌzenleme | +| HATCHEDIT | βœ… | β€” | | ATTEDIT | ⬜ | Attribute değerlerini dΓΌzenleme | | DDEDIT (Γ§ift tΔ±k metin) | βœ… | β€” | | REFEDIT | ⬜ | Block in-place dΓΌzenleme | diff --git a/src/app/cmd_result.rs b/src/app/cmd_result.rs index 7f19548ee..29779e341 100644 --- a/src/app/cmd_result.rs +++ b/src/app/cmd_result.rs @@ -619,6 +619,38 @@ impl H7CAD { self.tabs[i].scene.clear_preview_wire(); self.restore_pre_cmd_tangent(); } + CmdResult::HatcheditApply { handle, name, scale, angle } => { + if let Some(mut model) = self.tabs[i].scene.hatches.get(&handle).cloned() { + // Update model fields + if !name.is_empty() { + use crate::scene::hatch_model::HatchPattern; + use crate::scene::hatch_patterns; + model.name = name.clone(); + if name.to_uppercase() == "SOLID" { + model.pattern = HatchPattern::Solid; + } else if let Some(entry) = hatch_patterns::find(&name) { + model.pattern = entry.gpu.clone(); + } + // If not found in catalog, keep existing pattern type + } + model.scale = scale; + model.angle_offset = angle; + + self.push_undo_snapshot(i, "HATCHEDIT"); + // Remove old hatch (entity + GPU model) + self.tabs[i].scene.erase_entities(&[handle]); + // Re-add with updated model + self.tabs[i].scene.add_hatch(model); + self.tabs[i].dirty = true; + self.command_line.push_output("HATCHEDIT: hatch updated."); + } else { + self.command_line.push_error("HATCHEDIT: hatch entity not found."); + } + self.tabs[i].active_cmd = None; + self.tabs[i].snap_result = None; + self.tabs[i].scene.clear_preview_wire(); + self.restore_pre_cmd_tangent(); + } CmdResult::DdeditEntity { handle, new_text } => { let mut updated = false; if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) { diff --git a/src/app/commands.rs b/src/app/commands.rs index cea083416..012a3d1ee 100644 --- a/src/app/commands.rs +++ b/src/app/commands.rs @@ -802,6 +802,28 @@ impl H7CAD { self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } + "HATCHEDIT"|"HE" => { + use crate::modules::home::draw::hatchedit::HatcheditCommand; + // If a single hatch is already selected, skip the pick step. + let sel = self.tabs[i].scene.selected_entities(); + if sel.len() == 1 { + let (h, _) = sel[0]; + if let Some(model) = self.tabs[i].scene.hatches.get(&h).cloned() { + let cmd = HatcheditCommand::with_handle( + h, model.name.clone(), model.scale, model.angle_offset, + ); + self.command_line.push_info(&cmd.prompt()); + self.tabs[i].active_cmd = Some(Box::new(cmd)); + } else { + self.command_line.push_error("HATCHEDIT: selected entity is not a hatch."); + } + } else { + let cmd = HatcheditCommand::new(); + self.command_line.push_info(&cmd.prompt()); + self.tabs[i].active_cmd = Some(Box::new(cmd)); + } + } + "GRADIENT" => { use crate::modules::home::draw::hatch::GradientCommand; let outlines = self.tabs[i].scene.closed_outlines(); diff --git a/src/app/update.rs b/src/app/update.rs index ca78bbc18..06125eadf 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -1059,7 +1059,23 @@ impl H7CAD { let hit = scene::hit_test::click_hit(p, &all_wires2, vp_mat2, bounds) .and_then(|s| Scene::handle_from_wire_name(s)); if let Some(handle) = hit { - self.tabs[i].active_cmd.as_mut().map(|c| c.on_entity_pick(handle, world_pt)) + let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_entity_pick(handle, world_pt)); + // HATCHEDIT: after pick, inject hatch model data into the command. + if self.tabs[i].active_cmd.as_ref().map(|c| c.name() == "HATCHEDIT").unwrap_or(false) { + if let Some(model) = self.tabs[i].scene.hatches.get(&handle).cloned() { + use crate::command::CadCommand; + use crate::modules::home::draw::hatchedit::HatcheditCommand; + let cmd: Box = Box::new(HatcheditCommand::with_handle( + handle, model.name.clone(), model.scale, model.angle_offset, + )); + self.command_line.push_info(&cmd.prompt()); + self.tabs[i].active_cmd = Some(cmd); + } else { + self.command_line.push_error("HATCHEDIT: not a hatch entity."); + self.tabs[i].active_cmd = None; + } + } + result } else { self.command_line.push_info("Nothing found at that point."); None diff --git a/src/command/mod.rs b/src/command/mod.rs index 5a47abb9c..7625e25a6 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -126,6 +126,8 @@ pub enum CmdResult { SetPlotWindow { p1: Vec3, p2: Vec3 }, /// Replace the text content of a Text/MText entity in-place. DdeditEntity { handle: Handle, new_text: String }, + /// Apply new pattern/scale/angle to an existing hatch entity. + HatcheditApply { handle: Handle, name: String, scale: f32, angle: f32 }, } // ── Trait ───────────────────────────────────────────────────────────────── diff --git a/src/modules/home/draw/hatchedit.rs b/src/modules/home/draw/hatchedit.rs new file mode 100644 index 000000000..bd6aac080 --- /dev/null +++ b/src/modules/home/draw/hatchedit.rs @@ -0,0 +1,131 @@ +// HATCHEDIT β€” edit an existing hatch entity's pattern, scale, or angle. +// +// Workflow: +// 1. Pick or pre-select a Hatch entity. +// 2. Enter options: +// P β€” change pattern (ANSI31, SOLID, etc.) +// S β€” change scale +// A β€” change angle +// Press Enter to apply changes. + +use acadrust::Handle; +use glam::Vec3; + +use crate::command::{CadCommand, CmdResult}; + +enum HatcheditStep { + PickHatch, + EditOptions { + handle: Handle, + name: String, + scale: f32, + angle: f32, + }, +} + +pub struct HatcheditCommand { + step: HatcheditStep, +} + +impl HatcheditCommand { + pub fn new() -> Self { + Self { step: HatcheditStep::PickHatch } + } + + pub fn with_handle(handle: Handle, name: String, scale: f32, angle: f32) -> Self { + Self { + step: HatcheditStep::EditOptions { handle, name, scale, angle }, + } + } +} + +impl CadCommand for HatcheditCommand { + fn name(&self) -> &'static str { "HATCHEDIT" } + + fn prompt(&self) -> String { + match &self.step { + HatcheditStep::PickHatch => "HATCHEDIT Select hatch:".into(), + HatcheditStep::EditOptions { name, scale, angle, .. } => format!( + "HATCHEDIT Pattern:{name} Scale:{scale:.4} Angle:{angle:.1} \ + [P / S / A | Enter to apply]:" + ), + } + } + + fn needs_entity_pick(&self) -> bool { + matches!(self.step, HatcheditStep::PickHatch) + } + + fn on_entity_pick(&mut self, handle: Handle, _pt: Vec3) -> CmdResult { + if handle.is_null() { return CmdResult::NeedPoint; } + // Actual hatch model retrieval happens in commands.rs dispatch. + // Store handle; name/scale/angle filled in by dispatch. + self.step = HatcheditStep::EditOptions { + handle, + name: String::new(), + scale: 1.0, + angle: 0.0, + }; + CmdResult::NeedPoint + } + + fn wants_text_input(&self) -> bool { + matches!(self.step, HatcheditStep::EditOptions { .. }) + } + + fn on_text_input(&mut self, text: &str) -> Option { + let (handle, name, scale, angle) = match &mut self.step { + HatcheditStep::EditOptions { handle, name, scale, angle } => { + (*handle, name, scale, angle) + } + _ => return None, + }; + + let text = text.trim().to_uppercase(); + + if text.is_empty() { + // Apply and exit + return Some(CmdResult::HatcheditApply { + handle, + name: name.clone(), + scale: *scale, + angle: *angle, + }); + } + + // Parse option: P/S/A followed by value + if let Some(rest) = text.strip_prefix('P') { + let n = rest.trim().to_string(); + if !n.is_empty() { *name = n; } + return Some(CmdResult::NeedPoint); + } + if let Some(rest) = text.strip_prefix('S') { + if let Ok(v) = rest.trim().replace(',', ".").parse::() { + if v > 0.0 { *scale = v; } + } + return Some(CmdResult::NeedPoint); + } + if let Some(rest) = text.strip_prefix('A') { + if let Ok(v) = rest.trim().replace(',', ".").parse::() { + *angle = v; + } + return Some(CmdResult::NeedPoint); + } + + // Unrecognized β€” stay and re-prompt + Some(CmdResult::NeedPoint) + } + + fn on_point(&mut self, _pt: Vec3) -> CmdResult { CmdResult::NeedPoint } + fn on_enter(&mut self) -> CmdResult { + // Enter without text β†’ apply current settings + let (handle, name, scale, angle) = match &self.step { + HatcheditStep::EditOptions { handle, name, scale, angle } => { + (*handle, name.clone(), *scale, *angle) + } + _ => return CmdResult::Cancel, + }; + CmdResult::HatcheditApply { handle, name, scale, angle } + } + fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } +} diff --git a/src/modules/home/draw/mod.rs b/src/modules/home/draw/mod.rs index 55b0e5b3c..247902ba7 100644 --- a/src/modules/home/draw/mod.rs +++ b/src/modules/home/draw/mod.rs @@ -3,6 +3,7 @@ pub mod attdef; pub mod circle; pub mod donut; pub mod ellipse; +pub mod hatchedit; pub mod hatch; pub mod line; pub mod mline;