2

I am newbie of Blender and I never used the python interface. I would like to write a little program to make a generic die.

The generic die is build with a clever idea: to flatten N equally distributed circles in a sphere. Here is an example of a d9.

Throwing the chopped sphere will land on one of the flattened parts and you should be able to read the result on top.

The shape is a bit weird, but thanks to this question and answers I could put together the code you can see in the bottom (The configurations.h5 file comes from the known solutions of the Thompson problem).

If you execute the code blender3 generates the base shape for a d9. This one: enter image description here

The shape is a great start, but I still miss the final steps. I'm doing the final steps in GUI, how to do it in Python automatically?

Here how to do in GUI:

First I need to close the holes. Go under Modelling, select the mesh, go in Edit mode, and then you can add the missing faces: press 3af (3 to select faces mode, a for select all, f to add faces).

The mesh become like this: the base die

After covering the hole I need to put the digits. The digits have to appear in the opposite side compared to the flatten circles.

A flatten circle is always in the bottom (coord 0,0,-1), so let's start from that coordinate for it is easier.

Under Modelling, add Text, set up coordinates (0, 0, 1), put Alignment center both in Vertical and Horizontal, fix font and size. text placed in the opposite of 0,0,-1

Convert to Mesh, and finally extrude and move inside the die. the digit 9 is added

To dig the text from the die you can finally use the Difference Boolean tool: remove the extruded text from the die. dig the nine

Now, I need to repeat the text for every value. While doable for a d9, it would be extremely demanding for a larger dice, at very least to copy correctly coordinates and angles (in the example are 0,0,1 and 0,0,0. But other faces would have other numbers.)

So, would be possible to put those steps in the python code? How?

import bpy
import bmesh
from mathutils import Vector
import h5py

n = 9 #n is the number of faces rat = .86 # how far along the radius to bisect

u_segments = 128 # UV sphere settings v_segments = 128

h5 = h5py.File('/full/path/to/configurations.h5','r') hset = h5[str(n)] coords = hset.get('coordinates') ocoords = [] for item in coords: ocoords.append(item[0]) ocoords.append(item[1]) ocoords.append(item[2]) h5 = None hset = None coords = None

it = iter(ocoords) points = list(map(Vector, zip(it, it, it))) ocoords = None

context = bpy.context scene = context.scene

make one point south pole.

R = points[0].rotation_difference(Vector((0, 0, -1))).to_matrix() points = [R @ p for p in points]

bm = bmesh.new()

bmesh.ops.create_uvsphere(bm, radius=1, u_segments=u_segments, v_segments=v_segments)

for p in points: ret = bmesh.ops.bisect_plane(bm, geom=bm.faces[:]+bm.edges[:]+bm.verts[:], plane_co= rat * p, plane_no=-p, clear_outer=False, clear_inner=True)

me = bpy.data.meshes.new("dice") bm.to_mesh(me) ob = bpy.data.objects.new("dice", me) scene.collection.objects.link(ob) context.view_layer.objects.active = ob ob.select_set(True) ob.location = scene.cursor.location

  • I'm the sucker that flagged this question as too localized, though it can be improved, if you rephrase it along the line of "I'm doing this [explain clearly what] in GUI, how to do it in Python automatically?". It would be nice if you added some images of the process for clarity. – Markus von Broady Jan 21 '22 at 23:30
  • 1
    Edited as suggested. –  Jan 22 '22 at 11:17

2 Answers2

1

I do the first part for you. If you run it you will see all holes are filled.

import bpy
import bmesh
from mathutils import Vector
import random
from math import pi, asin, atan2, cos, sin, radians

parmaaters

n = 9 # number of points on sphere rat = .86 # how far along the radius to bisect u_segments = 32 # UV sphere settings v_segments = 32 thickness = 0.2 # solidify thickness TOL = 1e-7

