mirror of
https://github.com/thunderheronight/splat-workshop.git
synced 2026-10-17 15:01:32 +02:00
Mesh: OBJ with Y up as Blender (and OBJ custom) expects – checked every format by importing it in Blender 5.2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d01571fadd
commit
6f46a2b94a
5 files changed
+14
-12
No files matched your search
+1
-1
@@ -7,7 +7,7 @@ All notable changes to Splat Workshop. The format follows [Keep a Changelog](htt
|
||||
### Added
|
||||
|
||||
- **Mesh export**: *Mesh …* on every scene and tour turns the splats into a surface with Spirula Studio (`spirula mesh`) – for Blender and other 3D programs, or for 3D printing. Formats: **GLB** (recommended: colours or texture in one file), **STL** (no colours, for slicers), **OBJ** (with a texture if possible, otherwise without colours) and **PLY** (colours per corner).
|
||||
- **Stands upright, floor at height 0**: our scenes have Y pointing down like the Steam Frame's tracking, so a mesh used to lie on its side in Blender. Now OBJ, PLY and STL get Z up and GLB gets Y up, as the glTF standard wants (Blender turns that into Z up itself); the floor – found as for the floor plan – is moved to height 0. In metres.
|
||||
- **Stands upright, floor at height 0**: our scenes have Y pointing down like the Steam Frame's tracking, so a mesh used to lie on its side in Blender. Now PLY and STL get Z up, GLB and OBJ Y up as their standard and custom want (Blender turns those into Z up itself on import – checked in Blender 5.2 for every format); the floor – found as for the floor plan – is moved to height 0. In metres.
|
||||
- What lies well outside the room is left out before meshing (as the *Edges* clean-up does) – otherwise big blobs beyond the walls become lumps floating around the room.
|
||||
- A **texture** from the training images when they are still there; after *clean up → training images*, colours per corner from the splats.
|
||||
- The mesh lives in the scene's folder (`mesh/`); a new one replaces the old one. *Mesh ✓* shows which scenes have one.
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
# <formats> comma-separated: glb, ply, obj, stl
|
||||
# glb colours (texture or per corner); Y up as glTF wants it – Blender, Windows 3D viewer, web
|
||||
# ply colours per corner; Z up – MeshLab, CloudCompare, Blender
|
||||
# obj with a texture, or without colours; Z up – nearly every 3D program
|
||||
# obj with a texture, or without colours; Y up as is custom for OBJ – nearly every 3D program
|
||||
# stl no colours; Z up – slicers for 3D printing
|
||||
# texture 1: a texture from the training images (only if they are still there, see "clean up"),
|
||||
# otherwise colours per corner from the splats
|
||||
|
||||
+8
-6
@@ -1,10 +1,12 @@
|
||||
"""Stand a mesh upright: our scenes have Y pointing down (like the Steam Frame's tracking); 3D programs
|
||||
expect Z up (OBJ, PLY, STL – e.g. Blender, slicers for 3D printing) or Y up (glTF/GLB, by its standard;
|
||||
Blender turns that into Z up itself on import). Without this a room lies on its side or upside down.
|
||||
"""Stand a mesh upright: our scenes have Y pointing down (like the Steam Frame's tracking). 3D programs
|
||||
expect Z up for PLY and STL (Blender, slicers for 3D printing) and Y up for glTF/GLB (by its standard) and
|
||||
OBJ (by custom) – Blender turns those two into Z up itself on import. Without this a room lies on its side
|
||||
or upside down. (Checked by importing each format in Blender 5.2.)
|
||||
|
||||
Usage: python3 upright.py [--floor Y] <file> [<file> ...] (.ply .obj .stl .glb .gltf; changed in place)
|
||||
|
||||
OBJ, PLY, STL: positions and normals turned so that up (-Y) becomes +Z: (x, y, z) -> (x, z, -y)
|
||||
PLY, STL: positions and normals turned so that up (-Y) becomes +Z: (x, y, z) -> (x, z, -y)
|
||||
OBJ: turned 180° about X, up (-Y) becomes +Y: (x, y, z) -> (x, -y, -z)
|
||||
GLB/glTF: the scene's root nodes go under one new node turned 180° about X: (x, y, z) -> (x, -y, -z)
|
||||
– nothing else in the file is touched (textures, materials, accessors stay as they are)
|
||||
--floor Y where the floor is in the scene (Y down): it is moved to height 0, so the room stands on
|
||||
@@ -67,8 +69,8 @@ def obj(path: Path):
|
||||
tag, rest = line.split(None, 1)
|
||||
nums = rest.split()
|
||||
x, y, z = (float(n) for n in nums[:3])
|
||||
x, y, z = Z_UP @ (x, y, z)
|
||||
z += LIFT if tag == b"v" else 0.0
|
||||
x, y, z = x, -y, -z # Y up (180° about X)
|
||||
y += LIFT if tag == b"v" else 0.0
|
||||
line = b"%s %.7g %.7g %.7g%s\n" % (tag, x, y, z, (b" " + b" ".join(nums[3:])) if nums[3:] else b"")
|
||||
out.append(line)
|
||||
_replace(path, b"".join(out))
|
||||
|
||||
+2
-2
@@ -23,12 +23,12 @@ def test_upright_puts_the_floor_at_the_bottom(tmp_path, fmt, colour):
|
||||
r = subprocess.run([sys.executable, str(UPRIGHT), "--floor", str(fake_mesh.FLOOR_Y), str(f)], capture_output=True, text=True)
|
||||
assert r.returncode == 0, r.stderr
|
||||
p = fake_mesh.read_points(f)
|
||||
up = 1 if fmt == "glb" else 2 # glTF: Y up; the others: Z up
|
||||
up = 1 if fmt in ("glb", "obj") else 2 # glTF and OBJ: Y up; PLY and STL: Z up (as Blender imports them)
|
||||
k = 6 if fmt == "stl" else 4 # STL lists the corners of each triangle: 2 floor triangles
|
||||
floor, ceiling = p[:k, up], p[k:, up]
|
||||
assert np.allclose(floor, 0, atol=1e-5) # the floor at height 0 ...
|
||||
assert np.allclose(ceiling, fake_mesh.FLOOR_Y - fake_mesh.CEILING_Y, atol=1e-5) # ... the ceiling 2.5 m above
|
||||
if fmt in ("obj", "ply"): # a proper turn, not a mirror: x stays, the other two swap
|
||||
if fmt in ("obj", "ply"): # a proper turn, not a mirror: x stays
|
||||
assert np.allclose(p[:, 0], fake_mesh.V[:, 0])
|
||||
if fmt == "ply": # normals turn along: the floor faces up
|
||||
raw = f.read_bytes()
|
||||
|
||||
+2
-2
@@ -270,7 +270,7 @@ const I18N = {
|
||||
"ver.recommended": "(recommended)",
|
||||
"mesh.button": "Mesh …", "mesh.button.has": "Mesh ✓", "mesh.button.title": "A mesh (surface) for 3D programs and 3D printing",
|
||||
"mesh.title": "Mesh of “{name}”",
|
||||
"mesh.hint": "Spirula Studio turns the splats into a surface – for Blender and other 3D programs or for 3D printing. It stands upright (Z or, for GLB, Y up) with the floor at height 0; what lies well outside the room is left out. In metres.",
|
||||
"mesh.hint": "Spirula Studio turns the splats into a surface – for Blender and other 3D programs or for 3D printing. It stands upright (the way Blender & co. expect each format) with the floor at height 0; what lies well outside the room is left out. In metres.",
|
||||
"mesh.new": "New mesh", "mesh.have": "Mesh", "mesh.make": "Make mesh", "mesh.pick": "Pick at least one format",
|
||||
"mesh.started": "Making the mesh …", "mesh.delete": "Delete the mesh",
|
||||
"mesh.confirm": "Move the mesh of “{name}” to the trash?",
|
||||
@@ -567,7 +567,7 @@ const I18N = {
|
||||
"ver.recommended": "(empfohlen)",
|
||||
"mesh.button": "Mesh …", "mesh.button.has": "Mesh ✓", "mesh.button.title": "Ein Mesh (Oberfläche) für 3D-Programme und den 3D-Druck",
|
||||
"mesh.title": "Mesh von „{name}“",
|
||||
"mesh.hint": "Spirula Studio macht aus den Splats eine Oberfläche – für Blender und andere 3D-Programme oder für den 3D-Druck. Sie steht aufrecht (Z oben, bei GLB Y oben) mit dem Boden auf Höhe 0; was deutlich außerhalb des Raums liegt, bleibt weg. In Metern.",
|
||||
"mesh.hint": "Spirula Studio macht aus den Splats eine Oberfläche – für Blender und andere 3D-Programme oder für den 3D-Druck. Sie steht aufrecht (so, wie Blender & Co. jedes Format erwarten) mit dem Boden auf Höhe 0; was deutlich außerhalb des Raums liegt, bleibt weg. In Metern.",
|
||||
"mesh.new": "Neues Mesh", "mesh.have": "Mesh", "mesh.make": "Mesh erstellen", "mesh.pick": "Mindestens ein Format wählen",
|
||||
"mesh.started": "Erstelle das Mesh …", "mesh.delete": "Mesh löschen",
|
||||
"mesh.confirm": "Das Mesh von „{name}“ in den Papierkorb verschieben?",
|
||||
|
||||
Reference in new issue
Block a user