feat: HATCHEDIT command to edit existing hatch pattern, scale, and angle
Add HATCHEDIT (HE alias) command. Works on pre-selected hatch or via entity pick. Accepts text options: P <pattern>, S <scale>, A <angle>. Enter applies changes; rebuilds hatch entity and GPU model via add_hatch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8078c4c9da
commit
f6d716dfc5
7 changed files with 206 additions and 2 deletions
|
|
@ -126,7 +126,7 @@ Underlay (PDF/DWF/DGN)
|
||||||
| PEDIT (PE) | ✅ | — |
|
| PEDIT (PE) | ✅ | — |
|
||||||
| STRETCH (SS) | 🔧 | Crossing-window seçici yok (MOVE gibi çalışıyor) |
|
| STRETCH (SS) | 🔧 | Crossing-window seçici yok (MOVE gibi çalışıyor) |
|
||||||
| SPLINEDIT | ⬜ | — |
|
| SPLINEDIT | ⬜ | — |
|
||||||
| HATCHEDIT | ⬜ | Var olan hatch'i düzenleme |
|
| HATCHEDIT | ✅ | — |
|
||||||
| ATTEDIT | ⬜ | Attribute değerlerini düzenleme |
|
| ATTEDIT | ⬜ | Attribute değerlerini düzenleme |
|
||||||
| DDEDIT (çift tık metin) | ✅ | — |
|
| DDEDIT (çift tık metin) | ✅ | — |
|
||||||
| REFEDIT | ⬜ | Block in-place düzenleme |
|
| REFEDIT | ⬜ | Block in-place düzenleme |
|
||||||
|
|
|
||||||
|
|
@ -619,6 +619,38 @@ impl H7CAD {
|
||||||
self.tabs[i].scene.clear_preview_wire();
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
self.restore_pre_cmd_tangent();
|
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 } => {
|
CmdResult::DdeditEntity { handle, new_text } => {
|
||||||
let mut updated = false;
|
let mut updated = false;
|
||||||
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||||
|
|
|
||||||
|
|
@ -802,6 +802,28 @@ impl H7CAD {
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
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" => {
|
"GRADIENT" => {
|
||||||
use crate::modules::home::draw::hatch::GradientCommand;
|
use crate::modules::home::draw::hatch::GradientCommand;
|
||||||
let outlines = self.tabs[i].scene.closed_outlines();
|
let outlines = self.tabs[i].scene.closed_outlines();
|
||||||
|
|
|
||||||
|
|
@ -1059,7 +1059,23 @@ impl H7CAD {
|
||||||
let hit = scene::hit_test::click_hit(p, &all_wires2, vp_mat2, bounds)
|
let hit = scene::hit_test::click_hit(p, &all_wires2, vp_mat2, bounds)
|
||||||
.and_then(|s| Scene::handle_from_wire_name(s));
|
.and_then(|s| Scene::handle_from_wire_name(s));
|
||||||
if let Some(handle) = hit {
|
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<dyn CadCommand> = 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 {
|
} else {
|
||||||
self.command_line.push_info("Nothing found at that point.");
|
self.command_line.push_info("Nothing found at that point.");
|
||||||
None
|
None
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,8 @@ pub enum CmdResult {
|
||||||
SetPlotWindow { p1: Vec3, p2: Vec3 },
|
SetPlotWindow { p1: Vec3, p2: Vec3 },
|
||||||
/// Replace the text content of a Text/MText entity in-place.
|
/// Replace the text content of a Text/MText entity in-place.
|
||||||
DdeditEntity { handle: Handle, new_text: String },
|
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 ─────────────────────────────────────────────────────────────────
|
// ── Trait ─────────────────────────────────────────────────────────────────
|
||||||
|
|
|
||||||
131
src/modules/home/draw/hatchedit.rs
Normal file
131
src/modules/home/draw/hatchedit.rs
Normal file
|
|
@ -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 <name> — change pattern (ANSI31, SOLID, etc.)
|
||||||
|
// S <value> — change scale
|
||||||
|
// A <degrees> — 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 <pat> / S <scale> / A <angle> | 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<CmdResult> {
|
||||||
|
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::<f32>() {
|
||||||
|
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::<f32>() {
|
||||||
|
*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 }
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ pub mod attdef;
|
||||||
pub mod circle;
|
pub mod circle;
|
||||||
pub mod donut;
|
pub mod donut;
|
||||||
pub mod ellipse;
|
pub mod ellipse;
|
||||||
|
pub mod hatchedit;
|
||||||
pub mod hatch;
|
pub mod hatch;
|
||||||
pub mod line;
|
pub mod line;
|
||||||
pub mod mline;
|
pub mod mline;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue