2
$\begingroup$

I have a problem with my blender program, that causes blender to crash and my notebook is heating up quite fast. I think it is, because i want to create too many objects. My goal is to produce a filter media out of (glass) fibres. In order to do that i want to create cylinders, that represent those fibers. They appear in a random spot in a specific area and are then cut, so i have a cube in the dimension 1mmx1mmx0.6mm . For my example i creat fibers in the scale of 1*e12. I can run the program and in small scales it is working, but clicking on the button for the realistic scale causes my problem. So, my question is: Is my CPU too slow 4gb RAM? is blender incapable of creating so many objects and if so, is there a way i can run this program with a regular python compiler, that creates my file? I would like to have that as an .stl-file. Thank you in advance for your help and suggestions, I really appreciate it. Sorry for my bad grammar and for my sloppy commenting in the code which follows:

bl_info = {
"name" : "filter generator",
"author" : "T W",
"version" : (1,0),
"blender" : (2, 78, 0),
"location" : "View3D > Tools",
"description" : "Generate a filter made of cylindrical fibers",
"category" : "Object"}
import bpy
import math
from bpy.props import *
from mathutils import Vector
from random import random
from random import randint
from random import uniform

name = "Fiber "

rectHeight   = 0.6 # thickness of the filter media
rectRadius   = 1.0 # 1mm² for the surface are

bpy.types.Scene.meanflowporeSize = FloatProperty(
name="Mean Flow Poresize", default=20.0, min=1.0, max=100.0, description="the MFP for the filter media") #MFP for the filter media
bpy.types.Scene.fiberSize = FloatProperty(
name="Fiber Radius", default=5.0, min=5.0, max=24.0, description="radius of the fibers") #Property for the fiber radius

#The Panel GridGen AddOn
class gridGen(bpy.types.Panel):
    bl_space_type="VIEW_3D"
    bl_region_type="TOOLS"
    bl_label="FilterGenerator AddOn"
    bl_category="FilterGen"

    def draw(self, context):
        layout = self.layout

        col = layout.column(align=True)

        col.label("Filter Generator:")
        col.prop(bpy.context.scene, "meanflowporeSize")
        col.prop(bpy.context.scene, "fiberSize")
        col.operator("filter.button", icon="TEXTURE")
        col.operator("remove.button", icon="CANCEL")

mfp = bpy.context.scene.meanflowporeSize
fradius = bpy.context.scene.fiberSize

mfp = mfp*1e-6
fradius = fradius*1e-6

totalPieces = ((rectRadius/(mfp + fradius))*2)*(rectHeight/fradius)
totalCounter = 1

#Progress in percent        
def percentagePrint(printName, total):
    global totalCounter

    percent = (totalCounter/total)*100

    print("     "+printName+" "+str(round(percent))+"% complete")

    totalCounter+=1

#Button to remove every object in the scene
class removeButton(bpy.types.Operator): 
    bl_label="Remove everything!"
    bl_idname="remove.button"

    def execute(self, context):

        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.select_by_type(type='MESH')
        bpy.ops.object.delete(use_global=False)

        for item in bpy.data.meshes:                #Loop for deleting     objects in the scene
            bpy.data.meshes.remove(item)

        print("Removed!")

        return{"FINISHED"}

