Hi @mdr74,
I see you were busy on the weekend ;) Yes, your major mistake was that you did mix up drawing spaces in your code. There were also other problems in your code, most importantly the incomplete cache access. Below you will find a snippet which addresses these problems and scene file which I did test your code on.
edit: Something I forgot to mention in the file and here is that _leaf_node_walk()
should be implemented iteratively (currently it is recursive). But things would have gotten more bloated if I had done this. So I kept it this way. There are multiple examples here on the forum on how to walk a scene graph iteratively, which is a very similar problem. The major difference is that here you do not have a monohierarchical structure, but a polyhierarchical (child, cache and deform cache) which can make the implementation a bit cumbersome, as you do not have an easy way to get from a cache to its parent (GeListNode.GetUp()
or BaseList2D.GetMain()
won't work). You will have to build your own hierarchy lookup table to traverse the graph iteratively. The current implementation will fail at caches of roughly 500 objects or more, since Python sets its recursion limit to 1000
to prevent stack overflows. You could also increase that recursion limit with sys
, but at some point Python will then crash spectacularly when you exhaust all the stack memory for that process ;)
Cheers,
Ferdinand
pc13027_scene.c4d
"""Example for a TagData plugin that draws stuff into the viewport based on
the cache of the object it is attached to.
Your problems did not really stem from adding deformers, but the fact that you
did mix local and global coordinates. But instead of sifting through all your
code, I just quickly wrote a clean version, since also your cache access code
was faulty. I also added the "correct" draw pass in BaseTag.Draw(). You can of
course choose another pass, but you should not draw into all passes.
Your code also did suffer a bit from being overly complex or quite lenient
with system resources. You should be more conservative when writing code that
draws stuff onto the screen, or things might get quite slow.
For example functions like zip() are quite expensive to run in such context.
And there is also the fact that DrawLine() is not the fastest thing in the
world, which should make you even more conservative ;)
Cheers,
Ferdinand
"""
import c4d
import os
PLUGIN_ID = 1099997
class DrawPolyInfo(object):
"""Draws stuff for polygon objects.
"""
def __init__(self, bd, data, node):
"""DrawPolyInfo initializer.
Args:
bd (c4d.BaseDraw): The view port to draw in.
data (c4d.BaseContainer): The parameters of the tag node.
node (c4d.BaseObject): The object to draw for.
Raises:
TypeError: When arguments are not of expected type.
"""
if not isinstance(bd, c4d.BaseDraw):
raise TypeError(f"Expected BaseDraw. Received: {type(bd)}")
if not isinstance(data, c4d.BaseContainer):
raise TypeError(f"Expected BaseContainer. Received: {type(data)}")
if not isinstance(node, c4d.BaseObject):
raise TypeError(f"Expected BaseObject. Received: {type(node)}")
# Run the draw loop.
DrawPolyInfo._draw_information(bd, data, node)
@staticmethod
def _draw_information(bd, data, node):
"""The drawing loop.
Args:
bd (c4d.BaseDraw): The view port to draw in.
data (c4d.BaseContainer): The parameters of the tag node.
node (c4d.BaseObject): The host of the tag.
"""
# Go over all polygonal leaf nodes in cache of node.
for leaf_node in DrawPolyInfo._leaf_node_walk(node, is_cache=False):
# Draw the normals
if data[c4d.VIS_SETTINGS_NORMAL_ACTIVE]:
DrawPolyInfo._draw_normals(bd, data, leaf_node)
# Do other stuff you want to do here.
# ...
@staticmethod
def _leaf_node_walk(node, is_cache=True):
"""Yields recursively the the polygonal leaf nodes of a cache.
Args:
node (c4d.BaseObject): The node to walk the cache for.
is_cache (bool, optional): If passed node is a virtual object,
i.e. part of a cache, or not. Defaults to `True`.
Yields:
c4d.PolygonObject: The polygonal leaf nodes in the cache of
`node`.
"""
if node is None:
return
# Walk the deformed cache or the static cache when the former is
# not present.
cache = node.GetDeformCache() or node.GetCache()
if cache is not None:
# Yield the leaf nodes in that cache.
for leaf in DrawPolyInfo._leaf_node_walk(cache):
yield leaf
# A node with no caches is a terminal/leaf node. When it is a
# polygon object not marked as an input object, we yield it.
elif (isinstance(node, c4d.PolygonObject) and
not node.GetBit(c4d.BIT_CONTROLOBJECT)):
yield node
# Break out for the start node so that we do not include its children
# (i.e. non-virtual objects).
if is_cache is False:
return
# We have also to go over the children for composed caches.
for child in node.GetChildren():
for leaf in DrawPolyInfo._leaf_node_walk(child):
yield leaf
@staticmethod
def _draw_normals(bd, data, node):
"""Draws fancy normal indicators.
Args:
bd (c4d.BaseDraw): The view port to draw into.
data (c4d.BaseContainer): The parameters of the tag node.
node (c4d.PolygonObject): A polygonal node to draw stuff for.
"""
# We set the drawing space to the local space of the node we want
# to draw stuff for, making our life much more easier. I.e. we do
# not have to juggle global coordinates anymore.
bd.SetMatrix_Matrix(node, node.GetMg(), 4)
bd.SetPen(c4d.Vector(1., 1., 0.))
# Now we go over all polygons.
points = node.GetAllPoints()
for cpoly in node.GetAllPolygons():
a, b, c, d = (points[cpoly.a], points[cpoly.b],
points[cpoly.c], points[cpoly.d])
# Just as in your computations, these two are assuming that the
# polygons are coplanar. Which is not always the case, but there
# is no easy way to get hold of Cinema's internal triangulation.
# Your best bet is to triangulate the node manually beforehand
# (which is slow and therefore rather bad for drawing stuff). The
# best design would be probably to cache the triangulation in some
# way.
mid_point = (a + b + c + d) * .25
normal = ~((b - a) % (c - a))
# Note: DrawLine() will not perform very well when you have have
# any mildly heavy geometry as the input(s) here.
length = data[c4d.VIS_SETTINGS_NORMAL_LENGTH]
bd.DrawLine(mid_point, mid_point + normal * length, c4d.NOCLIP_D)
class PolyVisualiser(c4d.plugins.TagData):
"""Your plugin class.
I pruned some stuff to make this very straight forward.
"""
def Init(self, node):
"""
Called when Cinema 4D Initialize the TagData (used to define, default
values).
:param node: The instance of the TagData.
:type node: c4d.GeListNode
:return: True on success, otherwise False.
"""
self.InitAttr(node, bool, c4d.VIS_SETTINGS_CENTER_ACTIVE)
node[c4d.VIS_SETTINGS_CENTER_ACTIVE] = True
self.InitAttr(node, bool, c4d.VIS_SETTINGS_NORMAL_ACTIVE)
node[c4d.VIS_SETTINGS_NORMAL_ACTIVE] = False
pd = c4d.PriorityData()
if pd is None:
raise MemoryError("Failed to create a priority data.")
pd.SetPriorityValue(c4d.PRIORITYVALUE_CAMERADEPENDENT, True)
node[c4d.EXPRESSION_PRIORITY] = pd
return True
def Draw(self, tag, op, bd, bh):
"""
Called by Cinema 4d when the display is updated to display some v
isual element of your object in the 3D view. This is also the place
to draw Handle.
:param op: The instance of the ObjectData.
:type op: c4d.BaseObject
:param drawpass:
:param bd: The editor's view.
:type bd: c4d.BaseDraw
:param bh: The BaseDrawHelp editor's view.
:type bh: c4d.plugins.BaseDrawHelp
:return: The result of the drawing (most likely c4d.DRAWRESULT_OK)
"""
# We usually only want to draw in the object draw pass. The reason
# why your version did *flicker* was that you did draw into all draw
# passes at once.
if (not isinstance(bd, c4d.BaseDraw) or
bd.GetDrawPass() != c4d.DRAWPASS_OBJECT):
return c4d.DRAWRESULT_OK
# Set off our little drawing helper.
DrawPolyInfo(bd, tag.GetData(), op)
return c4d.DRAWRESULT_OK
if __name__ == "__main__":
# Retrieves the icon path
directory, _ = os.path.split(__file__)
fn = os.path.join(directory, "res", "polyNormal.png")
# Creates a BaseBitmap
bmp = c4d.bitmaps.BaseBitmap()
if bmp is None:
raise MemoryError("Failed to create a BaseBitmap.")
# Init the BaseBitmap with the icon
if bmp.InitWith(fn)[0] != c4d.IMAGERESULT_OK:
raise MemoryError("Failed to initialize the BaseBitmap.")
c4d.plugins.RegisterTagPlugin(id=PLUGIN_ID,
str="Draw Test 03",
info=(c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE |
c4d.TAG_IMPLEMENTS_DRAW_FUNCTION),
g=PolyVisualiser,
description="drawtest03",
icon=bmp)