points = [Vector((0, 0, 1))] for i in range(n - 1): theta = random.random() * radians(360) phi = 2 * asin(random.random() * 2 - 1) points.append(Vector((cos(theta) * cos(phi), sin(theta) * cos(phi), sin(phi))))

while True: # Determine the total force acting on each point. forces = [] for i in range(len(points)): p = points[i] f = Vector() ftotal = 0 for j in range(len(points)): if j == i: continue q = points[j] # Find the distance vector, and its length. dv = p - q dl = dv.length dl3 = dl * dl * dl fv = dv / dl3 # Add to the total force on the point p. f = f + fv # Stick this in the forces array. forces.append(f) # Add to the running sum of the total forces/distances. ftotal = ftotal + f.length

fscale = 1 if ftotal <= 0.25 else 0.25 / ftotal

# Move each point, and normalise. While we do this, also track
# the distance each point ends up moving.
dist = 0
for i in range(len(points)):
    p = points[i]
    f = forces[i]
    p2 = (p + fscale * f).normalized()

    dv = p - p2
    dist = dist + dv.length
    points[i] = p2
# Done. Check for convergence and finish.
if dist < TOL: # TOL
    break

context = bpy.context scene = context.scene

make one point north pole.

R = points[0].rotation_difference(Vector((0, 0, 1))).to_matrix() points = [R @ p for p in points]

bm = bmesh.new() #bmesh.ops.create_icosphere(bm, diameter=1, subdivisions=5 ) bmesh.ops.create_uvsphere(bm, diameter=1, u_segments=u_segments, v_segments=v_segments)

for p in points: ret = bmesh.ops.bisect_plane(bm, geom=bm.faces[:]+bm.edges[:]+bm.verts[:], plane_co= rat * p, plane_no=-p, clear_outer=False, clear_inner=True) ''' # add visible cutting planes (using bisect) R = Vector(p).to_track_quat('Z', 'Y').to_matrix().to_4x4() R.translation = rat * p bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=1, matrix=R) '''
me = bpy.data.meshes.new("dice") bm.to_mesh(me) ob = bpy.data.objects.new("dice", me)
scene.collection.objects.link(ob) context.view_layer.objects.active = ob ob.select_set(True) ob.location = scene.cursor.location

bpy.ops.object.mode_set(mode='EDIT') bpy.context.tool_settings.mesh_select_mode = (False, True, False) bpy.ops.mesh.select_non_manifold() bpy.ops.mesh.fill_holes(sides=0)

X Y
  • 5,234
  • 1
  • 6
  • 20
0

Since I already voted to close for being too localized, but you fixed it and I can't vote for "needs for focus", I'll answer instead...

Select edges on the boundary

outer_edges = [e for e in bm.edges if e.is_boundary]

Create and store faces

big_faces = bmesh.ops.holes_fill(bm, edges=outer_edges)["faces"]

Get normals of new faces and reverse them

opposite_normals = [f.normal * -1 for f in big_faces]

Get rotations to align normals

rotations = [n.to_track_quat().to_euler() for n in opposite_normals]

Setup Text Object

bpy.ops.object.text_add()
txt = context.object
txt.data.align_x = txt.data.align_y = 'CENTER'
mod = txt.modifiers.new(name='', type='SOLIDIFY')
mod.thickness = -.2
scene.collection.objects.link(txt)

Deselect everything

for o in context.selected_objects[:]:
    o.select_set(False)

Duplicate base text, change number, convert to mesh, add boolean

for i, rot in enumerate(rotations, start=1):
    txt.select_set(True)
    context.view_layer.objects.active = txt
    txt.data.body = str(i)
    bpy.ops.object.duplicate()
    bpy.ops.object.convert(target='MESH')
    txt_me = context.object
    for v in txt_me.data.vertices:
        v.co.z += 1.01  # move away from the center by radius
    txt_me.rotation_euler = rot
    boolean = ob.modifiers.new(name=f"{txt_me.name}", type='BOOLEAN')
    boolean.object = txt_me
    txt_me.select_set(False)
    txt_me.hide_set(True)

