4
$\begingroup$

I am interested in constructing figures of atoms using Blender from an external XYZ file containing atomic positions and species.

I have succesfully read the XYZ and found all atoms and their connections using MatLAB and exported everything as a JSON file, which the Blender Python easily reads. However, as soon as the structures become larger the processing time increases (since I presently use bpy.ops that redraws the scene for each added atom).

I want to change my approach to use a more low-level form of python.

JSON file (here, Benzene)

{
    "camera": {
        "position": [0.0000, 0.0000, 32.4760],
        "rotation": [0.0000, 0.0000, 0.0000]
    },
    "atoms": [
        { "element": "C", "location": [ -0.7150, -1.2384, 0.0000 ] },
        { "element": "C", "location": [ 0.7150, -1.2384, 0.0000 ] },
        { "element": "C", "location": [ -1.4300, 0.0000, 0.0000 ] },
        { "element": "C", "location": [ -0.7150, 1.2384, 0.0000 ] },
        { "element": "C", "location": [ 0.7150, 1.2384, 0.0000 ] },
        { "element": "C", "location": [ 1.4300, 0.0000, 0.0000 ] },
        { "element": "H", "location": [ -1.2150, -2.1044, 0.0000 ] },
        { "element": "H", "location": [ 1.2150, -2.1044, 0.0000 ] },
        { "element": "H", "location": [ -2.4300, 0.0000, 0.0000 ] },
        { "element": "H", "location": [ -1.2150, 2.1044, 0.0000 ] },
        { "element": "H", "location": [ 1.2150, 2.1044, 0.0000 ] },
        { "element": "H", "location": [ 2.4300, 0.0000, 0.0000 ] }
    ],
    "bonds": [
        { "atoms": [ 0, 1 ], "order": 1 },
        { "atoms": [ 0, 2 ], "order": 1 },
        { "atoms": [ 0, 6 ], "order": 1 },
        { "atoms": [ 1, 5 ], "order": 1 },
        { "atoms": [ 1, 7 ], "order": 1 },
        { "atoms": [ 2, 3 ], "order": 1 },
        { "atoms": [ 2, 8 ], "order": 1 },
        { "atoms": [ 3, 4 ], "order": 1 },
        { "atoms": [ 3, 9 ], "order": 1 },
        { "atoms": [ 4, 5 ], "order": 1 },
        { "atoms": [ 4, 10 ], "order": 1 },
        { "atoms": [ 5, 11 ], "order": 1 }
    ]
}

I read in the JSON and contain all elements in molecule["atoms"]. I then loop over every atom and create a copy of a sphere

    # Draw atoms
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.mesh.primitive_uv_sphere_add()
bpy.ops.object.shade_smooth()
sphere = bpy.context.object
for atom in molecule["atoms"]:
    ob = sphere.copy()
    ob.select = False
    ob.location = atom["location"]
    ob.data = sphere.data.copy()
    bpy.context.scene.objects.link(ob)
bpy.ops.object.delete()
bpy.context.scene.update()

But the sphere size (and rotation for when I get to the bonds) confuse me. After having searched for days and days, I still haven't found a simple command to scale, translate and rotate without using bpy.ops.

Am I missing something?

$\endgroup$
3

2 Answers 2

3
$\begingroup$

If the Atomic blender addons (which include an XYZ importer) don't help, I would suggest using dupliverts instead of creating multiple objects when creating a large number of objects. My earlier tests have shown that using dupliverts can be quicker than creating individual objects.

This example that works with your sample data creates a new mesh that only has vertices that are placed at the location of each atom, this is then set as the parent of a sphere and dupliverts is enabled, this places a copy of the sphere at each vertex location. Based on a previous answer I then created a curve and added a new spline for each bond. That makes only three objects to display the data.

Using your sample data, multiplied by 1000 (with a small z offset for each) it takes 3.3 secs on my corei7 machine.

import bpy, bmesh, bpy_extras

mesh = bpy.data.meshes.new("Atoms")
bm = bmesh.new()

for atom in molecule['atoms']:
    bm.verts.new(atom['location'])

bm.to_mesh(mesh)
mesh.update()
bpy_extras.object_utils.object_data_add(bpy.context, mesh)
points = bpy.context.object

bpy.ops.object.select_all(action='DESELECT')
bpy.ops.mesh.primitive_uv_sphere_add()
bpy.ops.object.shade_smooth()
sphere = bpy.context.object
sphere.scale = (0.2,0.2,0.2)

sphere.parent = points
points.dupli_type = "VERTS"

bpy.ops.curve.primitive_bezier_curve_add()
curve = bpy.context.object
curve.data.dimensions = '3D'
curve.data.fill_mode = 'FULL'
curve.data.bevel_depth = 0.05
curve.data.bevel_resolution = 4

c_splines = curve.data.splines
c_splines.remove(c_splines[0])

for bond in molecule['bonds']:
    spline = c_splines.new('BEZIER')
    spline.bezier_points[0].co = molecule['atoms'][bond['atoms'][0]]['location']
    spline.bezier_points[0].handle_left_type = 'VECTOR'
    spline.bezier_points[0].handle_right_type = 'VECTOR'

    spline.bezier_points.add(1)
    spline.bezier_points[1].co = molecule['atoms'][bond['atoms'][1]]['location']
    spline.bezier_points[1].handle_left_type = 'VECTOR'
    spline.bezier_points[1].handle_right_type = 'VECTOR'
$\endgroup$
0
$\begingroup$

I may be not reading this correctly is this what you're after for scaling?

for atom in atoms:
ob = sphere.copy()
ob.select = False
ob.location = atom["location"]
if (atom["element"] == "C"):
    ob.scale = [.3,.3,.3]
if (atom["element"] == "H"):
    ob.scale = [.1,.1,.1]
ob.data = sphere.data.copy()
bpy.context.scene.objects.link(ob)

As for the rotations, I think you need to start parenting the objects according to their bonds, such that when a parent rotates all the children do as well.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .