How do I split several asymmetric bodies into hundreds of cubes?

Anonymous

How do I split several asymmetric bodies into hundreds of cubes?

Anonymous
Not applicable

Hallo,
I'm new to Fusion and this forum so please tell me if I need to change anything in this question.

The problem I need to solve is how to split an eye model, which is constructed with the Fusion UI and consists of several asymmetric parts, into tiny cubes, like a rubics cube (at a later date probably into radial symmetric parts as well).

At the moment my code generates several construction planes in all three dimensions (see picture one).
My problem is that when I use these construction planes as splitTools to split all the bodies that are part of my eye model there will be planes that don't intersect with some of the bodies. This generates of course an error: "No intersection between target(s) and split tools." which stops the code.

So I had some ideas how to circumvent this problem by:
-  checking beforehand if the construction plane and body have an intersection, but I only found such a function for planes and curves.
- somehow catching the error thrown by the split function and telling the program to just continue, no idea how to do that
-  or create one combined splitting tool, because that combined splitting tool will have an intersection which each body. But it seems like it's not possible to combine construction planes.


 

Maybe my code is helpful(its also attached):
import adsk.core, adsk.fusion, adsk.cam, traceback

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

    product = app.activeProduct
    design = adsk.fusion.Design.cast(product)
    # Get the root component of the active design
    rootComp = design.rootComponent

    #Creating construction planes where I want them
    #Create a new Sketch on the xy-Plane
    sketches = rootComp.sketches
    # Make all xyz-Planes known
    xyPlane = rootComp.xYConstructionPlane
    sketch = sketches.add(xyPlane)
    xzPlane = rootComp.xZConstructionPlane
    sketch = sketches.add(xyPlane)
    yzPlane = rootComp.yZConstructionPlane
    sketch = sketches.add(xyPlane)
#-------------------------------
#-------------------------------
#creates new Planes in each of the three directions, in mm steps, since
#the diameter of the eye is about 23mm the loop goes from -15mm to +15mm
#this should be enough to include the 1mm thick eye applicator
#will be used to cut our eye-mdel into several cubes, to be used as
#sensitive scorers
    for xyz_planes in [xyPlane, xzPlane, yzPlane]:
         for i in range(-15, 16): #cm, will be converted into mm later
             # Get construction planes
             planes = rootComp.constructionPlanes
             # Create construction plane input
             planeInput = planes.createInput()
            # Add construction plane by offset
            offsetValue = adsk.core.ValueInput.createByReal(i/100.)#in mm now
            planeInput.setByOffset(xyz_planes, offsetValue)
            planeOne = planes.add(planeInput)
    #Creating Planes end
            # the following is just how it would work in an ideal world
            # simply take each body and split it with every possible
            # splitting tool and ignore any errors that occur
            allBodies = rootComp.bRepBodies
                for body in allBodies:
                #Splitting
                       #Create SplitBodyFeatureInput
                       splitBodyFeats = rootComp.features.splitBodyFeatures
                       splitBodyInput = splitBodyFeats.createInput(body, planeOne, True) #1: body to be split, 2: SplittingTool,       #3:SplittingToolExtended
                       # Create split body feature
                       split_ Object = splitBodyFeats.add(splitBodyInput) 
             #Splitting end
except:
    if ui:
        ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

I would be really thankful for any help regarding this problem. As well as general improvement ideas for my code since I'm a beginner. And is there a way to copy code here and not lose all the indentations?

 

Greetings,

Saskia

0 Likes
Reply
Accepted solutions (1)
1,727 Views
8 Replies
Replies (8)

kandennti
Mentor
Mentor

Hi @Anonymous .

 

See here for how to maintain indentation.

https://forums.autodesk.com/t5/fusion-360-api-and-scripts/how-to-efficiently-process-10000-individual-coordinates-to-build/m-p/9350288#M9434 

 

I would like to work on this theme because it seems to be a fun theme,

but I am busy with my work and I cannot secure time....

0 Likes

Anonymous
Not applicable

Hey @kandennti,

 

Thank you for the link and replying 😄

Wish you all the best!

0 Likes

Anonymous
Not applicable

Since I can't edit my original post anymore, here's the code so far with better indentation (in my opinion):

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

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



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

        # Get the root component of the active design
        rootComp = design.rootComponent


    #Creating Linear Dimension (Plane) where I want them
        #Create a new Sketch on the xy-Plane
        sketches = rootComp.sketches

        # Make all xyz-Planes known
        xyPlane = rootComp.xYConstructionPlane
        sketch = sketches.add(xyPlane)

        xzPlane = rootComp.xZConstructionPlane
        sketch = sketches.add(xyPlane)

        yzPlane = rootComp.yZConstructionPlane
        sketch = sketches.add(xyPlane)

#-------------------------------
#-------------------------------

        #creates new Planes in each of the three directions,in mm steps, since
        #the diameter of the eye is about 23mm the loop goes from -15mm to    +15mm
        #this should be enough to include the 1mm thick eye applicator
        #will be used to cut our eye-model into several cubes, to be used as
        #sensitive scorers
        for xyz_planes in [xyPlane, xzPlane, yzPlane]:
            for i in range(-15, 16):    #cm, will be converted into mm later
                # Get construction planes
                planes = rootComp.constructionPlanes

                # Create construction plane input
                planeInput = planes.createInput()

                # Add construction plane by offset
                offsetValue = adsk.core.ValueInput.createByReal(i/100.)#in mm now
                planeInput.setByOffset(xyz_planes, offsetValue)
                planeOne = planes.add(planeInput)
            #Creating Planes end

                # the following is just how it would work in an ideal world
                # simply take each body and split it with every possible
                # splitting tool and ignore any errors that occur
                allBodies = rootComp.bRepBodies
                for body in allBodies:
                #Splitting
                    #Create SplitBodyFeatureInput
                    splitBodyFeats = rootComp.features.splitBodyFeatures
                    splitBodyInput = splitBodyFeats.createInput(body, planeOne, True) #1: body to be split, 2: SplittingTool, 3:SplittingToolExtended

                    # Create split body feature
                    split_Object = splitBodyFeats.add(splitBodyInput)
                #Splitting end

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

 

 

0 Likes

BrianEkins
Mentor
Mentor
Accepted solution

I didn't try running your code but the simplest solution will be to try to do the split but continue if it should fail.  I added a try except around the code the does the split.

 

                for body in allBodies:
                #Splitting
                    try:
                        #Create SplitBodyFeatureInput
                        splitBodyFeats = rootComp.features.splitBodyFeatures
                        splitBodyInput = splitBodyFeats.createInput(body, planeOne, True) #1: body to be split, 2: SplittingTool, 3:SplittingToolExtended

                        # Create split body feature
                        split_Object = splitBodyFeats.add(splitBodyInput)
                    except:
                        pass

 

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
0 Likes

Anonymous
Not applicable

Hey @BrianEkins,

thank you very much, the pass command does exactly what I needed. I've never heard of it before, so I didn't even know to look for it.

My code works now but I will continue to look for other solutions since the running time needs to be improved 🙂

 

Thank you again,

Saskia

0 Likes

kandennti
Mentor
Mentor

Instead of split, I used TemporaryBRepManager's booleanOperation Method.

 

#Fusion360API Python script

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

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)

# cube size - unit Cm
_min = [-1.5, -1.5, -1.5]
_max = [1.5, 1.5, 1.5]
_step = 0.1

def run(context):
    try:
        global _app, _ui
        _app = adsk.core.Application.get()
        _ui = _app.userInterface

        des  :adsk.fusion.Design = _app.activeProduct
        parametricMode = adsk.fusion.DesignTypes.ParametricDesignType
        if des.designType != parametricMode:
            des.designType = parametricMode

        root :adsk.fusion.Component = des.rootComponent

        # select target body
        msg :str = 'Please select the body to split'
        selFiltter :str = 'SolidBodies'
        sel :adsk.core.Selection = selectEnt(msg ,selFiltter)
        if not sel: return
        targetBody :adsk.fusion.BRepBody = sel.entity

        # time
        startTime = time.time()

        # cube
        pnts = initCenterPoints()
        print('Points : {}s'.format(time.time() - startTime))

        cubes = initCubes(pnts)
        print('cubes : {}s'.format(time.time() - startTime))

        # Intersection
        inters = initIntersection(targetBody, cubes)
        print('Intersections : {}s'.format(time.time() - startTime))

        # baseFeature
        baseFeatures = root.features.baseFeatures
        baseFeature = baseFeatures.add()

        baseFeature.startEdit()
        for body in inters:
            root.bRepBodies.add(body, baseFeature)
        baseFeature.finishEdit()
        targetBody.isLightBulbOn = False
        print('baseFeature : {}s'.format(time.time() - startTime))

        # finish
        _ui.messageBox('Done\n{}s'.format(time.time() - startTime))

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

