Source code for openalea.plantconvert.geometry
from math import pi
import openalea.mtg as mtg
import openalea.plantgl.all as pgl
from .matrix import TRS_from_matrix4, mat4_to_numpy
# TODO:
# move the implementation of opf parser here :
# 1. taper along x axis (find a better for the class) ok,
# 2. transformed from mat
# 3. mat from transformed
# 4. tapering radius
# 5. get scene
# a class to apply tapering along x axis (in opf the tapering is along z axis)
[docs]
class taper_along_x(object):
"""Class with information to perform tapering along x axis.
To initialize this class with a list of reference meshes (represented by TriangleSet)
If the given mesh is not in the list then it will be added.
:param ref_meshes: list of reference meshes
:type ref_meshes: list
"""
def __init__(self, ref_meshes=None):
"""Constructor method.
"""
if ref_meshes is None:
self.rotated_ref_meshes = {}
return
self.rotated_ref_meshes = dict(
[
(tr.getPglId(), pgl.tesselate(pgl.EulerRotated(0.0, -pi / 2, 0.0, tr)))
for tr in ref_meshes
]
)
def __call__(self, up, down, mesh):
pgl_id = mesh.getPglId() # mesh is supposed to be one of the reference meshes
# get the rotated mesh and taper it with up/down
try:
rotated = self.rotated_ref_meshes[pgl_id]
except KeyError:
#
rotated = pgl.tesselate(pgl.EulerRotated(0.0, -pi / 2, 0.0, mesh))
self.rotated_ref_meshes[pgl_id] = rotated
tapered = pgl.Tapered(down, up, rotated) # proceed the tapering
tapered = pgl.EulerRotated(0.0, pi / 2, 0.0, tapered)
return tapered
[docs]
def transformed_from_mat(A, geo, is_mesh=False):
"""Apply the transformation A (a 3 by 4 matrix) on the geometry.
Qr transformation of matrix A is done so that A = Q*r where:
Q is a unity matrix and r is upper-triangular. To use the oriented object of plantgl,
Q should have positive determinant.
r is in our case diagonal since A is a composition of rotations and scaling.
:param A: a 3 by 4 array that describes the transformation in the reference of the scene. This
matrix is supposed to be TRS decomposible
:type A: numpy.ndarray
:param geo: a geometry object from PlantGL
:type geo: plantgl.Geometry
:param is_mesh: if true a new mesh (TriangleSet) will be created. Otherwise we only create a geometry
object (no duplication of mesh in the memory). Defaults to False.
:type is_mesh: bool
:return: Transformed geometry. if is_mesh then we get a new mesh otherwise
we only create a geometry object
:rtype: plantgl.Geometry
"""
if is_mesh:
# 1. get the geometric mesh
# 2. Create a Matrix3 or Matrix4
# 3. Apply the Matrix to the pointList
# 4. Modify the mesh with the new pointList
if isinstance(geo, pgl.Tapered):
mesh = pgl.tesselate(geo)
else:
mesh = geo.deepcopy()
n = len(mesh.pointList)
M = pgl.Matrix4(*A.T.tolist())
for i in range(n):
mesh.pointList[i] = M * mesh.pointList[i]
mesh.computeNormalList()
return mesh
else:
if isinstance(A, pgl.Matrix4):
A_np = mat4_to_numpy(A)
# print(A_np)
else:
A_np = A
t, Q, s = TRS_from_matrix4(A_np)
return pgl.Translated(
*t.tolist(), pgl.Oriented(Q[:, 0], Q[:, 1], pgl.Scaled(s.tolist(), geo))
)
[docs]
def mat_from_transformed(geo):
"""Extract the global transformation matrix from a transformed geometry.
:param geo: a geometry object from PlantGL
:type geo: plantgl.Geometry
:raises TypeError: if the transformation is not recognized (i.e. not a plantGL instance)
:return: transformation matrix used to obtain the transformed geometry
:rtype: plantGL.Matrix4
"""
# the identity matrix :
ID = pgl.Matrix4(
*[pgl.Vector4([1.0 * (j == i) for j in range(4)]) for i in range(4)]
)
try:
if isinstance(geo, pgl.EulerRotated): # geo is the output of tapered_opf
return ID
elif isinstance(geo, pgl.TriangleSet): # geo has not been tapered
return ID
elif isinstance(geo, pgl.Frustum):
return ID
elif isinstance(geo, pgl.Cylinder):
return ID
elif isinstance(geo, pgl.Shape):
return mat_from_transformed(geo.geometry)
elif isinstance(geo, pgl.Translated):
mat = geo.transformation().getMatrix()
return mat * mat_from_transformed(geo.geometry)
elif isinstance(geo, pgl.Oriented):
mat = geo.transformation().getMatrix()
return mat * mat_from_transformed(geo.geometry)
elif isinstance(geo, pgl.Scaled):
mat = geo.transformation().getMatrix()
return mat * mat_from_transformed(geo.geometry)
except TypeError:
raise TypeError("input geometry's type is not recognized : %s" % (type(geo)))
[docs]
def tapering_radius_from_transformed(geo):
"""Extract the tapering radii from the input geometry.
:param geo: a geometry object from PlantGL
:type geo: either a plantGL.TriangleSet, a plantGL.Tapered or a plantGL shape
:return: top and base radii of the tapering geometry
:rtype: tuple
"""
if isinstance(geo, pgl.TriangleSet):
return float("nan"), float("nan")
elif isinstance(geo, pgl.Tapered):
return geo.topRadius, geo.baseRadius
else:
return tapering_radius_from_transformed(geo.geometry)
[docs]
def get_scene(g: mtg.MTG, filter=None):
"""Generate a plantGL Scene object from mtg geometry properties.
This method traverses the mtg g and reads the geometry property
of the nodes that are allowed by the filter.
It creates a plantgl Scene object of all geometric objects combined.
:param g: mtg object that contains the plant
:type g: mtg.MTG
:param filter: takes 2 inputs: mtg.MTG and node ID and returns a bool.
Defaults to None, i.e. all nodes are taken into account.
:type filter: function, optional
:return: plantGL Scene object containing all geometric objects
:rtype: plantGL.Scene
"""
if filter is None:
# if no filter given then return all the nodes
def filter(g, v):
return True
shape_list = []
for v in g:
try:
if filter(g, v):
geo = g[v]["geometry"]
if isinstance(geo, pgl.Shape):
geo.id = v
elif isinstance(geo, pgl.Geometry):
geo = pgl.Shape(geo, pgl.Material.DEFAULT_MATERIAL)
geo.id = v
shape_list.append(geo)
except KeyError:
pass
return pgl.Scene(shape_list)