forked from Sollumz/Sollumz
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(ymap): error when loading cargen mesh .obj
Blender 4.0 removed `bpy.ops.import_scene.obj`. Refactored the .obj reader used in the archetype extension gizmos into its own module to use it in ymap code. Also added tests for this .obj reader. Fixes Sollumz#825.
- Loading branch information
1 parent
bff527b
commit a110936
Showing
7 changed files
with
195 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
""" | ||
Simple Wavefront .obj reader for loading Sollumz builtin models. Only supports | ||
vertices (`v`) and triangular faces (`f` with three vertex indices). | ||
""" | ||
|
||
import bpy | ||
import io | ||
import numpy as np | ||
from numpy.typing import NDArray | ||
from typing import NamedTuple | ||
from pathlib import Path | ||
|
||
|
||
class ObjMesh(NamedTuple): | ||
vertices: NDArray[np.float32] | ||
indices: NDArray[np.uint16] | ||
|
||
def as_vertices_only(self) -> NDArray[np.float32]: | ||
return self.vertices[self.indices.flatten()] | ||
|
||
def as_bpy_mesh(self, name: str) -> bpy.types.Mesh: | ||
mesh = bpy.data.meshes.new(name) | ||
mesh.from_pydata(self.vertices, [], self.indices) | ||
return mesh | ||
|
||
|
||
def obj_read(obj_io: io.TextIOBase) -> ObjMesh: | ||
vertices = [] | ||
indices = [] | ||
for line in obj_io.readlines(): | ||
line = line.strip() | ||
c = line[0] if len(line) > 0 else None | ||
match c: | ||
case "v": | ||
x, y, z = line.strip("v ").split(" ") | ||
vertices.extend((float(x), float(y), float(z))) | ||
case "f": | ||
v0, v1, v2 = line.strip("f ").split(" ") | ||
indices.extend((int(v0) - 1, int(v1) - 1, int(v2) - 1)) | ||
case _: | ||
# ignore unknown/unsupported elements | ||
pass | ||
|
||
return ObjMesh( | ||
vertices=np.reshape(np.array(vertices, dtype=np.float32), (-1, 3)), | ||
indices=np.reshape(np.array(indices, dtype=np.uint16), (-1, 3)) | ||
) | ||
|
||
|
||
def obj_read_from_file(file_path: Path) -> ObjMesh: | ||
with file_path.open("r") as f: | ||
return obj_read(f) | ||
|
||
|
||
def obj_read_from_str(obj_str: str) -> ObjMesh: | ||
with io.StringIO(obj_str) as s: | ||
return obj_read(s) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import bpy | ||
import pytest | ||
import numpy as np | ||
from numpy.testing import assert_array_equal | ||
from pathlib import Path | ||
from ..shared.obj_reader import obj_read_from_str, obj_read_from_file, ObjMesh | ||
|
||
|
||
def test_obj_read(): | ||
obj = obj_read_from_str(""" | ||
v 1.2 3.4 5.6 | ||
v 2.0 4.0 6.0 | ||
v 10.0 5.0 10.0 | ||
v 1.0 2.0 3.0 | ||
f 1 2 3 | ||
f 4 3 1 | ||
""") | ||
|
||
assert obj is not None | ||
|
||
assert_array_equal(obj.vertices, np.array([ | ||
[1.2, 3.4, 5.6], | ||
[2.0, 4.0, 6.0], | ||
[10.0, 5.0, 10.0], | ||
[1.0, 2.0, 3.0], | ||
], dtype=np.float32)) | ||
|
||
assert_array_equal(obj.indices, np.array([ | ||
[0, 1, 2], | ||
[3, 2, 0], | ||
], dtype=np.uint16)) | ||
|
||
|
||
def test_obj_read_ignores_comments_and_unknown_elements(): | ||
obj = obj_read_from_str(""" | ||
# comment | ||
v 1.0 2.0 3.0 | ||
# other comment | ||
u unknown | ||
f 1 2 3 | ||
""") | ||
|
||
assert obj is not None | ||
|
||
assert_array_equal(obj.vertices, np.array([ | ||
[1.0, 2.0, 3.0], | ||
], dtype=np.float32)) | ||
|
||
assert_array_equal(obj.indices, np.array([ | ||
[0, 1, 2], | ||
], dtype=np.uint16)) | ||
|
||
|
||
def test_obj_as_vertices_only(): | ||
obj_mesh = ObjMesh( | ||
vertices=np.array([ | ||
[1.0, 2.0, 3.0], | ||
[4.0, 5.0, 6.0], | ||
[7.0, 8.0, 9.0], | ||
], dtype=np.float32), | ||
indices=np.array([ | ||
[0, 1, 2], | ||
[2, 1, 0], | ||
], dtype=np.uint16) | ||
) | ||
|
||
obj_mesh_vertices = obj_mesh.as_vertices_only() | ||
assert_array_equal(obj_mesh_vertices, np.array([ | ||
[1.0, 2.0, 3.0], | ||
[4.0, 5.0, 6.0], | ||
[7.0, 8.0, 9.0], | ||
[7.0, 8.0, 9.0], | ||
[4.0, 5.0, 6.0], | ||
[1.0, 2.0, 3.0], | ||
])) | ||
|
||
|
||
@pytest.mark.parametrize("obj_relative_file_path", ( | ||
"../tools/car_model.obj", | ||
"../ytyp/gizmos/models/AudioEmitter.obj", | ||
"../ytyp/gizmos/models/AudioCollisionSettings.obj", | ||
"../ytyp/gizmos/models/Buoyancy.obj", | ||
"../ytyp/gizmos/models/Door.obj", | ||
"../ytyp/gizmos/models/Expression.obj", | ||
"../ytyp/gizmos/models/Ladder.obj", | ||
"../ytyp/gizmos/models/LadderTop.obj", | ||
"../ytyp/gizmos/models/LadderBottom.obj", | ||
"../ytyp/gizmos/models/LightShaft.obj", | ||
"../ytyp/gizmos/models/ParticleEffect.obj", | ||
"../ytyp/gizmos/models/ProcObject.obj", | ||
"../ytyp/gizmos/models/SpawnPoint.obj", | ||
"../ytyp/gizmos/models/SpawnPointOverride.obj", | ||
"../ytyp/gizmos/models/WindDisturbance.obj", | ||
)) | ||
def test_obj_read_sollumz_builtin_asset(obj_relative_file_path: str): | ||
obj_path = Path(__file__).parent.joinpath(obj_relative_file_path) | ||
obj = obj_read_from_file(obj_path) | ||
|
||
assert obj is not None | ||
assert len(obj.vertices.flatten()) > 0 | ||
assert len(obj.indices.flatten()) > 0 | ||
|
||
obj_mesh = obj.as_bpy_mesh(obj_path.stem) | ||
assert obj_mesh is not None | ||
|
||
invalid_geom = obj_mesh.validate(verbose=True) | ||
assert not invalid_geom | ||
|
||
bpy.data.meshes.remove(obj_mesh) |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters