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
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'}
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
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'}
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
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
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
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
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