예제 #1
0
 def load_textures(self) -> None:
     if self.vmt_importer is None:
         return
     if self.vmffs is None:
         raise Exception("cannot import materials: file system not defined")
     self.material_openers = {}
     for texture in self.mdl.file_data.textures:
         try:
             fp = self.vmffs.open_file_utf8(
                 "materials" / vmf_path(texture.path_file_name + ".vmt"))
         except FileNotFoundError:
             pass
         else:
             self.material_openers[texture.path_file_name.lower()] = fp
         for tex_path in self.mdl.file_data.texture_paths:
             if tex_path != "" and (tex_path[0] == '/'
                                    or tex_path[0] == '\\'):
                 tex_path = tex_path[1:]
             try:
                 fp = self.vmffs.open_file_utf8(
                     "materials" / vmf_path(tex_path) /
                     (texture.path_file_name.lower() + ".vmt"))
             except FileNotFoundError:
                 pass
             else:
                 self.material_openers[texture.path_file_name.lower()] = fp
예제 #2
0
 def execute(self, context: bpy.types.Context) -> set:
     self.existingBones = []  # type: ignore
     self.num_files_imported = 0
     self._missing_materials: Set[str] = set()
     self._cdmaterials = [vmf_path("")]
     # figure what the material dir should be for the qc
     with open(self.filepath, 'r') as fp:
         for match in _CDMATERIALS_REGEX.finditer(fp.read()):
             self._cdmaterials.append(vmf_path(match.group(1)))
     self.readQC(self.filepath, False, True, False, 'XYZ', outer_qc=True)
     return {'FINISHED'}
예제 #3
0
 def getMeshMaterial(self, mat_name: str) -> Tuple[bpy.types.Material, int]:
     mat_name = mat_name.lower().lstrip()
     if self.vmt_importer is None or not mat_name or mat_name == "phy":
         return super().getMeshMaterial(mat_name)
     smd = self.smd
     md: bpy.types.Mesh = smd.m.data
     # search for material file
     mat_name_path = vmf_path(mat_name + ".vmt")
     for mat_dir in self._cdmaterials:
         mat_path = "materials" / mat_dir / mat_name_path
         if mat_path in self.vmf_fs:
             break
     else:
         if mat_name not in self._missing_materials:
             print(f"WARNING: MISSING MATERIAL: {mat_path}")
             self._missing_materials.add(mat_name)
         return super().getMeshMaterial(mat_name)
     data = self.vmt_importer.load(
         mat_name, lambda: VMT(
             self.vmf_fs.open_file_utf8(mat_path),
             self.vmf_fs,
             allow_patch=True,
         ))
     mat_ind = md.materials.find(data.material.name)
     if mat_ind == -1:
         mat_ind = len(md.materials)
         md.materials.append(data.material)
     return data.material, mat_ind
예제 #4
0
 def execute(self, context: bpy.types.Context) -> set:
     self.existingBones = []  # type: ignore
     self.num_files_imported = 0
     self._missing_materials: Set[str] = set()
     self._cdmaterials = [vmf_path("")]
     SmdImporterWrapper.smd = None
     # figure what the material dir should be for the qc
     with open(self.filepath, 'r') as fp:
         content = fp.read()
         for match in _CDMATERIALS_REGEX.finditer(content):
             self._cdmaterials.append(vmf_path(match.group(1)))
         animations = "$staticprop" not in content.lower()
     self.readQC(self.filepath,
                 False,
                 animations,
                 False,
                 'QUATERNION',
                 outer_qc=True)
     return {'FINISHED'}
예제 #5
0
 def load_return_smd(self, name: str,
                     collection: bpy.types.Collection) -> Any:
     name = name.lower()
     if name in self._cache:
         if self.verbose:
             print(f"Model {name} already imported, copying...")
         smd = copy.copy(self._cache[name])
         original_arm = smd.a
         copy_arm = original_arm.copy()
         collection.objects.link(copy_arm)
         for child in original_arm.children:
             twin = child.copy()
             twin.parent = copy_arm
             if "Armature" in twin.modifiers:
                 twin.modifiers["Armature"].object = copy_arm
             collection.objects.link(twin)
         smd.a = copy_arm
         return smd
     SmdImporterWrapper.collection = collection
     path = join(self.dec_models_path, name + ".qc")
     if not isfile(path):
         mdl_path = vmf_path(name)
         mdl_dir = mdl_path.parent
         mdl_name = mdl_path.stem
         # decompiled model doesn't exist, decompile it
         # save required files
         try:
             for filename in self.vmf_fs.tree[mdl_dir].files:
                 if not filename.startswith(mdl_name):
                     continue
                 file_out_path = join(self.dec_models_path, mdl_dir,
                                      filename)
                 os.makedirs(dirname(file_out_path), exist_ok=True)
                 with self.vmf_fs[mdl_dir / filename] as in_f:
                     with open(file_out_path, 'wb') as out_f:
                         for line in in_f:
                             out_f.write(line)
         except KeyError:
             print(f"ERROR: MODEL {mdl_path} NOT FOUND")
             raise FileNotFoundError(mdl_path)
         else:
             # call the decompiler
             subprocess.run(
                 (_CROWBARCMD_PATH, "-p",
                  str(self.dec_models_path / mdl_path.with_suffix(".mdl")),
                  "-o", str(self.dec_models_path / mdl_dir)),
                 check=True)
     bpy.ops.import_scene._io_import_vmf_smd_wrapper(filepath=path)
     smd = SmdImporterWrapper.smd
     self._cache[name] = smd
     if smd.a.name not in collection.objects:
         collection.objects.link(smd.a)
     if smd.a.name in bpy.context.scene.collection.objects:
         bpy.context.scene.collection.objects.unlink(smd.a)
     return smd
