# [Blender] 编程基础

Please refresh the page if equations are not rendered correctly.
---------------------------------------------------------------

Blender 3.5

## 2. 创建平面网格

import bpy

# 切换到Object Mode
if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')

# 全选并删除
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# http://sinestesia.co/blog/tutorials/python-2d-grid/

# The Blender data system makes a distinction between mesh data and objects in the scene.
# We have to add a mesh and link it to an object, and then link that object to a scene before we can see any results.

name = 'Gridtastic' # will be both the mesh and object’s name
rows = 5  # Rows and columns will control how many vertices the grid has.
columns = 10

def face(column, row):
""" Create a single face """
# global variable rows is used.
return (column* rows + row,
(column + 1) * rows + row,
(column + 1) * rows + 1 + row,
column * rows + 1 + row)

size_x = 2
size_y = 1

def vert(column, row):
""" Create a single vert """
return (column * size_x, row * size_y, 0)

verts = [vert(x, y) for x in range(columns) for y in range(rows)]
#verts = [(x, y, 0) for x in range(columns) for y in range(rows)] # Each vertex is a tuple of 3 floats contained in a list
faces = [face(x, y) for x in range(columns - 1) for y in range(rows - 1)]
# counter-clockwise starting from the lower-left
# To make a face you need to add a tuple of indices to the faces list. This tuple can be 3 (tri), 4 (quad) or more (ngon) indices. These are all integers by the way. Blender won’t fail or complain, but it will round them. Since we are making quads we will need to find four vertex indices for each face.

# Create Mesh Datablock
# from_pydata() - This function creates a mesh from three python lists: vertices, edges and faces. Note that If you pass a faces list you can skip edges.
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(verts, [], faces)

# Create Object and link to scene
obj = bpy.data.objects.new(name, mesh)

# Select the object
bpy.context.view_layer.objects.active = obj
obj.select_set(True)  # https://blender.stackexchange.com/questions/132825/python-selecting-object-by-name-in-2-8/132829


## 3. 创建立方体

import bpy
import math
from mathutils import Matrix  # from Blender

if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')

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

# Every object in Blender has an origin point that determines their position in 3D space. In fact when we talk about an object’s position we actually talk about the position of its origin point. On the other hand when we talk about an origin points’ position we actually talk about the origin points’ position relative to the mesh.

# ---------------------------------------------------------------------
# Settings
name = 'Cubert'

# ---------------------------------------------------------------------
# Utility Functions

offset = (1, 1, 1)

def vert(x,y,z):
""" Make a vertex """

return (x + offset[0], y + offset[1], z + offset[2])

# ---------------------------------------------------------------------
# Cube Code
# changing the position of the mesh relative to the origin point by the vert() function.
verts = [vert(1.0, 1.0, -1.0),
vert(1.0, -1.0, -1.0),
vert(-1.0, -1.0, -1.0),
vert(-1.0, 1.0, -1.0),
vert(1.0, 1.0, 1.0),
vert(1.0, -1.0, 1.0),
vert(-1.0, -1.0, 1.0),
vert(-1.0, 1.0, 1.0)]

faces = [(0, 1, 2, 3),
(4, 7, 6, 5),
(0, 4, 5, 1),
(1, 5, 6, 2),
(2, 6, 7, 3),
(4, 0, 3, 7)]

# ---------------------------------------------------------------------

mesh = bpy.data.meshes.new(name)
mesh.from_pydata(verts, [], faces)

obj = bpy.data.objects.new(name, mesh)

bpy.context.view_layer.objects.active = obj
obj.select_set(True)

print(obj.location)

## Transformation Using Matrices

# Translation
#translation_matrix = Matrix.Translation((0, 0.5, 1))
#obj.matrix_world @= translation_matrix

# Scaling
# Scaling takes three arguments. The first one is the scale factor. The second one is the size of the matrix, it can be either 2 (2×2) or 4(4x4). But since we are working with 3D objects this should always be 4. The final arguments is a vector to specifies the axis to scale. This can be either zero for no scaling, or 1 to scale.
#scale_matrix = Matrix.Scale(2, 4, (0, 0, 1)) # Scale by 2 on Z
#obj.matrix_world @= scale_matrix

# Rotation
# Rotation takes almost the same arguments as scale. The first is the angle of rotation in radians. The second is the size of the matrix (same as before). And the third is the axis of the rotation. You can pass a string like ‘X’, ‘Y’ or ‘Z’, or a vector like the one in scale.

translation = (0, 0, 1)
scale_factor = 2
scale_axis = (0, 0, 1)
rotation_axis = 'X'

translation_matrix = Matrix.Translation(translation)
scale_matrix = Matrix.Scale(scale_factor, 4, scale_axis)
rotation_mat = Matrix.Rotation(rotation_angle, 4, rotation_axis)
# @ multiplication of matrices
obj.matrix_world @= translation_matrix @ rotation_mat @ scale_matrix

# ----------------------------------------------------------------------------- # Matrix Magic (in the mesh) # Uncomment this to change the mesh
# Matrices can also be used to change the mesh instead of the object. To do this we can use the transform() method of in objects. All it asks for is that you give it a matrix.
obj.data.transform(translation_matrix @ scale_matrix)

