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
$\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$

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.