occurence.transform is super slow

occurence.transform is super slow

JeromeBriot
Mentor Mentor
910 Views
4 Replies
Message 1 of 5

occurence.transform is super slow

JeromeBriot
Mentor
Mentor

Hello,

I try to improve the speed of my "Import IDF" add-in.

 

Basically, the code creates a board and places electrical components on it. So I heavily use the occurence.transform property. And this transformation takes ages even in its simpliest form.

 

import adsk.core, adsk.fusion, adsk.cam, traceback
import time

def run(context):

    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface

        design = adsk.fusion.Design.cast(app.activeProduct)

        design.designType = adsk.fusion.DesignTypes.DirectDesignType

        rootComponent = design.rootComponent

        trans = adsk.core.Matrix3D.create()

        occ = rootComponent.occurrences.addNewComponent(trans)

        firstComp = occ.component
        firstComp.name = '0'

        sketches = firstComp.sketches
        sketchesPlane = firstComp.xYConstructionPlane

        sketch1 = sketches.add(sketchesPlane)
        sketch1.name = 'Square'

        lines = sketch1.sketchCurves.sketchLines;

        lines.addTwoPointRectangle(adsk.core.Point3D.create(0, 0, 0), adsk.core.Point3D.create(2, 2, 0))

        extrudes = firstComp.features.extrudeFeatures

        extrudeInput1 = extrudes.createInput(sketch1.profiles.item(0), adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
        distance = adsk.core.ValueInput.createByReal(2.0)
        extrudeInput1.setSymmetricExtent(distance, True)
        extrudes.add(extrudeInput1)

        trans = adsk.core.Matrix3D.create()

        start_time = time.time()

        for i in range(0, 200):

            occ = rootComponent.occurrences.addExistingComponent(firstComp, trans)

#            occ.transform = trans

        elapsed_time = time.time() - start_time

        app.activeViewport.refresh()
        adsk.doEvents()

        ui.messageBox('{:.2f} s'.format(elapsed_time))

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

On my machine, it takes 5 seconds to create 200 occurences. Now if I uncomment the line with the transform action, the same code takes 18 seconds to finish. It's approximatly x4 just for a matrix product!

 

Note that I can't use the Component.transformOccurrences method because as I said before, in my real design, there are many different components (up to 1500).

 

For example, my add-in takes 26 seconds to create 277 components. It takes 74 seconds to create the same components with transformation. And the elapsed time is not linear. It's getting worst and worst.

 

Do you have any idea to accelerate this code?

 

Thanks.

0 Likes
911 Views
4 Replies
Replies (4)
Message 2 of 5

JesusFreke
Advocate
Advocate

If you don't need a new fusion component or linked component for every item, you can use the TemporaryBRepManager to perform the transformation much more quickly.

 

e.g. The following ran in about .5s for me

 

def run(context):

ui = None
try:
app = adsk.core.Application.get()
ui = app.userInterface

design = adsk.fusion.Design.cast(app.activeProduct)

design.designType = adsk.fusion.DesignTypes.DirectDesignType

rootComponent = design.rootComponent

trans = adsk.core.Matrix3D.create()

occ = rootComponent.occurrences.addNewComponent(trans)

firstComp = occ.component
firstComp.name = '0'

sketches = firstComp.sketches
sketchesPlane = firstComp.xYConstructionPlane

sketch1 = sketches.add(sketchesPlane)
sketch1.name = 'Square'

lines = sketch1.sketchCurves.sketchLines

lines.addTwoPointRectangle(adsk.core.Point3D.create(0, 0, 0), adsk.core.Point3D.create(2, 2, 0))

extrudes = firstComp.features.extrudeFeatures

extrudeInput1 = extrudes.createInput(sketch1.profiles.item(0), adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
distance = adsk.core.ValueInput.createByReal(2.0)
extrudeInput1.setSymmetricExtent(distance, True)
extrudes.add(extrudeInput1)

start_time = time.time()

brep = adsk.fusion.TemporaryBRepManager.get()

new_occurrence = rootComponent.occurrences.addNewComponent(trans)

vector = Vector3D.create(1, 0, 0)
trans = adsk.core.Matrix3D.create()

for i in range(0, 200):
for body in firstComp.bRepBodies:
new_translation = trans.translation
new_translation.add(vector)
trans.translation = new_translation

body_copy = brep.copy(body)
brep.transform(body_copy, trans)
new_occurrence.component.bRepBodies.add(body_copy)

elapsed_time = time.time() - start_time

app.activeViewport.refresh()
adsk.doEvents()

print('{:.2f} s'.format(elapsed_time))

except:
print(traceback.format_exc())

 

You can still create a new component for every item, and add the transformed body to the new component. I tested that out too, and it was still only about 1.5s for me.

 

Although, note that both of these solutions are creating completely separate bodies+components for every item, while in your original code, you were making new linked occurrences of the same component. So if you do actually need linked occurrences, neither of these will work.

Message 3 of 5

JeromeBriot
Mentor
Mentor

Hello @JesusFreke ,

 

I moved from occurrences.addNewComponent to occurrences.addExistingComponent for efficiency reason when I first wrote this add-in. That was just before the TemporaryBRepManager was added to the API.

 

Anyway, I did some tests and it does speed up the process.

 

For one design with 277 electronic component to be placed:

  • occurrences.addExistingComponent only => 13s
  • occurrences.addExistingComponent + transform => 57s
  • TemporaryBRepManager + transform => 45s

For another design with 447 electronic component to be placed:

  • occurrences.addExistingComponent only => 38s
  • occurrences.addExistingComponent + transform => 327s
  • TemporaryBRepManager + transform => 81s

The difference between "occurrences.addExistingComponent only" and "occurrences.addExistingComponent + transform" is that I just comment/uncomment the next line in my code:

#                occ.transform = trans

So my code is now faster but it's way far from other software I tried. I did tests with FreeCAD 0.18 and DesignSpark Mechanical 4.0 and the import of more than 1000 electonic components takes less than 30 seconds. It can take hours with Fusion 360!

 

Again, there is a efficiency issue with the transform process. It's fast for less than 100 components but becomes slower and slower when this number increase.

 

 

 

0 Likes
Message 4 of 5

JesusFreke
Advocate
Advocate

81s still seems way longer than I would expect. My sample code was able to do all 200 transforms in less than a second. Granted, they were just translations, but it's all just matrix multiplication, so I don't think more "complex" transformations would necessarily be more expensive.

 

Can you summarize the code you were using for that test?

0 Likes
Message 5 of 5

JeromeBriot
Mentor
Mentor

Here is another example that creates 1000 components:

 

import adsk.core, adsk.fusion, adsk.cam, traceback
import random
import time
import math
import platform
import os

def run(context):
    ui = None
    try:

        app = adsk.core.Application.get()
        ui = app.userInterface

        x = [[0,1,1,0,0], [1,2,2,0,0,1,1], [0,2,2,0], [0,1,1,0,0]]
        y = [[0,0,1,1,0], [0,0,2,2,1,1,0], [0,0,1,0], [0,0,1,1,0]]
        h = [0.2, 0.5, 0.1, 0.2]

        numIteration = 1000

        trans = adsk.core.Matrix3D.create()
        transformTrans = adsk.core.Matrix3D.create()
        transformRot = adsk.core.Matrix3D.create()
        transformFlip = adsk.core.Matrix3D.create()

        vector = adsk.core.Vector3D.create(0.0, 0.0, 0.0)
        vectorY = adsk.core.Vector3D.create(0.0, 1.0, 0.0)
        vectorZ = adsk.core.Vector3D.create(0.0, 0.0, 1.0)

        origin = adsk.core.Point3D.create(0.0, 0.0, 0.0)

        design = adsk.fusion.Design.cast(app.activeProduct)
        design.designType = adsk.fusion.DesignTypes.DirectDesignType

        rootComponent = design.rootComponent

        elapsedTime = [0]*numIteration

        for i in range(0, numIteration):

            n = random.randint(0, 3)

            startTime = time.time()

            trans.setToIdentity()

            vector.x = 100*random.random()
            vector.y = 100*random.random()
            vector.z = 0.0

            if random.random() > 0.5:
                transformFlip.setToRotation(math.pi, vectorY, origin)
                trans.transformBy(transformFlip)

            transformRot.setToRotation(random.random()*math.pi, vectorZ, origin)
            trans.transformBy(transformRot)

            transformTrans.translation = vector
            trans.transformBy(transformTrans)

            occ = rootComponent.occurrences.addNewComponent(trans)

            comp = occ.component

            sketches = comp.sketches
            sketchesPlane = comp.xYConstructionPlane

            sketch1 = sketches.add(sketchesPlane)
            sketch1.name = 'Square'

            lines = sketch1.sketchCurves.sketchLines;

            for j in range(0, len(x[n])-1):
                lines.addByTwoPoints(adsk.core.Point3D.create(x[n][j], y[n][j], 0), adsk.core.Point3D.create(x[n][j+1], y[n][j+1], 0))

            extrudes = comp.features.extrudeFeatures

            extrudeInput1 = extrudes.createInput(sketch1.profiles.item(0), adsk.fusion.FeatureOperations.NewBodyFeatureOperation)
            distance = adsk.core.ValueInput.createByReal(h[n])
            extrudeInput1.setSymmetricExtent(distance, True)
            extrudes.add(extrudeInput1)

            elapsedTime[i] = time.time() - startTime

        app.activeViewport.fit()
        app.activeViewport.refresh()
        adsk.doEvents()

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

    exportElapsedTimes(elapsedTime)


def exportElapsedTimes(elapsedTime):

    if platform.system() == 'Windows':
        pathName = os.path.join(os.environ["HOMEPATH"], 'Desktop')
    else:
        pathName = '~/Desktop'

    with open(os.path.join(pathName, 'fusion-360-time.txt'), 'w') as file:
        for et in elapsedTime:
            file.write('{:.3f}\n'.format(et))

 

Here is the plot of the elapsed time at each iteration:

fusion-360-slow-add-components.PNG

So the creation of the last components took 10 times more than for the first ones (0.02s vs. 0.2s)

 

The code takes 90s to finish. I would expect 1000*0.02 = 20s

 

I attached the result file and the code to plot the data:

import platform
import os

import matplotlib.pyplot as plt

if platform.system() == 'Windows':
    pathName = os.path.join(os.environ["HOMEPATH"], 'Desktop')
else:
    pathName = '~/Desktop'

with open(os.path.join(pathName, 'fusion-360-time.txt'), 'r') as file:
    lines = file.readlines()

lines = [x.replace('\n', '') for x in lines]

t = [float(x) for x in lines]

plt.plot(t)
plt.xlabel('Iterations')
plt.ylabel("Time (s)")

Thank you.

 

0 Likes