"""Methods for mtg interpretation with turtle"""
from math import degrees, pi, cos, sin
import openalea.plantgl.all as pgl
from openalea.mtg.plantframe.turtle import TurtleFrame
def _is_iterable(x):
try:
_ = iter(x)
except TypeError:
return False
return True
[docs]
def as_tuples(pgl_3List, offset=0):
"""return pgl list of 3 numbers kind (indes3, vector3) as a list of python tuples"""
if not _is_iterable(offset):
offset = [offset] * 3
return [(i[0] + offset[0], i[1] + offset[1], i[2] + offset[2]) for i in pgl_3List]
[docs]
def addSets(pglset1, pglset2, translate=(0, 0, 0)):
"""create a new TriangleSet by addition of two existing ones
if translate is not None, pglset2 is translated with vector translate
"""
points = as_tuples(pglset1.pointList) + as_tuples(
pglset2.pointList, offset=translate
)
index = as_tuples(pglset1.indexList) + as_tuples(
pglset2.indexList, offset=len(pglset1.pointList)
)
return pgl.TriangleSet(points, index)
# Meshing function for StemElements
[docs]
def slim_cylinder(length, radius_base, radius_top):
"Try to construct a cylinder with a low number of triangles."
rb, rt = radius_base, radius_top
a1, a2, a3 = 0, 2 * pi / 3.0, 4 * pi / 3.0
r = rb
p1 = (r * cos(a1), r * sin(a1), 0)
p2 = (r * cos(a2), r * sin(a2), 0)
p3 = (r * cos(a3), r * sin(a3), 0)
r = rt
q1 = (r * cos(a1 + pi), r * sin(a1 + pi), length)
q2 = (r * cos(a2 + pi), r * sin(a2 + pi), length)
q3 = (r * cos(a3 + pi), r * sin(a3 + pi), length)
set = pgl.TriangleSet(
[p1, p2, p3, q1, q2, q3],
[
(2, 1, 0),
(3, 4, 5),
(0, 5, 4),
(0, 4, 2),
(2, 4, 3),
(3, 1, 2),
(1, 3, 5),
(5, 0, 1),
],
)
return set
[docs]
def StemElement_mesh(length, diameter_base, diameter_top, classic=False):
"""Compute mesh for a stem element
- classic indicates
"""
if classic:
solid = True
slices = 6 # 6 is the minimal number of slices for a correct computation of star (percentage error lower than 5)
stem = pgl.Tapered(
diameter_base / 2.0,
diameter_top / 2.0,
pgl.Cylinder(1.0, length, solid, slices),
)
tessel = pgl.Tesselator()
stem.apply(tessel)
mesh = tessel.triangulation
else:
mesh = slim_cylinder(length, diameter_base / 2.0, diameter_top / 2.0)
return mesh
[docs]
def compute_element(element_node, leaves, min_length=0.01, classic=False):
"""compute geometry of Adel base elements (LeafElement and StemElement)
element_node should be a mtg node proxy"""
n = element_node
geom = None
if n.label.startswith("Leaf"): # leaf element
blade = n.complex()
species = blade.species
if blade.visible_length >= min_length:
if blade.shape_key is not None and n.srb is not None:
if leaves[species].dynamic:
inclin = 1 # inclination is encoded in db
else:
inclin = blade.inclination
geom = leaves[
species
].mesh(
blade.shape_key,
blade.shape_mature_length,
blade.shape_max_width,
blade.visible_length,
n.srb,
n.srt,
incline=inclin,
flipx=True,
min_area=min_length**2 / 100,
) # flipx allows x-> -x to place the shape along with the tiller positioned with turtle.down()
if n.lrolled > 0:
rolled = StemElement_mesh(n.lrolled, n.d_rolled, n.d_rolled, classic)
if geom is None:
geom = rolled
else:
geom = addSets(rolled, geom, translate=(0, 0, n.lrolled))
elif n.label.startswith("Stem"): # stem element
stem = n.complex()
# diameter_base = stem.parent().diameter if (stem.parent() and stem.parent().diameter > 0.) else stem.diameter
# diameter_top = n.diam
if n.length >= min_length:
diameter_base = stem.diameter
diameter_top = stem.diameter
geom = StemElement_mesh(n.length, diameter_base, diameter_top, classic)
return geom
[docs]
class AdelTurtle(pgl.PglTurtle):
def __init__(self):
super(AdelTurtle, self).__init__()
self.context = {}
[docs]
def getFrame(self):
pos = self.getPosition()
Head = self.getHeading()
Up = self.getUp()
return {"Position": pos, "Head": Head, "Up": Up}
[docs]
def setFrame(self, frame):
self.move(frame["Position"])
self.setHead(frame["Head"], frame["Up"])
[docs]
class AdelVisitor:
"""Performs geometric interpretation of mtg nodes"""
def __init__(self, leaves, min_length, classic, face_up):
self.classic = classic
self.face_up = face_up
self.min_length = min_length
self.leaves = leaves
def __call__(self, g, v, turtle):
geometry = g.property("geometry")
# 1. retrieve the node
n = g.node(v)
axis = n.complex_at_scale(scale=2).label
az_axis = n.complex_at_scale(scale=2).azimuth
prev_axis = turtle.context.get("axis", axis)
metamer = n.complex_at_scale(scale=3)
# Go to plant position if first plant element
if n.parent() is None: # this is a new plant base
p = n.complex_at_scale(scale=1)
if "position" in p.properties():
# print p.label, 'moving to ', p.position
turtle.move(list(map(float, p.position)))
else:
turtle.move(0, 0, 0)
# initial position to be compatible with canMTG positioning
turtle.setHead(0, 0, 1, -1, 0, 0)
if "azimuth" in p.properties():
turtle.rollR(p.azimuth)
# print prev_axis, 'stop'
# print 'new MS start'
turtle.context.update(
{
"MS_top": turtle.getFrame(),
"tiller_base": turtle.getFrame(),
"top": turtle.getFrame(),
"is_axis_first_StemElement": True,
}
)
if axis != prev_axis:
if metamer.edge_type() == "+":
turtle.context["is_axis_first_StemElement"] = True
if prev_axis == "MS":
# this is the begining of the first tiller
# print axis, 'start'
# top of mainstem saved
turtle.context["MS_top"] = turtle.context["top"]
turtle.context["tiller_base"] = turtle.context["top"]
else:
# this is a new tiller attached to the same point thatn the previous tiller
# print prev_axis, 'stop'
# print axis, 'start'
# return to tilller base
turtle.context["top"] = turtle.context["tiller_base"]
else: # this is the continuation of MS
# print prev_axis, 'stop'
# print axis, 'continue'
turtle.context["top"] = turtle.context["MS_top"]
# hypothesis that inclin is to be applied at the base of the visible elements
# if n.offset > 0:
# turtle.f(n.offset)
turtle.setFrame(turtle.context["top"])
# incline turtle at the base of stems,
if n.label.startswith("Stem"):
inclin = float(n.inclination) if n.inclination else 0.0
azim = float(n.azimuth) if n.azimuth else 0.0
if inclin:
# print 'node', n._vid, 'inclin', inclin
# incline along curent azimuth for ramification (tiller bases) or plant base
if turtle.context["is_axis_first_StemElement"]:
# print 'axis',axis, 'prev_axis', prev_axis,' node ', n._vid, 'edge', n.edge_type(),'up before inclin ', turtle.getUp(), 'inclin', inclin
if (
prev_axis != "MS"
): # new tiller attached to the same position than the firt
turtle.rollR(az_axis)
turtle.context["tiller_base"] = turtle.getFrame()
turtle.down(inclin)
turtle.context["is_axis_first_StemElement"] = False
# print 'up after inclin', turtle.getUp()
# if not incline towardss vertical
else:
up = turtle.getUp()
zleft = turtle.getLeft()[2]
turtle.rollToVert()
# print 'up after rollToVert', turtle.getUp()
angle = degrees(pgl.angle(up, turtle.getUp()))
dzl = zleft - turtle.getLeft()[2]
turtle.down(inclin)
# replace turtle in original azimuth plane
# print 'angle ', angle, 'dzl', dzl
if dzl < 0:
angle = -angle
turtle.rollR(-angle)
if azim:
# print 'node', n._vid, 'azim ', azim
turtle.rollR(azim)
if n.label.startswith("Leaf") or n.label.startswith("Stem"):
# update geometry of elements
mesh = None
if n.length > 0:
mesh = compute_element(n, self.leaves, min_length=self.min_length, classic=self.classic)
if mesh:
n.geometry = turtle.transform(
mesh, face_up=self.face_up and n.label.startswith("Leaf")
)
n.anchor_point = turtle.getPosition()
else:
if v in geometry: # delete existing geometry
geometry.pop(v)
# 3. Update the turtle and context
turtle.setId(v)
if n.label.startswith("Stem"):
if n.length > 0:
turtle.f(n.length)
turtle.context.update({"top": turtle.getFrame()})
if n.label.startswith("Leaf"):
if n.lrolled > 0:
turtle.f(n.lrolled)
turtle.context.update({"top": turtle.getFrame()})
turtle.context.update({"axis": axis})
[docs]
def mtg_interpreter(g, leaves, min_length=0.01, classic=False, face_up=False):
"""Compute/update the geometry on each node of the MTG using Turtle geometry."""
# BUG : sub_mtg mange le vertex plant => on perd la plante !
# plants = g.component_roots_at_scale(g.root, scale=1)
# nplants = g.nb_vertices(scale=1)
# gt = MTG()
# for plant in plants:
# gplant = g.sub_mtg(plant)
turtle = AdelTurtle()
visitor = AdelVisitor(leaves, min_length, classic, face_up)
_ = TurtleFrame(g, visitor=visitor, turtle=turtle, gc=False, all_roots=True)
# gt = union(gplant,gt)
return g
[docs]
def plot3d(g, leaf_material=None, stem_material=None, soil_material=None, colors=None):
"""
Returns a plantgl scene from an mtg.
"""
Material = pgl.Material
Color3 = pgl.Color3
Shape = pgl.Shape
Scene = pgl.Scene
if colors is None:
if leaf_material is None:
leaf_material = Material(Color3(0, 180, 0))
if stem_material is None:
stem_material = Material(Color3(0, 130, 0))
if soil_material is None:
soil_material = Material(Color3(170, 85, 0))
# colors = g.property('color')
geometries = g.property("geometry")
greeness = g.property("is_green")
labels = g.property("label")
scene = Scene()
def geom2shape(vid, mesh, scene, colors):
shape = None
if isinstance(mesh, list):
for m in mesh:
geom2shape(vid, m, scene, colors)
return
if mesh is None:
return
if isinstance(mesh, Shape):
shape = mesh
mesh = mesh.geometry
label = labels.get(vid)
is_green = greeness.get(vid)
if colors:
shape = Shape(mesh, Material(Color3(*colors.get(vid, [0, 0, 0]))))
elif not greeness:
if not shape:
shape = Shape(mesh)
elif label.startswith("Stem") and is_green:
shape = Shape(mesh, stem_material)
elif label.startswith("Leaf") and is_green:
shape = Shape(mesh, leaf_material)
elif not is_green:
shape = Shape(mesh, soil_material)
shape.id = vid
scene.add(shape)
for vid, mesh in geometries.items():
geom2shape(vid, mesh, scene, colors)
return scene