Skip to content
Snippets Groups Projects
Commit c6602c0f authored by Romain Guesdon's avatar Romain Guesdon
Browse files

Init repo

parent 54fd8410
No related branches found
No related tags found
No related merge requests found
car_models/*
output/*
mh_models/*
backgrounds/
.idea/
*.pyc
*.blend1
File added
import random
import glob
import json
import os
import sys
import shutil
from collections import OrderedDict
import bpy
from mathutils import Vector
print('#' * 30)
WORKING_DIR = r"D:\Mingming\synthe_dripe"
os.chdir(WORKING_DIR)
sys.path.append(WORKING_DIR)
from scripts import random_pose
from scripts import utils
from scripts import camera_proj
from scripts import human
import importlib
importlib.reload(random_pose)
importlib.reload(utils)
importlib.reload(camera_proj)
importlib.reload(human)
from scripts.human import Human, HumanLoader, set_shrinkwraps
def abs_path(rel_path):
return os.path.join(os.path.dirname(os.path.dirname(__file__)), rel_path)
random.seed()
C = bpy.context
D = bpy.data
# Clean scene
try:
bpy.ops.object.mode_set(mode='OBJECT')
utils.unselect_all()
except RuntimeError:
pass
for col in D.collections:
D.collections.remove(col)
for bpy_data_iter in (
D.objects,
D.meshes,
D.lights,
D.cameras,
D.armatures,
D.images,
):
for id_data in bpy_data_iter:
bpy_data_iter.remove(id_data)
for ob in D.objects:
C.scene.collection.objects.unlink(ob)
# Import car
car_collection = D.collections.new("Cars")
C.scene.collection.children.link(car_collection)
cars = []
for src_path, name in {
r"car_models\suv_car\car.blend": 'SUV',
r"car_models\red_car\red.blend": 'Red',
r"car_models\pickup_car\pickup.blend": 'PickUp',
r"car_models\family_car\family_car.blend": 'Family',
r"car_models\coupe_car\coupe_car.blend": 'Coupe',
r"car_models\truck\truck_open.blend": 'Truck',
}.items():
with D.libraries.load(abs_path(src_path)) as (data_from, data_to):
data_to.objects = data_from.objects
for obj in data_to.objects:
car_collection.objects.link(obj)
D.objects['Car'].name = name
cars.append(D.objects[name])
car_picker = utils.Randomizer(cars)
# import humans
human_loader = HumanLoader('mh_models/exports')
# Creation scene
# add camera
C.scene.render.engine = 'BLENDER_EEVEE'
camera_data = D.cameras.new(name='Camera')
camera_data.type = 'PERSP'
camera_data.lens_unit = 'FOV'
# camera_data.angle = utils.r(68)
camera_data.angle = utils.r(68)
C.scene.render.resolution_x = 640
C.scene.render.resolution_y = 480
camera_object = D.objects.new('Camera', camera_data)
C.scene.collection.objects.link(camera_object)
# camera_object.location = [-1.1, -0.21, 0.54]
# camera_object.rotation_euler = utils.r([76, 12, -76])
camera_object.location = [-.97, -0.11, 0.68]
camera_object.rotation_euler = utils.r([72, 8, -82])
# set background
back_folder = abs_path("backgrounds")
back_imgs = {}
for key in ['night', 'day']:
list_imgs = glob.glob(os.path.join(back_folder, f'{key}_*'))
back_imgs[key] = []
for img in list_imgs:
img_name = os.path.basename(img)
bpy.ops.image.open(filepath=img, directory=back_folder,
files=[{"name": img_name}], relative_path=False, show_multiview=False)
back_imgs[key].append(img_name)
# Create image holder
bpy.ops.import_image.to_plane(
directory=back_folder,
files=[{"name": "default_green.png"}],
shader='SHADELESS',
use_transparency=False,
offset=False,
height=round(10 / 2.25, 1),
align_axis="X-"
)
image_holder = C.active_object
image_holder.name = 'Image_holder'
image_holder.location = (4, 1.5, 1.3)
image_holder.rotation_euler.z = utils.r(-100)
image_holder.active_material.shadow_method = 'NONE'
# C.scene.node_tree.nodes["Rotate"].inputs[1].default_value = camera_object.rotation_euler[1] - utils.r(5)
# add light
sun_collection = D.collections.new("Sun")
C.scene.collection.children.link(sun_collection)
light_params = {
'day': {
'energy_bounds': (10, 35),
'back_color_bounds': (0.4, 0.65),
'sun_color': (1, 1, 1)
},
'night': {
'energy_bounds': (5, 15),
'back_color_bounds': (0.05, 0.15),
'sun_color': (1, 0.5, 0.2)
}
}
light_data = D.lights.new(name="Sun", type='SUN')
light_data.energy = 20
light_object = D.objects.new(name="Sun", object_data=light_data)
sun_collection.objects.link(light_object)
light_object.location = (5, 0, 3)
# Sun position
C.scene.sun_pos_properties.usage_mode = 'NORMAL'
C.scene.sun_pos_properties.sun_object = light_object
C.scene.sun_pos_properties.object_collection = sun_collection
C.scene.sun_pos_properties.object_collection_type = 'DIURNAL'
C.scene.sun_pos_properties.co_parser = "41°22′14″N 2°09′00″E"
C.scene.sun_pos_properties.sun_distance = 3
C.scene.sun_pos_properties.use_day_of_year = True
C.scene.sun_pos_properties.year = 2022
C.scene.sun_pos_properties.day_of_year = 182
fp = abs_path(r"output")
if os.path.isdir(fp):
shutil.rmtree(fp)
os.mkdir(fp)
fp_img = os.path.join(fp, 'images')
fp_ann_2D = os.path.join(fp, 'annots_2D')
fp_ann_3D = os.path.join(fp, 'annots_3D')
os.mkdir(fp_img)
os.mkdir(fp_ann_2D)
os.mkdir(fp_ann_3D)
frame_rate = 25
nb_scene = 2
nb_pose = 1
human_loader.max_len = min(human_loader.max_len, nb_scene)
ratio_conf_man = int(nb_scene / len(human_loader.human_paths))
scenes_ids = OrderedDict()
for sc in range(nb_scene):
## Random car
car = car_picker()
## Random personne
if ratio_conf_man < 1:
if not sc % 10:
human_loader.load_next()
man = human_loader(car=car)
else:
if not sc % ratio_conf_man:
man = human_loader.next(car=car)
else:
human.set_bounds(man, car)
human_path = human_loader.paths[man]
scenes_ids.setdefault(human_path, -1)
scenes_ids[human_path] += 1
## Random time
C.scene.sun_pos_properties.north_offset = utils.r(random.randint(-179, 180))
time_day = random.randint(0, 23)
if 6 <= time_day <= 21:
day_night = 'day'
C.scene.sun_pos_properties.time = time_day
else:
day_night = 'night'
C.scene.sun_pos_properties.time = (time_day + 12) % 24
light_param = light_params[day_night]
light_data.energy = random.randint(*light_param['energy_bounds'])
light_data.color = light_param['sun_color']
back_val = random.uniform(*light_param['back_color_bounds'])
bpy.data.worlds["World"].node_tree.nodes["Background.001"].inputs[0].default_value = \
(back_val, back_val, back_val, 1)
## Random background
back_img = random.choice(back_imgs[day_night])
# C.scene.node_tree.nodes["Image"].image = D.images[back_img]
image_holder.active_material.node_tree.nodes['Image Texture'].image = D.images[back_img]
# Camera movement
camera_object.rotation_euler = utils.r([72 + random.randint(-2, 2), 8, -82])
C.scene.render.filepath = fp
C.scene.render.image_settings.file_format = 'PNG' # set output format to .png
C.scene.camera = camera_object
P, K, RT = camera_proj.get_3x4_P_matrix_from_blender(camera_object)
file_root_name = f'{list(scenes_ids).index(human_path)}_{scenes_ids[human_path]}'
with open(os.path.join(fp, 'cameras.json'), 'a+') as f_cam:
previous_cameras = f_cam.read()
if previous_cameras:
previous_cameras = json.loads(previous_cameras)
else:
previous_cameras = {}
previous_cameras[file_root_name] = {
'P': utils.mat_to_list(P),
'K': utils.mat_to_list(K),
'RT': utils.mat_to_list(RT),
}
json.dump(previous_cameras, f_cam, indent=4)
man.animation_data_clear()
## Pour chaque paire, on veut NbTotalFrames / NbPersonnes / NbCar poses
## Exemple: 150k / 200 / 2 = 1500 pose
with open(os.path.join(fp_ann_2D, f'annotations_{file_root_name}.csv'), 'w') as annot_file_2D, \
open(os.path.join(fp_ann_3D, f'annotations_{file_root_name}.csv'), 'w') as annot_file_3D:
bone_lbls = list(man.pose.bones.keys())
annot_file_2D.write(';'.join([lbl for bone in bone_lbls for lbl in [bone + k for k in ['_x', '_y']]]) + '\n')
annot_file_3D.write(
';'.join([lbl for bone in bone_lbls for lbl in [bone + k for k in ['_X', '_Y', '_Z']]]) + '\n')
for po in range(nb_pose):
C.scene.frame_set(po * frame_rate)
### On random une pose à chaque fois
random_pose.random_pose_ik(man)
### On crée une frame
bpy.ops.object.mode_set(mode='OBJECT')
man.keyframe_insert(data_path="location", index=-1)
man.keyframe_insert(data_path="rotation_euler", index=-1)
bpy.ops.object.mode_set(mode='POSE')
for bone in man.pose.bones:
bone.keyframe_insert(data_path="rotation_euler", index=-1)
if bone.name[-3:] == '_IK':
bone.keyframe_insert(data_path="location", index=-1)
bpy.ops.object.mode_set(mode='OBJECT')
### Random caméra et lumières (Blender proc)
### On génère les images au bon FPS
### On sauvegarde les poses 3D + paramètres caméra (extra et intra)
# set output path so render won't get overwritten
C.scene.render.filepath = os.path.join(fp_img, f"{file_root_name}_{po}")
bpy.ops.render.render(write_still=True) # render still
annotations_2D = []
annotations_3D = []
for lbl in bone_lbls:
bone_3d = utils.get_head_pose(lbl, man)
annotations_3D.append(f"{bone_3d[0]:.3f};{bone_3d[1]:.3f};{bone_3d[2]:.3f}")
bone_2d = P @ bone_3d
bone_2d /= bone_2d[-1]
annotations_2D.append(f"{bone_2d[0]:.2f};{bone_2d[1]:.2f}")
annot_file_2D.write(';'.join(annotations_2D) + '\n')
annot_file_3D.write(';'.join(annotations_3D) + '\n')
C.scene.frame_end = int(frame_rate * (nb_pose - 0.5))
utils.select_only(man)
bpy.ops.object.mode_set(mode='POSE')
with open(os.path.join(fp, 'infos.json'), 'w') as f:
json.dump({
'models': list(scenes_ids),
'id_max_scenes': scenes_ids
}, f, indent=4)
print('Done', '#' * 25)
import bpy
import bpy_extras
from mathutils import Matrix
from mathutils import Vector
# ---------------------------------------------------------------
# 3x4 P matrix from Blender camera
# ---------------------------------------------------------------
# Build intrinsic camera parameters from Blender camera data
#
# See notes on this in
# blender.stackexchange.com/questions/15102/what-is-blenders-camera-projection-matrix-model
def get_calibration_matrix_K_from_blender(camd):
f_in_mm = camd.lens
scene = bpy.context.scene
resolution_x_in_px = scene.render.resolution_x
resolution_y_in_px = scene.render.resolution_y
scale = scene.render.resolution_percentage / 100
sensor_width_in_mm = camd.sensor_width
sensor_height_in_mm = camd.sensor_height
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y
if (camd.sensor_fit == 'VERTICAL'):
# the sensor height is fixed (sensor fit is horizontal),
# the sensor width is effectively changed with the pixel aspect ratio
s_u = resolution_x_in_px * scale / sensor_width_in_mm / pixel_aspect_ratio
s_v = resolution_y_in_px * scale / sensor_height_in_mm
else: # 'HORIZONTAL' and 'AUTO'
# the sensor width is fixed (sensor fit is horizontal),
# the sensor height is effectively changed with the pixel aspect ratio
pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y
s_u = resolution_x_in_px * scale / sensor_width_in_mm
s_v = resolution_y_in_px * scale * pixel_aspect_ratio / sensor_height_in_mm
# Parameters of intrinsic calibration matrix K
alpha_u = f_in_mm * s_u
alpha_v = f_in_mm * s_v
u_0 = resolution_x_in_px * scale / 2
v_0 = resolution_y_in_px * scale / 2
skew = 0 # only use rectangular pixels
K = Matrix(
((alpha_u, skew, u_0),
(0, alpha_v, v_0),
(0, 0, 1)))
return K
# Returns camera rotation and translation matrices from Blender.
#
# There are 3 coordinate systems involved:
# 1. The World coordinates: "world"
# - right-handed
# 2. The Blender camera coordinates: "bcam"
# - x is horizontal
# - y is up
# - right-handed: negative z look-at direction
# 3. The desired computer vision camera coordinates: "cv"
# - x is horizontal
# - y is down (to align to the actual pixel coordinates
# used in digital images)
# - right-handed: positive z look-at direction
def get_3x4_RT_matrix_from_blender(cam):
# bcam stands for blender camera
R_bcam2cv = Matrix(
((1, 0, 0),
(0, -1, 0),
(0, 0, -1)))
# Transpose since the rotation is object rotation,
# and we want coordinate rotation
# R_world2bcam = cam.rotation_euler.to_matrix().transposed()
# T_world2bcam = -1*R_world2bcam * location
#
# Use matrix_world instead to account for all constraints
# location, rotation = cam.matrix_world.decompose()[0:2]
location, rotation = cam.matrix_basis.decompose()[0:2]
R_world2bcam = rotation.to_matrix().transposed()
# Convert camera location to translation vector used in coordinate changes
# T_world2bcam = -1*R_world2bcam*cam.location
# Use location from matrix_world to account for constraints:
T_world2bcam = -1 * R_world2bcam @ location
# Build the coordinate transform matrix from world to computer vision camera
# NOTE: Use * instead of @ here for older versions of Blender
R_world2cv = R_bcam2cv @ R_world2bcam
T_world2cv = R_bcam2cv @ T_world2bcam
# put into 3x4 matrix
RT = Matrix((
R_world2cv[0][:] + (T_world2cv[0],),
R_world2cv[1][:] + (T_world2cv[1],),
R_world2cv[2][:] + (T_world2cv[2],)
))
return RT
def get_3x4_P_matrix_from_blender(cam):
K = get_calibration_matrix_K_from_blender(cam.data)
RT = get_3x4_RT_matrix_from_blender(cam)
return K @ RT, K, RT
# ----------------------------------------------------------
# Alternate 3D coordinates to 2D pixel coordinate projection code
# adapted from https://blender.stackexchange.com/questions/882/how-to-find-image-coordinates-of-the-rendered-vertex?lq=1
# to have the y axes pointing up and origin at the top-left corner
def project_by_object_utils(cam, point):
scene = bpy.context.scene
co_2d = bpy_extras.object_utils.world_to_camera_view(scene, cam, point)
render_scale = scene.render.resolution_percentage / 100
render_size = (
int(scene.render.resolution_x * render_scale),
int(scene.render.resolution_y * render_scale),
)
return Vector((co_2d.x * render_size[0], render_size[1] - co_2d.y * render_size[1]))
import sys
from math import cos, sin, pi, acos
import random
import json
import glob
import os
import numpy as np
from mathutils import Vector
from scripts import utils
import importlib
importlib.reload(utils)
from scripts.utils import *
D = bpy.data
# Load clothes list
with open(r"mh_models\mh_mass_produce.json") as f:
data = json.load(f)
hairs = data['hair']['allNames']
tops = []
bottoms = []
suits = []
for name, cloth in data['clothes']['clothesInfos'].items():
if cloth["maleUpper"] or cloth["femaleUpper"] or cloth["mixedUpper"]:
tops.append(name.replace(' ', '_'))
if cloth["maleLower"] or cloth["femaleLower"] or cloth["mixedLower"]:
bottoms.append(name.replace(' ', '_'))
if cloth["maleFull"] or cloth["femaleFull"] or cloth["mixedFull"]:
bottoms.append(name.replace(' ', '_'))
class Human:
def __init__(self, model):
self.model = model
self.name = self.model.name
self.init_textures()
self.init_bones()
self.top = self.init_color_ramp(tops + suits)
self.bot = self.init_color_ramp(bottoms + suits)
def init_color_ramp(self, clothes):
mat = None
for obj in self.model.children:
for clo in clothes:
if clo.lower() in obj.name.lower():
if not len(obj.material_slots):
break
utils.select_only(obj)
mat = obj.material_slots[0].material
mat.use_nodes = True
obj.active_material = mat
nodes = mat.node_tree.nodes
node_ramp = nodes.new("ShaderNodeValToRGB")
node_ramp.color_ramp.elements.remove(node_ramp.color_ramp.elements[0])
node_ramp.color_ramp.elements[0].position = 1.
input_links = nodes['Principled BSDF'].inputs[0].links
if len(input_links):
img_node = nodes['Principled BSDF'].inputs[0].links[0].from_node
node_mix = nodes.new("ShaderNodeMixRGB")
node_mix.blend_type = 'MIX'
node_mix.inputs[0].default_value = 0.5
mat.node_tree.links.new(img_node.outputs[0], node_mix.inputs[1])
mat.node_tree.links.new(node_ramp.outputs[0], node_mix.inputs[2])
mat.node_tree.links.new(node_mix.outputs[0], nodes['Principled BSDF'].inputs[0])
else:
mat.node_tree.links.new(node_ramp.outputs[0], nodes['Principled BSDF'].inputs[0])
node_ramp.color_ramp.elements[0].color = [random.random() for _ in range(3)] + [1]
node_ramp.color_ramp.elements[0].color = [random.random() for _ in range(3)] + [1]
break
return mat
def init_bones(self):
armature = bpy.data.armatures[self.model.name]
bpy.ops.object.mode_set(mode='EDIT')
for s in ('l', 'r'):
# Disconnect clavicle
armature.edit_bones[f'clavicle_{s}'].use_connect = False
# Create IK
hand = armature.edit_bones[f'hand_{s}']
hand_ik = armature.edit_bones.new(f'hand_{s}_IK')
utils.select_only_edit_bone(armature, hand_ik)
hand_ik.length = hand.length
bpy.ops.transform.resize(value=Vector((1.5, 1.5, 1.5)))
bpy.ops.object.mode_set(mode='POSE')
ik_constraints = {
'upperarm': [(-45, 120), (-90, 90), (-80, 80)],
'lowerarm': [(-30, 120), None, None]
}
for s in ('l', 'r'):
lowerarm = self.model.pose.bones[f'lowerarm_{s}']
lowerarm.constraints.new('IK')
lowerarm.constraints['IK'].target = self.model
lowerarm.constraints['IK'].subtarget = f'hand_{s}_IK'
lowerarm.constraints['IK'].chain_count = 2
for name, consts in ik_constraints.items():
bone = self.model.pose.bones[f'{name}_{s}']
for axe, const in zip(('x', 'y', 'z'), consts):
if const is None:
setattr(bone, f'lock_ik_{axe}', True)
else:
setattr(bone, f'lock_ik_{axe}', False)
setattr(bone, f'use_ik_limit_{axe}', True)
setattr(bone, f'ik_min_{axe}', utils.r(const[0]))
setattr(bone, f'ik_max_{axe}', utils.r(const[1]))
# self.model.pose.bones[f'hand_{s}_IK'].location = Vector((-15, 10, 0))
self.model.pose.bones[f'hand_{s}_IK'].location = Vector((0, 5, -10))
bpy.ops.object.mode_set(mode='OBJECT')
def init_textures(self):
for o in self.model.children:
if "eye" in o.name or "high" in o.name:
continue
try:
for hair in hairs:
if hair.lower().replace(' ', '_') in o.name.lower():
o.active_material.blend_method = 'HASHED'
break
else:
o.active_material.blend_method = 'OPAQUE'
except AttributeError:
continue
def __call__(self, car=None):
if self.top is not None:
node = self.top.node_tree.nodes["ColorRamp"]
node.color_ramp.elements[0].color = [random.random() for _ in range(3)] + [1, ]
else:
print('Top ', self.model.name)
if self.bot is not None:
node = self.bot.node_tree.nodes["ColorRamp"]
node.color_ramp.elements[0].color = [random.random() for _ in range(3)] + [1, ]
else:
print('Bot ', self.model.name)
set_bounds(self.model, car)
return self.model
def set_bounds(model, car=None):
# set_floors(model, car)
set_shrinkwraps(model, car)
def set_floors(model, car=None):
original_mode = bpy.context.mode
utils.select_only(model)
bpy.ops.object.mode_set(mode='POSE')
if car is not None:
planes = [ch for ch in car.children_recursive if ch.name[:5] == 'Plane']
for s in 'lr':
bone = model.pose.bones[f'hand_{s}_IK']
[bone.constraints.remove(constr) for constr in bone.constraints if 'Floor' in constr.name]
if car is None:
continue
for obj in planes:
constr = bone.constraints.new('FLOOR')
constr.target = obj
constr.floor_location = 'FLOOR_Z'
constr.use_rotation = True
constr.offset = 1
bpy.ops.object.mode_set(mode=original_mode)
def set_shrinkwraps(model, car=None):
original_mode = bpy.context.mode
utils.select_only(model)
bpy.ops.object.mode_set(mode='POSE')
if car is not None:
out_objects = [ch for ch in car.children_recursive if ch.name[:4] == 'OUT_']
for s in 'lr':
bone = model.pose.bones[f'hand_{s}_IK']
[bone.constraints.remove(constr) for constr in bone.constraints if 'Shrinkwrap' in constr.name]
if car is None:
continue
for obj in out_objects:
constr = bone.constraints.new('SHRINKWRAP')
constr.target = obj
constr.wrap_mode = 'OUTSIDE'
if 'Back' in obj.name:
constr.distance = model.pose.bones['lowerarm_l'].length * model.scale.x / obj.scale.y * 1
if 'Side' in obj.name:
constr.distance = model.pose.bones[
'lowerarm_l'].length * model.scale.x / obj.scale.z * 0.2
else:
constr.distance = model.pose.bones['hand_l'].length * model.scale.x / obj.scale.z * 1.5
bpy.ops.object.mode_set(mode=original_mode)
class HumanLoader:
def __init__(self, dir_path, max_len=10):
self.human_paths = glob.glob(os.path.join(dir_path, '*.fbx'))
# random.shuffle(self.human_paths)
self.paths = {}
self.humans = {}
self.max_len = max_len
self.start_idx = 0
self.current_idx = -1
self.collection = D.collections.new("Humans")
C.scene.collection.children.link(self.collection)
self.picker = None
# self.load_next()
def load_next(self):
if self.max_len >= len(self.human_paths):
# If asked too much
self.start_idx = 0
if len(self.paths) == len(self.human_paths):
# If everything already loaded
return
else:
next_idx = len(self.human_paths)
else:
next_idx = self.start_idx + self.max_len
human_paths = (self.human_paths * 2)[self.start_idx:next_idx]
human_paths = list({k: None for k in human_paths})
already_loaded = [hp for hp in human_paths if hp in self.paths.values()]
self.paths = {k: v for k, v in self.paths.items() if v in already_loaded}
self.humans = {k: v for k, v in self.humans.items() if k in already_loaded}
clear_humans(exceptions=list(self.paths))
for human_path in human_paths:
if human_path in already_loaded:
continue
bpy.ops.import_scene.fbx(filepath=human_path)
model = C.active_object
self.paths[model] = human_path
self.move_human(model)
self.humans[human_path] = Human(model)
self.picker = utils.Randomizer(list(self.humans.values()))
self.start_idx = next_idx % len(self.human_paths)
def next(self, *args, **kwargs):
self.current_idx += 1
if not self.current_idx % self.max_len:
self.start_idx = self.current_idx
self.load_next()
self.current_idx = 0
return self.picker.get(self.current_idx, *args, **kwargs)
def move_human(self, obj):
for ch in obj.children:
self.move_human(ch)
if obj.name in C.collection.objects:
self.collection.objects.link(obj)
C.collection.objects.unlink(obj)
def __call__(self, *args, **kwargs):
return self.picker(*args, **kwargs)
def clear_humans(exceptions=[]):
collection = D.collections["Humans"]
exceptions = exceptions + [obj.children_recursive for obj in exceptions]
for obj in collection.objects:
if obj in exceptions:
continue
for ch in obj.children_recursive + [obj]:
obj_data = ch.data
if isinstance(obj_data, bpy.types.Armature):
D.armatures.remove(obj_data)
elif isinstance(obj_data, bpy.types.Mesh):
D.meshes.remove(obj_data)
try:
D.objects.remove(obj_data)
except ReferenceError:
pass
import sys
from math import cos, sin, pi, acos
import random
from mathutils import Vector
import numpy as np
from scripts import utils
import importlib
importlib.reload(utils)
from scripts.utils import *
def default_rots():
return {
'thigh_r': (r(-70), r(-5 - random.random() * 15), 0),
'thigh_l': (r(-70), r(5 + random.random() * 15), 0),
'calf_r': (r(20 + random.random() * 50), r(30), 0),
'calf_l': (r(20 + random.random() * 50), r(-30), 0)
}
def bounds():
values = {
'spine_03': [[0, 40], 45, None],
'upperarm_l': [None, 10, 10],
'upperarm_r': [None, 25, 20],
'neck_01': [[0, 40], 0, 10],
'lowerarm_X': [[-25, 50], [0, 35], None]
}
temp = values.copy()
for k, v in temp.items():
if '_X' in k:
values.update({k.replace('X', u): v for u in ['l', 'r']})
del (values[k])
for k, v in values.items():
values[k] = [[-a, a] if isinstance(a, int) else a for a in v]
return values
def get_angles(angs):
new_angs = []
for ang in angs:
if isinstance(ang, list):
new_angs.append(random.randint(*ang))
elif isinstance(ang, int):
new_angs.append(random.randint(-ang, ang))
elif ang is None:
new_angs.append(0)
return Vector(r(new_angs))
def reset_subject_old(subject):
bpy.ops.object.mode_set(mode='OBJECT')
subject.scale = (0.01, 0.01, 0.01)
subject.location = [-0.15, 0.5, 0.08]
subject.rotation_euler = r([-18, 0, 0])
bpy.ops.object.mode_set(mode='POSE')
for bone in subject.pose.bones:
bone.location = (0, 0, 0)
bone.rotation_euler = (0, 0, 0)
def reset_subject(subject):
bpy.ops.object.mode_set(mode='POSE')
for bone in subject.pose.bones:
bone.rotation_mode = 'XYZ'
if bone.name in ["Root", "pelvis"]:
continue
bone.location = (0, 0, 0)
bone.rotation_euler = (0, 0, 0)
bpy.ops.object.mode_set(mode='OBJECT')
scale = round(subject.scale[0] * (1.6 + 0.3 * random.random()) / (subject.dimensions.y), 3)
# subject.scale = [scale] * 3
subject.scale = [0.1] * 3
height = subject.dimensions.y
# subject.location = [0, -0.16, -0.5 * 1.66]
subject.location = [0, -0.16, -0.5 * height]
subject.rotation_euler = r([65, 0, 0])
def hand_pose(pose):
for s in ['l', 'r']:
hand_ratio = random.uniform(0.1, 0.8)
for finger in ['thumb', 'index', 'middle', 'ring', 'pinky']:
angles = r([40, 40, 40]) if finger == 'thumb' else r([70, 90, 40])
solo_ratio = random.uniform(0.7, 1) * hand_ratio
for i in range(3):
pose.bones[f'{finger}_{i + 1:02}_{s}'].rotation_euler.x = angles[i] * solo_ratio
def random_pose_ik(subject, auto_ik=False):
"""
1- reset and fix legs
2- randomize back
2b- bend back when too much twisted
3- randomize neck (backward proportional to back bending)
4- move arms with IK
:param subject: subject Object
:return:
"""
# 1
bpy.ops.object.mode_set(mode='OBJECT')
pose = subject.pose
select_only(subject)
def rota(bone):
return Vector(pose.bones[bone].rotation_euler)
reset_subject(subject)
arm_length = dist(get_head_pose('upperarm_l', subject), get_head_pose('hand_l', subject))
base_rots = default_rots()
matrix_world = C.active_object.matrix_world.copy()
bpy.ops.object.mode_set(mode='POSE')
bounds_vals = bounds()
for bone, angles in base_rots.items():
pose.bones[bone].rotation_euler = angles
# 2
pose.bones['spine_03'].rotation_euler = get_angles(bounds_vals['spine_03'])
# 2b Compensate for shoulder in seat by bending back to front
pose.bones['spine_01'].rotation_euler = r(
Vector((random.randint(0, 10) + max(d(abs(rota('spine_03').y)) - 20, 0) / 2, 0,
0))) + rota('spine_01')
# 3
pose.bones['neck_01'].rotation_euler = (
get_angles(bounds_vals['neck_01']) +
get_angles(
[[d((rota('spine_01').x + rota('spine_03').x) * -0.5), 0], None, None]) +
Vector((0, random.uniform(0, 0.5) * rota('spine_03').y, 0))
)
hand_pose(pose)
pose.use_auto_ik = auto_ik
targets = {
'l': Vector((0.3, -0.1, 0.4)),
'r': Vector((-0.4, -0.1, 0.2))
}
back_rota_fact = sin(rota('spine_03').y) / sin(r(30))
for s in ['l', 'r']:
# Disconnect clavicle
armature = bpy.data.armatures[subject.name]
if auto_ik:
pose_bone = pose.bones[f'hand_{s}']
else:
pose_bone = pose.bones[f'hand_{s}_IK']
bone = pose_bone.bone
select_only_bone(armature, bone)
target = Vector()
min_arm_factor = 0.2
if False:
# target.x = random.random() * (0.4 if s == 'l' else -0.7)
target.x = random.uniform(max(-0.5, back_rota_fact * 0.4 + 0.4 if s == 'l' else 0),
min(0.4, back_rota_fact * 0.4 + 0 if s == 'l' else -0.4))
target.z = random.uniform(0.05, 0.7)
if abs(target.x) < 0.2:
target.y = random.uniform(-0.04, 0.02)
else:
target.y = random.uniform(-0.24, 0.02)
# sloap
target.y -= random.random() * (target.z * 0.1)
elif False:
phi = random.uniform(r(-180 + 45), 0) - (r(45) if s == 'r' else 0) + rota('spine_03').y
costheta = random.uniform(-1, 1)
u = random.uniform(0.1, 1)
theta = acos(costheta)
# u = arm_length * (u ** 1 / 3)
target.x = u * sin(theta) * cos(phi)
target.y = u * sin(theta) * sin(phi)
target.z = u * cos(theta)
shoulder_pose = get_head_pose(f'upperarm_{s}', subject)
target *= arm_length
target += shoulder_pose
# target.x = max(min(target.x, 0.32), -0.55)
# target.y = max(min(target.y, 0.02), -0.04 if abs(target.x) < 0.2 else -0.24)
# target.z = max(min(target.z, 0.7), 0.15)
bounded_width = Vector(((0.32 if s == 'l' else 0.55) + 0.35,
2 * arm_length,
0.7 - 0.15))
target = (
(target - shoulder_pose) *
(bounded_width / (2 * arm_length)) +
shoulder_pose + 0.5 * (bounded_width - Vector([2 * arm_length, ] * 3))
)
# y done afterward because of the wheel
bounded_width_y = 0.02 - (- 0.04 if abs(target.x) < 0.2 else -0.24)
target.y = (
(target.y - shoulder_pose.y) *
(bounded_width_y / (0.9 * arm_length)) +
shoulder_pose.y + 0.5 * (bounded_width_y - 0.9 * arm_length)
)
elif False:
phi = random.uniform(
r(-180) + rota('spine_03').y if s == 'r' else r(-120) + rota('spine_03').y,
r(-60) + rota('spine_03').y if s == 'r' else r(-60) + rota('spine_03').y
)
costheta = random.uniform(max(-0.8, 0.2 - cos(rota('spine_03').x + rota('spine_01').x)),
min(0.8, -0.2 + cos(rota('spine_03').x - rota('spine_01').x)))
theta = acos(costheta)
bounds_u = []
shoulder_pose = get_head_pose(f'upperarm_{s}', subject)
if not sin(theta) * cos(phi):
min_u_x = 0
max_u_x = arm_length
else:
bounds_u_x = (
(-0.55 - shoulder_pose.x) / (sin(theta) * cos(phi)),
(0.32 - shoulder_pose.x) / (sin(theta) * cos(phi))
)
min_u_x = (-0.55 - shoulder_pose.x) / (sin(theta) * cos(phi))
max_u_x = (0.32 - shoulder_pose.x) / (sin(theta) * cos(phi))
bounds_u += [min_u_x, max_u_x]
if not cos(theta):
min_u_z = 0
max_u_z = arm_length
else:
bounds_u_z = (
(0.15 - shoulder_pose.z) / cos(theta),
(0.7 - shoulder_pose.z) / cos(theta)
)
min_u_z = (0.15 - shoulder_pose.z) / cos(theta)
max_u_z = (0.7 - shoulder_pose.z) / cos(theta)
bounds_u += [min_u_z, max_u_z]
max_u_xz = min([m for m in bounds_u + [arm_length] if m > min_arm_factor * arm_length])
max_x = max_u_xz * sin(theta) * cos(phi) + shoulder_pose.x
if not sin(theta) * sin(phi):
min_u_y = 0
max_u_y = arm_length
else:
bounds_u_y = (
((-0.04 if abs(max_x) < 0.2 else -0.24) - shoulder_pose.y) / (sin(theta) * sin(phi)),
(0.02 - shoulder_pose.y) / (sin(theta) * sin(phi))
)
min_u_y = ((-0.04 if abs(max_x) < 0.2 else -0.24) - shoulder_pose.y) / (sin(theta) * sin(phi))
max_u_y = (0.02 - shoulder_pose.y) / (sin(theta) * sin(phi))
bounds_u += [min_u_y, max_u_y]
max_u = min([m for m in bounds_u + [arm_length] if m > min_arm_factor * arm_length])
# print(d(phi), d(theta))
# print(min_u_x, min_u_y, min_u_z)
# print(max_u_x, max_u_y, max_u_z, max_u / arm_length)
# u = random.uniform((min_arm_factor) ** 1/2, (max_u / arm_length) ** 1/2) ** 2 * arm_length
u = random.uniform(min_arm_factor * arm_length, max_u)
# print(u / arm_length)
target.x = u * sin(theta) * cos(phi)
target.y = u * sin(theta) * sin(phi)
target.z = u * cos(theta)
target += shoulder_pose
else:
back_forward_angle = rota('spine_03').x + rota('spine_01').x - r(30) # 0 = straight
phi = random.uniform(
max((r(-160) if s == 'r' else r(-100)) + rota('spine_03').y, r(-160)),
min((r(-80) if s == 'r' else r(-40)) + rota('spine_03').y, r(-20))
)
theta_bound = 0.8
costheta = random.uniform(max(-0.8, -cos(theta_bound - back_forward_angle - rota('neck_01').x)),
min(0.8, cos(theta_bound + back_forward_angle + rota('neck_01').x)))
theta = acos(costheta)
bounds_u = []
shoulder_pose = get_head_pose(f'upperarm_{s}', subject)
# u = random.uniform((min_arm_factor) ** 1/2, (max_u / arm_length) ** 1/2) ** 2 * arm_length
min_arm_factor = 0.2 + max(sin(back_forward_angle), 0)
# print(min_arm_factor, d(phi), d(theta))
u = random.uniform(min_arm_factor, 1) * arm_length
target.x = u * sin(theta) * cos(phi)
target.y = u * sin(theta) * sin(phi)
target.z = u * cos(theta)
target += shoulder_pose
temp_rota = rota(f'upperarm_{s}') + rota(f'lowerarm_{s}')
# target = targets[s]
location = get_head_pose(bone.name, subject)
# print(location)
# print(target)
bpy.ops.transform.translate(value=target - location)
bpy.ops.object.mode_set(mode='OBJECT')
if __name__ == '__main__':
subject = get_object('Subject')
import random
from math import pi
import bpy
import numpy as np
from mathutils import Matrix, Vector, Euler
# Maths utils
def rd(a, d):
if type(a) in [float, int]:
if d:
return int(180 * a / pi)
else:
return pi * a / 180.
else:
try:
iterator = iter(a)
except TypeError as te:
raise ValueError('Cant convert to radians ', a)
return type(a)([r(k) for k in a])
def r(a):
return rd(a, d=False)
def d(a):
return rd(a, d=True)
def dist(a, b):
return sum([k ** 2 for k in (a - b)]) ** 0.5
# Blender utils
C = bpy.context
D = bpy.data
def hide_object(obj, hide=True):
for o in obj.children:
hide_object(o, hide=hide)
obj.hide_set(hide)
obj.hide_select = hide
if 'Plane' in obj.name or 'OUT_' in obj.name:
obj.hide_render = True
obj.hide_set(True)
else:
obj.hide_render = hide
def get_object(name):
return bpy.data.objects[name]
def unselect_all():
select_only(None)
def select_only(obj=None):
for ob in bpy.data.objects:
ob.select_set(False)
if obj is None:
return
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
def select_only_bone(armature, bone=None):
for bo in armature.bones:
bo.select = False
if bone is None:
return
bone.select = True
def select_only_edit_bone(armature, bone=None):
for bo in armature.edit_bones:
bo.select = False
bo.select_head = False
bo.select_tail = False
if bone is None:
return
bone.select = True
bone.select_head = True
bone.select_tail = True
armature.edit_bones.active = bone
def get_head_pose(bone, struct='Subject'):
if isinstance(struct, str):
struct = get_object(struct)
current_mode = bpy.context.mode
bpy.ops.object.mode_set(mode='OBJECT')
matrix_world = struct.matrix_world
bpy.ops.object.mode_set(mode=current_mode)
return (matrix_world @ struct.pose.bones[bone].head.to_4d()).to_3d()
# Other utils
def mat_to_list(mat):
return np.array(mat).tolist()
class Randomizer:
def __init__(self, objects):
self.objects = objects
for obj in self:
hide_object(obj)
def to_list(self):
return [obj if isinstance(obj, bpy.types.Object) else obj.model for obj in self.objects]
def __iter__(self):
return iter(self.to_list())
def __getitem__(self, item):
return self.to_list()[item]
def __len__(self):
return len(self.objects)
def get(self, pick_idx, *args, **kwargs):
pick = self.objects[pick_idx]
pick = pick if isinstance(pick, bpy.types.Object) else pick(*args, **kwargs)
self.swap_object(self[pick_idx])
return pick
def __call__(self, *args, **kwargs):
pick_idx = random.randint(0, len(self) - 1)
return self.get(pick_idx)
def swap_object(self, obj=None):
for o in self:
if not o.hide_get():
hide_object(o)
if obj is not None:
hide_object(obj, hide=False)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment