def set_parent(blender_object:bpy.types.Object,parent_info:ParentInfo)->None: assert isinstance(blender_object,bpy.types.Object) blender_object.parent = parent_info.parent blender_object.parent_type = parent_info.parent_type if parent_info.parent_type == _BONE: assert parent_info.parent.type == _ARMATURE and\ parent_info.parent.data.bones.get(parent_info.parent_bone) is not None blender_object.parent_bone = parent_info.parent_bone
def check_pose_for_object(obj: bpy.types.Object, position: mathutils.Vector, rotation: mathutils.Vector, bvh_cache: dict, objects_to_check_against: list, list_of_objects_with_no_inside_check: list): """ Checks if a object placed at the given pose intersects with any object given in the list. The bvh_cache adds all current objects to the bvh tree, which increases the speed. If an object is already in the cache it is removed, before performing the check. :param obj: Object which should be checked. Type: :class:`bpy.types.Object` :param position: 3D Vector of the location of the object. Type: :class:`mathutils.Vector` :param rotation: 3D Vector of the rotation in euler angles. If this is None, the rotation is not changed \ Type: :class:`mathutils.Vector` :param bvh_cache: Dict of all the bvh trees, removes the `obj` from the cache before adding it again. \ Type: :class:`dict` :param objects_to_check_against: List of objects which the object is checked again \ Type: :class:`list` :param list_of_objects_with_no_inside_check: List of objects on which no inside check is performed. \ This check is only done for the objects in \ `objects_to_check_against`. Type: :class:`list` :return: Type: :class:`bool`, True if no collision was found, false if at least one collision was found """ # assign it a new pose obj.location = position if rotation: obj.rotation_euler = rotation bpy.context.view_layer.update() # Remove bvh cache, as object has changed if obj.name in bvh_cache: del bvh_cache[obj.name] no_collision = True # Now check for collisions for already_placed in objects_to_check_against: # First check if bounding boxes collides intersection = check_bb_intersection(obj, already_placed) # if they do if intersection: skip_inside_check = already_placed in list_of_objects_with_no_inside_check # then check for more refined collisions intersection, bvh_cache = check_intersection( obj, already_placed, bvh_cache=bvh_cache, skip_inside_check=skip_inside_check) if intersection: no_collision = False break return no_collision
def extract_plane_from_room(obj: bpy.types.Object, used_split_height: float, up_vec: mathutils.Vector, new_name_for_obj: str): """ Extract a plane from the current room object. This uses the FloorExtractor Module functions :param obj: The current room object :param used_split_height: The height at which the split should be performed. Usually 0 or self.wall_height :param up_vec: The up_vec corresponds to the face.normal of the selected faces :param new_name_for_obj: This will be the new name of the created object :return: (bool, bpy.types.Object): Returns True if the object was split and also returns the object. \ Else it returns (False, None). """ compare_height = 0.15 compare_angle = math.radians(7.5) obj.select_set(True) bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='DESELECT') bm = bmesh.from_edit_mesh(mesh) bm.faces.ensure_lookup_table() # split the floor at the wall height counter = FloorExtractor.split_at_height_value( bm, used_split_height, compare_height, mathutils.Vector(up_vec), compare_angle, obj.matrix_world) # if any faces are selected split them up if counter: bpy.ops.mesh.separate(type='SELECTED') bpy.ops.object.mode_set(mode='OBJECT') bm.free() mesh.update() cur_selected_objects = bpy.context.selected_objects if cur_selected_objects: if len(cur_selected_objects) == 2: cur_selected_objects = [ o for o in cur_selected_objects if o != bpy.context.view_layer.objects.active ] cur_selected_objects[0].name = new_name_for_obj cur_created_obj = cur_selected_objects[0] else: raise Exception( "There is more than one selection after splitting, this should not happen!" ) else: raise Exception("No floor object was constructed!") bpy.ops.object.select_all(action='DESELECT') return True, cur_created_obj else: bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') return False, None
def clear_selection(mesh_obj: bpy.types.Object): """Make sure that the editor is in the following state: * OBJECT_MODE * selected: one object, the mesh object. * active: the mesh object. """ # Switch to OBJECT mode bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') # Deselect all objects # The MESH object will be the active and the only selected object mesh_obj.select_set(True) bpy.context.view_layer.objects.active = mesh_obj
def apply_ob_props(ob: bpy.types.Object, new_name: str = name) -> bpy.types.Object: ob.name = new_name ob.location = [ location[0] * 0.01, location[1] * -0.01, location[2] * 0.01 ] ob.rotation_mode = 'XYZ' ob.rotation_euler = [ radians(rotation[2]), radians(-rotation[0]), radians(-rotation[1]) ] ob.scale = scale return ob
def from_blender(self, lookup: Lookup, armature: bpy.types.Object, action: bpy.types.Action): if not armature: frame = RestFrame(self.settings) self.frames.append(frame) elif not action: frame = RestFrame(self.settings) frame.from_blender(lookup, armature) self.frames.append(frame) else: if not armature.animation_data: new_animation_data = armature.animation_data_create() else: new_animation_data = None original_action = armature.animation_data.action armature.animation_data.action = action if not original_action: pose_backup = {} for pose_bone in armature.pose.bones: pose_bone: bpy.types.PoseBone pose_backup[pose_bone] = pose_bone.matrix.copy() original_frame = bpy.context.scene.frame_current start = int(action.frame_range[0]) end = int(action.frame_range[1]) for time in range(start, end + 1): bpy.context.scene.frame_set(time) frame = PoseFrame(self.settings) frame.from_blender(lookup, armature, time) self.frames.append(frame) bpy.context.scene.frame_set(original_frame) if not original_action: for pose_bone, matrix in pose_backup.items(): pose_bone.matrix = matrix if new_animation_data: armature.animation_data_clear() else: armature.animation_data.action = original_action
def visible_face_from_vertex( vertex: bpy.types.MeshVertex, scene: bpy.types.Scene, mesh_object: bpy.types.Object) -> bpy.types.MeshPolygon: """Convert the vertex's coordinates into camera space, and check whether its coordinates are within the frustum. Then cast a ray at it to see whether it's occluded.""" cam = scene.camera cc = world_to_camera_view(scene, cam, mesh_object.matrix_world @ vertex.co) cs = cam.data.clip_start ce = cam.data.clip_end # If the vertex's screen coordinates are within camera view if 0.0 < cc.x < 1.0 and 0.0 < cc.y < 1.0 and cs < cc.z < ce: # Convert the screen coordinates to a 3D vector frame = cam.data.view_frame(scene=scene) top_left = frame[3] pixel_vector = Vector((cc.x, cc.y, top_left[2])) pixel_vector.rotate(cam.matrix_world.to_quaternion()) # Convert to target object space wmatrix_inv = mesh_object.matrix_world.inverted() origin = wmatrix_inv @ (pixel_vector + cam.matrix_world.translation) # Destination is the original vertex, in the same object space destination = wmatrix_inv @ vertex.co direction = (destination - origin).normalized() # Cast a ray from those screen coordinates to the vertex result, location, normal, index = mesh_object.ray_cast( origin, direction) if result and index > -1: # Return the face the vertex belongs to return mesh_object.data.polygons[index] return False
def setting_object_duplicate(arg_object: bpy.types.Object) -> bpy.types.Object: """指定オブジェクトを複製して複製元の名称を付けるなどの設定を行う Args: arg_object (bpy.types.Object): 複製元オブジェクト Returns: bpy.types.Object: 複製オブジェクトの参照 """ # 指定オブジェクトが存在するか確認する if arg_object == None: # 指定オブジェクトが存在しない場合は処理しない return None # オブジェクトがメッシュであるか確認する if arg_object.type != 'MESH': # 指定オブジェクトがメッシュでない場合は処理しない return None # 対象オブジェクトを複製する duplicate_object = singlecopy_object_target(arg_object=arg_object) # 複製元オブジェクトの名前を取得する base_name = arg_object.name # 複製元オブジェクトの名前を変更する arg_object.name = base_name + "_base" # 複製オブジェクトに複製元オブジェクトの名前を設定する duplicate_object.name = base_name return duplicate_object
def __get_channel_groups(blender_action: bpy.types.Action, blender_object: bpy.types.Object): targets = {} for fcurve in blender_action.fcurves: target_property = get_target_property_name(fcurve.data_path) object_path = get_target_object_path(fcurve.data_path) # find the object affected by this action if not object_path: target = blender_object else: try: target = blender_object.path_resolve(object_path) except ValueError: # if the object is a mesh and the action target path can not be resolved, we know that this is a morph # animation. if blender_object.type == "MESH": # if you need the specific shape key for some reason, this is it: # shape_key = blender_object.data.shape_keys.path_resolve(object_path) target = blender_object.data.shape_keys else: gltf2_io_debug.print_console("WARNING", "Can not export animations with target {}".format(object_path)) continue # group channels by target object and affected property of the target target_properties = targets.get(target, {}) channels = target_properties.get(target_property, []) channels.append(fcurve) target_properties[target_property] = channels targets[target] = target_properties groups = [] for p in targets.values(): groups += list(p.values()) return map(tuple, groups)
def disposable_mode(bl_obj: bpy.types.Object, mode='OBJECT'): ''' モードを変更して元のモードに戻る ''' bpy.context.view_layer.objects.active = bl_obj bl_obj.select_set(True) restore = MODE_MAP[bpy.context.mode] try: if restore != mode: bpy.ops.object.mode_set(mode=mode, toggle=False) yield None finally: if bpy.context.mode != restore: bpy.ops.object.mode_set(mode=restore, toggle=False) bl_obj.select_set(False)
def _get_shapenet_attribute(shapenet_obj: bpy.types.Object, attribute_name: str): """ Returns the value of the requested attribute for the given object. :param shapenet_obj: The ShapeNet object. :param attribute_name: The attribute name. :return: The attribute value. """ if attribute_name == "used_synset_id": return shapenet_obj.get("used_synset_id", "") elif attribute_name == "used_source_id": return shapenet_obj.get("used_source_id", "") else: return WriterUtility.get_common_attribute(shapenet_obj, attribute_name)
def __init__(self, blenderObject: bpy.types.Object) -> None: # When true, keyframes and Custom Animation Properties # are included in OBJ # True for split parent feature or not visible self.export_animation_only = blenderObject.hide_get( ) or blenderObject.hide_viewport self.blenderObject = blenderObject #This is assigned and tied together in in XPlaneBone's constructor self.xplaneBone = None # type: xplane_bone.XPlaneBone self.name = blenderObject.name # type: str self.type = self.blenderObject.type # type: str self.datarefs = {} # type: Dict[str,str] self.bakeMatrix = None # type: Optional[mathutils.Matrix] self.attributes = XPlaneAttributes() self.cockpitAttributes = XPlaneAttributes() self.animAttributes = XPlaneAttributes() self.conditions = [ ] # type: List[io_xplane2blender.xplane_props.XPlaneCondition] # This represents all specializations of lods, on this subject, # including it's parents. Set in XPlaneBone's constructor self.effective_buckets: Tuple[...] = (False, ) * 4 for i, dataref in self.blenderObject.xplane.datarefs.items(): self.datarefs[dataref.path] = dataref self.setWeight()
def object_duplicate_helper(obj: bpy.types.Object, name: str) -> bpy.types.Object: """ オブジェクトに任意の名前をつけて複製するヘルパー 複製したオブジェクトを返す """ _mode = bpy.context.mode temp_name = random_name(10) orig_name = obj.name obj.name = temp_name bpy.ops.object.duplicate({"selected_objects": [obj]}) obj.name = orig_name new_obj = bpy.data.objects[temp_name + ".001"] new_obj.name = name bpy.ops.object.mode_set(mode=_mode) new_obj.select_set(False) return new_obj
def __gather_node(channels: typing.Tuple[bpy.types.FCurve], blender_object: bpy.types.Object, export_settings, bake_bone: typing.Union[str, None], driver_obj ) -> gltf2_io.Node: if driver_obj is not None: return gltf2_blender_gather_nodes.gather_node(driver_obj, driver_obj.library.name if driver_obj.library else None, None, None, export_settings) if blender_object.type == "ARMATURE": # TODO: get joint from fcurve data_path and gather_joint if bake_bone is not None: blender_bone = blender_object.pose.bones[bake_bone] else: blender_bone = blender_object.path_resolve(channels[0].data_path.rsplit('.', 1)[0]) if isinstance(blender_bone, bpy.types.PoseBone): if export_settings["gltf_def_bones"] is False: obj = blender_object.proxy if blender_object.proxy else blender_object return gltf2_blender_gather_joints.gather_joint(obj, blender_bone, export_settings) else: bones, _, _ = gltf2_blender_gather_skins.get_bone_tree(None, blender_object) if blender_bone.name in [b.name for b in bones]: obj = blender_object.proxy if blender_object.proxy else blender_object return gltf2_blender_gather_joints.gather_jointb(obj, blender_bone, export_settings) return gltf2_blender_gather_nodes.gather_node(blender_object, blender_object.library.name if blender_object.library else None, None, None, export_settings)
def generate_evaluated_mesh(self, mesh_object: bpy.types.Object, reference_frame: mathutils.Matrix = None): if self.i3d.get_setting('apply_modifiers'): self.object = mesh_object.evaluated_get(self.i3d.depsgraph) self.logger.debug(f"is exported with modifiers applied") else: self.object = mesh_object self.logger.debug(f"is exported without modifiers applied") self.mesh = self.object.to_mesh(preserve_all_data_layers=False, depsgraph=self.i3d.depsgraph) # If a reference is given transform the generated mesh by that frame to place it somewhere else than center of # the mesh origo if reference_frame is not None: self.mesh.transform( reference_frame.inverted() @ self.object.matrix_world) conversion_matrix = self.i3d.conversion_matrix if self.i3d.get_setting('apply_unit_scale'): self.logger.debug(f"applying unit scaling") conversion_matrix = \ mathutils.Matrix.Scale(bpy.context.scene.unit_settings.scale_length, 4) @ conversion_matrix self.mesh.transform(conversion_matrix) if conversion_matrix.is_negative: self.mesh.flip_normals() self.logger.debug( f"conversion matrix is negative, flipping normals") # Calculates triangles from mesh polygons self.mesh.calc_loop_triangles() # Recalculates normals after the scaling has messed with them self.mesh.calc_normals_split()
def export_filepath(ob: bpy.types.Object, ext: str = "fbx") -> Path: """ Generates a valid export file path. Args: ob (bpy.types.Object): The object at the stem of the path. ext (str, optional): The path extension. Defaults to "fbx". Returns: Path: A WindowsPath to the export file. """ scene = bpy.context.scene obj_type = objects.get_type(ob) name = ob.name if obj_type != "COLLISION": name = f"{ob.export_prefix}{name}" name = f"{name}.{ext}" filepath = Path(bpy.path.abspath(scene.export_path)) if ob.export_subpath != "": ob.export_subpath = ob.export_subpath.lstrip("\\/") filepath = filepath / ob.export_subpath filepath = filepath / name return filepath
def export_object(self, parent: Optional[Node], o: bpy.types.Object, indent: str='')->Node: node = Node(o.name, o.matrix_world.to_translation(), parent) self.nodes.append(node) # only mesh if o.type == 'MESH': # copy new_obj = o.copy() new_obj.data = o.data.copy() bpy.data.scenes[0].objects.link(new_obj) mesh = new_obj.data # apply modifiers for m in new_obj.modifiers: if m.type == 'ARMATURE': # skin node.skin = self.get_or_create_skin(node, m.object) # export bone_names = [ b.name for b in node.skin.object.data.bones] if node.skin else [] node.mesh = self.export_mesh(mesh, o.vertex_groups, bone_names) elif o.type == 'ARMATURE': skin = self.get_or_create_skin(node, o) for child in o.children: child_node = self.export_object(node, child, indent+self.indent) node.children.append(child_node) return node
def set_object_location(self, position_properties: WorkpiecePosition, scene: bpy.types.Scene, scene_object: bpy.types.Object, selected_objects): if position_properties.origin_location == "3D cursor": scene_object.location = scene.cursor_location elif position_properties.origin_location == "position": scene_object.location = position_properties.location_coordinates elif position_properties.origin_location == "selected": if len(selected_objects) == 1: selected = selected_objects[0] scene_object.location = selected.location + \ Vector(position_properties.distance) else: self.report({'WARNING'}, "Woodworking: One object should be selected")
def change_rotation_mode(obj: bpy.types.Object, rotation_mode: str, normalized: bool = True): if rotation_mode == "rotation_axis_angle": rotation_mode = "AXIS_ANGLE" elif rotation_mode == "rotation_quaternion": rotation_mode = "QUATERNION" elif rotation_mode == "rotation_euler": rotation_mode = "XYZ" obj.rotation_mode = rotation_mode if normalized and obj.rotation_mode == "AXIS_ANGLE": axis_angle = normalize_axis_angle(vec2np(obj.rotation_axis_angle))[0] obj.rotation_axis_angle = (axis_angle[0], axis_angle[1], axis_angle[2], axis_angle[3]) elif normalized and obj.rotation_mode == "QUATERNION": quat = normalize_quaternion(vec2np(obj.rotation_quaternion))[0] obj.rotation_quaternion = (quat[0], quat[1], quat[2], quat[3])
def set_rotation(blender_object: bpy.types.Object, rotation: Any, rotation_mode: str) -> None: """ Sets the rotation of a Blender Object and takes care of picking which rotation type to give the value to """ if rotation_mode == "AXIS_ANGLE": assert len(rotation[1]) == 3 blender_object.rotation_axis_angle = rotation elif rotation_mode == "QUATERNION": assert len(rotation) == 4 blender_object.rotation_quaternion = rotation elif set(rotation_mode) == {"X", "Y", "Z"}: assert len(rotation) == 3 blender_object.rotation_euler = rotation else: assert False, "Unsupported rotation mode: " + blender_object.rotation_mode
def project_uv_normal( arg_object: bpy.types.Object) -> bpy.types.MeshUVLoopLayer: """通常のUV展開を実行する Args: arg_object (bpy.types.Object): 指定オブジェクト Returns: bpy.types.MeshUVLoopLayer: 作成UVマップレイヤーの参照 """ # 不要なオブジェクトを選択しないように # 全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態に設定する ob.select_set(False) # オブジェクトを選択状態にする arg_object.select_set(True) # 対象オブジェクトをアクティブに変更する bpy.context.view_layer.objects.active = arg_object # 編集モードに移行する bpy.ops.object.mode_set(mode='EDIT', toggle=False) # 頂点を全選択した状態とする bpy.ops.mesh.select_all(action='SELECT') # 通常のUV展開を実行する # 方式:アングルベース,穴を埋める:True,アスペクト比の補正:True, # 細分化モディファイアを使用:False,余白:0.1 bpy.ops.uv.unwrap(method='ANGLE_BASED', fill_holes=True, correct_aspect=True, use_subsurf_data=False, margin=0.1) # オブジェクトモードに移行する bpy.ops.object.mode_set(mode='OBJECT', toggle=False) # 対象オブジェクトに追加されたUVマップを取得する active_uvlayer = arg_object.data.uv_layers[-1] return active_uvlayer
def sync(rpr_context, obj: bpy.types.Object, **kwargs): """ Converts object into blender's mesh and exports it as mesh """ try: # This operation adds new mesh into bpy.data.meshes, that's why it should be removed after usage. # obj.to_mesh() could also return None for META objects. new_mesh = obj.to_mesh() log("sync", obj, new_mesh) if new_mesh: mesh.sync(rpr_context, obj, mesh=new_mesh, **kwargs) return True return False finally: # it's important to clear created mesh obj.to_mesh_clear()
def send_object_visibility(client: Client, object_: bpy.types.Object): logger.debug("send_object_visibility %s", object_.name_full) buffer = (common.encode_string(object_.name_full) + common.encode_bool(object_.hide_viewport) + common.encode_bool(object_.hide_select) + common.encode_bool(object_.hide_render) + common.encode_bool(object_.hide_get())) client.add_command( common.Command(common.MessageType.OBJECT_VISIBILITY, buffer, 0))
def GetListOfOrientationsToRender(cameraPoint: bpy.types.Object) -> list: """ From blender data, grab all objects of type Orientation which have the cameraPoint as their parent. """ parentId: str = cameraPoint.get("zag.uuid") return [ obj for obj in bpy.data.objects if obj.get("zag.type") == "Orientation" and obj.parent.get("zag.uuid") == parentId ]
def look_at(obj: bpy.types.Object, target: Vector): """Rotate an object such that it looks at a target. The object's Y axis will point upwards, and the -Z axis towards the target. This is the 'correct' rotation for cameras and lights.""" t = obj.location dir = target - t quat = dir.to_track_quat('-Z', 'Y') obj.rotation_euler = quat.to_euler()
def bake_frame(ob: bpy.types.Object, frame: int, export_obj=None): scene = bpy.context.scene shape_name = 'cache__F{:04d}'.format(frame) data_path = 'key_blocks["{}"].value'.format(shape_name) ## clean out the old keys if ob.data.shape_keys.animation_data and ob.data.shape_keys.animation_data.action: action = ob.data.shape_keys.animation_data.action for curve in action.fcurves: if curve.data_path == data_path: action.fcurves.remove(curve) break scene.frame_set(frame) mesh = ob.to_mesh(scene, apply_modifiers=True, settings="RENDER") shape = add_shape_key(ob, shape_name) for index in range(len(ob.data.vertices)): shape.data[index].co = mesh.vertices[index].co ## this keys them on for the duration of the animation shape.value = 0.0 ob.data.shape_keys.keyframe_insert(data_path, frame=frame - 1) shape.value = 1.0 ob.data.shape_keys.keyframe_insert(data_path, frame=frame) shape.value = 0.0 ob.data.shape_keys.keyframe_insert(data_path, frame=frame + 1) if export_obj: ## the blender OBJ importer adjusts for Y up in other packages ## so rotate the mesh here before export rotate_mat = mathutils.Matrix.Rotation(-math.pi / 2, 4, 'X') mesh.transform(rotate_mat) ## man the OBJ format is simple. No wonder people love it. print('+ Exporting frame {} to "{}"'.format(frame, export_obj)) with open(export_obj, 'w') as fp: for v in mesh.vertices: fp.write('v {:6f} {:6f} {:6f}\n'.format( v.co[0], v.co[1], v.co[2])) ## smoothing # fp.write( 's 1\n' ) for f in mesh.polygons: msg = "f" for v in f.vertices: msg += ' {:d}'.format(v + 1) msg += '\n' fp.write(msg) ## one extra line at the end fp.write('\n') bpy.data.meshes.remove(mesh)
def _get_camera_space_bounding_box(context: bpy.types.Context, camera_obj: bpy.types.Object, target_obj: bpy.types.Object) -> Bounds2D: # TODO support more than just meshes (esp. metaballs) if target_obj.type != 'MESH': raise Exception(f"Target object {target_obj} is not a mesh") # Get latest version of target object with modifiers such as armature applied depsgraph = context.evaluated_depsgraph_get() target_obj = target_obj.evaluated_get(depsgraph) m_obj_to_world = target_obj.matrix_world m_world_to_cam = camera_obj.rotation_euler.to_matrix().inverted() obj_verts = target_obj.to_mesh().vertices cam_verts = [m_world_to_cam @ (m_obj_to_world @ v.co) for v in obj_verts] return Bounds2D.from_points(cam_verts)
def set_object_rotation(context, position_properties: WorkpiecePosition, scene_object: bpy.types.Object): rotations = WorkpieceOperator.visible_surface_rotation( position_properties.visible_surface) rotations.extend(WorkpieceOperator.view_rotation( context, position_properties.view)) rotations.extend(WorkpieceOperator.orientation_rotation( context, position_properties.orientation, position_properties.view)) scene_object.rotation_mode = 'QUATERNION' object_rotations = scene_object.rotation_quaternion.copy() for rotation in rotations: object_rotations = WorkpieceOperator.quaternion_rotation( rotation, object_rotations) scene_object.rotation_quaternion = object_rotations
def project_uv_smart( arg_object: bpy.types.Object) -> bpy.types.MeshUVLoopLayer: """スマートUV展開を実行する Args: arg_object (bpy.types.Object): 指定オブジェクト Returns: bpy.types.MeshUVLoopLayer: 作成UVマップレイヤーの参照 """ # 不要なオブジェクトを選択しないように # 全てのオブジェクトを走査する for ob in bpy.context.scene.objects: # 非選択状態に設定する ob.select_set(False) # オブジェクトを選択状態にする arg_object.select_set(True) # 対象オブジェクトをアクティブに変更する bpy.context.view_layer.objects.active = arg_object # 編集モードに移行する bpy.ops.object.mode_set(mode='EDIT', toggle=False) # 頂点を全選択した状態とする bpy.ops.mesh.select_all(action='SELECT') # スマートUV展開を実行する # 角度制限:66,島の余白:0.1,エリアウェイト:0,アスペクト比の補正:True,UV境界に合わせる:True bpy.ops.uv.smart_project(angle_limit=66, island_margin=0.1, user_area_weight=0, use_aspect=True, stretch_to_bounds=True) # オブジェクトモードに移行する bpy.ops.object.mode_set(mode='OBJECT', toggle=False) # 対象オブジェクトに追加されたUVマップを取得する active_uvlayer = arg_object.data.uv_layers[-1] return active_uvlayer
def obj2np(obj: bpy.types.Object, dtype: type = np.float32, apply_modifier: bool = False, frame: int = bpy.context.scene.frame_current, geo_type: str = "position", is_local: bool = False, as_homogeneous: bool = False, mode: str = "dynamic") -> np.ndarray: # Input: obj(bpy.types.Object), Output: positions or normals bpy.context.scene.frame_set(frame) if type(obj.data) == bpy.types.Mesh : if apply_modifier: depsgraph = bpy.context.evaluated_depsgraph_get() obj = obj.evaluated_get(depsgraph) mesh = obj.to_mesh() return np.array([vec2np(v.co) for v in mesh.vertices], dtype=dtype) world_matrix = world_matrix2np(obj, dtype=dtype) # (4, 4) return mesh2np(obj.data, world_matrix=world_matrix, geo_type=geo_type, dtype=dtype, is_local=is_local, frame=frame, as_homogeneous=as_homogeneous) elif type(obj.data) == bpy.types.Armature: return armature2np(obj.data, dtype=dtype, mode=mode, frame=frame) else: raise NotImplementedError( f"{type(obj.data)} is not supported with obj2np")
def __gather_node(channels: typing.Tuple[bpy.types.FCurve], blender_object: bpy.types.Object, export_settings ) -> gltf2_io.Node: if blender_object.type == "ARMATURE": # TODO: get joint from fcurve data_path and gather_joint blender_bone = blender_object.path_resolve(channels[0].data_path.rsplit('.', 1)[0]) return gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings) return gltf2_blender_gather_nodes.gather_node(blender_object, export_settings)
def fix_cube_rotation(obj: bpy.types.Object): ''' Rotate the bounding box of a cuboid so it's aligned with the cube rotation. The scale and rotation of the object must be in default position for this function to work. :param obj: blender object with cuboid mesh. ''' # Get coordinates of 3 points (a,b and c) from any polygon # I'm assuming this is a cuboid so I also can assume that # vectors u and v are not planar: # u = vector(b, a) and v = (b, c) poly = obj.data.polygons[0] vertices = obj.data.vertices a = vertices[poly.vertices[0]].co b = vertices[poly.vertices[1]].co c = vertices[poly.vertices[2]].co # Calculate the normal vector of the surface with points # a, b and c u: mathutils.Vector = (a-b).normalized() v: mathutils.Vector = (c-b).normalized() # The cross product creates the 3rd vector that defines # the rotated space w = u.cross(v).normalized() # Recalculate V to make sure that all of the vectors are at # the right angle (even though they should be) v = w.cross(u).normalized() # Create rotation matrix (unit vectors x, y, z in columns) rotation_matrix = mathutils.Matrix((w, v, -u)) # (w, v, -u) - this order of normals in rotation matrix is set up in # such way that applying the operator to the default cube (without # rotations) will not change its rotation and won't flip its scale to -1. # It will have no effect. # Rotate the mesh for vertex in obj.data.vertices: vertex.co = rotation_matrix @ vertex.co # Counter rotate object around its origin counter_rotation = rotation_matrix.to_4x4().inverted() loc, rot, scl = obj.matrix_local.decompose() loc_mat = mathutils.Matrix.Translation(loc) rot_mat = rot.to_matrix().to_4x4() scl_mat = ( mathutils.Matrix.Scale(scl[0],4,(1,0,0)) @ mathutils.Matrix.Scale(scl[1],4,(0,1,0)) @ mathutils.Matrix.Scale(scl[2],4,(0,0,1))) obj.matrix_local = loc_mat @ counter_rotation @ rot_mat @ scl_mat
def from_blender(self, lookup: Lookup, armature: bpy.types.Object, object: bpy.types.Object): if object.type not in {'MESH', 'CURVE', 'SURFACE', 'FONT'}: return collection = bpy.data.collections.new('SourceOps') bpy.context.scene.collection.children.link(collection) object = object.copy() collection.objects.link(object) mod: bpy.types.TriangulateModifier = object.modifiers.new( 'Triangulate', 'TRIANGULATE') mod.min_vertices = 4 mod.quad_method = 'FIXED' mod.ngon_method = 'CLIP' mod.keep_custom_normals = True for mod in getattr(object, 'modifiers', []): if mod.type == 'ARMATURE': mod.show_viewport = False bpy.context.view_layer.update() depsgraph: bpy.types.Depsgraph = bpy.context.evaluated_depsgraph_get() evaluated: bpy.types.Object = object.evaluated_get(depsgraph) mesh = bpy.data.meshes.new_from_object(evaluated, preserve_all_data_layers=True, depsgraph=depsgraph) if not self.settings.ignore_transforms: mesh.transform(object.matrix_world) mesh.calc_normals_split() for poly in mesh.polygons: triangle = Triangle(self.settings) triangle.from_blender(lookup, armature, object, mesh, poly) self.triangles.append(triangle) mesh.free_normals_split() bpy.data.meshes.remove(mesh) bpy.data.objects.remove(object) bpy.data.collections.remove(collection)
def __gather_node(channels: typing.Tuple[bpy.types.FCurve], blender_object: bpy.types.Object, export_settings ) -> gltf2_io.Node: if blender_object.type == "ARMATURE": # TODO: get joint from fcurve data_path and gather_joint blender_bone = blender_object.path_resolve(channels[0].data_path.rsplit('.', 1)[0]) if isinstance(blender_bone, bpy.types.PoseBone): return gltf2_blender_gather_joints.gather_joint(blender_bone, export_settings) return gltf2_blender_gather_nodes.gather_node(blender_object, export_settings)
def get_select(obj: bpy.types.Object) -> bool: if IS_LEGACY: return obj.select return obj.select_get()
def set_select(obj: bpy.types.Object, select: bool) -> None: if IS_LEGACY: obj.select = select else: obj.select_set(select)
def set_hide(obj: bpy.types.Object, hide: bool): if IS_LEGACY: obj.hide = hide else: obj.hide_viewport = hide