Generator sticks in a dirty loop
-
Dear Python experts,
I am working on a Python Generator Plugin which creates Splines from Polygon Edges including some cleanup procedures. The problem I am facing is that the Generator works in a simple setup (e.g. it has a Voronoi Fracture or Clone under it) but when it comes in complex setups with more children it spins in an endless dirty loop. It really gets evil when Fields are involved in that setup. I tried everything including SetOptimizeCache(True) or checking dirty states of the children with a loop but nothing really helped here. Maybe it is because of all these modelling commands which probably make a scene dirty.
Could you provide me with some help? I know it is a generic question, I would like to be more specific but I don't know how. I would really appreciate your help here.
Here is the code of the entire plugin:
import os import c4d from c4d import plugins, utils, bitmaps from c4d.utils import SplineHelp class SplFromEdge(plugins.ObjectData): def Init(self, node): # Retrieves the BaseContainer Instance to set the default values #self.SetOptimizeCache(True) data = node.GetDataInstance() if data is None: return False data.SetBool(c4d.WELDSIMILAR, False) data.SetFloat(c4d.THRESHOLD, 0.01) data.SetInt32(c4d.O_SPLINEFROMEDGEMODE, c4d.O_SPLINEFROMEDGEMODE_TAG) data.SetBool(c4d.O_SPLINEFROMEDGEMODE_AUTOUPDATE , True) data.SetBool(c4d.CLOSE,True) data.SetBool(c4d.O_SPLINEFROMEDGEMODE_OPTIMIZE, True) return True #grey out the threshold in the UI def GetDEnabling(self, node, id, t_data, flags, itemdesc): data = node.GetDataInstance() weld = data.GetBool(c4d.WELDSIMILAR) if id[0].id == c4d.THRESHOLD: return weld # Retrieves the current interpolation inter = node[c4d.O_SPLINEFROMEDGEMODE] # Defines enable state for the selection if id[0].id == c4d.EDGESELECTION: return inter == c4d.O_SPLINEFROMEDGEMODE_TAG return True #iterator to go through the whole hierarchy def walk(self,op) : if not op: return elif op.GetDown() : return op.GetDown() while op.GetUp() and not op.GetNext(): op = op.GetUp() return op.GetNext() #has all modeling operators def ExecModelingCommand(self, doc,opCtrl, op, parent): if op is None: return data = opCtrl.GetDataInstance() threshold = data.GetReal(c4d.THRESHOLD) weld = data.GetBool(c4d.WELDSIMILAR) mode = data.GetInt32(c4d.O_SPLINEFROMEDGEMODE) selectionSet = data.GetString(c4d.EDGESELECTION) optimize = data.GetBool(c4d.O_SPLINEFROMEDGEMODE_OPTIMIZE) close = data.GetBool(c4d.CLOSE) splineHelper = SplineHelp() container = c4d.BaseContainer() childObject = op.GetClone(c4d.COPYFLAGS_NONE) # Creates a temporary document. tempDoc = c4d.documents.BaseDocument() # Insert the cloned object to the temporary document. tempDoc.InsertObject(childObject) tempParent = c4d.BaseObject(c4d.Onull) if childObject is None or (mode==c4d.O_SPLINEFROMEDGEMODE_TAG and selectionSet==""): return parent.SetMg(childObject.GetMg()) c4d.StatusSetText("Creating Splines from Edges") c4d.StatusSetSpin() res = utils.SendModelingCommand(command=c4d.MCOMMAND_CURRENTSTATETOOBJECT, list=[childObject], mode=c4d.MODELINGCOMMANDMODE_ALL, bc=container, doc=tempDoc) if res is None: return editableObject = res[0] #select edges based on a selection tag def selectEdges(obj,tag): selection = tag.GetBaseSelect() polyselection = obj.GetEdgeS() selection.CopyTo(polyselection) #iterate through the new created polygon objects and look for the ones with an edge selection tag objectList = [] while editableObject: if editableObject.CheckType(c4d.Opolygon): if mode==c4d.O_SPLINEFROMEDGEMODE_TAG: for i in editableObject.GetTags(): if i.GetName()==selectionSet and i.CheckType(c4d.Tedgeselection): #select selectEdges(editableObject,i) #add to list objectList.append(editableObject) elif mode==c4d.O_SPLINEFROMEDGEMODE_ALL: utils.SendModelingCommand(c4d.MCOMMAND_SELECTALL, list = [editableObject], mode = c4d.MODELINGCOMMANDMODE_EDGESELECTION, bc = c4d.BaseContainer()) objectList.append(editableObject) editableObject = self.walk(editableObject) if not objectList: c4d.StatusClear() return for i in objectList: i.InsertUnderLast(tempParent) spl = utils.SendModelingCommand(command=c4d.MCOMMAND_EDGE_TO_SPLINE, list=objectList, mode=c4d.MODELINGCOMMANDMODE_ALL, doc=objectList[0].GetDocument()) c4d.StatusSetText("Optimizing Spline Objects") #Exploding Splines with multiple Segments which came from Edge to Spline splitList = [] for i in objectList: child = i.GetDown() if child is not None: if child.CheckType(c4d.Ospline): splineHelper.InitSpline(child) if splineHelper.GetSegmentCount() !=1: splitList.append(child) #Explode Splines using the Explode Command if splitList: expSplines = utils.SendModelingCommand(command=c4d.MCOMMAND_EXPLODESEGMENTS, list=splitList, mode=c4d.MODELINGCOMMANDMODE_ALL, flags=c4d.MODELINGCOMMANDFLAGS_NONE, doc=splitList[0].GetDocument()) #Creating a Spline List which will be later added under a parent object splineList = [] obj=objectList[0] while obj: if obj.CheckType(c4d.Ospline): splineHelper.InitSpline(obj) if splineHelper.GetSegmentCount() !=0: #Add all spline objects to a dedicated spline list. Every object which is not in that list will be deleted splineList.append(obj) obj = self.walk(obj) #optimize the spline for duplicate points optimizeBC = c4d.BaseContainer() optimizeBC[c4d.MDATA_OPTIMIZE_POINTS]= optimize optimizeBC[c4d.MDATA_OPTIMIZE_UNUSEDPOINTS]= True optimizeBC[c4d.MDATA_OPTIMIZE_TOLERANCE]= 0.01 optimizedSplines = utils.SendModelingCommand(command=c4d.MCOMMAND_OPTIMIZE, list=splineList, bc= optimizeBC, mode=c4d.MODELINGCOMMANDMODE_ALL, flags=c4d.MODELINGCOMMANDFLAGS_NONE) #filter overlapping objects c4d.StatusSetText("Cleaning Up and Welding") positionList = [] keyList = [] noDuplicateSplineList = [] for i in splineList: points = i.GetAllPoints() bary = c4d.Vector(0) #find the center of all points for j in points: bary+=j bary = bary / len(points) # move the points by the amount represented by the new axis center for index, point in enumerate(points) : point = point - bary i.SetPoint(index, point) # notify about the points repositioning i.Message(c4d.MSG_UPDATE) # adjust the matrix offset component accordingly currentMg = i.GetMg() currentMg.off += bary # reposition the object in the space i.SetMg(currentMg) #adjust object position polyParent = i.GetUp() offset = polyParent.GetMg().off #insert and adjust object position i.InsertUnderLast(tempParent) i[c4d.SPLINEOBJECT_CLOSED]=close currentMg = i.GetMg() currentMg.off += offset i.SetMg(currentMg) #remove overlapping splines if welding is active if weld: #check spline object position if it mateches a certain threshold overlapping = False if positionList: for j in positionList: p1 = i.GetMg().off p2 = j.GetMg().off distance = (p2-p1).GetLength() if distance <= threshold: overlapping = True #check if it's overlapping and clear it key = int((i.GetRad().x+i.GetRad().y+i.GetRad().z)/threshold) if key in keyList and overlapping: i.Remove() else: keyList.append(key) positionList.append(i) noDuplicateSplineList.append(i) else: noDuplicateSplineList.append(i) c4d.StatusClear() #put everything under the parent null and return it for i in noDuplicateSplineList: i.InsertUnderLast(parent) return parent def GetVirtualObjects(self, op, hh): doc = op.GetDocument() data = op.GetDataInstance() objInput = op.GetDown() if objInput is None: return None objRet = c4d.BaseObject(c4d.Onull) if doc is None or objRet is None: return None hierarchyClone = op.GetAndCheckHierarchyClone(hh, objInput, c4d.HIERARCHYCLONEFLAGS_ASPOLY, True) if hierarchyClone["dirty"] is False: return hierarchyClone["clone"] clone = hierarchyClone["clone"] if clone is None: return op.GetCache() self.ExecModelingCommand(doc, op, clone, objRet) return objRet ############################################################################################################### # Plugin Registration ############################################################################################################### PLUGIN_ID_GENERATOR = 954679 def RegisterObjectData(id, name, bmpPath, objData, desc, flags): bmp = bitmaps.BaseBitmap() bmp.InitWith(os.path.join(bmpPath, "res", "icon.tif")) plugins.RegisterObjectPlugin(id=id, str=name, g=objData, description=desc, icon=bmp, info=flags) if __name__ == "__main__": path, fn = os.path.split(__file__) RegisterObjectData(PLUGIN_ID_GENERATOR, "Spline from Edge", path, SplFromEdge, "osplinefromedge", c4d.OBJECT_GENERATOR | c4d.OBJECT_INPUT)
-
Hi,
I think your biggest problem is your coding style. And I do not mean that in an offensive way, but simply am pointing out that you seem to have cornered yourself into a state of code that contains bugs where you (and also I) have no clue where they exactly stem from. You might want to read PEP 8 and the excellent Google Python Style Guide.
With that out of the way, the obvious thing first: When you use the dirty state of the children of your generator to determine if it has to be rebuild or not, either touching one of the children or having a child which determines its dirty state by its parent state (i.e. the generator), will result in a loop.
Some points:
- Returning
None
inGetVirtualObjects
indicates a memory error to Cinema 4D. You rarely want to do this. Return ac4d.Onull
-object instead. Also neitherdoc
norobjRet
can beNone
in your GVO, so checking for that is somewhat moot. - You clone the children of your generator with
GetAndCheckHierarchyClone
and one of the first things you do inExecModelingCommand
is to clone that clone again without ever using or comparing against that first clone again. This obviously won't break anything, but it is a good indicator for being lost in ones own code.
I do not have any more specific advice, since I stopped reading at some point as it got more and more convoluted (sorry, no offense intended). Instead I have some general advice:
- You probably need some abstraction or specifically split up
ExecModelingCommand
into its steps, so that you can describe and test each part. - Follow a style and write some documentation (for yourself as one tends to overlook or forget things when they are not written down).
- Since your intention seems to be to return a spline, you should overwrite
GetContour
and notGVO
. - While your approach is valid, the heavy use of
SendModellingCommand
is probably not the best for the performance of your plugin and also a source of uncertainty for debugging. Creating a consecutive line segment array (i.e a linear spline) from an edge selection is not that hard. By doing so, you would also remove the need of any clean up clutter.
I hope this helps.
Cheers,
zipit - Returning
-
Hi @matniedoba, thanks for reaching out us.
With regard to your issue, given that it's not our primary task to debug plugins, to understand what's getting wrong I need you to provide:
- test scenes of different complexity;
- the smallest code able to reproduce the issue - rather than the whole plugin - considering that it's a matter of object dirty check.
I also second @zipit recommendations because improving code design via better function abstraction and documentation will lead to an increased clarity, reliability and, in the end, comprehension.
I'll try to find time in the course of the week to have a look at your issue given the items requested above.Best, R
-
Thank you @zipit for your comprehensive response, especially with the link to the coding guide. I guess it might be better to create the spline manually from the points which are connected by the edges without the usage of
SendModellingCommand
. It was good for prototyping functionality but as you mentioned, it is hard to debug. I appreciate you took the time (and I know it's a lot of convoluted code) to read through it. @r_gigante -> the work is currently on my side so you don't need to bother. I think I have enough information to do a second iteration and rewriting this from scratch.