top of page

How it works ?

1. Add a cube in the scene.

Set a size of the cube as 40*22*4,8 units. Add the cube to physics simulation as Rigid Body.

As a object has  form of a cube you may set 'collision_shape' as 'box'.

import bpy

import random


#add_cube
bpy.ops.mesh.primitive_cube_add(radius=1, location = (0.0, 0.0, 0.0))
bpy.ops.transform.resize(value = (20.0, 11.0, 2.4 ))
bpy.ops.object.transform_apply(location=False, rotation =False, scale = True)


#add a cube as Rigid Bodies
bpy.ops.rigidbody.objects_add(type = 'PASSIVE')
bpy.context.object.rigid_body.collision_shape = 'BOX'

Peek 2018-08-29 15-05.gif

2. Add a first sphere to the scene. 

Set a radius of the sphere as 1.75 units. Add the sphere to physics simulation as Rigid Body. At time of simulation first sphere of linker must stay static.  Set  'Rigid Body' as 'Passive' for the sphere and for all static objects. As the object has a form of sphere you may set 'collision_shape' as 'sphere'.

#add_a_first_sphere_of_linker___passive
bpy.ops.mesh.primitive_uv_sphere_add(segments = 4,ring_count = 4, size = 1.75, location=(21.75, 0.0, 0.0))
bpy.ops.rigidbody.objects_add(type = 'PASSIVE')
bpy.context.object.rigid_body.collision_shape = 'SPHERE'

Peek 2018-08-29 15-11.gif

3. Add another spheres of the linker.

Firstly, set a number of spheres ( residues of the linker)

len_of_linker = 6

Secondly, get locations ( by x axis ) for new spheres.

#add_second_sphere
loc_sphere_x = 21.75
rep = -1 # for _Limit_Distance
len_lin = len_of_linker-1
for index in range(len_lin):
    rep += 1 # for _Limit_Distance
    loc_sphere_x += 3.5

 

Add new spheres. At time of simulation all spheres of linker (without a first sphere) must move free.  Set  'Rigid Body' as 'Active' for those spheres and for all moving objects.

    bpy.ops.mesh.primitive_uv_sphere_add (segments = 4, ring_count = 4, size = 1.75, location=(loc_sphere_x, 0.0, 0.0))
    bpy.ops.rigidbody.objects_add(type = 'ACTIVE')
    bpy.context.object.rigid_body.collision_shape = 'SPHERE'

In the code I use a two sort of connection between spheres. A first  is 'Rigid Body Constraint',a type is 'Point'. The 'Rigid Body Constraint' create a physical connection between objects. In result spheres moves as rope. But a physical connections may be stretch. For fix this, I use also a 'object constraint' 'Limit Distance'. 'Limit Distance' set a maximum distance between two objects.

 

Right now we add only the constraint 'Limit Distance' to all spheres of the linker. A physical connection between those objects will add later.

    bpy.ops.object.constraint_add(type='LIMIT_DISTANCE')
    if rep == 0:  
       bpy.context.object.constraints['Limit Distance'].target = bpy.data.objects['Sphere']
    if rep > 0 :
       str_rep = str(rep)
       bpy.context.object.constraints['Limit Distance'].target = bpy.data.objects['Sphere.'+str_rep.rjust(3,'0')]

   

Peek 2018-08-30 19-45.gif

4. Add a big sphere to the 3D scene.

#add_a _big  sphere
diameter = 30
radius = diameter/2
location_x = loc_sphere_x+ 1.75 + radius
bpy.ops.mesh.primitive_uv_sphere_add(segments=32, ring_count=16, size= radius,   location=(location_x, 0.0, 0.0))

bpy.ops.rigidbody.objects_add(type = 'ACTIVE')bpy.context.object.rigid_body.collision_shape = 'SPHERE'

Create a 'group' for the big sphere. 'Groups' or 'collections' helps to work with many objects at once.
bpy.ops.group.create(name = 'globula')
bpy.ops.object.group_link(group = 'globula')

Add the constraint 'Limit Distance' for the big sphere.

bpy.ops.object.constraint_add(type='LIMIT_DISTANCE')
if rep == 0:  
       bpy.context.object.constraints['Limit Distance'].target = bpy.data.objects['Sphere']
if rep > 0 :
       str_rep = str(rep)
       bpy.context.object.constraints['Limit Distance'].target = bpy.data.objects['Sphere.'+str_rep.rjust(3,'0')]

Screenshot from 2018-08-30 20-01-51.png

5. Create physical connections between all spheres.

 

Firstly, get  locations ( by x axis ) for all 'constraints'.

#rigid_body_constraints
loc_empty_x = 18.25 #location
repeat_empty = -1 #var for name
for index in range(len_of_linker):
    loc_empty_x += 3.5 #location

    repeat_empty += 1  #name

    rep_e_2 = repeat_empty+1 #name

    str_ob1 = str(repeat_empty)  #name

    str_ob2 = str(rep_e_2)  #name

Secondly, set a type of  'Rigid Bodies Constraints' as 'Point'

    bpy.ops.object.empty_add(type = 'PLAIN_AXES', location = (loc_empty_x, 0.0, 0.0))
    bpy.ops.rigidbody.constraint_add(type='POINT')

 'Rigid Body Constraint' create a physical connection between two objects. Write names of  those objects in 'constraint' settings.

    if repeat_empty ==0:
      bpy.context.object.rigid_body_constraint.object1 = bpy.data.objects['Sphere']
      bpy.context.object.rigid_body_constraint.object2 = bpy.data.objects['Sphere.001']
    if repeat_empty > 0:
      bpy.context.object.rigid_body_constraint.object1 = bpy.data.objects['Sphere.'+str_ob1.rjust(3,'0')]
      bpy.context.object.rigid_body_constraint.object2 = bpy.data.objects['Sphere.'+str_ob2.rjust(3,'0')]

