Compare commits
2 Commits
950b1473b2
...
7132ff83ce
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7132ff83ce | ||
![]() |
5905669dbd |
296
blender_util.py
296
blender_util.py
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import bpy
|
import bpy
|
||||||
@ -7,6 +6,7 @@ import gc
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import mathutils
|
import mathutils
|
||||||
|
|
||||||
|
|
||||||
class BlenderUtils:
|
class BlenderUtils:
|
||||||
|
|
||||||
TABLE_NAME: str = "table"
|
TABLE_NAME: str = "table"
|
||||||
@ -19,7 +19,7 @@ class BlenderUtils:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_obj_path(obj_dir, name):
|
def get_obj_path(obj_dir, name):
|
||||||
return os.path.join(obj_dir, name, BlenderUtils.MESH_FILE_NAME)
|
return os.path.join(obj_dir, name, BlenderUtils.MESH_FILE_NAME)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_obj(name, mesh_path, scale=1):
|
def load_obj(name, mesh_path, scale=1):
|
||||||
print(mesh_path)
|
print(mesh_path)
|
||||||
@ -34,32 +34,31 @@ class BlenderUtils:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_obj(name):
|
def get_obj(name):
|
||||||
return bpy.data.objects.get(name)
|
return bpy.data.objects.get(name)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_obj_pose(name):
|
def get_obj_pose(name):
|
||||||
obj = BlenderUtils.get_obj(name)
|
obj = BlenderUtils.get_obj(name)
|
||||||
return np.asarray(obj.matrix_world)
|
return np.asarray(obj.matrix_world)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_plane(name, location, orientation, size=10):
|
def add_plane(name, location, orientation, size=10):
|
||||||
bpy.ops.mesh.primitive_plane_add(size=size,location=location)
|
bpy.ops.mesh.primitive_plane_add(size=size, location=location)
|
||||||
plane = bpy.context.selected_objects[-1]
|
plane = bpy.context.selected_objects[-1]
|
||||||
plane.name = name
|
plane.name = name
|
||||||
plane.rotation_euler = orientation
|
plane.rotation_euler = orientation
|
||||||
bpy.ops.rigidbody.object_add()
|
bpy.ops.rigidbody.object_add()
|
||||||
bpy.context.object.rigid_body.type = 'PASSIVE'
|
bpy.context.object.rigid_body.type = "PASSIVE"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_table(table_model_path):
|
def add_table(table_model_path):
|
||||||
table = BlenderUtils.load_obj(BlenderUtils.TABLE_NAME, table_model_path, scale=0.01)
|
table = BlenderUtils.load_obj(
|
||||||
|
BlenderUtils.TABLE_NAME, table_model_path, scale=0.01
|
||||||
|
)
|
||||||
bpy.ops.rigidbody.object_add()
|
bpy.ops.rigidbody.object_add()
|
||||||
bpy.context.object.rigid_body.type = 'PASSIVE'
|
bpy.context.object.rigid_body.type = "PASSIVE"
|
||||||
|
|
||||||
mat = bpy.data.materials.new(name="TableYellowMaterial")
|
mat = bpy.data.materials.new(name="TableYellowMaterial")
|
||||||
mat.diffuse_color = (1.0, 1.0, 0.0, 1.0)
|
mat.diffuse_color = (1.0, 1.0, 0.0, 1.0)
|
||||||
if len(table.data.materials) > 0:
|
if len(table.data.materials) > 0:
|
||||||
table.data.materials[0] = mat
|
table.data.materials[0] = mat
|
||||||
else:
|
else:
|
||||||
@ -67,107 +66,124 @@ class BlenderUtils:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def setup_scene(init_light_and_camera_config, table_model_path, binocular_vision):
|
def setup_scene(init_light_and_camera_config, table_model_path, binocular_vision):
|
||||||
bpy.context.scene.render.engine = 'BLENDER_EEVEE_NEXT'
|
bpy.context.scene.render.engine = "BLENDER_EEVEE_NEXT"
|
||||||
bpy.context.scene.display.shading.show_xray = False
|
bpy.context.scene.display.shading.show_xray = False
|
||||||
bpy.context.scene.display.shading.use_dof = False
|
bpy.context.scene.display.shading.use_dof = False
|
||||||
bpy.context.scene.display.render_aa = 'OFF'
|
bpy.context.scene.display.render_aa = "OFF"
|
||||||
bpy.context.scene.view_settings.view_transform = 'Standard'
|
bpy.context.scene.view_settings.view_transform = "Standard"
|
||||||
|
|
||||||
bpy.context.scene.eevee.use_ssr = False # 关闭屏幕空间反射
|
bpy.context.scene.eevee.use_ssr = False # 关闭屏幕空间反射
|
||||||
bpy.context.scene.eevee.use_bloom = False # 关闭辉光
|
bpy.context.scene.eevee.use_bloom = False # 关闭辉光
|
||||||
bpy.context.scene.eevee.use_gtao = False # 关闭环境光遮蔽
|
bpy.context.scene.eevee.use_gtao = False # 关闭环境光遮蔽
|
||||||
bpy.context.scene.eevee.use_soft_shadows = False # 关闭软阴影
|
bpy.context.scene.eevee.use_soft_shadows = False # 关闭软阴影
|
||||||
bpy.context.scene.eevee.use_shadows = False # 关闭所有阴影
|
bpy.context.scene.eevee.use_shadows = False # 关闭所有阴影
|
||||||
bpy.context.scene.world.use_nodes = False # 如果你不需要环境光,关闭环境节点
|
bpy.context.scene.world.use_nodes = False # 如果你不需要环境光,关闭环境节点
|
||||||
|
|
||||||
|
# bpy.context.scene.eevee.use_sss = False # 关闭次表面散射
|
||||||
#bpy.context.scene.eevee.use_sss = False # 关闭次表面散射
|
|
||||||
|
|
||||||
# 2. 设置最低的采样数
|
# 2. 设置最低的采样数
|
||||||
bpy.context.scene.eevee.taa_render_samples = 1
|
bpy.context.scene.eevee.taa_render_samples = 1
|
||||||
bpy.context.scene.eevee.taa_samples = 1
|
bpy.context.scene.eevee.taa_samples = 1
|
||||||
BlenderUtils.init_light_and_camera(init_light_and_camera_config, binocular_vision)
|
BlenderUtils.init_light_and_camera(
|
||||||
|
init_light_and_camera_config, binocular_vision
|
||||||
BlenderUtils.add_plane("plane_floor", location=(0,0,0), orientation=(0,0,0))
|
)
|
||||||
BlenderUtils.add_plane("plane_ceil", location=(0,0,10), orientation=(0,0,0))
|
|
||||||
BlenderUtils.add_plane("plane_wall_1", location=(5,0,5), orientation=(0,np.pi/2,0))
|
BlenderUtils.add_plane("plane_floor", location=(0, 0, 0), orientation=(0, 0, 0))
|
||||||
BlenderUtils.add_plane("plane_wall_2", location=(-5,0,5), orientation=(0,np.pi/2,0))
|
BlenderUtils.add_plane("plane_ceil", location=(0, 0, 10), orientation=(0, 0, 0))
|
||||||
BlenderUtils.add_plane("plane_wall_3", location=(0,5,5), orientation=(np.pi/2,0,0))
|
BlenderUtils.add_plane(
|
||||||
BlenderUtils.add_plane("plane_wall_4", location=(0,-5,5), orientation=(np.pi/2,0,0))
|
"plane_wall_1", location=(5, 0, 5), orientation=(0, np.pi / 2, 0)
|
||||||
|
)
|
||||||
|
BlenderUtils.add_plane(
|
||||||
|
"plane_wall_2", location=(-5, 0, 5), orientation=(0, np.pi / 2, 0)
|
||||||
|
)
|
||||||
|
BlenderUtils.add_plane(
|
||||||
|
"plane_wall_3", location=(0, 5, 5), orientation=(np.pi / 2, 0, 0)
|
||||||
|
)
|
||||||
|
BlenderUtils.add_plane(
|
||||||
|
"plane_wall_4", location=(0, -5, 5), orientation=(np.pi / 2, 0, 0)
|
||||||
|
)
|
||||||
|
|
||||||
BlenderUtils.add_table(table_model_path)
|
BlenderUtils.add_table(table_model_path)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_light_params(light, config):
|
def set_light_params(light, config):
|
||||||
light.location = config["location"]
|
light.location = config["location"]
|
||||||
light.rotation_euler = config["orientation"]
|
light.rotation_euler = config["orientation"]
|
||||||
if light.type == 'SUN':
|
if light.type == "SUN":
|
||||||
light.data.energy = config["power"]
|
light.data.energy = config["power"]
|
||||||
elif light.type == 'POINT':
|
elif light.type == "POINT":
|
||||||
light.data.energy = config["power"]
|
light.data.energy = config["power"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_camera_params(camera, config, binocular_vision):
|
def set_camera_params(camera, config, binocular_vision):
|
||||||
|
|
||||||
camera_object = bpy.data.objects.new(BlenderUtils.CAMERA_OBJECT_NAME, None)
|
camera_object = bpy.data.objects.new(BlenderUtils.CAMERA_OBJECT_NAME, None)
|
||||||
bpy.context.collection.objects.link(camera_object)
|
bpy.context.collection.objects.link(camera_object)
|
||||||
cameras = [bpy.data.objects.get("Camera")]
|
cameras = [bpy.data.objects.get("Camera")]
|
||||||
camera.location = [0,0,0]
|
camera.location = [0, 0, 0]
|
||||||
camera.rotation_euler = [0,0,0]
|
camera.rotation_euler = [0, 0, 0]
|
||||||
camera.parent = camera_object
|
camera.parent = camera_object
|
||||||
if binocular_vision:
|
if binocular_vision:
|
||||||
left_camera = cameras[0]
|
left_camera = cameras[0]
|
||||||
right_camera = left_camera.copy()
|
right_camera = left_camera.copy()
|
||||||
right_camera.name = BlenderUtils.CAMERA_RIGHT_NAME
|
right_camera.name = BlenderUtils.CAMERA_RIGHT_NAME
|
||||||
right_camera.data = left_camera.data.copy()
|
right_camera.data = left_camera.data.copy()
|
||||||
right_camera.data.name = BlenderUtils.CAMERA_RIGHT_NAME
|
right_camera.data.name = BlenderUtils.CAMERA_RIGHT_NAME
|
||||||
bpy.context.collection.objects.link(right_camera)
|
bpy.context.collection.objects.link(right_camera)
|
||||||
right_camera.parent = camera_object
|
right_camera.parent = camera_object
|
||||||
right_camera.location = [config["eye_distance"]/2, 0, 0]
|
right_camera.location = [config["eye_distance"] / 2, 0, 0]
|
||||||
left_camera.location = [-config["eye_distance"]/2, 0, 0]
|
left_camera.location = [-config["eye_distance"] / 2, 0, 0]
|
||||||
binocular_angle = config["eye_angle"]
|
binocular_angle = config["eye_angle"]
|
||||||
half_angle = np.radians(binocular_angle / 2)
|
half_angle = np.radians(binocular_angle / 2)
|
||||||
|
|
||||||
left_camera.rotation_euler[1] = -half_angle
|
left_camera.rotation_euler[1] = -half_angle
|
||||||
right_camera.rotation_euler[1] = half_angle
|
right_camera.rotation_euler[1] = half_angle
|
||||||
cameras.append(right_camera)
|
cameras.append(right_camera)
|
||||||
|
|
||||||
for camera in cameras:
|
for camera in cameras:
|
||||||
camera.data.clip_start = config["near_plane"]
|
camera.data.clip_start = config["near_plane"]
|
||||||
camera.data.clip_end = config["far_plane"]
|
camera.data.clip_end = config["far_plane"]
|
||||||
|
|
||||||
bpy.context.scene.render.resolution_x = config["resolution"][0]
|
bpy.context.scene.render.resolution_x = config["resolution"][0]
|
||||||
bpy.context.scene.render.resolution_y = config["resolution"][1]
|
bpy.context.scene.render.resolution_y = config["resolution"][1]
|
||||||
sensor_height = 24.0
|
sensor_height = 24.0
|
||||||
focal_length = sensor_height / (2 * np.tan(np.radians(config["fov_vertical"]) / 2))
|
focal_length = sensor_height / (
|
||||||
|
2 * np.tan(np.radians(config["fov_vertical"]) / 2)
|
||||||
|
)
|
||||||
camera.data.lens = focal_length
|
camera.data.lens = focal_length
|
||||||
camera.data.sensor_width = sensor_height * config["resolution"][0] / config["resolution"][1]
|
camera.data.sensor_width = (
|
||||||
|
sensor_height * config["resolution"][0] / config["resolution"][1]
|
||||||
|
)
|
||||||
camera.data.sensor_height = sensor_height
|
camera.data.sensor_height = sensor_height
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def init_light_and_camera(init_light_and_camera_config, binocular_vision):
|
def init_light_and_camera(init_light_and_camera_config, binocular_vision):
|
||||||
|
|
||||||
camera = BlenderUtils.get_obj(BlenderUtils.CAMERA_NAME)
|
camera = BlenderUtils.get_obj(BlenderUtils.CAMERA_NAME)
|
||||||
BlenderUtils.set_camera_params(camera, init_light_and_camera_config[BlenderUtils.CAMERA_NAME], binocular_vision)
|
BlenderUtils.set_camera_params(
|
||||||
|
camera,
|
||||||
|
init_light_and_camera_config[BlenderUtils.CAMERA_NAME],
|
||||||
|
binocular_vision,
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_obj_diag(name):
|
def get_obj_diag(name):
|
||||||
obj = BlenderUtils.get_obj(name)
|
obj = BlenderUtils.get_obj(name)
|
||||||
return np.linalg.norm(obj.dimensions)
|
return np.linalg.norm(obj.dimensions)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def matrix_to_blender_pose(matrix):
|
def matrix_to_blender_pose(matrix):
|
||||||
location = matrix[:3, 3]
|
location = matrix[:3, 3]
|
||||||
rotation_matrix = matrix[:3, :3]
|
rotation_matrix = matrix[:3, :3]
|
||||||
rotation_matrix_blender = mathutils.Matrix(rotation_matrix.tolist())
|
rotation_matrix_blender = mathutils.Matrix(rotation_matrix.tolist())
|
||||||
rotation_euler = rotation_matrix_blender.to_euler()
|
rotation_euler = rotation_matrix_blender.to_euler()
|
||||||
return location, rotation_euler
|
return location, rotation_euler
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_camera_at(pose):
|
def set_camera_at(pose):
|
||||||
camera = BlenderUtils.get_obj(BlenderUtils.CAMERA_OBJECT_NAME)
|
camera = BlenderUtils.get_obj(BlenderUtils.CAMERA_OBJECT_NAME)
|
||||||
location, rotation_euler = BlenderUtils.matrix_to_blender_pose(pose)
|
location, rotation_euler = BlenderUtils.matrix_to_blender_pose(pose)
|
||||||
|
|
||||||
camera.location = location
|
camera.location = location
|
||||||
camera.rotation_euler = rotation_euler
|
camera.rotation_euler = rotation_euler
|
||||||
|
|
||||||
@ -177,27 +193,59 @@ class BlenderUtils:
|
|||||||
vertices_world = [obj.matrix_world @ v for v in vertices]
|
vertices_world = [obj.matrix_world @ v for v in vertices]
|
||||||
min_z = min([v.z for v in vertices_world])
|
min_z = min([v.z for v in vertices_world])
|
||||||
return min_z
|
return min_z
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def render_normal(
|
||||||
|
output_dir, file_name, binocular_vision=False, target_object=None
|
||||||
|
):
|
||||||
|
target_cameras = [BlenderUtils.CAMERA_NAME]
|
||||||
|
if binocular_vision:
|
||||||
|
target_cameras.append(BlenderUtils.CAMERA_RIGHT_NAME)
|
||||||
|
|
||||||
|
for cam_name in target_cameras:
|
||||||
|
bpy.context.scene.camera = BlenderUtils.get_obj(cam_name)
|
||||||
|
cam_suffix = "L" if cam_name == BlenderUtils.CAMERA_NAME else "R"
|
||||||
|
scene = bpy.context.scene
|
||||||
|
scene.render.filepath = ""
|
||||||
|
|
||||||
|
mask_dir = os.path.join(output_dir, "normal")
|
||||||
|
if not os.path.exists(mask_dir):
|
||||||
|
os.makedirs(mask_dir)
|
||||||
|
|
||||||
|
scene.render.filepath = os.path.join(
|
||||||
|
output_dir, mask_dir, f"{file_name}_{cam_suffix}.png"
|
||||||
|
)
|
||||||
|
scene.render.image_settings.color_depth = "8"
|
||||||
|
scene.render.resolution_percentage = 100
|
||||||
|
scene.render.use_overwrite = False
|
||||||
|
scene.render.use_file_extension = False
|
||||||
|
scene.render.use_placeholder = False
|
||||||
|
bpy.ops.render.render(write_still=True)
|
||||||
|
|
||||||
|
msg = "success"
|
||||||
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def render_and_save(output_dir, file_name, binocular_vision=False, target_object=None):
|
def render_mask_and_depth(
|
||||||
|
output_dir, file_name, binocular_vision=False, target_object=None
|
||||||
|
):
|
||||||
target_cameras = [BlenderUtils.CAMERA_NAME]
|
target_cameras = [BlenderUtils.CAMERA_NAME]
|
||||||
if binocular_vision:
|
if binocular_vision:
|
||||||
target_cameras.append(BlenderUtils.CAMERA_RIGHT_NAME)
|
target_cameras.append(BlenderUtils.CAMERA_RIGHT_NAME)
|
||||||
for cam_name in target_cameras:
|
for cam_name in target_cameras:
|
||||||
bpy.context.scene.camera = BlenderUtils.get_obj(cam_name)
|
bpy.context.scene.camera = BlenderUtils.get_obj(cam_name)
|
||||||
bpy.context.scene.view_layers["ViewLayer"].use_pass_z = True
|
|
||||||
bpy.context.scene.view_layers["ViewLayer"].use_pass_normal = True
|
|
||||||
cam_suffix = "L" if cam_name == BlenderUtils.CAMERA_NAME else "R"
|
cam_suffix = "L" if cam_name == BlenderUtils.CAMERA_NAME else "R"
|
||||||
scene = bpy.context.scene
|
scene = bpy.context.scene
|
||||||
scene.render.filepath = ""
|
scene.render.filepath = ""
|
||||||
|
|
||||||
|
|
||||||
mask_dir = os.path.join(output_dir, "mask")
|
mask_dir = os.path.join(output_dir, "mask")
|
||||||
if not os.path.exists(mask_dir):
|
if not os.path.exists(mask_dir):
|
||||||
os.makedirs(mask_dir)
|
os.makedirs(mask_dir)
|
||||||
|
|
||||||
scene.render.filepath = os.path.join(output_dir, mask_dir, f"{file_name}_{cam_suffix}.png")
|
scene.render.filepath = os.path.join(
|
||||||
scene.render.image_settings.color_depth = '8'
|
output_dir, mask_dir, f"{file_name}_{cam_suffix}.png"
|
||||||
|
)
|
||||||
|
scene.render.image_settings.color_depth = "8"
|
||||||
scene.render.resolution_percentage = 100
|
scene.render.resolution_percentage = 100
|
||||||
scene.render.use_overwrite = False
|
scene.render.use_overwrite = False
|
||||||
scene.render.use_file_extension = False
|
scene.render.use_file_extension = False
|
||||||
@ -209,91 +257,47 @@ class BlenderUtils:
|
|||||||
for node in tree.nodes:
|
for node in tree.nodes:
|
||||||
tree.nodes.remove(node)
|
tree.nodes.remove(node)
|
||||||
|
|
||||||
rl = tree.nodes.new('CompositorNodeRLayers')
|
rl = tree.nodes.new("CompositorNodeRLayers")
|
||||||
|
|
||||||
map_range = tree.nodes.new('CompositorNodeMapRange')
|
map_range = tree.nodes.new("CompositorNodeMapRange")
|
||||||
map_range.inputs['From Min'].default_value = 0.01
|
map_range.inputs["From Min"].default_value = 0.01
|
||||||
map_range.inputs['From Max'].default_value = 5
|
map_range.inputs["From Max"].default_value = 5
|
||||||
map_range.inputs['To Min'].default_value = 0
|
map_range.inputs["To Min"].default_value = 0
|
||||||
map_range.inputs['To Max'].default_value = 1
|
map_range.inputs["To Max"].default_value = 1
|
||||||
tree.links.new(rl.outputs['Depth'], map_range.inputs[0])
|
tree.links.new(rl.outputs["Depth"], map_range.inputs[0])
|
||||||
|
|
||||||
output_depth = tree.nodes.new('CompositorNodeOutputFile')
|
output_depth = tree.nodes.new("CompositorNodeOutputFile")
|
||||||
|
|
||||||
depth_dir = os.path.join(output_dir, "depth")
|
depth_dir = os.path.join(output_dir, "depth")
|
||||||
if not os.path.exists(depth_dir):
|
if not os.path.exists(depth_dir):
|
||||||
os.makedirs(depth_dir)
|
os.makedirs(depth_dir)
|
||||||
output_depth.base_path = depth_dir
|
output_depth.base_path = depth_dir
|
||||||
output_depth.file_slots[0].path = f"{file_name}_{cam_suffix}.####"
|
output_depth.file_slots[0].path = f"{file_name}_{cam_suffix}.####"
|
||||||
output_depth.format.file_format = 'PNG'
|
output_depth.format.file_format = "PNG"
|
||||||
output_depth.format.color_mode = 'BW'
|
output_depth.format.color_mode = "BW"
|
||||||
output_depth.format.color_depth = '16'
|
output_depth.format.color_depth = "16"
|
||||||
|
|
||||||
# 创建 Separate XYZ 节点来分离法线的 X, Y, Z 分量
|
|
||||||
separate_xyz = tree.nodes.new('CompositorNodeSeparateXYZ')
|
|
||||||
|
|
||||||
# 将法线向量连接到 Separate XYZ 节点
|
|
||||||
tree.links.new(rl.outputs['Normal'], separate_xyz.inputs[0])
|
|
||||||
|
|
||||||
# 创建 Map Range 节点来分别映射 X, Y, Z 分量
|
|
||||||
map_range_x = tree.nodes.new('CompositorNodeMapRange')
|
|
||||||
map_range_y = tree.nodes.new('CompositorNodeMapRange')
|
|
||||||
map_range_z = tree.nodes.new('CompositorNodeMapRange')
|
|
||||||
|
|
||||||
# 设置映射范围
|
|
||||||
for map_range in [map_range_x, map_range_y, map_range_z]:
|
|
||||||
map_range.inputs['From Min'].default_value = -1
|
|
||||||
map_range.inputs['From Max'].default_value = 1
|
|
||||||
map_range.inputs['To Min'].default_value = 0
|
|
||||||
map_range.inputs['To Max'].default_value = 1
|
|
||||||
|
|
||||||
# 分别连接到法线的 X, Y, Z 输出
|
|
||||||
tree.links.new(separate_xyz.outputs['X'], map_range_x.inputs[0])
|
|
||||||
tree.links.new(separate_xyz.outputs['Y'], map_range_y.inputs[0])
|
|
||||||
tree.links.new(separate_xyz.outputs['Z'], map_range_z.inputs[0])
|
|
||||||
|
|
||||||
# 合并 X, Y, Z 分量到一个 RGB 输出
|
|
||||||
combine_rgb = tree.nodes.new('CompositorNodeCombineXYZ')
|
|
||||||
tree.links.new(map_range_x.outputs[0], combine_rgb.inputs['X'])
|
|
||||||
tree.links.new(map_range_y.outputs[0], combine_rgb.inputs['Y'])
|
|
||||||
tree.links.new(map_range_z.outputs[0], combine_rgb.inputs['Z'])
|
|
||||||
|
|
||||||
# 输出到文件
|
|
||||||
output_normal = tree.nodes.new('CompositorNodeOutputFile')
|
|
||||||
normal_dir = os.path.join(output_dir, "normal")
|
|
||||||
if not os.path.exists(normal_dir):
|
|
||||||
os.makedirs(normal_dir)
|
|
||||||
output_normal.base_path = normal_dir
|
|
||||||
output_normal.file_slots[0].path = f"{file_name}_{cam_suffix}.####"
|
|
||||||
output_normal.format.file_format = 'PNG'
|
|
||||||
output_normal.format.color_mode = 'RGB'
|
|
||||||
output_normal.format.color_depth = '8'
|
|
||||||
|
|
||||||
tree.links.new(combine_rgb.outputs[0], output_normal.inputs[0])
|
|
||||||
bpy.ops.render.render(write_still=True)
|
bpy.ops.render.render(write_still=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
msg = "success"
|
msg = "success"
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def save_cam_params(scene_dir, idx, binocular_vision=False):
|
def save_cam_params(scene_dir, idx, binocular_vision=False):
|
||||||
camera = BlenderUtils.get_obj(BlenderUtils.CAMERA_NAME)
|
camera = BlenderUtils.get_obj(BlenderUtils.CAMERA_NAME)
|
||||||
extrinsic = np.array(camera.matrix_world)
|
extrinsic = np.array(camera.matrix_world)
|
||||||
cam_data = camera.data
|
cam_data = camera.data
|
||||||
focal_length = cam_data.lens
|
focal_length = cam_data.lens
|
||||||
sensor_width = cam_data.sensor_width
|
sensor_width = cam_data.sensor_width
|
||||||
sensor_height = cam_data.sensor_height
|
sensor_height = cam_data.sensor_height
|
||||||
resolution_x = bpy.context.scene.render.resolution_x
|
resolution_x = bpy.context.scene.render.resolution_x
|
||||||
resolution_y = bpy.context.scene.render.resolution_y
|
resolution_y = bpy.context.scene.render.resolution_y
|
||||||
intrinsic = np.zeros((3, 3))
|
intrinsic = np.zeros((3, 3))
|
||||||
intrinsic[0, 0] = focal_length * resolution_x / sensor_width # fx
|
intrinsic[0, 0] = focal_length * resolution_x / sensor_width # fx
|
||||||
intrinsic[1, 1] = focal_length * resolution_y / sensor_height # fy
|
intrinsic[1, 1] = focal_length * resolution_y / sensor_height # fy
|
||||||
intrinsic[0, 2] = resolution_x / 2.0 # cx
|
intrinsic[0, 2] = resolution_x / 2.0 # cx
|
||||||
intrinsic[1, 2] = resolution_y / 2.0 # cy
|
intrinsic[1, 2] = resolution_y / 2.0 # cy
|
||||||
intrinsic[2, 2] = 1.0
|
intrinsic[2, 2] = 1.0
|
||||||
cam_object = BlenderUtils.get_obj(BlenderUtils.CAMERA_OBJECT_NAME)
|
cam_object = BlenderUtils.get_obj(BlenderUtils.CAMERA_OBJECT_NAME)
|
||||||
extrinsic_cam_object = np.array(cam_object.matrix_world)
|
extrinsic_cam_object = np.array(cam_object.matrix_world)
|
||||||
data = {
|
data = {
|
||||||
"extrinsic": extrinsic.tolist(),
|
"extrinsic": extrinsic.tolist(),
|
||||||
@ -305,21 +309,28 @@ class BlenderUtils:
|
|||||||
if binocular_vision:
|
if binocular_vision:
|
||||||
right_camera = BlenderUtils.get_obj(BlenderUtils.CAMERA_RIGHT_NAME)
|
right_camera = BlenderUtils.get_obj(BlenderUtils.CAMERA_RIGHT_NAME)
|
||||||
extrinsic_right = np.array(right_camera.matrix_world)
|
extrinsic_right = np.array(right_camera.matrix_world)
|
||||||
print("result:",extrinsic_right)
|
print("result:", extrinsic_right)
|
||||||
|
|
||||||
data["extrinsic_R"] = extrinsic_right.tolist()
|
data["extrinsic_R"] = extrinsic_right.tolist()
|
||||||
|
|
||||||
cam_params_dir = os.path.join(scene_dir, "camera_params")
|
cam_params_dir = os.path.join(scene_dir, "camera_params")
|
||||||
if not os.path.exists(cam_params_dir):
|
if not os.path.exists(cam_params_dir):
|
||||||
os.makedirs(cam_params_dir)
|
os.makedirs(cam_params_dir)
|
||||||
cam_params_path = os.path.join(cam_params_dir, f"{idx}.json")
|
cam_params_path = os.path.join(cam_params_dir, f"{idx}.json")
|
||||||
with open(cam_params_path, "w") as f:
|
with open(cam_params_path, "w") as f:
|
||||||
json.dump(data, f, indent=4)
|
json.dump(data, f, indent=4)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reset_objects_and_platform():
|
def reset_objects_and_platform():
|
||||||
all_objects = bpy.data.objects
|
all_objects = bpy.data.objects
|
||||||
keep_objects = {"plane_floor", "plane_ceil", "plane_wall_1", "plane_wall_2", "plane_wall_3", "plane_wall_4"}
|
keep_objects = {
|
||||||
|
"plane_floor",
|
||||||
|
"plane_ceil",
|
||||||
|
"plane_wall_1",
|
||||||
|
"plane_wall_2",
|
||||||
|
"plane_wall_3",
|
||||||
|
"plane_wall_4",
|
||||||
|
}
|
||||||
keep_objects.add(BlenderUtils.CAMERA_OBJECT_NAME)
|
keep_objects.add(BlenderUtils.CAMERA_OBJECT_NAME)
|
||||||
keep_objects.add(BlenderUtils.CAMERA_NAME)
|
keep_objects.add(BlenderUtils.CAMERA_NAME)
|
||||||
keep_objects.add(BlenderUtils.CAMERA_RIGHT_NAME)
|
keep_objects.add(BlenderUtils.CAMERA_RIGHT_NAME)
|
||||||
@ -345,18 +356,28 @@ class BlenderUtils:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def save_scene_info(scene_root_dir, display_table_config, target_name):
|
def save_scene_info(scene_root_dir, display_table_config, target_name):
|
||||||
all_objects = bpy.data.objects
|
all_objects = bpy.data.objects
|
||||||
no_save_objects = {"plane_floor", "plane_ceil", "plane_wall_1", "plane_wall_2", "plane_wall_3", "plane_wall_4"}
|
no_save_objects = {
|
||||||
|
"plane_floor",
|
||||||
|
"plane_ceil",
|
||||||
|
"plane_wall_1",
|
||||||
|
"plane_wall_2",
|
||||||
|
"plane_wall_3",
|
||||||
|
"plane_wall_4",
|
||||||
|
}
|
||||||
no_save_objects.add(BlenderUtils.CAMERA_OBJECT_NAME)
|
no_save_objects.add(BlenderUtils.CAMERA_OBJECT_NAME)
|
||||||
no_save_objects.add(BlenderUtils.CAMERA_NAME)
|
no_save_objects.add(BlenderUtils.CAMERA_NAME)
|
||||||
no_save_objects.add(BlenderUtils.CAMERA_RIGHT_NAME)
|
no_save_objects.add(BlenderUtils.CAMERA_RIGHT_NAME)
|
||||||
no_save_objects.add(BlenderUtils.TABLE_NAME)
|
no_save_objects.add(BlenderUtils.TABLE_NAME)
|
||||||
scene_info = {}
|
scene_info = {}
|
||||||
for obj in all_objects:
|
for obj in all_objects:
|
||||||
if obj.name not in no_save_objects and obj.name != BlenderUtils.DISPLAY_TABLE_NAME:
|
if (
|
||||||
|
obj.name not in no_save_objects
|
||||||
|
and obj.name != BlenderUtils.DISPLAY_TABLE_NAME
|
||||||
|
):
|
||||||
obj_info = {
|
obj_info = {
|
||||||
"location": list(obj.location),
|
"location": list(obj.location),
|
||||||
"rotation_euler": list(obj.rotation_euler),
|
"rotation_euler": list(obj.rotation_euler),
|
||||||
"scale": list(obj.scale)
|
"scale": list(obj.scale),
|
||||||
}
|
}
|
||||||
scene_info[obj.name] = obj_info
|
scene_info[obj.name] = obj_info
|
||||||
scene_info[BlenderUtils.DISPLAY_TABLE_NAME] = display_table_config
|
scene_info[BlenderUtils.DISPLAY_TABLE_NAME] = display_table_config
|
||||||
@ -364,6 +385,3 @@ class BlenderUtils:
|
|||||||
scene_info_path = os.path.join(scene_root_dir, "scene_info.json")
|
scene_info_path = os.path.join(scene_root_dir, "scene_info.json")
|
||||||
with open(scene_info_path, "w") as outfile:
|
with open(scene_info_path, "w") as outfile:
|
||||||
json.dump(scene_info, outfile)
|
json.dump(scene_info, outfile)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -243,7 +243,7 @@ class DataGenerator:
|
|||||||
np.savetxt(os.path.join(scene_dir, "points_and_normals.txt"), points_normals)
|
np.savetxt(os.path.join(scene_dir, "points_and_normals.txt"), points_normals)
|
||||||
for i, cam_pose in enumerate(view_data["cam_poses"]):
|
for i, cam_pose in enumerate(view_data["cam_poses"]):
|
||||||
BlenderUtils.set_camera_at(cam_pose)
|
BlenderUtils.set_camera_at(cam_pose)
|
||||||
BlenderUtils.render_and_save(scene_dir, f"{i}", binocular_vision=self.binocular_vision, target_object = self.target_obj)
|
BlenderUtils.render_mask_and_depth(scene_dir, f"{i}", binocular_vision=self.binocular_vision, target_object = self.target_obj)
|
||||||
BlenderUtils.save_cam_params(scene_dir, i, binocular_vision=self.binocular_vision)
|
BlenderUtils.save_cam_params(scene_dir, i, binocular_vision=self.binocular_vision)
|
||||||
self.set_progress("render frame", i, len(view_data["cam_poses"]))
|
self.set_progress("render frame", i, len(view_data["cam_poses"]))
|
||||||
self.set_progress("render frame", len(view_data["cam_poses"]), len(view_data["cam_poses"]))
|
self.set_progress("render frame", len(view_data["cam_poses"]), len(view_data["cam_poses"]))
|
||||||
@ -255,15 +255,208 @@ class DataGenerator:
|
|||||||
file_path = os.path.join(depth_dir, depth_file)
|
file_path = os.path.join(depth_dir, depth_file)
|
||||||
new_file_path = os.path.join(depth_dir, f"{name}.png")
|
new_file_path = os.path.join(depth_dir, f"{name}.png")
|
||||||
os.rename(file_path,new_file_path)
|
os.rename(file_path,new_file_path)
|
||||||
normal_dir = os.path.join(scene_dir, "normal")
|
|
||||||
for normal_file in os.listdir(normal_dir):
|
self.change_target_obj_material_to_normal()
|
||||||
if not normal_file.endswith(".png"):
|
for i, cam_pose in enumerate(view_data["cam_poses"]):
|
||||||
name, _ = os.path.splitext(normal_file)
|
BlenderUtils.set_camera_at(cam_pose)
|
||||||
file_path = os.path.join(normal_dir, normal_file)
|
BlenderUtils.render_normal(scene_dir, f"{i}", binocular_vision=self.binocular_vision, target_object = self.target_obj)
|
||||||
new_file_path = os.path.join(normal_dir, f"{name}.png")
|
BlenderUtils.save_cam_params(scene_dir, i, binocular_vision=self.binocular_vision)
|
||||||
os.rename(file_path,new_file_path)
|
self.set_progress("render normal frame", i, len(view_data["cam_poses"]))
|
||||||
|
self.set_progress("render normal frame", len(view_data["cam_poses"]), len(view_data["cam_poses"]))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def change_target_obj_material_to_normal(self):
|
||||||
|
|
||||||
|
material_name = "normal"
|
||||||
|
mat = bpy.data.materials.get(material_name) or bpy.data.materials.new(
|
||||||
|
material_name
|
||||||
|
)
|
||||||
|
mat.use_nodes = True
|
||||||
|
node_tree = mat.node_tree
|
||||||
|
nodes = node_tree.nodes
|
||||||
|
nodes.clear()
|
||||||
|
|
||||||
|
links = node_tree.links
|
||||||
|
links.clear()
|
||||||
|
|
||||||
|
# Nodes:
|
||||||
|
new_node = nodes.new(type="ShaderNodeMath")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.location = (151.59744262695312, 854.5482177734375)
|
||||||
|
new_node.name = "Math"
|
||||||
|
new_node.operation = "MULTIPLY"
|
||||||
|
new_node.select = False
|
||||||
|
new_node.use_clamp = False
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.inputs[0].default_value = 0.5
|
||||||
|
new_node.inputs[1].default_value = 1.0
|
||||||
|
new_node.inputs[2].default_value = 0.0
|
||||||
|
new_node.outputs[0].default_value = 0.0
|
||||||
|
|
||||||
|
new_node = nodes.new(type="ShaderNodeLightPath")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.location = (602.9912719726562, 1046.660888671875)
|
||||||
|
new_node.name = "Light Path"
|
||||||
|
new_node.select = False
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.outputs[0].default_value = 0.0
|
||||||
|
new_node.outputs[1].default_value = 0.0
|
||||||
|
new_node.outputs[2].default_value = 0.0
|
||||||
|
new_node.outputs[3].default_value = 0.0
|
||||||
|
new_node.outputs[4].default_value = 0.0
|
||||||
|
new_node.outputs[5].default_value = 0.0
|
||||||
|
new_node.outputs[6].default_value = 0.0
|
||||||
|
new_node.outputs[7].default_value = 0.0
|
||||||
|
new_node.outputs[8].default_value = 0.0
|
||||||
|
new_node.outputs[9].default_value = 0.0
|
||||||
|
new_node.outputs[10].default_value = 0.0
|
||||||
|
new_node.outputs[11].default_value = 0.0
|
||||||
|
new_node.outputs[12].default_value = 0.0
|
||||||
|
|
||||||
|
new_node = nodes.new(type="ShaderNodeOutputMaterial")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.is_active_output = True
|
||||||
|
new_node.location = (1168.93017578125, 701.84033203125)
|
||||||
|
new_node.name = "Material Output"
|
||||||
|
new_node.select = False
|
||||||
|
new_node.target = "ALL"
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.inputs[2].default_value = [0.0, 0.0, 0.0]
|
||||||
|
|
||||||
|
new_node = nodes.new(type="ShaderNodeBsdfTransparent")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.location = (731.72900390625, 721.4832763671875)
|
||||||
|
new_node.name = "Transparent BSDF"
|
||||||
|
new_node.select = False
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.inputs[0].default_value = [1.0, 1.0, 1.0, 1.0]
|
||||||
|
|
||||||
|
new_node = nodes.new(type="ShaderNodeCombineXYZ")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.location = (594.4229736328125, 602.9271240234375)
|
||||||
|
new_node.name = "Combine XYZ"
|
||||||
|
new_node.select = False
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.inputs[0].default_value = 0.0
|
||||||
|
new_node.inputs[1].default_value = 0.0
|
||||||
|
new_node.inputs[2].default_value = 0.0
|
||||||
|
new_node.outputs[0].default_value = [0.0, 0.0, 0.0]
|
||||||
|
|
||||||
|
new_node = nodes.new(type="ShaderNodeMixShader")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.location = (992.7239990234375, 707.2142333984375)
|
||||||
|
new_node.name = "Mix Shader"
|
||||||
|
new_node.select = False
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.inputs[0].default_value = 0.5
|
||||||
|
|
||||||
|
new_node = nodes.new(type="ShaderNodeEmission")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.location = (774.0802612304688, 608.2547607421875)
|
||||||
|
new_node.name = "Emission"
|
||||||
|
new_node.select = False
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.inputs[0].default_value = [1.0, 1.0, 1.0, 1.0]
|
||||||
|
new_node.inputs[1].default_value = 1.0
|
||||||
|
|
||||||
|
new_node = nodes.new(type="ShaderNodeSeparateXYZ")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.location = (-130.12167358398438, 558.1497802734375)
|
||||||
|
new_node.name = "Separate XYZ"
|
||||||
|
new_node.select = False
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.inputs[0].default_value = [0.0, 0.0, 0.0]
|
||||||
|
new_node.outputs[0].default_value = 0.0
|
||||||
|
new_node.outputs[1].default_value = 0.0
|
||||||
|
new_node.outputs[2].default_value = 0.0
|
||||||
|
|
||||||
|
new_node = nodes.new(type="ShaderNodeMath")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.location = (162.43240356445312, 618.8094482421875)
|
||||||
|
new_node.name = "Math.002"
|
||||||
|
new_node.operation = "MULTIPLY"
|
||||||
|
new_node.select = False
|
||||||
|
new_node.use_clamp = False
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.inputs[0].default_value = 0.5
|
||||||
|
new_node.inputs[1].default_value = 1.0
|
||||||
|
new_node.inputs[2].default_value = 0.0
|
||||||
|
new_node.outputs[0].default_value = 0.0
|
||||||
|
|
||||||
|
new_node = nodes.new(type="ShaderNodeMath")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.location = (126.8158187866211, 364.5539855957031)
|
||||||
|
new_node.name = "Math.001"
|
||||||
|
new_node.operation = "MULTIPLY"
|
||||||
|
new_node.select = False
|
||||||
|
new_node.use_clamp = False
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.inputs[0].default_value = 0.5
|
||||||
|
new_node.inputs[1].default_value = -1.0
|
||||||
|
new_node.inputs[2].default_value = 0.0
|
||||||
|
new_node.outputs[0].default_value = 0.0
|
||||||
|
|
||||||
|
new_node = nodes.new(type="ShaderNodeVectorTransform")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.convert_from = "WORLD"
|
||||||
|
new_node.convert_to = "CAMERA"
|
||||||
|
new_node.location = (-397.0209045410156, 594.7037353515625)
|
||||||
|
new_node.name = "Vector Transform"
|
||||||
|
new_node.select = False
|
||||||
|
new_node.vector_type = "VECTOR"
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.inputs[0].default_value = [0.5, 0.5, 0.5]
|
||||||
|
new_node.outputs[0].default_value = [0.0, 0.0, 0.0]
|
||||||
|
|
||||||
|
new_node = nodes.new(type="ShaderNodeNewGeometry")
|
||||||
|
# new_node.active_preview = False
|
||||||
|
new_node.color = (0.6079999804496765, 0.6079999804496765, 0.6079999804496765)
|
||||||
|
new_node.location = (-651.8067016601562, 593.0455932617188)
|
||||||
|
new_node.name = "Geometry"
|
||||||
|
new_node.width = 140.0
|
||||||
|
new_node.outputs[0].default_value = [0.0, 0.0, 0.0]
|
||||||
|
new_node.outputs[1].default_value = [0.0, 0.0, 0.0]
|
||||||
|
new_node.outputs[2].default_value = [0.0, 0.0, 0.0]
|
||||||
|
new_node.outputs[3].default_value = [0.0, 0.0, 0.0]
|
||||||
|
new_node.outputs[4].default_value = [0.0, 0.0, 0.0]
|
||||||
|
new_node.outputs[5].default_value = [0.0, 0.0, 0.0]
|
||||||
|
new_node.outputs[6].default_value = 0.0
|
||||||
|
new_node.outputs[7].default_value = 0.0
|
||||||
|
new_node.outputs[8].default_value = 0.0
|
||||||
|
|
||||||
|
# Links :
|
||||||
|
|
||||||
|
links.new(nodes["Light Path"].outputs[0], nodes["Mix Shader"].inputs[0])
|
||||||
|
links.new(nodes["Separate XYZ"].outputs[0], nodes["Math"].inputs[0])
|
||||||
|
links.new(nodes["Separate XYZ"].outputs[1], nodes["Math.002"].inputs[0])
|
||||||
|
links.new(nodes["Separate XYZ"].outputs[2], nodes["Math.001"].inputs[0])
|
||||||
|
links.new(nodes["Vector Transform"].outputs[0], nodes["Separate XYZ"].inputs[0])
|
||||||
|
links.new(nodes["Combine XYZ"].outputs[0], nodes["Emission"].inputs[0])
|
||||||
|
links.new(nodes["Math"].outputs[0], nodes["Combine XYZ"].inputs[0])
|
||||||
|
links.new(nodes["Math.002"].outputs[0], nodes["Combine XYZ"].inputs[1])
|
||||||
|
links.new(nodes["Math.001"].outputs[0], nodes["Combine XYZ"].inputs[2])
|
||||||
|
links.new(nodes["Transparent BSDF"].outputs[0], nodes["Mix Shader"].inputs[1])
|
||||||
|
links.new(nodes["Emission"].outputs[0], nodes["Mix Shader"].inputs[2])
|
||||||
|
links.new(nodes["Mix Shader"].outputs[0], nodes["Material Output"].inputs[0])
|
||||||
|
links.new(nodes["Geometry"].outputs[1], nodes["Vector Transform"].inputs[0])
|
||||||
|
|
||||||
|
self.target_obj.data.materials.clear()
|
||||||
|
self.target_obj.data.materials.append(mat)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def simulate_scene(self, frame_limit=120, depth = 0, diag = 0):
|
def simulate_scene(self, frame_limit=120, depth = 0, diag = 0):
|
||||||
bpy.context.view_layer.update()
|
bpy.context.view_layer.update()
|
||||||
bpy.ops.screen.animation_play()
|
bpy.ops.screen.animation_play()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user