mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 14:23:53 +00:00
ifcopenshell-python: move mathutils related tests to Bonsai
Previously tests never ran since workflow targets 3.11 and running tests is gated by Python 3.13.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>, @Andrej730
|
||||
#
|
||||
# This file is part of Bonsai.
|
||||
#
|
||||
# Bonsai is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Bonsai is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
These tests actually belong to the `ifcopenshell.util.shape_builder` test suite
|
||||
(ifcopenshell-python/test/util/test_shape_builder.py) but live here because they need
|
||||
`mathutils`.
|
||||
|
||||
`mathutils` is available from PyPI either as part of `bpy` or `mathutils` package,
|
||||
but those are not widely used and there were issues in the past with these going out of date.
|
||||
Testing against them would also require separately building ifcopenshell for targeted Blender's Python version
|
||||
(usually for tests we use the minimal Python version supported instead).
|
||||
|
||||
Therefore we just test it against the real `mathutils` inside Blender.
|
||||
"""
|
||||
|
||||
from math import radians
|
||||
|
||||
import numpy as np
|
||||
from ifcopenshell.util.shape_builder import (
|
||||
V,
|
||||
is_x,
|
||||
np_angle,
|
||||
np_angle_signed,
|
||||
np_intersect_line_line,
|
||||
np_matrix_to_euler,
|
||||
np_normal,
|
||||
np_rotation_matrix,
|
||||
)
|
||||
|
||||
from test.bim.bootstrap import NewFile
|
||||
|
||||
|
||||
class TestMathutilsCompatibleMethods(NewFile):
|
||||
def test_np_rotation_matrix(self):
|
||||
from mathutils import Matrix, Vector
|
||||
|
||||
# 2D.
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 2), np_rotation_matrix(radians(45), 2))
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 2, "Z"), np_rotation_matrix(radians(45), 2, "Z"))
|
||||
|
||||
# 3D.
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 3, "X"), np_rotation_matrix(radians(45), 3, "X"))
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 3, "Y"), np_rotation_matrix(radians(45), 3, "Y"))
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 3, "Z"), np_rotation_matrix(radians(45), 3, "Z"))
|
||||
rotation_vector_args = radians(45), 3, Vector((1, 1, 1)).normalized()
|
||||
assert np.allclose(Matrix.Rotation(*rotation_vector_args), np_rotation_matrix(*rotation_vector_args))
|
||||
|
||||
# Size 4.
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 4, "X"), np_rotation_matrix(radians(45), 4, "X"))
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 4, "Y"), np_rotation_matrix(radians(45), 4, "Y"))
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 4, "Z"), np_rotation_matrix(radians(45), 4, "Z"))
|
||||
rotation_vector_args = radians(45), 4, Vector((1, 1, 1)).normalized()
|
||||
assert np.allclose(Matrix.Rotation(*rotation_vector_args), np_rotation_matrix(*rotation_vector_args))
|
||||
|
||||
def test_np_matrix_to_euler(self):
|
||||
from mathutils import Euler
|
||||
|
||||
# Test 3x3.
|
||||
rot = Euler((0.5, 0.5, 0.5)).to_matrix()
|
||||
assert np.allclose(rot.to_euler(), np_matrix_to_euler(V(rot)))
|
||||
|
||||
rot = rot.to_4x4()
|
||||
assert np.allclose(rot.to_euler(), np_matrix_to_euler(V(rot)))
|
||||
|
||||
# Ensure support scaled matrices.
|
||||
rot = Euler((0.5, 0.5, 0.5)).to_matrix()
|
||||
rot.col[0] *= 2
|
||||
assert np.allclose(rot.to_euler(), np_matrix_to_euler(V(rot)))
|
||||
|
||||
def test_np_angle(self):
|
||||
from mathutils import Vector
|
||||
|
||||
v1, v2 = (1, 0, 0), (0, 1, 0)
|
||||
angle = np_angle(v1, v2)
|
||||
assert is_x(angle, Vector(v1).angle(Vector(v2)))
|
||||
assert is_x(angle, radians(90))
|
||||
|
||||
v1, v2 = v1[:2], v2[:2]
|
||||
angle = np_angle_signed(v1, v2)
|
||||
assert is_x(angle, Vector(v1).angle_signed(Vector(v2)))
|
||||
assert is_x(angle, -radians(90))
|
||||
|
||||
v1, v2 = (0, 1, 0), (1, 0, 0)
|
||||
angle = np_angle(v1, v2)
|
||||
assert is_x(angle, Vector(v1).angle(Vector(v2)))
|
||||
assert is_x(angle, radians(90))
|
||||
|
||||
v1, v2 = v1[:2], v2[:2]
|
||||
angle = np_angle_signed(v1, v2)
|
||||
assert is_x(angle, Vector(v1).angle_signed(Vector(v2)))
|
||||
assert is_x(angle, radians(90))
|
||||
|
||||
def test_np_normal(self):
|
||||
import mathutils.geometry
|
||||
|
||||
vectors = (0, 0, 0), (1, 0, 0), (0, 1, 0)
|
||||
n = mathutils.geometry.normal(vectors)
|
||||
assert np.allclose(n, np_normal(vectors))
|
||||
assert np.allclose(n, (0, 0, 1))
|
||||
|
||||
vectors = (0, 0, 0), (0, 1, 0), (1, 0, 0)
|
||||
n = mathutils.geometry.normal(vectors)
|
||||
assert np.allclose(n, np_normal(vectors))
|
||||
assert np.allclose(n, (0, 0, -1))
|
||||
|
||||
def test_np_intersect_line_line(self):
|
||||
import mathutils.geometry
|
||||
|
||||
p1, p2 = [0, 0, 0], [1, 1, 1]
|
||||
q1, q2 = [0, 1, 0], [1, 0, 1]
|
||||
expected = mathutils.geometry.intersect_line_line(tuple(p1), tuple(p2), tuple(q1), tuple(q2))
|
||||
result = np_intersect_line_line(p1, p2, q1, q2)
|
||||
assert np.allclose(expected, result)
|
||||
@@ -71,16 +71,12 @@ build-urls:
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
pytest --import-mode=importlib -p no:pytest-blender test --ignore=test/util/test_shape_builder.py
|
||||
pytest --import-mode=importlib -p no:pytest-blender test
|
||||
|
||||
.PHONY: test-parallel
|
||||
test-parallel:
|
||||
@NPROCS=$$(nproc 2>/dev/null || sysctl -n hw.ncpu); \
|
||||
pytest --import-mode=importlib -p no:pytest-blender -n $$NPROCS test --ignore=test/util/test_shape_builder.py
|
||||
|
||||
.PHONY: test-mathutils
|
||||
test-mathutils:
|
||||
pytest --import-mode=importlib -p no:pytest-blender test/util/test_shape_builder.py
|
||||
pytest --import-mode=importlib -p no:pytest-blender -n $$NPROCS test
|
||||
|
||||
|
||||
.PHONY: license
|
||||
|
||||
@@ -31,11 +31,10 @@ This runs:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -p no:pytest-blender test --ignore=test/util/test_shape_builder.py
|
||||
pytest -p no:pytest-blender test
|
||||
|
||||
The ``pytest-blender`` plugin is disabled because these are IfcOpenShell-Python
|
||||
tests, not Bonsai Blender tests. The shape builder tests are split into a
|
||||
separate target because they require Blender's ``mathutils`` package.
|
||||
tests, not Bonsai Blender tests.
|
||||
|
||||
Parallel tests
|
||||
--------------
|
||||
@@ -53,12 +52,9 @@ This automatically uses the available CPU count and runs the same tests as
|
||||
Shape builder tests
|
||||
-------------------
|
||||
|
||||
The shape builder tests require ``mathutils``. Run them separately:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install mathutils
|
||||
make test-mathutils
|
||||
Most of ``ifcopenshell.util.shape_builder`` is covered here, but the tests
|
||||
that check its ``mathutils``-compatible helpers live in the Bonsai test suite instead,
|
||||
since they need Blender's ``mathutils`` package. See ``src/bonsai/test/tool/test_shape_builder.py``.
|
||||
|
||||
Running individual tests
|
||||
------------------------
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from math import degrees, radians, sqrt
|
||||
from math import degrees, sqrt
|
||||
from typing import Any, Union
|
||||
|
||||
import numpy as np
|
||||
@@ -31,11 +31,6 @@ from ifcopenshell.util.shape_builder import (
|
||||
arc_to_polyline_points,
|
||||
is_x,
|
||||
np_angle,
|
||||
np_angle_signed,
|
||||
np_intersect_line_line,
|
||||
np_matrix_to_euler,
|
||||
np_normal,
|
||||
np_rotation_matrix,
|
||||
np_to_3d,
|
||||
polygonal_face_set_to_faceted_brep,
|
||||
)
|
||||
@@ -147,89 +142,6 @@ class TestPolygonalFaceSetToFacetedBrep(test.bootstrap.IFC4):
|
||||
polygonal_face_set_to_faceted_brep(face_set)
|
||||
|
||||
|
||||
class TestMathutilsCompatibleMethods(test.bootstrap.IFC4):
|
||||
def test_np_rotation_matrix(self):
|
||||
from mathutils import Matrix, Vector # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import]
|
||||
|
||||
# 2D.
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 2), np_rotation_matrix(radians(45), 2))
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 2, "Z"), np_rotation_matrix(radians(45), 2, "Z"))
|
||||
|
||||
# 3D.
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 3, "X"), np_rotation_matrix(radians(45), 3, "X"))
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 3, "Y"), np_rotation_matrix(radians(45), 3, "Y"))
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 3, "Z"), np_rotation_matrix(radians(45), 3, "Z"))
|
||||
rotation_vector_args = radians(45), 3, Vector((1, 1, 1)).normalized()
|
||||
assert np.allclose(Matrix.Rotation(*rotation_vector_args), np_rotation_matrix(*rotation_vector_args))
|
||||
|
||||
# Size 4.
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 4, "X"), np_rotation_matrix(radians(45), 4, "X"))
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 4, "Y"), np_rotation_matrix(radians(45), 4, "Y"))
|
||||
assert np.allclose(Matrix.Rotation(radians(45), 4, "Z"), np_rotation_matrix(radians(45), 4, "Z"))
|
||||
rotation_vector_args = radians(45), 4, Vector((1, 1, 1)).normalized()
|
||||
assert np.allclose(Matrix.Rotation(*rotation_vector_args), np_rotation_matrix(*rotation_vector_args))
|
||||
|
||||
def test_np_matrix_to_euler(self):
|
||||
from mathutils import Euler # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import]
|
||||
|
||||
# Test 3x3.
|
||||
rot = Euler((0.5, 0.5, 0.5)).to_matrix()
|
||||
assert np.allclose(rot.to_euler(), np_matrix_to_euler(V(rot)))
|
||||
|
||||
rot = rot.to_4x4()
|
||||
assert np.allclose(rot.to_euler(), np_matrix_to_euler(V(rot)))
|
||||
|
||||
# Ensure support scaled matrices.
|
||||
rot = Euler((0.5, 0.5, 0.5)).to_matrix()
|
||||
rot.col[0] *= 2
|
||||
assert np.allclose(rot.to_euler(), np_matrix_to_euler(V(rot)))
|
||||
|
||||
def test_np_angle(self):
|
||||
from mathutils import Vector # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import]
|
||||
|
||||
v1, v2 = (1, 0, 0), (0, 1, 0)
|
||||
angle = np_angle(v1, v2)
|
||||
assert is_x(angle, Vector(v1).angle(Vector(v2)))
|
||||
assert is_x(angle, radians(90))
|
||||
|
||||
v1, v2 = v1[:2], v2[:2]
|
||||
angle = np_angle_signed(v1, v2)
|
||||
assert is_x(angle, Vector(v1).angle_signed(Vector(v2)))
|
||||
assert is_x(angle, -radians(90))
|
||||
|
||||
v1, v2 = (0, 1, 0), (1, 0, 0)
|
||||
angle = np_angle(v1, v2)
|
||||
assert is_x(angle, Vector(v1).angle(Vector(v2)))
|
||||
assert is_x(angle, radians(90))
|
||||
|
||||
v1, v2 = v1[:2], v2[:2]
|
||||
angle = np_angle_signed(v1, v2)
|
||||
assert is_x(angle, Vector(v1).angle_signed(Vector(v2)))
|
||||
assert is_x(angle, radians(90))
|
||||
|
||||
def test_np_normal(self):
|
||||
import mathutils.geometry # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import]
|
||||
|
||||
vectors = (0, 0, 0), (1, 0, 0), (0, 1, 0)
|
||||
n = mathutils.geometry.normal(vectors)
|
||||
assert np.allclose(n, np_normal(vectors))
|
||||
assert np.allclose(n, (0, 0, 1))
|
||||
|
||||
vectors = (0, 0, 0), (0, 1, 0), (1, 0, 0)
|
||||
n = mathutils.geometry.normal(vectors)
|
||||
assert np.allclose(n, np_normal(vectors))
|
||||
assert np.allclose(n, (0, 0, -1))
|
||||
|
||||
def test_np_intersect_line_line(self):
|
||||
import mathutils.geometry # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import]
|
||||
|
||||
p1, p2 = [0, 0, 0], [1, 1, 1]
|
||||
q1, q2 = [0, 1, 0], [1, 0, 1]
|
||||
expected = mathutils.geometry.intersect_line_line(tuple(p1), tuple(p2), tuple(q1), tuple(q2))
|
||||
result = np_intersect_line_line(p1, p2, q1, q2)
|
||||
assert np.allclose(expected, result)
|
||||
|
||||
|
||||
class TestRectangle(test.bootstrap.IFC4):
|
||||
def test_get_rectangle_coords(self):
|
||||
builder = ShapeBuilder(self.file)
|
||||
|
||||
Reference in New Issue
Block a user