Click a 'play' button to start a simulation. After a simulation return on a first frame.

Peek 2018-08-31 13-33.gif

6. Copy all objects and all constraints on another layers.

There is one difficulty here.

All layers in a oligomer rotate around a center by Z axis  2 angle degrees.

Objects in a 3D scene rotate around some point. It may be rotation around itself, around a center of 3d view or around something another.

Before run a code snap a 3D cursor to the center and set 'Pivot Point' as '3D Cursor'.

3D View --> Object --> Snap --> Cursor to Center         and

Pivot Point -->3D Cursor

Peek 2018-08-31 20-50.gif

After this set a rotation around the 3D cursor by a code.

#This code set a rotation around the 3D cursor.

for window in bpy.context.window_manager.windows:
    screen = window.screen
    
    for area in screen.areas:
        if area.type == 'VIEW_3D':
            override = {'window': window, 'screen': screen, 'area': area}
            bpy.ops.screen.screen_full_area(override)
            break

original_type = bpy.context.area.type
bpy.context.area.type = 'VIEW_3D'




################____CURSOR____####################################
def get_override(area_type, region_type):
    for area in bpy.context.screen.areas:
        if area.type == area_type:             
            for region in area.regions:                 
                if region.type == region_type:                    
                    override = {'area': area, 'region': region}
                    return override
    #error message if the area or region wasn't found
    raise RuntimeError("Wasn't able to find", region_type," in area ", area_type,
                        "\n Make sure it's open while executing script.")


#we need to override the context of our operator    
override = get_override( 'VIEW_3D', 'WINDOW' )

Go to a first frame ( to a start of simulation).

bpy.data.scenes["Scene"].frame_current = 1

Select all objects, copy, move and rotate.

layer = 20
# 2 layer
if layer >1:
    bpy.ops.object.select_all(action = 'SELECT')

    bpy.ops.object.duplicate_move() 

    bpy.ops.transform.translate (value = (0, 0, 4.8), constraint_axis = (False, False, True)) 

    bpy.ops.transform.rotate( override, value = 0.034 , axis=(0,0,1))

Attention. The angle value measures in radians.

Repeat it.

# 3 or more layers
layer_up = layer - 2
if layer>2:
     for index in range(layer_up):

        bpy.ops.object.duplicate_move()

        bpy.ops.transform.translate (value = (0, 0, 4.8), constraint_axis = (False, False, True))

        bpy.ops.transform.rotate( override, value = 0.034 , axis=(0,0,1))

Screenshot from 2018-08-31 17-36-18.png

Run a simulation again. As you may see a simulation time is  some seconds.
This is a real-time simulation.

Peek 2018-08-31 13-44.gif

7. Next, run a simulation by a script.

Import random # for a value of gravity

Go to a first frame ( to a start of simulation).

bpy.data.scenes["Scene"].frame_current = 1

 

A simulation in 3D scene has a gravity. A gravity influences on a motion of all objects. Set a value of gravity force as random.

#random
bpy.context.scene.gravity[2] = random.uniform(-0.5, 0.5)
bpy.context.scene.gravity[1] = random.uniform(-0.5, 0.5)
bpy.context.scene.gravity[0] = random.uniform(-0.5, 0.5)

Calculate and save a motion of all objects by 'bake physics'.

#bake physics

bpy.data.scenes['Scene'].rigidbody_world.time_scale = 2.000  #objects moves quickler
bpy.ops.ptcache.free_bake_all()  #clean all a past move
bpy.ops.ptcache.bake_all(bake=True)  #save a new move

8. Check intersection between big spheres.

 

Join all big spheres.
str_linker = str(len_of_linker)
bpy.ops.object.select_all(action = 'DESELECT' ) # deselect all objects)
bpy.ops.object.select_same_group(group="globula" ) # select only big spheres by group
bpy.context.scene.objects.active = bpy.data.objects['Sphere.'+str_linker.rjust(3,'0')] # change a one big sphere as active object
bpy.ops.rigidbody.bake_to_keyframes(frame_start=1, frame_end = 251, step = 50) # save a motion of big spheres as anim.actions

bpy.ops.screen.frame_jump(end = True)  #go to a last frame (to a end of simulation)
bpy.ops.object.join() # join all big spheres
bpy.ops.object.editmode_toggle()

Check a geometry of the object.

Open a right panel (Hotkey is 'N') ---> Mesh Analysis ---> Intersect

This tool highlight all intersection by red color.

Peek 2018-08-31 17-58.gif

For example, a the object on the image has intersections.

Screenshot from 2018-08-31 18-21-46.png

Or you may do it by the script.

import mathutils

import bmesh

obj = bpy.context.active_object
me = obj.data
bm = bmesh.from_edit_mesh(me)   
tree = mathutils.bvhtree.BVHTree.FromBMesh(bm, epsilon=0.00001)
overlap = tree.overlap(tree)
faces_error = {i for i_pair in overlap for i in i_pair}

#print a result
if len(faces_error)>0:
        print(' intersection ')
else:  
         print(' successfull ')

 Download a text  tutorial.py

 Download my blend file  tutorial.blend  

blender.png

 I hope you will find this information useful. Please feel free to contact me with any questions.

bottom of page