def initIntersection(
    targetBody :adsk.fusion.BRepBody,
    cubes :list) -> list:

    tmpMgr = adsk.fusion.TemporaryBRepManager.get()
    tgt = tmpMgr.copy(targetBody)

    typeIntersect = adsk.fusion.BooleanTypes.IntersectionBooleanType
    inters = []
    for cube in cubes:
        tmpMgr.booleanOperation(cube, tgt, typeIntersect)
        if cube.volume > 0:
            inters.append(cube)
    
    return inters

def initCubes(
    centerPnts :list) -> list:

    tmpMgr = adsk.fusion.TemporaryBRepManager.get()
    box3D = adsk.core.OrientedBoundingBox3D
    vec3D = adsk.core.Vector3D

    vecX = vec3D.create(1.0, 0.0, 0.0)
    vecY = vec3D.create(0.0, 1.0, 0.0)

    cubes = []
    global _step
    for pnt in centerPnts:
        cube = box3D.create(pnt, vecX, vecY, _step, _step, _step)
        cubes.append(tmpMgr.createBox(cube))

    return cubes

def initCenterPoints() -> list:

    pnt3D = adsk.core.Point3D

    pnts = []
    global _min, _max, _step
    half = _step / 2
    for z in dRange(_min[2], _max[2], _step):
        for y in dRange(_min[1], _max[1], _step):
            for x in dRange(_min[0], _max[0], _step):
                pnts.append(pnt3D.create(x + half, y + half, z + half))
    
    return pnts

def dRange(start, end, step):

    from decimal import Decimal

    start = Decimal(str(start))
    end = Decimal(str(end))
    step = Decimal(str(step))

    for i in range(int((end - start) / step)):
        yield float(start + i * step)

def selectEnt(
        msg :str, 
        filtterStr :str) -> adsk.core.Selection :

    global _ui
    try:
        sel = _ui.selectEntity(msg, filtterStr)
        return sel
    except:
        return None
0 Likes

Anonymous
Not applicable

Hello @kandennti,

First of all thank you very much for your code. It worked amazingly fast and taught me quite a lot!

I was able to adept it and the idea to use temporary bRepBodies to cut objects into radial-symmetric parts as well, which was a huge step for me.

 

But in the very last step of my program something doesn't work like I expected it to. I'm not sure whether to put it on the forum as a new question but since it belongs to this problem I decided to put it here.

 

I would like to make sure that the newly created objects, be that cube-shaped objects or some other form have the same material as the original object.  My idea on how to realise this worked but only in one of two different cases.

 

I broke the problem down to a minimal working example that only needs an object in the UI with a material different from Steel (which seems to be the norm). There are comments and ui messages on the lines that don't work as I expected, maybe you can tell me what I did wrong.

 

Thank you again,

Saskia

 

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


_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)


def run(context):
    try:
        global _app, _ui, _construct_doc
        _app = adsk.core.Application.get()
        _ui = _app.userInterface

        des  :adsk.fusion.Design = _app.activeProduct
        parametricMode = adsk.fusion.DesignTypes.ParametricDesignType
        if des.designType != parametricMode:
            des.designType = parametricMode

        root :adsk.fusion.Component = des.rootComponent

        msg :str = 'Please select the body to split'
        selFiltter :str = 'SolidBodies'
        sel :adsk.core.Selection = selectEnt(msg ,selFiltter)
        if not sel: return
        targetBody :adsk.fusion.BRepBody = sel.entity

        tmpMgr = adsk.fusion.TemporaryBRepManager.get()

        targetmaterial = targetBody.material

        # case 1
        # only one of the cases should be run at the same time
        # correct material is assigned -> works, except for the position of the
        # sphere, which is (0,0,0) after baseFeature.finishEdit() in tempbRep2bRep
        sphere_list = []
        sphere_list.append(tmpMgr.createSphere(adsk.core.Point3D.create(4,0,0), 0.5))
        sphere_list.append(tmpMgr.createSphere(adsk.core.Point3D.create(1,0,0), 0.3))
        tempbRep2bRep(root, sphere_list, targetmaterial)
        _ui.messageBox('spherelist' + str(sphere_list).format(traceback.format_exc()))

        # case 2
        # does not assign the correct material
        tempBodies = []
        for body in root.bRepBodies:
            tempBodies.append(tmpMgr.copy(body))
        tempbRep2bRep(root, tempBodies, targetmaterial)
        _ui.messageBox('tempBodies' + str(tempBodies).format(traceback.format_exc()))

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


