Repository navigation
Add Edit > Change mesh... to move a project onto another mesh #230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e840fa6
f3217da
00f770e
b33f173
60ca949
0791e9f
d180ee3
d5bdeed
f10255f
b944a04
b52fca2
0cf1edb
2e021b8
5588501
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,15 @@ pub fn specs() -> Vec<CommandSpec> { | |
| always, | ||
| reimport | ||
| ), | ||
| cmd!( | ||
| "file.changeMesh", | ||
| "Change mesh...", | ||
| ["Edit"], | ||
| None, | ||
| "{path: the new mesh file, preserveStrokes?: bool (painted pixels follow the surface where the UVs differ; default true)} → {preserved?: texels re-projected}. Keeps layers, texture sets and settings; texture sets map to the new mesh by material index", | ||
| always, | ||
| change_mesh | ||
| ), | ||
| cmd!( | ||
| "file.exportTextures", | ||
| "Export textures", | ||
|
|
@@ -438,6 +447,13 @@ fn reimport(s: &mut Session, p: &Value) -> Result<Value> { | |
| Ok(reply) | ||
| } | ||
|
|
||
| #[cfg(not(target_arch = "wasm32"))] | ||
| fn change_mesh(s: &mut Session, p: &Value) -> Result<Value> { | ||
| let path = native::path_param(p, "path", "file.changeMesh")?; | ||
| let file = native::read_mesh("file.changeMesh", path)?; | ||
| s.reimport_mesh(Some(file), crate::commands::boolp(p, "preserveStrokes").unwrap_or(true)) | ||
| } | ||
|
|
||
| #[cfg(not(target_arch = "wasm32"))] | ||
| impl Session { | ||
| /// The file the mesh was imported from, when it is still there: what Reimport mesh reads again. | ||
|
|
@@ -471,6 +487,19 @@ impl Session { | |
| let mut next = Session::new(doc, mesh, source)?; | ||
| next.log.extend(warnings); | ||
| next.path = self.path.clone(); | ||
| next.sync_images(); | ||
| // The high polys and the skew map stay. The cage is sampled by triangle index and | ||
| // barycentrics, so it stays only for an identical mesh; the low poly's base colours are | ||
| // read again from the new mesh. | ||
| next.bake_meshes = self.bake_meshes.clone(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reset the low-poly color cache when replacing the mesh. If the old mesh has already supplied colors to an opacity or material-color ID bake, cloning Proposed fix
|
||
| next.bake_meshes.low_base_color = Default::default(); | ||
| if next.bake_meshes.cage.is_some() && *next.mesh != *self.mesh { | ||
| next.bake_meshes.cage = None; | ||
| next.log.push("the cage mesh was dropped: the low-poly mesh changed".into()); | ||
| } | ||
| if self.doc.bake_paint.keys().any(|(_, target)| *target == texturecraft_core::BakePaintTarget::Skew) && self.source.uvs != next.source.uvs { | ||
| next.log.push("the skew map is laid over the old UV layout; check it in Bake mode".into()); | ||
| } | ||
| next.modified = true; | ||
| next.project_id = self.project_id; | ||
| let preserved = preserve_strokes.then(|| crate::preserve::preserve_strokes(self, &mut next)); | ||
|
|
@@ -621,6 +650,8 @@ use unsupported as save_copy; | |
| #[cfg(target_arch = "wasm32")] | ||
| use unsupported as reimport; | ||
| #[cfg(target_arch = "wasm32")] | ||
| use unsupported as change_mesh; | ||
| #[cfg(target_arch = "wasm32")] | ||
| use unsupported as export_textures; | ||
| #[cfg(target_arch = "wasm32")] | ||
| use unsupported as export_mesh; | ||
|
|
@@ -764,6 +795,49 @@ f 1/1 2/2 3/3 | |
| let _ = std::fs::remove_dir_all(&dir); | ||
| } | ||
|
|
||
| /// A project built on one mesh moves to another mesh file with its layers intact, even when | ||
| /// it carries its own copy of the mesh (a plain reimport would only re-read that copy). | ||
| #[test] | ||
| fn change_mesh_swaps_the_mesh_and_keeps_the_layers() { | ||
| let dir = std::env::temp_dir().join(format!("tc_change_mesh_{}", std::process::id())); | ||
| std::fs::create_dir_all(&dir).unwrap(); | ||
| let quad = dir.join("quad.obj"); | ||
| std::fs::write(&quad, "v 0 0 0\nv 1 0 0\nv 1 1 0\nv 0 1 0\nvt 0 0\nvt 1 0\nvt 1 1\nvt 0 1\nf 1/1 2/2 3/3 4/4\n").unwrap(); | ||
| let tri = dir.join("tri.obj"); | ||
| std::fs::write(&tri, "v 0 0 0\nv 1 0 0\nv 0 1 0\nvt 0 0\nvt 1 0\nvt 0 1\nf 1/1 2/2 3/3\n").unwrap(); | ||
| let mut s = Session::from_mesh_bytes("quad.obj", &std::fs::read(&quad).unwrap(), 128, "quad").unwrap(); | ||
| crate::execute(&mut s, "layer.add.fill", &json!({"color": [1.0, 0.0, 0.0]})).unwrap(); | ||
| let layers = s.doc.project.layers.len(); | ||
| assert!(crate::execute(&mut s, "file.changeMesh", &json!({})).is_err(), "a path is required"); | ||
| crate::execute(&mut s, "file.changeMesh", &json!({"path": tri.display().to_string()})).unwrap(); | ||
| assert_eq!(s.mesh.triangle_count(), 1, "the new mesh is in use"); | ||
| assert_eq!(s.doc.project.mesh_file, "tri.obj"); | ||
| assert_eq!(s.doc.project.layers.len(), layers, "the layers stay"); | ||
| std::fs::remove_dir_all(&dir).ok(); | ||
| } | ||
|
|
||
| #[test] | ||
| fn change_mesh_resets_base_colours_and_drops_a_cage_of_another_mesh() { | ||
| let dir = std::env::temp_dir().join(format!("tc_change_mesh_cage_{}", std::process::id())); | ||
| std::fs::create_dir_all(&dir).unwrap(); | ||
| let a = dir.join("a.obj"); | ||
| std::fs::write(&a, "v 0 0 0\nv 1 0 0\nv 0 1 0\nvt 0 0\nvt 1 0\nvt 0 1\nf 1/1 2/2 3/3\n").unwrap(); | ||
| // Same triangle count, other vertices. | ||
| let b = dir.join("b.obj"); | ||
| std::fs::write(&b, "v 0 0 0\nv 2 0 0\nv 0 2 0\nvt 0 0\nvt 1 0\nvt 0 1\nf 1/1 2/2 3/3\n").unwrap(); | ||
| let mut s = Session::from_mesh_bytes("a.obj", &std::fs::read(&a).unwrap(), 128, "a").unwrap(); | ||
| let cage = crate::bake::BakeMesh { name: "cage".into(), bytes: Default::default(), mesh: s.mesh.clone(), base_color: Vec::new() }; | ||
| s.bake_meshes.cage = Some(cage); | ||
| let _ = s.bake_meshes.low_base_color.set(Err("old".into())); | ||
| // The same mesh again keeps the cage but not the cached colours. | ||
| crate::execute(&mut s, "file.changeMesh", &json!({"path": a.display().to_string()})).unwrap(); | ||
| assert!(s.bake_meshes.cage.is_some()); | ||
| assert!(s.bake_meshes.low_base_color.get().is_none(), "the colours are read again"); | ||
| crate::execute(&mut s, "file.changeMesh", &json!({"path": b.display().to_string()})).unwrap(); | ||
| assert!(s.bake_meshes.cage.is_none(), "the cage was made for another mesh"); | ||
| std::fs::remove_dir_all(&dir).ok(); | ||
| } | ||
|
|
||
| #[test] | ||
| fn unnamed_parts_keep_their_names_and_masks_through_save_and_reopen() { | ||
| // A GLB quad whose mesh and node have no name: its one part is named after the file. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.