3
$\begingroup$

I'm trying to edit code that imports atom positions (xyz) and an arrow on each one with the spin (uvw). The atom positions can easily be imported with an add-on called XYZ importer, but it uses instances/dupliverts to duplicate a representative object to create the overall structure. However, each duplicated object needs to have a custom rotation, specified by the spin vector. I'm struggling in how to modify dupliverts to achieve this and I can't find any simple alternatives. It should look like the image I attached, with an arrow centered on each atom.

The image should look like the one I attached with an arrow on each atom.

$\endgroup$
2
$\begingroup$

batFINGER's answer is good and will do what you need. When I was playing with their code, however, I noticed that the custom vertex normal information was lost when switching between Edit and Object modes (Blender 2.82). Perhaps I'm doing something wrong, but this effect seems to be known (see e.g. comments of this answer by Mutant Bob)

Overwritten vertex information

Vertex normal information overwritten

I modified this code to instead create quads at each position, rotate them in a preferred direction (using this approach), then parent them to the duplication object.

Code adapted from batFINGER's answer

import bpy
import bmesh 

from itertools import *
from mathutils import *
from math import *

bpy.ops.object.select_all(action = 'SELECT')
bpy.ops.object.delete(use_global = False)

quad = [Vector(i) for i in [[0.05, 0.05, 0], [0.05, -0.05, 0], [-0.05, -0.05, 0], [-0.05, 0.05, 0]]]
up = Vector([0, 0, 1])

C = bpy.context
D = bpy.data

m = D.meshes.new('')
o = D.objects.new('', m)
C.scene.collection.objects.link(o)

dipole_angs = []
verts, edges, faces = [], [], []

for a, v in enumerate(product(*(((-1, 1),) * 3)), start = 0):
    cent = Vector(v)
    norm = Vector(noise.random() * a for a in v)
    q = up.rotation_difference(norm).to_matrix()
    verts.extend(list(map(lambda x: cent + (q @ x), quad)))
    faces.append(list(map(lambda x: x + (4 * a), list(range(0, 4)))))

m.from_pydata(verts, edges, faces)
m.update()

bpy.ops.mesh.primitive_cylinder_add()
cylinder = C.object
cylinder.scale *= 0.2
cylinder.parent = o

o.instance_type = 'FACES'
o.use_instance_vertices_rotation = True

Result

Normal information automatically calculated

$\endgroup$
3
$\begingroup$

Assign and align to the vertex normals

Similarly to https://blender.stackexchange.com/a/163958/15543 using the cone as a duplivert instance

enter image description here

The vertex normals of suzanne are determined by the faces. If we construct a vert only mesh, then set v.normal = (x, y, z) for each vertex

Here is a script that adds a pointmesh cube, assigns the normals to the coordinate normalized and adds an empty as duplivert object. Used the cone display type, as it points in Y to which the normals point.

Assign your "spin" direction to as the normal of the vert. Make sure align to vert normal is checked. Remember that instances will be aligned Y axis to vert normal.

import bpy
import bmesh
from itertools import product
from mathutils import Vector
from bpy_extras.io_utils import axis_conversion

M = axis_conversion
context = bpy.context
coll = context.collection

bm = bmesh.new()
for v in  product(*(((-1, 1),) * 3)):
    vert = bm.verts.new(v)
    #norm = Vector(random() * a for a in v)
    vert.normal =  vert.co.normalized()


cube = bpy.data.objects.new(
        "Cube",
        bpy.data.meshes.new("Foo")
        )
bm.to_mesh(cube.data)
coll.objects.link(cube)


bpy.ops.object.empty_add(
                radius=0.5,
                type='CONE',
                location = (0, 0, 0),
                )
arrow = context.object

arrow.parent = cube

cube.instance_type = 'VERTS'
cube.use_instance_vertices_rotation = True
$\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.