def transform_mesh(mesh: bpy.types.Mesh, matrix: mathutils.Matrix): # There is a disparity in terms of how negative scaling is displayed in Blender versus how it is # applied (Ctrl+A) in that the normals are different. Even though negative scaling is evil, we # prefer to match the visual behavior, not the non-intuitive apply behavior. So, we'll need to # flip the normals if the scaling is negative. The Blender documentation even "helpfully" warns # us about this. mesh.transform(matrix) if matrix.is_negative: mesh.flip_normals()
def delete_mesh(mesh: bpy.types.Mesh, clear_users=True): if mesh.users > 0: print( f"Not deleting mesh '{mesh.name}' because it still has " f"{mesh.users} users." ) return False try: if clear_users: mesh.user_clear() bpy.data.meshes.remove(mesh) return True except Exception as e: raise Warning(f"Mesh '{mesh.name}' not deleted because of this " f"exception: {e}") return False
def gen_mesh_wall(context: bpy.types.Context, wall_loops: list, section_mesh: bpy.types.Mesh) -> bpy.types.Object: """ Creates the wall object All walls will be generated, and there is no need to duplicate/move them Args: context: bpy.types.Context wall_loops: list(list(tuple(x,y,z))) - list of wall loops, result of gen_layout. section_mesh: cross section/side profile of the wall Returns: The wall object """ bm = bmesh.new() for loop in wall_loops: mesh = Utils.extrude_along_edges(section_mesh.copy(), loop, False) bm.from_mesh(mesh) # end for # check if the object for walls already exists obj = bpy.data.objects.get("PBGWalls") if obj is not None: context.scene.objects.unlink(obj) bpy.data.objects.remove(obj) # end if m = bpy.data.meshes.new("PBGWall") bm.to_mesh(m) bm.free() # link the created object to the scene obj = bpy.data.objects.new("PBGWalls", m) context.scene.objects.link(obj) return obj
def update_mesh_data(mesh: bpy.types.Mesh): global mario_geo vcol = mesh.vertex_colors.active for i in range(mario_geo.numTrianglesUsed): mesh.vertices[3 * i + 0].co.x = mario_geo.position_data[9 * i + 0] / SM64_SCALE_FACTOR mesh.vertices[3 * i + 0].co.z = mario_geo.position_data[9 * i + 1] / SM64_SCALE_FACTOR mesh.vertices[ 3 * i + 0].co.y = -mario_geo.position_data[9 * i + 2] / SM64_SCALE_FACTOR mesh.vertices[3 * i + 1].co.x = mario_geo.position_data[9 * i + 3] / SM64_SCALE_FACTOR mesh.vertices[3 * i + 1].co.z = mario_geo.position_data[9 * i + 4] / SM64_SCALE_FACTOR mesh.vertices[ 3 * i + 1].co.y = -mario_geo.position_data[9 * i + 5] / SM64_SCALE_FACTOR mesh.vertices[3 * i + 2].co.x = mario_geo.position_data[9 * i + 6] / SM64_SCALE_FACTOR mesh.vertices[3 * i + 2].co.z = mario_geo.position_data[9 * i + 7] / SM64_SCALE_FACTOR mesh.vertices[ 3 * i + 2].co.y = -mario_geo.position_data[9 * i + 8] / SM64_SCALE_FACTOR mesh.uv_layers.active.data[mesh.loops[3 * i + 0].index].uv = ( mario_geo.uv_data[6 * i + 0], mario_geo.uv_data[6 * i + 1]) mesh.uv_layers.active.data[mesh.loops[3 * i + 1].index].uv = ( mario_geo.uv_data[6 * i + 2], mario_geo.uv_data[6 * i + 3]) mesh.uv_layers.active.data[mesh.loops[3 * i + 2].index].uv = ( mario_geo.uv_data[6 * i + 4], mario_geo.uv_data[6 * i + 5]) vcol.data[3 * i + 0].color = (mario_geo.color_data[9 * i + 0], mario_geo.color_data[9 * i + 1], mario_geo.color_data[9 * i + 2], 1.0) vcol.data[3 * i + 1].color = (mario_geo.color_data[9 * i + 3], mario_geo.color_data[9 * i + 4], mario_geo.color_data[9 * i + 5], 1.0) vcol.data[3 * i + 2].color = (mario_geo.color_data[9 * i + 6], mario_geo.color_data[9 * i + 7], mario_geo.color_data[9 * i + 8], 1.0) mesh.update()
def export_mesh(self, mesh: bpy.types.Mesh, vertex_groups: List[bpy.types.VertexGroup], bone_names: List[str])->MeshStore: def get_texture_layer(layers): for l in layers: if l.active: return l mesh.update(calc_tessface=True) uv_texture_faces = get_texture_layer(mesh.tessface_uv_textures) store = MeshStore(mesh.name, mesh.vertices, mesh.materials, vertex_groups, bone_names) for i, face in enumerate(mesh.tessfaces): submesh = store.get_or_create_submesh(face.material_index) for triangle in store.add_face(face, uv_texture_faces.data[i] if uv_texture_faces else None): for fv in triangle: submesh.indices.append(fv) self.mesh_stores.append(store) return store
def __init__(self, mesh: bpy.types.Mesh, o2w: mathutils.Matrix): self.name = mesh.name self.bbox = BBox() self.o2p = Mat4() self.verts = [] self.colrs = [] self.norms = [] self.tex0s = [] self.nuggets = [] # TODO # Handle nested meshes # Handle normal generation # If the mesh has no geometry... if not mesh.polygons: return # Determine the geometry format geom = EGeom.Vert | EGeom.Norm geom = (geom | EGeom.Colr) if len(mesh.vertex_colors) != 0 else geom geom = (geom | EGeom.Tex0) if mesh.uv_layers.get("UVMap") != None else geom # Initialise the verts lists self.verts = [Vec3] * len(mesh.vertices) self.norms = [Vec3] * len(mesh.vertices) for i, v in enumerate(mesh.vertices): self.verts[i] = self.bbox.encompass(Vec3(v.co.x, v.co.y, v.co.z)) self.norms[i] = Vec3(v.normal.x, v.normal.y, v.normal.z) # Initialise the colours if geom & EGeom.Colr: vertex_colors_layer0 = mesh.vertex_colors[0].data self.colrs = [Col4] * len(vertex_colors_layer0) for i, c in enumerate(vertex_colors_layer0): self.colrs[i] = Col4(c[0], c[1], c[2], c[3]) # Initialise the UVs if geom & EGeom.Tex0: uvs = mesh.uv_layers.get("UVMap").data self.tex0s = [Vec2] * len(uvs) for i, t in enumerate(uvs): self.tex0s[i] = Vec2(t.uv.x, t.uv.y) # Set the stride based on the number of verts stride = 2 if len(self.verts) < 0x10000 else 4 # Ensure the polygons have been triangulated if not mesh.loop_triangles: mesh.calc_loop_triangles() # Partition the triangles by material index nuggets = {} for tri in mesh.loop_triangles: if not tri.material_index in nuggets: nuggets[tri.material_index] = [] nuggets[tri.material_index].extend(tri.vertices) # Create nuggets for each material for mat_idx, vidx in nuggets.items(): if len(vidx) == 0: continue # Find the material, fallback to a default empty material mat = mesh.materials[mat_idx] if mat_idx < len( mesh.materials) else { "name": "null" } # bpy.data.materials[0] # Save a nugget for the geometry that uses this material nugget = Nugget(topo=ETopo.TriList, geom=geom, mat=mat.name, stride=stride, vidx=vidx) self.nuggets.append(nugget) # Object transform self.o2p = Mat4( Vec3(o2w[0][0], o2w[0][1], o2w[0][2]), Vec3(o2w[1][0], o2w[1][1], o2w[1][2]), Vec3(o2w[2][0], o2w[2][1], o2w[2][2]), Vec3(o2w.translation[0], o2w.translation[1], o2w.translation[2])) return
def create_mesh_geometry(mesh: bpy.types.Mesh) -> List[GeometrySegment]: """ Creates a list of GeometrySegment objects from a Blender mesh. Does NOT create triangle strips in the GeometrySegment however. """ if mesh.has_custom_normals: mesh.calc_normals_split() mesh.validate_material_indices() mesh.calc_loop_triangles() material_count = max(len(mesh.materials), 1) segments: List[GeometrySegment] = [ GeometrySegment() for i in range(material_count) ] vertex_cache: List[Dict[Tuple[float], int]] = [dict() for i in range(material_count)] vertex_remap: List[Dict[Tuple[int, int], int]] = [dict() for i in range(material_count)] polygons: List[Set[int]] = [set() for i in range(material_count)] if mesh.vertex_colors.active is not None: for segment in segments: segment.colors = [] for segment, material in zip(segments, mesh.materials): segment.material_name = material.name def add_vertex(material_index: int, vertex_index: int, loop_index: int, use_smooth_normal: bool, face_normal: Vector) -> int: nonlocal segments, vertex_remap vertex_cache_miss_index = -1 segment = segments[material_index] cache = vertex_cache[material_index] remap = vertex_remap[material_index] vertex_normal: Vector if use_smooth_normal or mesh.use_auto_smooth: if mesh.has_custom_normals: vertex_normal = mesh.loops[loop_index].normal else: vertex_normal = mesh.vertices[vertex_index].normal else: vertex_normal = face_normal def get_cache_vertex(): yield mesh.vertices[vertex_index].co.x yield mesh.vertices[vertex_index].co.y yield mesh.vertices[vertex_index].co.z yield vertex_normal.x yield vertex_normal.y yield vertex_normal.z if mesh.uv_layers.active is not None: yield mesh.uv_layers.active.data[loop_index].uv.x yield mesh.uv_layers.active.data[loop_index].uv.y if segment.colors is not None: for v in mesh.vertex_colors.active.data[loop_index].color: yield v vertex_cache_entry = tuple(get_cache_vertex()) cached_vertex_index = cache.get(vertex_cache_entry, vertex_cache_miss_index) if cached_vertex_index != vertex_cache_miss_index: remap[(vertex_index, loop_index)] = cached_vertex_index return cached_vertex_index new_index: int = len(segment.positions) cache[vertex_cache_entry] = new_index remap[(vertex_index, loop_index)] = new_index segment.positions.append( convert_vector_space(mesh.vertices[vertex_index].co)) segment.normals.append(convert_vector_space(vertex_normal)) if mesh.uv_layers.active is None: segment.texcoords.append(Vector((0.0, 0.0))) else: segment.texcoords.append( mesh.uv_layers.active.data[loop_index].uv.copy()) if segment.colors is not None: segment.colors.append( list(mesh.vertex_colors.active.data[loop_index].color)) return new_index for tri in mesh.loop_triangles: polygons[tri.material_index].add(tri.polygon_index) segments[tri.material_index].triangles.append([ add_vertex(tri.material_index, tri.vertices[0], tri.loops[0], tri.use_smooth, tri.normal), add_vertex(tri.material_index, tri.vertices[1], tri.loops[1], tri.use_smooth, tri.normal), add_vertex(tri.material_index, tri.vertices[2], tri.loops[2], tri.use_smooth, tri.normal) ]) for segment, remap, polys in zip(segments, vertex_remap, polygons): for poly_index in polys: poly = mesh.polygons[poly_index] segment.polygons.append([ remap[(v, l)] for v, l in zip(poly.vertices, poly.loop_indices) ]) return segments
def init_from_mesh(mesh: bpy.types.Mesh, calc_area=False, obj=None): """ Returns MeshData from bpy.types.Mesh """ # Looks more like Blender's bug that we have to check that mesh has calc_normals_split(). # It is possible after deleting corresponded object with such mesh from the scene. if not hasattr(mesh, 'calc_normals_split'): log.warn("No calc_normals_split() in mesh", mesh) return None # preparing mesh to export mesh.calc_normals_split() mesh.calc_loop_triangles() # getting mesh export data tris_len = len(mesh.loop_triangles) if tris_len == 0: return None data = MeshData() data.vertices = get_data_from_collection(mesh.vertices, 'co', (len(mesh.vertices), 3)) len_loop_triangles = len(mesh.loop_triangles) data.normals = get_data_from_collection(mesh.loop_triangles, 'split_normals', (len_loop_triangles * 3, 3)) data.uvs = [] data.uv_indices = [] primary_uv = mesh.rpr.primary_uv_layer if primary_uv: uvs = get_data_from_collection(primary_uv.data, 'uv', (len(primary_uv.data), 2)) uv_indices = get_data_from_collection(mesh.loop_triangles, 'loops', (len_loop_triangles * 3, ), np.int32) if len(uvs) > 0: data.uvs.append(uvs) data.uv_indices.append(uv_indices) secondary_uv = mesh.rpr.secondary_uv_layer(obj) if secondary_uv: uvs = get_data_from_collection(secondary_uv.data, 'uv', (len(secondary_uv.data), 2)) if len(uvs) > 0: data.uvs.append(uvs) data.uv_indices.append(uv_indices) data.num_face_vertices = np.full((tris_len, ), 3, dtype=np.int32) data.vertex_indices = get_data_from_collection( mesh.loop_triangles, 'vertices', (len_loop_triangles * 3, ), np.int32) data.normal_indices = np.arange(tris_len * 3, dtype=np.int32) if calc_area: data.area = sum(tri.area for tri in mesh.loop_triangles) # set active vertex color map if mesh.vertex_colors.active: color_data = mesh.vertex_colors.active.data # getting vertex colors and its indices (the same as uv_indices) colors = get_data_from_collection(color_data, 'color', (len(color_data), 4)) color_indices = data.uv_indices[0] if (data.uv_indices is not None and len(data.uv_indices) > 0) else \ get_data_from_collection(mesh.loop_triangles, 'loops', (len_loop_triangles * 3,), np.int32) # preparing vertex_color buffer with the same size as vertices and # setting its data by indices from vertex colors if colors[color_indices].size > 0: data.vertex_colors = np.zeros((len(data.vertices), 4), dtype=np.float32) data.vertex_colors[data.vertex_indices] = colors[color_indices] return data
def decode_base_mesh(client, obj: bpy.types.Object, mesh: bpy.types.Mesh, data, index): bm = bmesh.new() position_count, index = common.decode_int(data, index) logger.debug("Reading %d vertices", position_count) for _pos_idx in range(position_count): co, index = common.decode_vector3(data, index) bm.verts.new(co) bm.verts.ensure_lookup_table() index = decode_bmesh_layer(data, index, bm.verts.layers.bevel_weight, bm.verts, decode_layer_float) edge_count, index = common.decode_int(data, index) logger.debug("Reading %d edges", edge_count) edges_data = struct.unpack(f"{edge_count * 4}I", data[index:index + edge_count * 4 * 4]) index += edge_count * 4 * 4 for edge_idx in range(edge_count): v1 = edges_data[edge_idx * 4] v2 = edges_data[edge_idx * 4 + 1] edge = bm.edges.new((bm.verts[v1], bm.verts[v2])) edge.smooth = bool(edges_data[edge_idx * 4 + 2]) edge.seam = bool(edges_data[edge_idx * 4 + 3]) index = decode_bmesh_layer(data, index, bm.edges.layers.bevel_weight, bm.edges, decode_layer_float) index = decode_bmesh_layer(data, index, bm.edges.layers.crease, bm.edges, decode_layer_float) face_count, index = common.decode_int(data, index) logger.debug("Reading %d faces", face_count) for _face_idx in range(face_count): material_idx, index = common.decode_int(data, index) smooth, index = common.decode_bool(data, index) vert_count, index = common.decode_int(data, index) face_vertices = struct.unpack(f"{vert_count}I", data[index:index + vert_count * 4]) index += vert_count * 4 verts = [bm.verts[i] for i in face_vertices] face = bm.faces.new(verts) face.material_index = material_idx face.smooth = smooth index = decode_bmesh_layer(data, index, bm.faces.layers.face_map, bm.faces, decode_layer_int) index = decode_bmesh_layer(data, index, bm.loops.layers.uv, loops_iterator(bm), decode_layer_uv) index = decode_bmesh_layer(data, index, bm.loops.layers.color, loops_iterator(bm), decode_layer_color) bm.normal_update() bm.to_mesh(mesh) bm.free() # Load shape keys shape_keys_count, index = common.decode_int(data, index) obj.shape_key_clear() if shape_keys_count > 0: logger.debug("Loading %d shape keys", shape_keys_count) shapes_keys_list = [] for _i in range(shape_keys_count): shape_key_name, index = common.decode_string(data, index) shapes_keys_list.append(obj.shape_key_add(name=shape_key_name)) for i in range(shape_keys_count): shapes_keys_list[i].vertex_group, index = common.decode_string( data, index) for i in range(shape_keys_count): relative_key_name, index = common.decode_string(data, index) shapes_keys_list[i].relative_key = obj.data.shape_keys.key_blocks[ relative_key_name] for i in range(shape_keys_count): shape_key = shapes_keys_list[i] shape_key.mute, index = common.decode_bool(data, index) shape_key.value, index = common.decode_float(data, index) shape_key.slider_min, index = common.decode_float(data, index) shape_key.slider_max, index = common.decode_float(data, index) shape_key_data_size, index = common.decode_int(data, index) for i in range(shape_key_data_size): shape_key.data[i].co = Vector( struct.unpack("3f", data[index:index + 3 * 4])) index += 3 * 4 obj.data.shape_keys.use_relative, index = common.decode_bool( data, index) # Vertex Groups vg_count, index = common.decode_int(data, index) obj.vertex_groups.clear() for _i in range(vg_count): vg_name, index = common.decode_string(data, index) vertex_group = obj.vertex_groups.new(name=vg_name) vertex_group.lock_weight, index = common.decode_bool(data, index) vg_size, index = common.decode_int(data, index) for _elmt_idx in range(vg_size): vert_idx, index = common.decode_int(data, index) weight, index = common.decode_float(data, index) vertex_group.add([vert_idx], weight, "REPLACE") # Normals mesh.use_auto_smooth, index = common.decode_bool(data, index) mesh.auto_smooth_angle, index = common.decode_float(data, index) has_custom_normal, index = common.decode_bool(data, index) if has_custom_normal: normals = [] for _loop in mesh.loops: normal, index = common.decode_vector3(data, index) normals.append(normal) mesh.normals_split_custom_set(normals) # UV Maps and Vertex Colors are added automatically based on layers in the bmesh # We just need to update their name and active_render state: # UV Maps for uv_layer in mesh.uv_layers: uv_layer.name, index = common.decode_string(data, index) uv_layer.active_render, index = common.decode_bool(data, index) # Vertex Colors for vertex_colors in mesh.vertex_colors: vertex_colors.name, index = common.decode_string(data, index) vertex_colors.active_render, index = common.decode_bool(data, index) return index