Example #1
0
    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
Example #2
0
 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
Example #3
0
    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
Example #4
0
 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
Example #6
0
    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)
Example #7
0
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)
Example #8
0
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