Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
Hi, I'm trying to write a Tag plugin to visualise data on objects. Everything's work fine until I put a deformer under the object and get the deformed Cache. In that case, the matrix of the generated Lines are translated ( I think they are calculated twice or something similar ).
this is the code:
import random import os import sys import c4d from c4d import utils as u from c4d import Vector as vc PLUGIN_ID = 1099997 # VIS_SETTINGS # POLYCENTERINFO # POLYNORMALINFO class PolyVisualiserHelper(object): def polysCenter(self, ob, local=True): polys = ob.GetAllPolygons() pts = ob.GetAllPoints() nbPolys = ob.GetPolygonCount() center = vc() for i, poly in enumerate(polys): if poly.c != poly.d: center = (pts[poly.a] + pts[poly.b] + pts[poly.c] + pts[poly.d]) / 4 else: center = (pts[poly.a] + pts[poly.b] + pts[poly.c]) / 3 yield center def polysNormal(self, ob, local=True): polys = ob.GetAllPolygons() pts = ob.GetAllPoints() nbPolys = ob.GetPolygonCount() norPolys = [vc()] * nbPolys nor = vc() for i, poly in enumerate(polys): nor = (pts[poly.a] - pts[poly.c]).Cross(pts[poly.b] - pts[poly.d]) nor.Normalize() yield nor def DrawPolyNormals(self, bd, obj): ob = obj mx = ob.GetMg() for p, n in zip(self.polysCenter(ob), self.polysNormal(ob)): normalDistance = n * 50 destV = p + normalDistance bd.DrawLine(p * mx, destV * mx, 0) def AccessCache(self, bd, op) : temp = op.GetDeformCache() if temp is not None: # recurse over the deformed cache self.AccessCache(bd, temp) else: # verify existance of the cache temp = op.GetCache() if temp is not None: # recurve over the cache self.AccessCache(bd, temp) else: # the relevant data has been found # and it should not be a generator if not op.GetBit(c4d.BIT_CONTROLOBJECT) : # check the cache is an instance of Opoint if op.IsInstanceOf(c4d.Opoint) : self.DrawPolyNormals(bd, op) class PolyVisualiser(c4d.plugins.TagData, PolyVisualiserHelper): """Look at Camera""" def Init(self, node): 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): self.AccessCache(bd, 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)
I already tried to disable the Matrix multiplication in the DrawPolyNormals() method when deformCache is generated but the matrix were still not correct
thanks for future help
Hi @mdr74,
thank you for reaching out to us. If possible, we would like to ask you to provide the following, as it would be then easier for us to answer your question:
sdk_support(at)maxon.net
A few things that stand out to me regarding your code without me being able to run it.
BaseDraw
BaseDraw.SetMatrix_Matrix()
polysCenter
destV * mx
# This will also fully transform the normal, because matrix multiplication is # distributive, i.e.: M * (u + v) = Mu + Mv destV = p + normalDistance bd.DrawLine(p * mx, destV * mx, 0) # Instead you should first transform the point and then transform the # normal by the normalized frame of your first transform, i.e. apply the # transform without the offset and scale. q = p * transform r = q + normal * c4d.Matrix(v1=~transform.v1, v2=~transform.v2, v3=~transform.v3) bd.DrawLine(q, r, 0)
Cheers, Ferdinand
@zipit Thanks
I corrected the matrix calculation as suggested but the problem persist. I'll try to check the cache issue thread you linked
for now I upload the plugin...it's very messy I think, but I use it also for testing other things ;): PLUGIN
I also attach 2 screenshots of the problem
when there is no deformer everything's correct (with base object and polygonal object):
as soon as I put a deformer below the lines shift away:
the same problem occurs when I change the deformer alignment to other planes and fit to parent:
thanks for the support
jeah, it looks a bit like you not grabbing the right part of the cache. The link I have posted above shows that deform caches can be imbedded within the GetCache() cache - for example in the case of parametric objects, as they will hold a PolygonObject cache which then will be deformed by modifiers. I will have a look at your file on Monday, as I today probably won't have time to do it anymore. Feel free to look at the mentioned example in the mean time, in case you are impatient
GetCache()
PolygonObject
@zipit I can wait, no hurry
Just to let you know...the same problem occurs with polygonal object and deformer everything's correct in world center but as soon as I move the object it seems that the matrix are calculated 2 times. It seems a cache issue
I'm checking the link provided
cheers
I don't know exactly how and why, but I found the solution. setting the basedraw matrix relative to the object when deformer is selected
It's not a clean solution, but I cannot understand the hierarchy cache problem
the modified method is:
def DrawPolyNormals(self, obj): for p, n in zip(self.objPolysCenter, self.objPolysNormal): mx = self.mx if obj.GetDown(): if obj.GetDown().GetBit(c4d.BIT_ACTIVE): self.basedraw.SetMatrix_Matrix(None, mx) else: self.basedraw.SetMatrix_Matrix(None, c4d.Matrix()) normalDistance = n * self.normalLength q = p r = q + normalDistance * c4d.Matrix(v1=~mx.v1, v2=~mx.v2, v3=~mx.v3) self.basedraw.DrawLine(q, r, 0)
the object points global position are calculated in self.objPolysCenter:
def polysCenter(self, local=False): polys = self.polyPlgList pts = self.polyPtsList nbPolys = self.polyPlgCount center = vc() for i, poly in enumerate(self.polyPlgList): if poly.c != poly.d: center = (pts[poly.a] + pts[poly.b] + pts[poly.c] + pts[poly.d]) / 4 else: center = (pts[poly.a] + pts[poly.b] + pts[poly.c]) / 3 if not local: yield center * self.mx else: yield center
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
_leaf_node_walk()
GeListNode.GetUp()
BaseList2D.GetMain()
1000
sys
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)
Thanks a lot !!!. I will try it in the next days. I'm free for my personal projects only during weekends