#for face in mesh.polygons:
#    face.use_smooth = True


## 4. 创建圆角立方体

import bpy

import json
import os

from mathutils import Matrix

if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')

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

import bpy

import json
import os
from random import random, uniform

from mathutils import Matrix

# -----------------------------------------------------------------------------
# Functions

def object_from_data(data, name, scene, select=True):
""" Create a mesh object and link it to a scene """

mesh = bpy.data.meshes.new(name)
mesh.from_pydata(data['verts'], data['edges'], data['faces'])

obj = bpy.data.objects.new(name, mesh)

bpy.context.view_layer.objects.active = obj
obj.select_set(True)

mesh.validate(verbose=True)

return obj

def transform(obj, position=None, scale=None, rotation=None):
""" Apply transformation matrices to an object or mesh """

position_mat = 1 if not position else Matrix.Translation(position)

if scale:
scale_x = Matrix.Scale(scale[0], 4, (1, 0, 0))
scale_y = Matrix.Scale(scale[1], 4, (0, 1, 0))
scale_z = Matrix.Scale(scale[2], 4, (0, 0, 1))

scale_mat = scale_x @ scale_y @ scale_z
else:
scale_mat = 1

if rotation:
else:
rotation_mat = 1

try:
obj.matrix_world @= position_mat @ rotation_mat @ scale_mat
return
except AttributeError:
# I used return/pass here to avoid nesting try/except blocks
pass

try:
obj.transform(position_mat @ rotation_mat @ scale_mat)
except AttributeError:
raise TypeError('First parameter must be an object or mesh')

def apply_modifiers(obj):
""" Apply all modifiers on an object """

bm = bmesh.new()
dg = bpy.context.evaluated_depsgraph_get()
bm.from_object(obj, dg)
bm.to_mesh(obj.data)
bm.free()

obj.modifiers.clear()

def set_smooth(obj):
""" Enable smooth shading on an mesh object """

for face in obj.data.polygons:
face.use_smooth = True

def get_filename(filename):
""" Return an absolute path for a filename relative to the blend's path """

base = os.path.dirname(bpy.context.blend_data.filepath)
return os.path.join(base, filename)

# -----------------------------------------------------------------------------
# Using the functions together

def make_object(datafile, name):
""" Make a cube object """

subdivisions = 0
roundness = 2.5
position = (uniform(-5,5), uniform(-5,5), uniform(-5,5))
scale = (5 * random(), 5 * random(), 5 * random())
rotation = (20, 'X')

with open(datafile, 'r') as jsonfile:

scene = bpy.context.scene
obj = object_from_data(mesh_data, name, scene)

transform(obj, position, scale, rotation)
set_smooth(obj)

mod = obj.modifiers.new('Bevel', 'BEVEL')
mod.segments = 10
mod.width = (roundness / 10) / (sum(scale) / 3)

if subdivisions > 0:
mod = obj.modifiers.new('Subdivision', 'SUBSURF')
mod.levels = subdivisions
mod.render_levels = subdivisions

#apply_modifiers(obj)

return obj

# -----------------------------------------------------------------------------
# Main code and error control

try:
make_object(get_filename('cube.json'), 'Rounded Cube')

except FileNotFoundError as e:

except PermissionError as e:
print('[!] Could not open JSON file {0}'.format(e))

except KeyError as e:
print('[!] Mesh data error. {0}'.format(e))

except RuntimeError as e:
print('[!] from_pydata() failed. {0}'.format(e))

except TypeError as e:
print('[!] Passed the wrong type of object to transform. {0}'.format(e))


## 5.创建圆与圆柱形网格

import bpy
import bmesh
import math

if bpy.context.mode != 'OBJECT':
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# ------------------------------------------------------------------------------
# Utility Functions

def set_smooth(obj):
""" Enable smooth shading on an mesh object """

for face in obj.data.polygons:
face.use_smooth = True

def object_from_data(data, name, scene, select=True):
"""
Create a mesh object and link it to a scene
data: Dictionary
Including lists of tuples for vets, edges and faces
name: String
The name of mesh and object
"""

mesh = bpy.data.meshes.new(name)
mesh.from_pydata(data['verts'], data['edges'], data['faces'])

obj = bpy.data.objects.new(name, mesh)

bpy.context.view_layer.objects.active = obj
obj.select_set(True)

mesh.update(calc_edges=True)
mesh.validate(verbose=True)

return obj

# Bmesh is a special Blender API that gives you very close access to the internal mesh editing API. It’s quite faster than other methods and more flexible. However, when it comes to creating meshes from scratch Bmesh doesn’t offer anything too different from the other way. That’s why this series hasn’t touched on Bmesh until this point. In order to use bmesh we first create an bmesh object, then fill it with data (in this case using from_mesh()). Once we are finished with it, we write the new data to the mesh and free the bmesh object from memory.
def recalculate_normals(mesh):
""" Make normals consistent for mesh """

bm = bmesh.new()
bm.from_mesh(mesh)

bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
# write the new data to the mesh
bm.to_mesh(mesh)
bm.free()  # free the bmesh object from memory

# ------------------------------------------------------------------------------
# Geometry functions

def vertex_circle(segments, z):
""" Return a ring of vertices """
verts = []

for i in range(segments):
angle = (math.pi*2) * i / segments # start from angle = 0 degree
verts.append((math.cos(angle), math.sin(angle), z))

return verts

def face(segments, i, row):
""" Return a face on a cylinder """

if i == segments - 1:
ring_start = segments * row
base = segments * (row + 1)

return (base - 1, ring_start, base, (base + segments) - 1)

else:
base = (segments * row) + i
return (base, base + 1, base + segments + 1, base + segments)

def bottom_cap(verts, faces, segments, cap='NGON'):
""" Build bottom caps as triangle fans """

if cap == 'TRI':
verts.append((0, 0, 0))
center_vert = len(verts) - 1

[faces.append((i, i+1, center_vert)) for i in range(segments - 1)]
faces.append((segments - 1, 0, center_vert))

elif cap == 'NGON':
faces.append([i for i in range(segments)])

else:
print('[!] Passed wrong type to bottom cap')

def top_cap(verts, faces, segments, rows, cap='TRI'):
""" Build top caps as triangle fans """

if cap == 'TRI':
verts.append((0, 0, rows - 1))
center_vert = len(verts) - 1
base = segments * (rows - 1)

[faces.append((base+i, base+i+1, center_vert))
for i in range(segments - 1)]

faces.append((segments * rows - 1, base, center_vert))

elif cap == 'NGON':
base = (rows - 1) * segments
faces.append([i + base for i in range(segments)])

else:
print('[!] Passed wrong type to top cap')

# ------------------------------------------------------------------------------
# Main Functions

def make_circle(name, z, segments=15, fill=None):
"""
Make a circle: Predefined function vertex_circle is used to generate vertices.
"""

data = {
'verts': vertex_circle(segments, z),
'edges': [],
'faces': [],
}

if fill:
bottom_cap(data['verts'], data['faces'], segments, fill)
else:
data['edges'] = [(i, i+1) for i in range(segments)] # not closed
data['edges'].append((segments - 1, 0))  # closing the circle
pass

scene = bpy.context.scene
return object_from_data(data, name, scene)

def make_cylinder(name, segments=64, rows=4, cap=None):
""" Make a cylinder """

data = { 'verts': [], 'edges': [], 'faces': [] }

# add all the verts to the list at the same time.
for z in range(rows):
data['verts'].extend(vertex_circle(segments, z))

for i in range(segments):
for row in range(0, rows - 1):
data['faces'].append(face(segments, i, row))
pass

if cap:
bottom_cap(data['verts'], data['faces'], segments, cap)
top_cap(data['verts'], data['faces'], segments, rows, cap)

scene = bpy.context.scene
obj = object_from_data(data, name, scene)
recalculate_normals(obj.data)
#    set_smooth(obj)

#  chamfer for edge on top and bottom
#    bevel = obj.modifiers.new('Bevel', 'BEVEL')
#    bevel.limit_method = 'ANGLE'

#    obj.modifiers.new('Edge Split', 'EDGE_SPLIT')

return obj

# ------------------------------------------------------------------------------
# Main Code

#make_circle('Circle', 0, 15)

make_cylinder('Cylinder', 10, 14, 'NGON')


## 6. 根据输入坐标绘制曲线

from bpy import context, data, ops
from mathutils import geometry, Euler, Matrix, Quaternion, Vector
import numpy as np

ops.object.mode_set(mode='OBJECT')
ops.object.select_all(action='SELECT')
ops.object.delete(use_global=False)

# Load centroid data and assigned to the bezier points
filepath = "D:\\04_coding\\Python\\06_Visualization\Mesh\\00_data_acquisition\\3D_2_4_5layers\\Long_0\\centerline.csv"
centerline = np.loadtxt(filepath, delimiter = ',', skiprows = 1)
count = centerline.shape[0] - 2

num_point = count + 2
ops_mesh = ops.mesh

# Create curve and randomize its points.
ops.curve.subdivide(number_cuts=count) # 18 lines for every 1/4 circle.

# Acquire a reference to the bezier points.
bez_curve = context.active_object
bez_curve.name = 'Centerline'
bez_points = bez_curve.data.splines[0].bezier_points
print(bez_points)

# Set handles to desired handle type.
for bez_point in bez_points:
bez_point.handle_left_type = 'AUTO'  # 'AUTO', 'FREE', 'VECTOR'
bez_point.handle_right_type = 'AUTO'

slice = 0
while slice < num_point:
bez_points[slice].co = Vector(centerline[slice,:])
print(Vector(centerline[slice,:]))
slice += 1

context.object.data.bevel_depth = 1.5
context.object.data.bevel_resolution = 10


## 7. Render NumPy voxels in Blender

import bpy
import numpy as np

objs = bpy.data.objects
scn = bpy.context.scene

scene_objs = [
objs['Cube_transparent'],
objs['Cube_body']
#        objs['sand'],
#        objs['grass'],
#        objs['water'],
]