def tempbRep2bRep(root, body_list :list, body_material):
    # turn the temporary bRepBodies in body_list into real bRepbodies in the UI
    # with assigned material
    baseFeatures = root.features.baseFeatures
    baseFeature = baseFeatures.add()
    baseFeature.startEdit()
    for body in body_list:
        real_body = root.bRepBodies.add(body, baseFeature)
        real_body.material = body_material
    _ui.messageBox('In both cases the position and material is right as of now'.format(traceback.format_exc()))
    baseFeature.finishEdit()
    _ui.messageBox('Now the position of the spheres in case 1 is changed to (0,0,0) and the material in case 2 is changed as well to steel instead of the targetmaterial'.format(traceback.format_exc()))
    return

def selectEnt(
        msg :str,
        filtterStr :str) -> adsk.core.Selection :

    global _ui
    try:
        sel = _ui.selectEntity(msg, filtterStr)
        return sel
    except:
        return None

 

0 Likes

kandennti
Mentor
Mentor

I found two measures.

 

One worked correctly when run in direct mode.

 

The other is to create a new occurrence and add it to component.material
This was possible by setting the material.

import adsk.core, adsk.fusion, traceback

_app = adsk.core.Application.cast(None)
_ui = adsk.core.UserInterface.cast(None)

def run(context):
    try:
        global _app, _ui, _construct_doc
        _app = adsk.core.Application.get()
        _ui = _app.userInterface

        des  :adsk.fusion.Design = _app.activeProduct
        parametricMode = adsk.fusion.DesignTypes.ParametricDesignType
        if des.designType != parametricMode:
            des.designType = parametricMode

        root :adsk.fusion.Component = des.rootComponent

        msg :str = 'Please select the body to split'
        selFiltter :str = 'SolidBodies'
        sel :adsk.core.Selection = selectEnt(msg ,selFiltter)
        if not sel: return
        targetBody :adsk.fusion.BRepBody = sel.entity

        targetmaterial = targetBody.material

        # create Occurrence & setting component material
        mat :adsk.core.Matrix3D = adsk.core.Matrix3D.create()
        occ :adsk.fusion.Occurrence = root.occurrences.addNewComponent(mat)
        occ.component.material = targetmaterial


        tmpMgr = adsk.fusion.TemporaryBRepManager.get()

        # case 1
        sphere_list = []
        sphere_list.append(tmpMgr.createSphere(adsk.core.Point3D.create(4,0,0), 0.5))
        sphere_list.append(tmpMgr.createSphere(adsk.core.Point3D.create(1,0,0), 0.3))
        tempbRep2bRep(occ, sphere_list, targetmaterial)
        _ui.messageBox('spherelist' + str(sphere_list).format(traceback.format_exc()))

        # case 2
        tempBodies = [] 
        cloneBody = adsk.fusion.BRepBody.cast(None)
        for body in root.bRepBodies:
            cloneBody = tmpMgr.copy(body)
            tempBodies.append(cloneBody)
        tempbRep2bRep(occ, tempBodies, targetmaterial)
        _ui.messageBox('tempBodies' + str(tempBodies).format(traceback.format_exc()))

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

def tempbRep2bRep(
    occ :adsk.fusion.Occurrence,
    body_list :list, 
    body_material :adsk.core.Material):

    comp :adsk.fusion.Component = occ.component
    baseFeatures = comp.features.baseFeatures
    baseFeature = baseFeatures.add()

    baseFeature.startEdit()
    [comp.bRepBodies.add(body, baseFeature) for body in body_list]
    baseFeature.finishEdit()

    _ui.messageBox('In both cases the position and material is right as of now'.format(traceback.format_exc()))
    _ui.messageBox('Now the position of the spheres in case 1 is changed to (0,0,0) and the material in case 2 is changed as well to steel instead of the targetmaterial'.format(traceback.format_exc()))
    return

def selectEnt(
        msg :str,
        filtterStr :str) -> adsk.core.Selection :

    global _ui
    try:
        sel = _ui.selectEntity(msg, filtterStr)
        return sel
    except:
        return None


There is one problem here.The component.material property is a read / write property.
https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-149E4821-BE47-4D18-A351-A8339FC3E0BB 
This time I used this.

 

If there are multiple Bodies in the component and multiple materials are used, changing the component.material will result in all the materials being set.
(This time we are creating a new occurrence for that reason)
I don't think this is the desired result.

Shouldn't component.material be read only?

0 Likes