feat: raster image render pipeline (IMAGE command + GPU textured quad)
- Add IMAGE / IMAGEATTACH / IM command with async file-picker (rfd) - src/scene/image_model.rs: ImageModel with RGBA pixel data + quad corners - src/scene/pipeline/image_gpu.rs: ImageGpu with wgpu texture, sampler, opacity uniform - src/scene/pipeline/mod.rs: image render pipeline (pass 2, between hatches and meshes) - src/scene/render.rs: include images in Primitive, call upload_images in prepare - src/scene/mod.rs: images HashMap, populate_images_from_document, cache on add_entity - src/modules/home/draw/raster_image.rs: ImageCommand (two-click: origin + width, aspect-correct preview) - src/io/mod.rs: pick_image_file() async helper (file dialog + dimension decode) - app: ImagePick / ImagePickResult messages, populate on file open and undo/redo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3d1f355a5c
commit
4312b6c3a5
39 changed files with 702 additions and 15 deletions
|
|
@ -410,6 +410,10 @@ impl H7CAD {
|
|||
self.tabs[i].active_cmd = Some(Box::new(wo_cmd));
|
||||
}
|
||||
|
||||
cmd if cmd == "IMAGE" || cmd == "IMAGEATTACH" || cmd == "IM" => {
|
||||
return Task::done(Message::ImagePick);
|
||||
}
|
||||
|
||||
"REVCLOUD" => {
|
||||
use crate::modules::home::draw::revcloud::RevCloudCommand;
|
||||
let cmd = RevCloudCommand::new();
|
||||
|
|
|
|||
|
|
@ -35,8 +35,10 @@ impl H7CAD {
|
|||
.filter(|h| self.tabs[i].scene.document.get_entity(*h).is_some())
|
||||
.collect::<HashSet<_>>();
|
||||
self.tabs[i].scene.populate_hatches_from_document();
|
||||
self.tabs[i].scene.populate_images_from_document();
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
self.tabs[i].scene.meshes.clear();
|
||||
self.tabs[i].scene.images.clear();
|
||||
self.tabs[i].active_cmd = None;
|
||||
self.tabs[i].snap_result = None;
|
||||
self.tabs[i].active_grip = None;
|
||||
|
|
|
|||
|
|
@ -397,6 +397,11 @@ pub enum Message {
|
|||
// Field edit messages:
|
||||
DsEdit(DsField, String),
|
||||
DsToggle(DsField),
|
||||
// ── Raster Image ──────────────────────────────────────────────────────
|
||||
/// Open file-picker dialog for IMAGE command (async).
|
||||
ImagePick,
|
||||
/// Result of the image file picker + pixel dimension decode.
|
||||
ImagePickResult(Result<(std::path::PathBuf, u32, u32), String>),
|
||||
}
|
||||
|
||||
impl H7CAD {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ impl H7CAD {
|
|||
self.tabs[i].current_path = Some(path);
|
||||
self.tabs[i].scene.document = doc;
|
||||
self.tabs[i].scene.populate_hatches_from_document();
|
||||
self.tabs[i].scene.populate_images_from_document();
|
||||
self.tabs[i].scene.selected = std::collections::HashSet::new();
|
||||
self.tabs[i].scene.preview_wires = vec![];
|
||||
self.tabs[i].scene.current_layout = "Model".to_string();
|
||||
|
|
@ -71,6 +72,35 @@ impl H7CAD {
|
|||
Task::none()
|
||||
}
|
||||
|
||||
Message::ImagePick => {
|
||||
Task::perform(crate::io::pick_image_file(), Message::ImagePickResult)
|
||||
}
|
||||
|
||||
Message::ImagePickResult(Ok((path, pw, ph))) => {
|
||||
use crate::command::CadCommand;
|
||||
use crate::modules::home::draw::raster_image::ImageCommand;
|
||||
let path_str = path.to_string_lossy().into_owned();
|
||||
let short = std::path::Path::new(&path_str)
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or(&path_str)
|
||||
.to_string();
|
||||
self.command_line
|
||||
.push_output(&format!("IMAGE \"{short}\": {pw}×{ph} px"));
|
||||
let cmd = ImageCommand::new(path_str, pw, ph);
|
||||
let i = self.active_tab;
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::ImagePickResult(Err(e)) => {
|
||||
if e != "Cancelled" {
|
||||
self.command_line.push_error(&format!("IMAGE: {e}"));
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::SaveFile => {
|
||||
let i = self.active_tab;
|
||||
if let Some(path) = &self.tabs[i].current_path {
|
||||
|
|
|
|||
Loading…
Reference in a new issue