#Button to create the filter
class filterButton(bpy.types.Operator):
    bl_label="Filter generation"
    bl_idname="filter.button"

    def execute(self, context):

        global totalCounter
        totalCounter = 1

        print("---------------------------------------------------------------------------------")
        print("Creating Filter")

        #create random holes
        def check_circle_bounbox( circC, circR, rectC, rectR ):
            ''' Make sure this circle does not protrude outside the rectangle's bounding box '''
            maxX = rectC.x + rectR
            minX = rectC.x - rectR
            maxY = rectC.y + rectR
            minY = rectC.y - rectR

            withinX = ( circC.x + circR <= maxX ) and ( circC.x - circR >= minX )
            withinY = ( circC.y + circR <= maxY ) and ( circC.y - circR >= minY )

            return withinX and withinY

        def check_overlap( circles, circC, circR ):
            ''' Make sure the distance between the current circle's center and all
                other circle centers is greater than or equal to the circle's perimeter (2r)
        '''
            return len([ True for c in circles if ( c - circC ).length >= circR * 2 ]) == len( circles )

        circleRadius = fradius
        circleCount  = ((rectRadius/(mfp + fradius))**2)*(rectHeight/fradius) #the number of fibers is calculated with the MFP, the Fiber radius and the fiber thickness
        rectCenter   = Vector((0, 0, 0))

        circles = []

        maxIterations = 10000000000# Set max number of loop iterations to prevent infinite loop

        nameCounter = 1

        i = 0 # Current loop iteration
        while len( circles ) < circleCount and i < maxIterations:
            x = rectCenter.x + 2 * rectRadius * random() - rectRadius # x-coordinate
            y = rectCenter.y + 2 * rectRadius * random() - rectRadius # y-coordinate
            z = abs(rectCenter.z + 2 * rectRadius * random() - rectRadius) #z-cootdinate always positive via abs()
            circC = Vector((x, y, z ))
            #no overlapping radius is checked
            if check_circle_bounbox( circC, circleRadius, rectCenter, rectRadius ) and check_overlap( circles, circC, circleRadius ):
                circles.append( circC )

            i += 1

        for c in circles:
            rot_x = uniform(0, 2*math.pi) #rotation around x-axis
            rot_y = uniform(0, 2*math.pi) #rotation around y-axis
            rot_z = uniform(0, 2*math.pi) #rotation around z-axis

            #creating a high resolution cylinder
            bpy.ops.mesh.primitive_cylinder_add(vertices=256, radius = circleRadius, depth= 20, location = c, rotation=(rot_x, rot_y, rot_z))

            currentPiece = bpy.context.active_object
            currentPiece.name = name+str(nameCounter)
            nameCounter += 1

            percentagePrint("Grid", totalPieces)


        #combine all objects into one
        bpy.ops.object.select_all(action='SELECT')
        bpy.ops.object.join()   

        #deselecting and sctivating the object
        bpy.ops.object.select_all(action='DESELECT')
        bpy.context.scene.objects.active = bpy.data.objects[currentPiece.name]

        #cutting out the square that will be the final object in 6 bisections

        #biscet 1 on x=.5
        bpy.ops.object.mode_set(mode = 'EDIT')
        bpy.ops.mesh.bisect(plane_co=(.5, 0, 0), plane_no=(1, 0, 0), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

        #bisect 2 on x=-.5
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.bisect(plane_co=(-.5, 0, 0), plane_no=(-1, 0, 0), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

        #bisect 3 on y=.5
        bpy.ops.mesh.select_all(action='SELECT') 
        bpy.ops.mesh.bisect(plane_co=(0, .5, 0), plane_no=(0, 1, 0), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

        #bisect 4 on y=-.5
        bpy.ops.mesh.select_all(action='SELECT')  
        bpy.ops.mesh.bisect(plane_co=(0, -.5, 0), plane_no=(0, -1, 0), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

        #bisect 5 on z=.6
        bpy.ops.mesh.select_all(action='SELECT') 
        bpy.ops.mesh.bisect(plane_co=(0, 0, .6+circleRadius/2), plane_no=(0, 0, 1), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

        #bisect 6 on z=-circleRadius/2
        bpy.ops.mesh.select_all(action='SELECT')  
        bpy.ops.mesh.bisect(plane_co=(0, 0, circleRadius/2*-1), plane_no=(0, 0, -1), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

        #going back to object mode
        bpy.ops.object.mode_set(mode = 'OBJECT')

        print("Filter created")

        return{"FINISHED"}    


#REGISTER
def register():
    bpy.utils.register_module(__name__)

def unregister():
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()
$\endgroup$
  • $\begingroup$ Have a look at Python performance with Blender operators. It appears as tho this could be done with one bmesh. $\endgroup$ – batFINGER Jul 26 '17 at 10:17
  • $\begingroup$ And Creating many primitive objects quickly with Python $\endgroup$ – batFINGER Jul 26 '17 at 10:32
  • $\begingroup$ I am now using the linked copies and a executable script, that runs multiple times and started timing my progress: for 10 x 1000 Fibers it takes 85.7 seconds and with the bisection 520 seconds. Creating 10000 Fibers at once with bisection right away it takes 798.4 seconds. I am thinking about multithreading right now. Do you have any advice or experience on that? $\endgroup$ – thrbn Aug 1 '17 at 12:23
3
$\begingroup$

I can't comment too much on the code, but if I understand correctly each fiber has 256 sides? that's a lot, not sure what's the end goal here, but unless you are doing close up shots that's unnecessary.

Also if your objects geometry is the same, which I believe it is you should make linked copies, this reuses mesh data, so you just create one fiber and then duplicate its transform, this will also reduce the files size dramatically.

Finally 4GB of RAM is in the very low end, it shouldn't be too much of a problem in this instance, I think, but consider upgrading if you are doing heavy work in blender.

I'm not really a programmer so I can't help you much on wether you can or cannot run this externally, I'd wager you could, but someone else is going to have to help you in that front.

| improve this answer | |
$\endgroup$
  • 1
    $\begingroup$ I'm on the same train, I'm no coder, but I suspect running the script will probably never be a problem per-se, the problem will always be the resulting 3D data. No matter how high end your computer might be most applications dislike very high number of independent objects, and exporting it to STL will probably make it worse. Representing each independent fiber sounds extremely inefficient, highly unpractical, and totally unnecessary. Particle systems would sound like a more adequate tool, but even so. $\endgroup$ – Duarte Farrajota Ramos Jul 18 '17 at 19:13
  • $\begingroup$ Thanks for your advice so far, this sounds good and plausible. I will reduce the sides and run it again. I'm kind of new to coding too, so i will have to looh in the making of linked copies or if you could share some advice here too? The filter media i want to produce will be exported, so I can use it in solidworx for instance and finally I am going to simulate airflow through it with openFOAM. Thanks in advance $\endgroup$ – thrbn Jul 19 '17 at 9:14
  • $\begingroup$ Reducing the sides of the cylinders hasn't worked so far $\endgroup$ – thrbn Jul 21 '17 at 9:29
1
$\begingroup$

Currently I am using this script and let it run mutliple times with a System that has 16GB RAM and it's not fully occupied:

import bpy
import math

half_pi = math.pi/2
quat_pi = math.pi/4

name = "Fiber "

#create random holes
from mathutils import Vector
from random import random
from random import randint
from random import uniform

def check_circle_bounbox( circC, circR, rectC, rectR ):
    ''' Make sure this circle does not protrude outside the rectangle's bounding box '''
    maxX = rectC.x + rectR
    minX = rectC.x - rectR
    maxY = rectC.y + rectR
    minY = rectC.y - rectR

    withinX = ( circC.x + circR <= maxX ) and ( circC.x - circR >= minX )
    withinY = ( circC.y + circR <= maxY ) and ( circC.y - circR >= minY )

    return withinX and withinY

def check_overlap( circles, circC, circR ):
    ''' Make sure the distance between the current circle's center and all
    other circle centers is greater than or equal to the circle's perimeter (2r)
'''
    return len(
    [ True for c in circles if ( c - circC ).length >= circR * 2 ]
) == len( circles )

circleRadius = .000005#.000001
circleCount  = 100000#1679656006
rectCenter   = Vector((0, 0, 0))
rectRadius   = 5

circles = []

maxIterations = 1000 # Set max number of loop iterations to prevent infinite loop

nameCounter = 1

#z = 0 # All cylinders lie on Z = 0
i = 0 # Current loop iteration
while len( circles ) < circleCount and i < maxIterations:
    x = rectCenter.x + 2 * rectRadius * random() - rectRadius #- math.sqrt(2*(rectRadius^2)) #* 2 for limiting the recctangular area
    y = rectCenter.y + 2 * rectRadius * random() - rectRadius #- math.sqrt(2*(rectRadius^2)) #* 2 for limiting the recctangular area
    z = abs(rectCenter.z + 2 * rectRadius * random() - rectRadius) #z-position always positive
    circC = Vector((x, y, z ))

    if check_circle_bounbox( circC, circleRadius, rectCenter, rectRadius ) \
   and check_overlap( circles, circC, circleRadius ):
           circles.append( circC )

    i += 1

for c in circles:
    rot_x = uniform(0, 2*math.pi)
    rot_y = uniform(0, 2*math.pi)
    rot_z = uniform(0, 2*math.pi)
    #bpy.ops.mesh.primitive_circle_add( radius = circleRadius, location = c, fill_type = 'NGON'  )
    bpy.ops.mesh.primitive_cylinder_add(vertices=16, radius = circleRadius, depth= 20, location = c, rotation=(rot_x, rot_y, rot_z))

    currentPiece = bpy.context.active_object
    currentPiece.name = name+str(nameCounter)
    nameCounter += 1

bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.join()   #make one object out of the grid

#deselecting and sctivating the object
bpy.ops.object.select_all(action='DESELECT')
bpy.context.scene.objects.active = bpy.data.objects[currentPiece.name]

#cutting out the square that will be the final object in 4 bisection

#biscet 1 on x=2.5
bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.mesh.bisect(plane_co=(2.5, 0, 0), plane_no=(1, 0, 0), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

#bisect 2 on x=-2,5
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.bisect(plane_co=(-2.5, 0, 0), plane_no=(-1, 0, 0), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

#bisect 3 on y=2.5
bpy.ops.mesh.select_all(action='SELECT') 
bpy.ops.mesh.bisect(plane_co=(0, 2.5, 0), plane_no=(0, 1, 0), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

#bisect 4 on y=-2.5
bpy.ops.mesh.select_all(action='SELECT')  
bpy.ops.mesh.bisect(plane_co=(0, -2.5, 0), plane_no=(0, -1, 0), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

#bisect 5 on z=5
bpy.ops.mesh.select_all(action='SELECT') 
bpy.ops.mesh.bisect(plane_co=(0, 0, 5+circleRadius/2), plane_no=(0, 0, 1), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

#bisect 6 on z=-circleRadius/2
bpy.ops.mesh.select_all(action='SELECT')  
bpy.ops.mesh.bisect(plane_co=(0, 0, circleRadius/2*-1), plane_no=(0, 0, -1), use_fill=True, clear_inner=False, clear_outer=True, threshold=0.0001, xstart=0, xend=0, ystart=0, yend=0, cursor=1002)

#going back to object mode
bpy.ops.object.mode_set(mode = 'OBJECT')

I run thi with another script:

import bpy

filepath = bpy.path.abspath("/path/to/Random_Fiber_Generator.py")
u = 0
for l in range(10):
    exec(compile(open(filepath).read(), filepath, 'exec'))
    u += 1

print("This program ran "+str(u)+" times") 

runnig it ten times is causing Problems already. Can somebody please Show me a more efficient way. I still don't know how to implemt linked copies to duplicate the cylinder, but with my random coordinates.

I am very grateful for your help, thanks

| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.