DEFORMCACHE AND MATRIX ISSUE
-
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:
- The whole Python plugin (i.e. a folder including the resources for the plugin)
- An example file which makes use of your plugin and also demonstrates the problem.
- When you are not at liberty to share things publicly, you can also send your data to
sdk_support(at)maxon.net
.
A few things that stand out to me regarding your code without me being able to run it.
- You never set the matrix for the drawing operations in the
BaseDraw
. Since you draw in global space, you should be fine, since theBaseDraw
should normally does come initialized for that. But it feels a bit dicey. Have a look atBaseDraw.SetMatrix_Matrix()
on how to set up a drawing matrix in world or object coordinates (link). - You do not fully traverse the cache of an object. Caches can contain children and since you never go down in your cache evaluation, you might miss crucial data. I have talked here a bit about caches.
- There is also the fact that your
polysCenter
is assuming all polygons, or more precisely their internal triangulation, to be coplanar. Which is not guaranteed and subsequently will cause your normal indicators not be attached to non-planar polygons. There is not really a performant fix for that in Python AFIAK, I am just mentioning it for completeness. - And finally, you are transforming your normals by the global matrix of the object (
destV * mx
). Which is most likely not what you want to do.
# 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
ThanksI corrected the matrix calculation as suggested but the problem persist.
I'll try to check the cache issue thread you linkedfor now I upload the plugin...it's very messy I think, but I use it also for testing other things ;):
PLUGINI 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
-
Hi @mdr74,
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 aPolygonObject
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 impatientCheers,
Ferdinand -
@zipit
I can wait, no hurryJust 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 issueI'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 selectedIt'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
-
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()
orBaseList2D.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 to1000
to prevent stack overflows. You could also increase that recursion limit withsys
, but at some point Python will then crash spectacularly when you exhaust all the stack memory for that processCheers,
Ferdinand"""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