Full code

import bpy
import bmesh
from mathutils import Vector
import random
from math import pi, asin, atan2, cos, sin, radians

parmaaters

n = 9 # number of points on sphere rat = .86 # how far along the radius to bisect u_segments = 32 # UV sphere settings v_segments = 32 thickness = 0.2 # solidify thickness TOL = 1e-7

points = [Vector((0, 0, 1))] for i in range(n - 1): theta = random.random() * radians(360) phi = 2 * asin(random.random() * 2 - 1) points.append(Vector((cos(theta) * cos(phi), sin(theta) * cos(phi), sin(phi))))

while True: # Determine the total force acting on each point. forces = [] for i in range(len(points)): p = points[i] f = Vector() ftotal = 0 for j in range(len(points)): if j == i: continue q = points[j] # Find the distance vector, and its length. dv = p - q dl = dv.length dl3 = dl * dl * dl fv = dv / dl3 # Add to the total force on the point p. f = f + fv # Stick this in the forces array. forces.append(f) # Add to the running sum of the total forces/distances. ftotal = ftotal + f.length

fscale = 1 if ftotal <= 0.25 else 0.25 / ftotal

# Move each point, and normalise. While we do this, also track
# the distance each point ends up moving.
dist = 0
for i in range(len(points)):
    p = points[i]
    f = forces[i]
    p2 = (p + fscale * f).normalized()

    dv = p - p2
    dist = dist + dv.length
    points[i] = p2
# Done. Check for convergence and finish.
if dist < TOL: # TOL
    break

context = bpy.context

scene = context.scene

make one point north pole.

R = points[0].rotation_difference(Vector((0, 0, 1))).to_matrix() points = [R @ p for p in points]

bm = bmesh.new() #bmesh.ops.create_icosphere(bm, diameter=1, subdivisions=5 ) bmesh.ops.create_uvsphere(bm, radius=1, u_segments=u_segments, v_segments=v_segments)

for p in points: ret = bmesh.ops.bisect_plane(bm, geom=bm.faces[:]+bm.edges[:]+bm.verts[:], plane_co= rat * p, plane_no=-p, clear_outer=False, clear_inner=True) ''' # add visible cutting planes (using bisect) R = Vector(p).to_track_quat('Z', 'Y').to_matrix().to_4x4() R.translation = rat * p bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=1, matrix=R) '''

addition 1

outer_edges = [e for e in bm.edges if e.is_boundary] big_faces = bmesh.ops.holes_fill(bm, edges=outer_edges)["faces"] opposite_normals = [f.normal * -1 for f in big_faces] rotations = [n.to_track_quat().to_euler() for n in opposite_normals]

old code

me = bpy.data.meshes.new("dice") bm.to_mesh(me) ob = bpy.data.objects.new("dice", me)
scene.collection.objects.link(ob)

addition 2

bpy.ops.object.text_add() txt = context.object txt.data.align_x = txt.data.align_y = 'CENTER' mod = txt.modifiers.new(name='', type='SOLIDIFY') mod.thickness = .2 scene.collection.objects.link(txt)

for o in context.selected_objects[:]: o.select_set(False)

for i, rot in enumerate(rotations, start=1): txt.select_set(True) context.view_layer.objects.active = txt txt.data.body = str(i) bpy.ops.object.duplicate() bpy.ops.object.convert(target='MESH') txt_me = context.object for v in txt_me.data.vertices: v.co.z += 1.01 # move away from the center by radius txt_me.rotation_euler = rot boolean = ob.modifiers.new(name=f"{txt_me.name}", type='BOOLEAN') boolean.object = txt_me txt_me.select_set(False) txt_me.hide_set(True)

Markus von Broady
  • 36,563
  • 3
  • 30
  • 99