예제 #6
0
 def load_textures(self) -> None:
     self.material_openers = {}
     for texture in self.mdl.file_data.textures:
         try:
             fp = self.vmffs.open_file_utf8(
                 "materials" / vmf_path(texture.path_file_name + ".vmt"))
         except FileNotFoundError:
             pass
         else:
             self.material_openers[texture.path_file_name.lower()] = fp
         for tex_path in self.mdl.file_data.texture_paths:
             if tex_path != "" and (tex_path[0] == '/'
                                    or tex_path[0] == '\\'):
                 tex_path = tex_path[1:]
             try:
                 fp = self.vmffs.open_file_utf8(
                     "materials" / vmf_path(tex_path) /
                     (texture.path_file_name.lower() + ".vmt"))
             except FileNotFoundError:
                 pass
             else:
                 self.material_openers[texture.path_file_name.lower()] = fp
예제 #7
0
 def __init__(self, path: str, fp: AnyBinaryIO, vmffs: VMFFileSystem):
     self.filepath = vmf_path(path)
     self.mdl_reader = ByteIOWrapper(file=fp)
     self.vvd_reader: byte_io_mdl.ByteIO = None
     self.vtx_reader: byte_io_mdl.ByteIO = None
     magic, self.version = self.mdl_reader.peek_fmt('II')
     if self.version in self.mdl_version_list:
         self.mdl_version = self.mdl_version_list[self.version]
     else:
         raise NotImplementedError(f"Unsupported mdl v{self.version} version")
     self.vvd = None
     self.vtx = None
     self.mdl: Any = None
     self.vmffs = vmffs
예제 #8
0
 def load(self, name: str, path: str,
          collection: bpy.types.Collection) -> bpy.types.Object:
     name = name.lower()
     truncated_name = truncate_name(name)
     if name in self._cache:
         if self.verbose:
             print(f"[VERBOSE] Model {name} already imported, copying...")
         original = self._cache[name]
         copy = original.copy()
         collection.objects.link(copy)
         for child in original.children:
             twin = child.copy()
             twin.parent = copy
             armature_modifier = find_armature_modifier(twin)
             if armature_modifier is not None:
                 armature_modifier.object = copy
             collection.objects.link(twin)
         return copy
     path_obj = vmf_path(path)
     mdl_file = self._open_path(path_obj)
     vvd_file = self._open_path(path_obj.with_suffix(".vvd"))
     vtx_file = self._find_vtx(path_obj)
     container: Source1ModelContainer = import_model(
         mdl_file, vvd_file, vtx_file)
     for mesh in container.objects:
         mesh.name = truncated_name
         mesh.data.name = truncated_name
         collection.objects.link(mesh)
     if container.armature:
         container.armature.name = truncated_name
         container.armature.data.name = truncated_name
         collection.objects.link(container.armature)
     if container.attachments:
         for attachment in container.attachments:
             collection.objects.link(attachment)
     if self.vmf_fs is not None and self.vmt_importer is not None:
         vmf_fs = self.vmf_fs
         for material in container.mdl.materials:
             material_name = material.name
             material_name_truncated = material_name[-63:]
             material = bpy.data.materials[material_name_truncated]
             mat_name_path = vmf_path(material_name + ".vmt")
             for mat_dir in container.mdl.materials_paths:
                 mat_path = "materials" / vmf_path(mat_dir) / mat_name_path
                 if mat_path in vmf_fs:
                     material_name = splitext(mat_path)[0]
                     break
             else:
                 if material_name not in self._missing_materials:
                     print(f"WARNING: MISSING MATERIAL: {material_name}\n")
                     self._missing_materials.add(material_name)
             staged = self.vmt_importer.stage(
                 material_name, lambda: VMT(
                     vmf_fs.open_file_utf8(mat_path),
                     vmf_fs,
                     allow_patch=True,
                 ))
             staged.set_material(material)
     import_result = container.armature or container.objects[0]
     self._cache[name] = import_result
     return import_result
