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:
@@ -269,13 +269,6 @@ jobs:
|
|||||||
pip install -e ../ifctester --no-deps
|
pip install -e ../ifctester --no-deps
|
||||||
cd ../ifctester && make test || fail "ifctester"
|
cd ../ifctester && make test || fail "ifctester"
|
||||||
make build-ids-docs || fail "build-ids-docs"
|
make build-ids-docs || fail "build-ids-docs"
|
||||||
# Run mathutils related tests at the end to ensure no other code is relying on mathutils.
|
|
||||||
# mathutils only has pre-built wheels for Python 3.13+; skip on older versions.
|
|
||||||
cd ../ifcopenshell-python
|
|
||||||
if python -c "import sys; sys.exit(0 if sys.version_info >= (3, 13) else 1)"; then
|
|
||||||
pip install mathutils
|
|
||||||
make test-mathutils || fail "mathutils"
|
|
||||||
fi
|
|
||||||
if [ $ERROR -ne 0 ]; then
|
if [ $ERROR -ne 0 ]; then
|
||||||
echo "One or more tests failed";
|
echo "One or more tests failed";
|
||||||
exit 1;
|
exit 1;
|
||||||
|
|||||||
@@ -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
|
.PHONY: test
|
||||||
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
|
.PHONY: test-parallel
|
||||||
test-parallel:
|
test-parallel:
|
||||||
@NPROCS=$$(nproc 2>/dev/null || sysctl -n hw.ncpu); \
|
@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
|
pytest --import-mode=importlib -p no:pytest-blender -n $$NPROCS test
|
||||||
|
|
||||||
.PHONY: test-mathutils
|
|
||||||
test-mathutils:
|
|
||||||
pytest --import-mode=importlib -p no:pytest-blender test/util/test_shape_builder.py
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: license
|
.PHONY: license
|
||||||
|
|||||||
@@ -31,11 +31,10 @@ This runs:
|
|||||||
|
|
||||||
.. code-block:: bash
|
.. 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
|
The ``pytest-blender`` plugin is disabled because these are IfcOpenShell-Python
|
||||||
tests, not Bonsai Blender tests. The shape builder tests are split into a
|
tests, not Bonsai Blender tests.
|
||||||
separate target because they require Blender's ``mathutils`` package.
|
|
||||||
|
|
||||||
Parallel tests
|
Parallel tests
|
||||||
--------------
|
--------------
|
||||||
@@ -53,12 +52,9 @@ This automatically uses the available CPU count and runs the same tests as
|
|||||||
Shape builder tests
|
Shape builder tests
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
The shape builder tests require ``mathutils``. Run them separately:
|
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,
|
||||||
.. code-block:: bash
|
since they need Blender's ``mathutils`` package. See ``src/bonsai/test/tool/test_shape_builder.py``.
|
||||||
|
|
||||||
pip install mathutils
|
|
||||||
make test-mathutils
|
|
||||||
|
|
||||||
Running individual tests
|
Running individual tests
|
||||||
------------------------
|
------------------------
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
# You should have received a copy of the GNU Lesser General Public License
|
# You should have received a copy of the GNU Lesser General Public License
|
||||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# 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
|
from typing import Any, Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@@ -31,11 +31,6 @@ from ifcopenshell.util.shape_builder import (
|
|||||||
arc_to_polyline_points,
|
arc_to_polyline_points,
|
||||||
is_x,
|
is_x,
|
||||||
np_angle,
|
np_angle,
|
||||||
np_angle_signed,
|
|
||||||
np_intersect_line_line,
|
|
||||||
np_matrix_to_euler,
|
|
||||||
np_normal,
|
|
||||||
np_rotation_matrix,
|
|
||||||
np_to_3d,
|
np_to_3d,
|
||||||
polygonal_face_set_to_faceted_brep,
|
polygonal_face_set_to_faceted_brep,
|
||||||
)
|
)
|
||||||
@@ -147,89 +142,6 @@ class TestPolygonalFaceSetToFacetedBrep(test.bootstrap.IFC4):
|
|||||||
polygonal_face_set_to_faceted_brep(face_set)
|
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):
|
class TestRectangle(test.bootstrap.IFC4):
|
||||||
def test_get_rectangle_coords(self):
|
def test_get_rectangle_coords(self):
|
||||||
builder = ShapeBuilder(self.file)
|
builder = ShapeBuilder(self.file)
|
||||||
|
|||||||
Reference in New Issue
Block a user