Apparent memory leaks after multiple mesh rotations

Hi,

I’m running a test I have written in pythonscript, which involve a high number of translations and rotations of a mesh. After 2 minutes, I run out of memory, and I think the problem is located at line 1141 of file object.py:

id = scriptcontext.doc.Objects.Transform(object_id, xform, not copy)

I show you here a simple script which reproduces the problem:

import Rhino
import rhinoscriptsyntax as rs
import math

rs.Command("_-Open " + “teapot.obj” + " _Enter ")
Meshes = rs.ObjectsByLayer(“Default”, 32)

x_axis = Rhino.Geometry.Vector3f(1,0,0)
center_point = Rhino.Geometry.Vector3f(0,0,0)

for i in xrange(0,100000):
rs.RotateObject( Meshes[0], center_point, 1.1, axis=x_axis, copy=False)

I think my post may be related to this one: https://discourse.mcneel.com/t/scripting-lots-of-rotations-and-memory-consumption/7933

Any idea of what this may be?

Thank you,

Alexandre

(I’m running Windows 7 Pro 64 bits, 16GB RAM, Rhino Version 5 SR13 32-bit)

teapot.3dm (126.0 KB)

TestCrash.py (358 Bytes)

One thing that I find most curious is that if you pass copy=False to rs.RotateObject, the scriptcontext.doc.Objects.Transform function specifically uses not copy which is then True! This would seems to trigger a copy contradicting what the function documentation says! As the default value of the copy parameter in RotateObject is False, it is a most surprising line of code indeed?

In doing the above, you just added 10000 objects to Rhino’s Undo stack. This is the incorrect way of doing an animation. Rather than continue to replace the existing object (in the object table) with different one, the correct way of animating an object is to drawing it transforming dynamically using a display conduit.

– Dale

Hello Dale,

The code in Alexandre example is just a small subset of an optimization loop where we iteratively modify the orientation of the mesh (translation, rotation, etc) in order to best fit the projection of this mesh to some 2D image we have. I don’t know what a Display Conduit is but I’m skeptical it would apply to this scenario. We do not want to animate anything; in fact, we even suspend the display while running to accelerate the execution.

Could you suggest what should be done, beside already asking not to copy the object, to prevent adding to the Undo/Redo stack? Could we work at a lower level directly on the Mesh object instead of working through the scriptcontext.doc.Objects level?

Bruno

Yes, but to do so you will need to stop using rhinoscriptsyntax and use RhinoCommon directly.

RhinoScript is a document-centric scripting tool. Python’s rhinoscriptsyntax functions are designed to emulate RhinoScript.

– Dale

Hi @bmartin and @Alexandre_Filiatraul,

apart from the fact that in the code example above existing document objects are rotated, the real optimisation cannot be very efficient because of the angle increment which seems to be a constant angle of 1.1 degree. In short this would increment 10000 times by an angle of 1.1 degree eg:

1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8....

at some point this would overshoot the maximum angle of 360 degree which seems counterproductive to me. I am not sure how the best fit of the projection is calculated in your code but it would be more efficient if you eg. start the rotation in 10 degree steps, find the increment with the least amount of deviation using your best fit method eg:

0, 10, 20, 30, 40, 50, 60 ... 350

Then if the least amount of deviation is found, eg. at an increment of 60 degree, increment one step back to 50 degree and reduce the angle increment, to 1 degree. Now try these rotatons:

50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60 ... 70

now repeat the best fit routine at every angle and save the result so you can tell at which of the angles the deviation is the smallest. Then repeat above method by reducing the increment by another tenth of the last angle used, in this case, 0.1 degree. So if the lowest deviation was found eg. at 58 degree, the next rotation angles to test would be:

57.0, 57.1, 57.2, 57.3, 57.4, 57.5 ... 59.0

Using this iterative approach, you can define exactly up to which accuracy your fitting routine would run and get away with fewer samples.

c.

Hello @clement,

Sorry, I think we simplified our example so much it lost all relevance to it’s original goal. The point was that

  1. The code we presented is just the smallest code we thought of that could reproduce the bug we wanted to demonstrate.
  2. The real code would use the Downhill Simplex to modify the X,Y translation as well as the pitch and yaw angles. Thus, we have a 4-dimensional optimization problem where the quantity to minimize is the square of the distance to some ground truth model and where of course, the position and angle variations are not constants.

I think @dale really got the root cause of the problem and we will code this part of our algorithm with the lower level API provided by RhinoCommon in order to prevent busting the Undo/Redo stack.

Anyway, thank you both very much for your help!

Bruno

1 Like

Hi @bmartin,

You could clear the undo stack in each loop by using

rs.Command("_-ClearUndo ") 

Doing it in RhinoCommon would probably make the script run faster although it may take longer to code.

Very good idea! Do you know if this clears ALL the Undo/Redo stack or just the last Undo? Depending on the use case, it might be better to just remove the last Undo caused by a transformation instead of clearing ALL the Undos from the opening of the project…
Is there a way to remove just the last Undo from the stack (without applying it)?

Bruno

I think so.

No idea. there might be something here though:

It would be nice if there was a way to suspend the undo stack for a script like this.

@bmartin,

i would not try to mess with the undo stack. Not sure if this is helpful, below is an example based on the simplified one above doing 10000 rotations in 0.2 seconds. The object in the document is replaced once, after the final rotation.

import Rhino
import scriptcontext
import rhinoscriptsyntax as rs
from math import radians

def DoSomething():
    
    id = rs.GetObject("Select Mesh to rotate")
    if not id: return
    
    mesh = rs.coercemesh(id, True)
    axis = Rhino.Geometry.Vector3d(1, 0, 0)
    center = Rhino.Geometry.Point3d(0, 0, 0)

    for i in xrange(0, 10000):
        angle = radians(0.1)
        xform = Rhino.Geometry.Transform.Rotation(angle, axis, center)
        mesh.Transform(xform)
        # do your fitting calculation here
    
    scriptcontext.doc.Objects.Replace(id, mesh)
    scriptcontext.doc.Views.Redraw()
    
DoSomething()

It seems to put a single item in the undo stack, so it does not fill up.

c.