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 ensure_object(self, data_block, name: str, object_template: bpy.types.Object = None): """Add object if it does not exist, if object_template is given new object will be copied from it""" if not self.obj: # it looks like it means only that the property group item was created newly if object_template: self.obj = object_template.copy() self.obj.data = data_block else: self.obj = bpy.data.objects.new(name=name, object_data=data_block) else: # in case if data block was changed self.obj.data = data_block
def reconstruct_object( self, original_object: bpy.types.Object, pos_size_rot: Tuple[float, float, float, float, float, float, float, float, float], tag: str = "", alter_material: bool = False, spawnbox: Optional[str] = None, ): """Recreate given blender object and its physical attributes (position, size and rotation) Parameters ---------- original_object : bpy.types.Object box : Tuple[float, float, float, float, float, float, float, float, float] pos size rotation x, y, z, w, l, h, rx, ry, rz tag : str, optional nametag, by default "" alter_material : bool, optional turn objects green transparent, by default False spawnbox : Optional[str], optional Name of spawnbox, by default None Returns ------- bpy.types.Object Possibly altered copy of given object """ x, y, z, w, l, h, rx, ry, rz = pos_size_rot new_obj = original_object.copy() new_obj.data = original_object.data.copy() new_obj.location = np.array( (x, y, z)) * (spawnbox.dimensions / 2) + spawnbox.location new_obj.dimensions = (w, l, h) new_obj.rotation_euler = np.array((rx, ry, rz)) * 2 * np.pi new_obj.name += tag new_obj.show_bounds = True new_obj.show_name = True if alter_material: # Assuming original_object has one material slot, the line below is the same as # new_obj.material_slots[0].material = _og_obj.active_material.copy() new_material = original_object.active_material.copy() new_material = make_fish_colored_transparent(new_material) new_obj.active_material = new_material # Link to target collection self.target_collection.objects.link(new_obj) return new_obj
def recreate_object(self, object_template: bpy.types.Object = None): """ Object will be replaced by new object recreated from scratch or copied from given object_template if given Previous object will be removed, data block remains unchanged """ # in case recreated object should have a chance to get the same name of previous object # previous object should be deleted first data_block = self.obj.data obj_name = self.obj.name bpy.data.objects.remove(self.obj) if object_template: new_obj = object_template.copy() new_obj.data = data_block else: new_obj = bpy.data.objects.new(name=obj_name, object_data=data_block) self.obj = new_obj
def singlecopy_object_target(arg_object: bpy.types.Object) -> bpy.types.Object: """指定オブジェクトをしてシングルユーザ化する Args: arg_object (bpy.types.Object): 指定オブジェクト Returns: bpy.types.Object: 複製オブジェクトの参照 """ # オブジェクトを複製する duplicatob = arg_object.copy() # オブジェクトのメッシュデータを取得する # IDアクセスのマニュアル # (https://docs.blender.org/api/current/bpy.types.ID.html) mesh = duplicatob.data # メッシュの参照ユーザ数を取得する user_count = mesh.users # 複数のユーザが参照しているか確認する if user_count > 1: # シングルユーザ化するため、メッシュのコピーを作成して参照する duplicatob.data = mesh.copy() # 複製先のコレクションの参照 target_collection = None # 全てのコレクションを操作する for collection in bpy.data.collections: # コレクション内に複製元オブジェクトが含まれるか確認する if arg_object.name in collection.objects: # 複製元オブジェクトのコレクションを複製先とする target_collection = collection # 複製先のコレクションが存在するか確認する if target_collection != None: # 複製したオブジェクトをシーンの複製先コレクションにリンクする target_collection.objects.link(duplicatob) else: # 複製先のコレクションが見つからない場合はコンテキストにリンクする bpy.context.scene.collection.objects.link(duplicatob) return duplicatob
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 apply_positions(obj: bpy.types.Object, positions: list): """ Duplicates (linked duplicate) the given object onto the given positions applies the given rotation Args: obj: object to duplicate, origin should be in (0, 0, 0) positions: list(tuple(tuple(x,y,z), rot)) - object positions and rotations Returns: """ for position in positions: dup = obj.copy() # move it dup.location.x = position[0][0] dup.location.y = position[0][1] dup.location.z = position[0][2] # rotate it dup.rotation_euler.z = position[1] # link it to the scene bpy.context.scene.objects.link(dup)
def convert_object(settings: typing.Any, obj: bpy.types.Object): '''Convert single object to solids for VMF export.''' # Make sure object is mesh if obj.type != 'MESH': print(f'Skipping {obj.name} because it is not a mesh') return [] # Make sure all polygons are quads if any(len(polygon.vertices) != 4 for polygon in obj.data.polygons): print(f'Skipping {obj.name} because all faces must be quads') return [] # Get levels and width levels, width = get_levels_and_width(obj) # Make sure multires or subsurf mod exists if levels == -1: print(f'Skipping {obj.name} because no multires or subsurf modifier was found') return [] # Make sure subdivision levels are in range if not (2 <= levels <= 4): print(f'Skipping {obj.name} because subdivision levels must be 2, 3, or 4') return [] # Calculate matrix and space for transform matrix, space = get_matrix_and_space(obj, settings.geometry_scale) # Setup subd object and mesh obj_subd = obj.copy() setup_subd_mesh(obj_subd, matrix, space) # Align subd mesh verts to grid if settings.align_to_grid: align_to_grid(obj_subd) # Setup new UV layer, face maps, and subsurf modifier uv_layer = setup_uv_layer(obj_subd) face_maps = setup_face_maps(obj_subd) mod_subd = setup_subd_mod(obj_subd, levels) # Get evaluated dependency graph depsgraph = bpy.context.evaluated_depsgraph_get() # Create bmesh from subdivided object bm_subd = bmesh.new() bm_subd.from_object(obj_subd, depsgraph) # Get uv layer and face map layer from subd bmesh uv_subd = bm_subd.loops.layers.uv.verify() fm_subd = bm_subd.faces.layers.face_map.verify() # Create bmesh from sculpted object and transform it bm_mres = bmesh.new() bm_mres.from_object(obj, depsgraph) bmesh.ops.transform(bm_mres, matrix=matrix, space=space, verts=bm_mres.verts) # Create bmesh from base mesh bm_base = bmesh.new() bm_base.from_mesh(obj.data) # Setup displacements list displacements = [{ 'levels': levels, 'corners': [None for i in range(8)], 'normals': [[None for x in range(width + 1)] for y in range(width + 1)], 'lengths': [[None for x in range(width + 1)] for y in range(width + 1)], 'material': 'dev/dev_blendmeasure'.upper(), } for face_map in face_maps] # Populate displacements with data from subd and mres faces for face_subd, face_mres in zip(bm_subd.faces, bm_mres.faces): # Get index for this displacement z = face_subd[fm_subd] # Iterate through loops of each subd and mres face for loop_subd, loop_mres in zip(face_subd.loops, face_mres.loops): # Get row and column for this point uv = loop_subd[uv_subd].uv y = round(uv[1] * width) x = round(uv[0] * width) # Get verts for these loops vert_subd = loop_subd.vert vert_mres = loop_mres.vert # Calculate and write data for this point vector = vert_mres.co - vert_subd.co data = displacements[z]['normals'][y][x] = vector.normalized() data = displacements[z]['lengths'][y][x] = vector.length # If this is a corner, store its position if x == 0 and y == 0: displacements[z]['corners'][0] = vert_subd.co.copy() displacements[z]['corners'][4] = vert_subd.co - face_subd.normal * 8 elif x == width and y == 0: displacements[z]['corners'][1] = vert_subd.co.copy() displacements[z]['corners'][5] = vert_subd.co - face_subd.normal * 8 elif x == width and y == width: displacements[z]['corners'][2] = vert_subd.co.copy() displacements[z]['corners'][6] = vert_subd.co - face_subd.normal * 8 elif x == 0 and y == width: displacements[z]['corners'][3] = vert_subd.co.copy() displacements[z]['corners'][7] = vert_subd.co - face_subd.normal * 8 # Check if mesh has materials if obj.data.materials: # Get displacement materials from base mesh faces for displacement, face in zip(displacements, bm_base.faces): material = obj.data.materials[face.material_index] # Use material if it exists if material: displacement['material'] = material.name.upper() # Setup solids list solids = [] # Iterate through displacements for displacement in displacements: # Get brush corners v1, v2, v3, v4, v5, v6, v7, v8 = displacement['corners'] # Create brush sides f1 = pyvmf.Side(dic={'plane': f'({v1.x} {v1.y} {v1.z}) ({v3.x} {v3.y} {v3.z}) ({v2.x} {v2.y} {v2.z})'}) # Top f2 = pyvmf.Side(dic={'plane': f'({v7.x} {v7.y} {v7.z}) ({v5.x} {v5.y} {v5.z}) ({v6.x} {v6.y} {v6.z})'}) # Bottom f3 = pyvmf.Side(dic={'plane': f'({v4.x} {v4.y} {v4.z}) ({v7.x} {v7.y} {v7.z}) ({v3.x} {v3.y} {v3.z})'}) # Front f4 = pyvmf.Side(dic={'plane': f'({v6.x} {v6.y} {v6.z}) ({v1.x} {v1.y} {v1.z}) ({v2.x} {v2.y} {v2.z})'}) # Back f5 = pyvmf.Side(dic={'plane': f'({v3.x} {v3.y} {v3.z}) ({v6.x} {v6.y} {v6.z}) ({v2.x} {v2.y} {v2.z})'}) # Right f6 = pyvmf.Side(dic={'plane': f'({v1.x} {v1.y} {v1.z}) ({v8.x} {v8.y} {v8.z}) ({v4.x} {v4.y} {v4.z})'}) # Left # Set U axis, V axis, lightmap scale, and material for top face f1.uaxis, f1.vaxis = calc_uv_axes(v1, v3, v2, settings.texture_scale) f1.lightmapscale = settings.lightmap_scale f1.material = displacement['material'] # Prepare dispinfo dictionary power = displacement['levels'] startposition = f'[{v1.x} {v1.y} {v1.z}]' dic = {'power': power, 'startposition': startposition} # Prepare dispinfo children normals = pyvmf.Child('normals', {}) distances = pyvmf.Child('distances', {}) children = [normals, distances] # Populate dispinfo normals for index, row in enumerate(displacement['normals']): normals.dic[f'row{index}'] = ' '.join(f'{x} {y} {z}' for x, y, z in row) # Populate dispinfo distances for index, row in enumerate(displacement['lengths']): distances.dic[f'row{index}'] = ' '.join(f'{length}' for length in row) # Create dispinfo for top face f1.dispinfo = pyvmf.DispInfo(dic=dic, children=children) # Create solid with sides and append it solid = pyvmf.Solid() solid.add_sides(f1, f2, f3, f4, f5, f6) solid.editor = pyvmf.Editor() solids.append(solid) # Free bmeshes bm_base.free() bm_mres.free() bm_subd.free() # Remove temporary object and mesh, in that order mesh_subd = obj_subd.data bpy.data.objects.remove(obj_subd) bpy.data.meshes.remove(mesh_subd) # Return generated solids return solids