2025-07-01
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
# #### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import bpy
|
||||
|
||||
from .modules.poliigon_core.assets import AssetData
|
||||
from .modules.poliigon_core.user import UserDownloadPreferences
|
||||
from .material_import_cycles import CyclesMaterial, RENDERER_CYCLES
|
||||
from .material_importer_params import MaterialImportParameters
|
||||
from . import reporting
|
||||
|
||||
|
||||
SUPPORTED_RENDERERS = [RENDERER_CYCLES]
|
||||
|
||||
|
||||
class MaterialImporter():
|
||||
|
||||
def __init__(self, cTB, renderer: str = RENDERER_CYCLES):
|
||||
self.cTB = cTB
|
||||
self.renderer = None
|
||||
self.importer = None
|
||||
self.params = None
|
||||
self.asset_data = None
|
||||
|
||||
self.set_renderer(renderer)
|
||||
|
||||
def set_renderer(self, renderer: str) -> bool:
|
||||
"""Sets the renderer to import materials for."""
|
||||
|
||||
if renderer not in IMPORTERS:
|
||||
raise RuntimeError(
|
||||
f"Unsupported renderer: {renderer}\n"
|
||||
f"Supported: {IMPORTERS.keys}")
|
||||
self.renderer = renderer
|
||||
self.importer = IMPORTERS[renderer]()
|
||||
|
||||
def reset_asset(self) -> None:
|
||||
self.asset_data = None
|
||||
|
||||
def convert_dict_to_asset_data(
|
||||
self, asset_dict: Dict) -> Optional[AssetData]:
|
||||
"""Converts a P4B asset data dictionary into an addon-core AssetData
|
||||
instance.
|
||||
"""
|
||||
|
||||
asset_id = asset_dict.get("id", -1)
|
||||
if asset_id >= 0:
|
||||
# Backdoor import expects negative ID
|
||||
asset_id *= -1
|
||||
|
||||
asset_name = asset_dict["name"]
|
||||
|
||||
if len(asset_dict["files"]) == 0:
|
||||
raise RuntimeError("Material import for asset without any files")
|
||||
|
||||
# Separate asset files per library directory
|
||||
# (if distributed across multiple)
|
||||
dirs_libraries = self.cTB.get_library_paths()
|
||||
files_per_dir = {}
|
||||
asset_files = asset_dict["files"]
|
||||
for _idx_dir, _dir in enumerate(dirs_libraries):
|
||||
_dir = os.path.normpath(_dir)
|
||||
files_per_dir[_idx_dir] = [
|
||||
_file
|
||||
for _file in asset_files
|
||||
if os.path.normpath(_file).startswith(_dir)
|
||||
]
|
||||
# Get asset's base directory in each library directory
|
||||
dir_asset_per_lib = {}
|
||||
for _idx_dir, _files in files_per_dir.items():
|
||||
try:
|
||||
dir_asset_per_lib[_idx_dir] = os.path.commonpath(_files)
|
||||
except ValueError:
|
||||
pass # deliberately ignored
|
||||
# Build backdoor file list (per library dir)
|
||||
file_list = []
|
||||
for _dir_asset in dir_asset_per_lib.values():
|
||||
file_list_from_dir = self.cTB._asset_index_mat.file_list_from_directory(
|
||||
asset_dir=_dir_asset, ignore_dirs=[])
|
||||
file_list.extend(file_list_from_dir)
|
||||
|
||||
if len(file_list) == 0:
|
||||
# Backdoor imported asset outside of libraries?
|
||||
dir_asset = os.path.commonpath(asset_files)
|
||||
file_list_from_dir = self.cTB._asset_index_mat.file_list_from_directory(
|
||||
asset_dir=dir_asset, ignore_dirs=[])
|
||||
file_list.extend(file_list_from_dir)
|
||||
|
||||
result = self.cTB._asset_index_mat.load_asset_from_list(
|
||||
asset_id=asset_id,
|
||||
asset_name=asset_name,
|
||||
asset_type=asset_dict["type"],
|
||||
size=asset_dict["sizes"][0], # any size will do here
|
||||
lod="", # not in use
|
||||
workflow_expected=asset_dict.get("workflows", ["METALNESS"])[0],
|
||||
file_list_json=json.dumps(file_list),
|
||||
query_string="p4b_mat_import/All Assets",
|
||||
convention=asset_dict.get("api_convention", 0)
|
||||
)
|
||||
if result is False:
|
||||
msg = f"Failed to convert to AssetData: {asset_id}"
|
||||
reporting.capture_message(
|
||||
"build_mat_error_create", msg, "error")
|
||||
self.cTB._asset_index_mat.flush(all_assets=True)
|
||||
return None
|
||||
|
||||
asset_data = self.cTB._asset_index_mat.get_asset(asset_id)
|
||||
|
||||
self.cTB._asset_index_mat.flush(all_assets=True)
|
||||
return asset_data
|
||||
|
||||
def set_parameters(self,
|
||||
reuse_existing: bool,
|
||||
do_apply: bool,
|
||||
workflow: str,
|
||||
lod: str,
|
||||
size: str,
|
||||
size_bg: Optional[str] = None,
|
||||
variant: Optional[str] = None,
|
||||
name_material: Optional[str] = None,
|
||||
name_mesh: Optional[str] = None,
|
||||
ref_objs: List[any] = [],
|
||||
projection: str = "FLAT",
|
||||
use_16bit: bool = True,
|
||||
mode_disp: str = "NORMAL",
|
||||
translate_x: float = 0.0,
|
||||
translate_y: float = 0.0,
|
||||
scale: float = 1.0,
|
||||
global_rotation: float = 0.0,
|
||||
aspect_ratio: float = 1.0,
|
||||
displacement: float = 0.0,
|
||||
keep_unused_tex_nodes: bool = False,
|
||||
map_prefs: Optional[UserDownloadPreferences] = None
|
||||
) -> None:
|
||||
"""Sets the parameterts for a material import."""
|
||||
|
||||
if self.asset_data is None:
|
||||
raise RuntimeError("No asset set!")
|
||||
|
||||
self.params = MaterialImportParameters(
|
||||
asset_data=self.asset_data,
|
||||
reuse_existing=reuse_existing,
|
||||
do_apply=do_apply,
|
||||
workflow=workflow,
|
||||
lod=lod,
|
||||
size=size,
|
||||
size_bg=size_bg,
|
||||
variant=variant,
|
||||
name_material=name_material,
|
||||
name_mesh=name_mesh,
|
||||
ref_objs=ref_objs,
|
||||
projection=projection,
|
||||
use_16bit=use_16bit,
|
||||
mode_disp=mode_disp,
|
||||
translate_x=translate_x,
|
||||
translate_y=translate_y,
|
||||
scale=scale,
|
||||
global_rotation=global_rotation,
|
||||
aspect_ratio=aspect_ratio,
|
||||
displacement=displacement,
|
||||
keep_unused_tex_nodes=keep_unused_tex_nodes,
|
||||
addon_convention=self.cTB.addon_convention,
|
||||
map_prefs=map_prefs
|
||||
)
|
||||
|
||||
def reset_parameters(self) -> None:
|
||||
"""Resets all parameters."""
|
||||
|
||||
self.params = None
|
||||
|
||||
def get_existing_material(self) -> Optional[bpy.types.Material]:
|
||||
"""Returns an already existing material of identical name.
|
||||
|
||||
This is what legacy import in P4B did for Model assets.
|
||||
Texture assets were handeled differently via
|
||||
find_identical_material().
|
||||
"""
|
||||
|
||||
if not self.params.reuse_existing:
|
||||
return None
|
||||
|
||||
name_mat = self.params.name_material
|
||||
if name_mat in bpy.data.materials.keys():
|
||||
return bpy.data.materials[name_mat]
|
||||
|
||||
return None
|
||||
|
||||
def import_material(self,
|
||||
*,
|
||||
asset_data: AssetData,
|
||||
do_apply: bool,
|
||||
workflow: str,
|
||||
size: str,
|
||||
size_bg: Optional[str] = None,
|
||||
lod: Optional[str] = None,
|
||||
variant: Optional[str] = None,
|
||||
name_material: Optional[str] = None,
|
||||
name_mesh: Optional[str] = None,
|
||||
ref_objs: Optional[List[any]] = None,
|
||||
projection: str = "FLAT",
|
||||
use_16bit: bool = True,
|
||||
mode_disp: str = "NORMAL",
|
||||
translate_x: float = 0.0,
|
||||
translate_y: float = 0.0,
|
||||
scale: float = 1.0,
|
||||
global_rotation: float = 0.0,
|
||||
aspect_ratio: float = 1.0,
|
||||
displacement: float = 0.0,
|
||||
keep_unused_tex_nodes: bool = False,
|
||||
reuse_existing: bool = True,
|
||||
map_prefs: Optional[UserDownloadPreferences] = None
|
||||
) -> Optional[bpy.types.Material]:
|
||||
"""Imports a single material for an asset regardless of type."""
|
||||
|
||||
if asset_data is None:
|
||||
return None
|
||||
self.asset_data = asset_data
|
||||
|
||||
self.set_parameters(
|
||||
reuse_existing=reuse_existing,
|
||||
do_apply=do_apply,
|
||||
workflow=workflow,
|
||||
lod=lod,
|
||||
size=size,
|
||||
size_bg=size_bg,
|
||||
variant=variant,
|
||||
name_material=name_material,
|
||||
name_mesh=name_mesh,
|
||||
ref_objs=ref_objs,
|
||||
projection=projection,
|
||||
use_16bit=use_16bit,
|
||||
mode_disp=mode_disp,
|
||||
translate_x=translate_x,
|
||||
translate_y=translate_y,
|
||||
scale=scale,
|
||||
global_rotation=global_rotation,
|
||||
aspect_ratio=aspect_ratio,
|
||||
displacement=displacement,
|
||||
keep_unused_tex_nodes=keep_unused_tex_nodes,
|
||||
map_prefs=map_prefs
|
||||
)
|
||||
|
||||
# Case for Model import, Texture assets handle material re-use still
|
||||
# in operator. TODO(Andreas)
|
||||
mat = self.get_existing_material()
|
||||
if mat is not None:
|
||||
self.reset_parameters()
|
||||
self.reset_asset()
|
||||
return mat
|
||||
|
||||
mat = self.importer.import_material(self.asset_data, self.params)
|
||||
|
||||
self.reset_parameters()
|
||||
self.reset_asset()
|
||||
return mat
|
||||
|
||||
|
||||
IMPORTERS = {
|
||||
RENDERER_CYCLES: CyclesMaterial
|
||||
}
|
||||
Reference in New Issue
Block a user