예제 #9
0
 def _load(self, name: str, staged: StagedQC) -> None:
     name = name.lower()
     truncated_name = truncate_name(name)
     if staged.reused is not None:
         scene_collection = staged.context.scene.collection
         # qc is already imported
         if self.verbose:
             print(
                 f"[VERBOSE] Model {name} previously imported, recreating..."
             )
         armature = staged.reused
         qc_data = armature.qc_data
         armature_obj: bpy.types.Object = bpy.data.objects.new(
             armature.name, armature)
         scene_collection.objects.link(armature_obj)
         for mesh_obj in qc_data.read_meshes():
             new_obj = mesh_obj.copy()
             new_obj.name = new_obj.data.name
             new_obj.parent = armature_obj
             new_obj.scale = (1, 1, 1)
             new_obj.location = (0, 0, 0)
             new_obj.rotation_euler = (0, 0, 0)
             if "Armature" not in new_obj.modifiers:
                 new_obj.modifiers.new("Armature", 'ARMATURE')
             new_obj.modifiers["Armature"].object = armature_obj
             scene_collection.objects.link(new_obj)
         if qc_data.action is not None:
             anim_data = armature_obj.animation_data_create()
             anim_data.action = qc_data.action
         staged.context.view_layer.update()
         self._cache[name] = FakeSmd(armature_obj,
                                     qc_data.read_bone_id_map())
         return
     if staged.info is None:
         raise Exception(
             "required information was not specified for non-reused staged qc"
         )
     path, root = staged.info
     if self.verbose:
         print(f"[VERBOSE] Importing model {name}...")
     SmdImporterWrapper.collection = staged.context.scene.collection
     SmdImporterWrapper.name = truncated_name
     SmdImporterWrapper.full_name = name
     if path.endswith(".mdl"):
         qc_path = join(self.dec_models_path, name + ".qc")
         if not isfile(qc_path):
             # decompiled model doesn't exist, decompile it
             mdl_path = vmf_path(name + ".mdl")
             mdl_dir = mdl_path.parent
             if not isabs(path):
                 mdl_name = mdl_path.stem
                 # save required files
                 saved_files = 0
                 if mdl_dir not in self.vmf_fs.tree:
                     raise FileNotFoundError(mdl_dir)
                 for filename in self.vmf_fs.tree[mdl_dir].files:
                     if not filename.startswith(mdl_name):
                         continue
                     file_out_path = join(self.dec_models_path, mdl_dir,
                                          filename)
                     os.makedirs(dirname(file_out_path), exist_ok=True)
                     with self.vmf_fs[mdl_dir / filename] as in_f:
                         with open(file_out_path, 'wb') as out_f:
                             for line in in_f:
                                 out_f.write(line)
                     saved_files += 1
                 if saved_files == 0:
                     print(f"[ERROR] MODEL {mdl_path} NOT FOUND")
                     raise FileNotFoundError(mdl_path)
                 full_mdl_path = str(self.dec_models_path / mdl_path)
             else:
                 full_mdl_path = path
             # call the decompiler
             result = subprocess.run(
                 (_CROWBARCMD_PATH, "-p", full_mdl_path, "-o",
                  str(self.dec_models_path / mdl_dir)),
                 text=True,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.STDOUT,
             )
             alternate_qc_dir = splitext(qc_path)[0]
             alternate_qc_path = join(alternate_qc_dir,
                                      basename(name) + ".qc")
             if isdir(alternate_qc_dir) and isfile(alternate_qc_path):
                 # model could be decompiled into different location if user has edited settings in Crowbar
                 qc_dir = dirname(qc_path)
                 for filename in os.listdir(alternate_qc_dir):
                     filepath = join(alternate_qc_dir, filename)
                     try:
                         move(filepath, qc_dir)
                     except (FileExistsError, ShError):
                         os.remove(filepath)
                 os.rmdir(alternate_qc_dir)
             if result.returncode != 0 or not isfile(qc_path):
                 print(result.stdout)
                 raise Exception(f"Decompiling model {mdl_path} failed")
         path = qc_path
         SmdImporterWrapper.root = self.dec_models_path
     else:
         SmdImporterWrapper.root = root
     log_capture = StringIO()
     try:
         with redirect_stdout(log_capture):
             bpy.ops.import_scene._io_import_vmf_smd_wrapper(
                 filepath=path,
                 skip_collision=self.skip_collision,
                 skip_lod=self.skip_lod,
             )
     except Exception:
         print(log_capture.getvalue())
         raise
     try:
         fake_smd = FakeSmd.from_bst(SmdImporterWrapper.smd)
     except Exception as err:
         raise Exception(f"Error importing {name}: {err}")
     self._cache[name] = fake_smd
     if fake_smd.a.name in bpy.context.scene.collection.objects:
         bpy.context.scene.collection.objects.unlink(fake_smd.a)
     qc_data = fake_smd.a.data.qc_data
     qc_data.save_meshes(fake_smd.a.children)
     qc_data.save_bone_id_map(fake_smd.boneIDs)
     if fake_smd.a.animation_data is not None:
         qc_data.action = fake_smd.a.animation_data.action
     self._cache[name] = fake_smd