diff --git a/docs/conf.py b/docs/conf.py
index d90b9116..b56d8c43 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -119,7 +119,7 @@
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
- html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+ # html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
pass
except:
html_theme = "default"
@@ -313,10 +313,10 @@
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
- "https://docs.python.org/": None,
+ "https://docs.python.org/": ("https://docs.python.org/", None),
# 'http://docs.scipy.org/doc/numpy/': None,
# 'http://docs.scipy.org/doc/scipy/reference/': None,
- "http://propertiespy.readthedocs.io/en/latest/": None,
+ "http://propertiespy.readthedocs.io/en/latest/": ("http://propertiespy.readthedocs.io/en/latest/", None),
}
linkcheck_ignore = ["https://gmggroup.org"]
linkcheck_retries = 10
diff --git a/docs/content/surface.rst b/docs/content/surface.rst
index a6e576f5..5b7762a9 100644
--- a/docs/content/surface.rst
+++ b/docs/content/surface.rst
@@ -13,11 +13,17 @@ Elements
.. autoclass:: omf.surface.Surface
-.. image:: /images/SurfaceGridGeometry.png
+.. image:: /images/grid2_tensor.svg
:align: center
.. autoclass:: omf.surface.TensorGridSurface
+.. image:: /images/grid2_regular.svg
+ :align: center
+
+.. autoclass:: omf.surface.RegularGridSurface
+
+
Attributes
----------
diff --git a/docs/images/SurfaceGeometry.png b/docs/images/SurfaceGeometry.png
deleted file mode 100644
index 5ea00e75..00000000
Binary files a/docs/images/SurfaceGeometry.png and /dev/null differ
diff --git a/docs/images/grid2_regular.svg b/docs/images/grid2_regular.svg
new file mode 100644
index 00000000..105fd967
--- /dev/null
+++ b/docs/images/grid2_regular.svg
@@ -0,0 +1,431 @@
+
+
+
+
diff --git a/docs/images/grid2_tensor.svg b/docs/images/grid2_tensor.svg
new file mode 100644
index 00000000..71c21f84
--- /dev/null
+++ b/docs/images/grid2_tensor.svg
@@ -0,0 +1,426 @@
+
+
+
+
diff --git a/omf/__init__.py b/omf/__init__.py
index 5ac8d2a5..40728d34 100644
--- a/omf/__init__.py
+++ b/omf/__init__.py
@@ -20,7 +20,7 @@
)
from .lineset import LineSet
from .pointset import PointSet
-from .surface import Surface, TensorGridSurface
+from .surface import Surface, TensorGridSurface, RegularGridSurface
from .texture import ProjectedTexture, UVMappedTexture
from .fileio import load, save, __version__
diff --git a/omf/attribute.py b/omf/attribute.py
index f0fccb53..458ff932 100644
--- a/omf/attribute.py
+++ b/omf/attribute.py
@@ -118,7 +118,7 @@ def deserialize(cls, value, trusted=False, strict=False, assert_valid=False, **k
array_dtype = DATA_TYPE_LOOKUP_TO_NUMPY[value["data_type"]]
if value["data_type"] == "BooleanArray":
int_arr = np.frombuffer(array_binary, dtype="uint8")
- bit_arr = np.unpackbits(int_arr)[: np.product(value["shape"])]
+ bit_arr = np.unpackbits(int_arr)[: np.prod(value["shape"])]
arr = bit_arr.astype(array_dtype)
else:
arr = np.frombuffer(array_binary, dtype=array_dtype)
diff --git a/omf/surface.py b/omf/surface.py
index aac10016..72499953 100644
--- a/omf/surface.py
+++ b/omf/surface.py
@@ -1,7 +1,10 @@
"""surface.py: Surface element definitions"""
+
import numpy as np
import properties
+EPSILON = np.finfo(float).eps
+
from .base import ProjectElement
from .attribute import ArrayInstanceProperty
from .texture import HasTexturesMixin
@@ -78,12 +81,12 @@ class TensorGridSurface(BaseSurfaceElement): # pylint: disable=R0901
tensor_u = properties.List(
"Grid cell widths, u-direction",
- properties.Float("", min=0.0),
+ properties.Float("", min=EPSILON),
coerce=True,
)
tensor_v = properties.List(
"Grid cell widths, v-direction",
- properties.Float("", min=0.0),
+ properties.Float("", min=EPSILON),
coerce=True,
)
axis_u = properties.Vector3(
@@ -116,7 +119,7 @@ def num_cells(self):
@properties.validator
def _validate_mesh(self):
"""Check if mesh content is built correctly"""
- if not np.abs(self.axis_u.dot(self.axis_v)) < 1e-6:
+ if not np.abs(self.axis_u.dot(self.axis_v)) < EPSILON:
raise properties.ValidationError("axis_u and axis_v must be orthogonal")
if self.offset_w is properties.undefined or self.offset_w is None:
return True
@@ -126,3 +129,64 @@ def _validate_mesh(self):
"{nnode}".format(zlen=len(self.offset_w.array), nnode=self.num_nodes)
)
return True
+
+
+class RegularGridSurface(BaseSurfaceElement): # pylint: disable=R0901
+ """Surface element with regular spacing in each dimension"""
+
+ schema = "org.omf.v2.element.surfaceregulargrid"
+
+ cell_count = properties.List(
+ "Number of cells along u, and v axes",
+ properties.Integer("", min=1),
+ min_length=2,
+ max_length=2,
+ )
+ cell_size = properties.List(
+ "Size of cells in the u and v dimensions",
+ properties.Float("", min=EPSILON),
+ min_length=2,
+ max_length=2,
+ )
+ axis_u = properties.Vector3(
+ "Vector orientation of u-direction",
+ default="X",
+ length=1,
+ )
+ axis_v = properties.Vector3(
+ "Vector orientation of v-direction",
+ default="Y",
+ length=1,
+ )
+ offset_w = ArrayInstanceProperty(
+ "Node offset",
+ shape=("*",),
+ dtype=float,
+ required=False,
+ )
+
+ @property
+ def num_nodes(self):
+ """Number of nodes (vertices)"""
+ cell_count = self.cell_count
+ return (cell_count[0] + 1) * (cell_count[1] + 1)
+
+ @property
+ def num_cells(self):
+ """Number of cells (faces)"""
+ cell_count = self.cell_count
+ return cell_count[0] * cell_count[1]
+
+ @properties.validator
+ def _validate_mesh(self):
+ """Check if mesh content is built correctly"""
+ if not np.abs(self.axis_u.dot(self.axis_v)) < 1e-6:
+ raise properties.ValidationError("axis_u and axis_v must be orthogonal")
+ if self.offset_w is properties.undefined or self.offset_w is None:
+ return True
+ elif len(self.offset_w.array) != self.num_nodes:
+ raise properties.ValidationError(
+ "Length of offset_w, {zlen}, must equal number of nodes, "
+ "{nnode}".format(zlen=len(self.offset_w.array), nnode=self.num_nodes)
+ )
+ return True
diff --git a/tests/test_surface.py b/tests/test_surface.py
index 88b064d6..053b15dd 100644
--- a/tests/test_surface.py
+++ b/tests/test_surface.py
@@ -1,4 +1,5 @@
"""Tests for Surface validation"""
+
import numpy as np
import pytest
@@ -21,8 +22,8 @@ def test_surface():
elem.validate()
-def test_surfacegrid():
- """Test surface grid geometry validation"""
+def test_tensor_surface():
+ """Test tensor surface grid geometry validation"""
elem = omf.surface.TensorGridSurface()
elem.tensor_u = [1.0, 1.0]
elem.tensor_v = [2.0, 2.0, 2.0]
@@ -38,3 +39,23 @@ def test_surfacegrid():
elem.offset_w = np.random.rand(6)
with pytest.raises(ValueError):
elem.validate()
+
+
+def test_regular_surface():
+ """Test regular surface geometry validation"""
+ elem = omf.surface.RegularGridSurface()
+ elem.origin = [0.0, 0.0, 0.0]
+ elem.cell_size = [1.0, 1.0]
+ elem.cell_count = [2, 2]
+ assert elem.validate()
+ assert elem.location_length("vertices") == 9
+ assert elem.location_length("faces") == 4
+ with pytest.raises(ValueError):
+ elem.cell_size = [0.0, 1.0]
+ with pytest.raises(ValueError):
+ elem.origin = [0.0, 0.0]
+ elem.offset_w = np.random.rand(9)
+ elem.validate()
+ elem.offset_w = np.random.rand(4)
+ with pytest.raises(ValueError):
+ elem.validate()