From 6216b63638fccd8b7524b61a5a4b8295e8ebdf3a Mon Sep 17 00:00:00 2001 From: popomore Date: Sat, 24 Jan 2026 17:34:00 +0800 Subject: [PATCH 1/2] feat: improve materials --- examples/basic_usage.py | 198 +++++++++- ufbx/__init__.py | 4 + ufbx/_ufbx.pyx | 780 +++++++++++++++++++++++++++++++++++++++- ufbx/src/ufbx_wrapper.c | 374 +++++++++++++++++-- ufbx/src/ufbx_wrapper.h | 81 ++++- 5 files changed, 1386 insertions(+), 51 deletions(-) diff --git a/examples/basic_usage.py b/examples/basic_usage.py index 983d6b3..f3b78e6 100755 --- a/examples/basic_usage.py +++ b/examples/basic_usage.py @@ -130,7 +130,11 @@ def print_node_hierarchy(node, depth=0, max_depth=3): indent = " " * depth mesh_info = " [mesh]" if node.mesh else "" - print(f"{indent}- {node.name}{mesh_info}") + structure_note = "" + if node.mesh and node.children: + structure_note = " (note: mesh node has children)" + + print(f"{indent}- {node.name}{mesh_info}{structure_note}") # Show node's parent if depth == 0 and node.parent: @@ -197,11 +201,17 @@ def print_transform_info(node): scale_y = math.sqrt(local_matrix[0, 1] ** 2 + local_matrix[1, 1] ** 2 + local_matrix[2, 1] ** 2) # Column 2: Z-axis basis vector scale_z = math.sqrt(local_matrix[0, 2] ** 2 + local_matrix[1, 2] ** 2 + local_matrix[2, 2] ** 2) - print(f" Scale: ({scale_x:8.3f}, {scale_y:8.3f}, {scale_z:8.3f})") + + if abs(scale_x - scale_y) < 1e-3 and abs(scale_y - scale_z) < 1e-3: + scale_note = "uniform" + else: + scale_note = "non-uniform" + + print(f" Scale: ({scale_x:8.3f}, {scale_y:8.3f}, {scale_z:8.3f}) [{scale_note}]") # Extract rotation (Euler angles) rot_x, rot_y, rot_z = extract_euler_angles(local_matrix, scale_x, scale_y, scale_z) - print(f" Rotation: ({rot_x:8.3f}°, {rot_y:8.3f}°, {rot_z:8.3f}°) [XYZ Euler]") + print(f" Rotation: ({rot_x:8.3f}°, {rot_y:8.3f}°, {rot_z:8.3f}°) [derived Euler, from baked matrix, XYZ]") # Check if there are significant transformations has_translation = abs(pos_x) > 0.001 or abs(pos_y) > 0.001 or abs(pos_z) > 0.001 @@ -223,7 +233,7 @@ def print_transform_info(node): # World transform world_matrix = node.world_transform - print(" - Transform (World):") + print(" - Transform (World, inherited through hierarchy):") world_pos_x = world_matrix[0, 3] world_pos_y = world_matrix[1, 3] world_pos_z = world_matrix[2, 3] @@ -237,6 +247,25 @@ def print_transform_info(node): print(f" Rotation: ({world_rot_x:8.3f}°, {world_rot_y:8.3f}°, {world_rot_z:8.3f}°) [XYZ Euler]") +def format_texture_info(texture, indent=" "): + """Format detailed texture information""" + if not texture: + return None + + info = [] + if texture.name: + info.append(f"{indent}Name: {texture.name}") + if texture.filename: + info.append(f"{indent}File: {texture.filename}") + if texture.relative_filename: + info.append(f"{indent}Relative: {texture.relative_filename}") + if texture.absolute_filename: + info.append(f"{indent}Absolute: {texture.absolute_filename}") + info.append(f"{indent}Type: {texture.type}") + + return "\n".join(info) if info else None + + def main(): if len(sys.argv) < 2: print("Usage: python3 basic_usage.py ") @@ -262,7 +291,10 @@ def main(): print(f" Nodes: {stats['nodes']}") print(f" - With mesh: {stats['nodes_with_mesh']}") print(f" - Leaf nodes: {stats['leaf_nodes']}") - print(f" Meshes: {stats['meshes']}") + mesh_note = "" + if stats["meshes"] == 1 and stats["nodes"] > 1: + mesh_note = " (note: many nodes reference a single mesh)" + print(f" Meshes: {stats['meshes']}{mesh_note}") if stats["meshes"] > 0: print(f" - Total vertices: {stats['total_vertices']:,}") print(f" - Total faces: {stats['total_faces']:,}") @@ -281,7 +313,7 @@ def main(): print(" System type:") print(f" - Handedness: {coord_info['handedness']}") if coord_info["description"]: - print(f" - Description: {coord_info['description']}") + print(f" - Description: {coord_info['description']} (axis conversion likely applied)") print(" Basis matrix (4x4, column-major):") matrix = coord_info["matrix"] for row in range(4): @@ -337,7 +369,11 @@ def main(): ) if normals is not None: - print(f" ✓ Normals: shape={normals.shape}") + note = "" + if positions is not None and len(normals) != len(positions): + note = " (note: normal count != vertex count)" + + print(f" ✓ Normals: shape={normals.shape}{note}") if len(normals) > 0: print(f" First normal: ({normals[0][0]:.3f}, {normals[0][1]:.3f}, {normals[0][2]:.3f})") @@ -357,10 +393,156 @@ def main(): # Material information if scene.materials: print(f"🎨 Material Details ({len(scene.materials)} materials):") + + # Collect statistics + shader_types = {} + shading_models = {} + materials_with_textures = 0 + + for material in scene.materials: + # Count shader types + shader_type = material.shader_type + shader_types[shader_type] = shader_types.get(shader_type, 0) + 1 + + # Count shading models + shading_model = material.shading_model_name + if shading_model: + shading_models[shading_model] = shading_models.get(shading_model, 0) + 1 + + # Count materials with textures (check common maps) + has_texture = any([ + material.pbr_base_color.texture_enabled and material.pbr_base_color.texture, + material.pbr_normal_map.texture_enabled and material.pbr_normal_map.texture, + material.fbx_diffuse_color.texture_enabled and material.fbx_diffuse_color.texture, + ]) + if has_texture: + materials_with_textures += 1 + + # Display statistics + print(" Overview:") + print(f" - Materials with textures: {materials_with_textures}") + if shading_models: + print(f" - Shading models: {', '.join(f'{k} ({v})' for k, v in sorted(shading_models.items()))}") + print() + + # Display individual materials + print(" Materials:") for i, material in enumerate(scene.materials): - print(f" [{i}] Material: '{material.name}'") + print(f"\n [{i}] '{material.name}'") + + # Shading information + shading_info = [] + if material.shading_model_name: + shading_info.append(f"model={material.shading_model_name}") + shading_info.append(f"shader_type={material.shader_type}") + print(f" - Shading: {', '.join(shading_info)}") + + # PBR properties + print(" - PBR properties:") + + # Base properties + base_color = material.pbr_base_color + if base_color.has_value: + r, g, b, a = base_color.value_vec4 + print(f" Base color: RGB({r:.3f}, {g:.3f}, {b:.3f}), A={a:.3f}") + if base_color.texture_enabled and base_color.texture: + print(" Base color texture:") + tex_info = format_texture_info(base_color.texture) + if tex_info: + print(tex_info) + + roughness = material.pbr_roughness + if roughness.has_value: + print(f" Roughness: {roughness.value_vec4[0]:.3f}") + if roughness.texture_enabled and roughness.texture: + print(" Roughness texture:") + tex_info = format_texture_info(roughness.texture) + if tex_info: + print(tex_info) + + metalness = material.pbr_metalness + if metalness.has_value: + print(f" Metalness: {metalness.value_vec4[0]:.3f}") + if metalness.texture_enabled and metalness.texture: + print(" Metalness texture:") + tex_info = format_texture_info(metalness.texture) + if tex_info: + print(tex_info) + + # Extended properties + emission = material.pbr_emission_color + if emission.has_value: + r, g, b, a = emission.value_vec4 + print(f" Emission: RGB({r:.3f}, {g:.3f}, {b:.3f})") + if emission.texture_enabled and emission.texture: + print(" Emission texture:") + tex_info = format_texture_info(emission.texture) + if tex_info: + print(tex_info) + + opacity = material.pbr_opacity + if opacity.has_value: + alpha = opacity.value_vec4[0] + print(f" Opacity: {alpha:.3f}") + if opacity.texture_enabled and opacity.texture: + print(" Opacity texture:") + tex_info = format_texture_info(opacity.texture) + if tex_info: + print(tex_info) + + # Maps + normal_map = material.pbr_normal_map + if normal_map.texture_enabled and normal_map.texture: + print(" Normal map:") + tex_info = format_texture_info(normal_map.texture) + if tex_info: + print(tex_info) + + ao_map = material.pbr_ambient_occlusion + if ao_map.texture_enabled and ao_map.texture: + print(" AO map:") + tex_info = format_texture_info(ao_map.texture) + if tex_info: + print(tex_info) + + # FBX properties (for compatibility) + fbx_diffuse = material.fbx_diffuse_color + if fbx_diffuse.texture_enabled or ( + fbx_diffuse.has_value and any(v != 0 for v in fbx_diffuse.value_vec4[:3]) + ): + print(" - FBX properties:") + if fbx_diffuse.has_value: + r, g, b, a = fbx_diffuse.value_vec4 + print(f" Diffuse color: RGB({r:.3f}, {g:.3f}, {b:.3f})") + if fbx_diffuse.texture_enabled and fbx_diffuse.texture: + print(" Diffuse texture:") + tex_info = format_texture_info(fbx_diffuse.texture) + if tex_info: + print(tex_info) + + print() + + # Texture information + if scene.textures: + print(f"🖼️ Texture Details ({len(scene.textures)} textures):") print() + for i, texture in enumerate(scene.textures): + print(f" [{i}] Texture: '{texture.name}'") + + # File information + if texture.filename: + print(f" - Filename: {texture.filename}") + if texture.relative_filename: + print(f" - Relative: {texture.relative_filename}") + if texture.absolute_filename: + print(f" - Absolute: {texture.absolute_filename}") + + # Texture type + print(f" - Type: {texture.type}") + + print() + except FileNotFoundError: print(f"❌ Error: File not found - {filename}") sys.exit(1) diff --git a/ufbx/__init__.py b/ufbx/__init__.py index a55a9d9..4f22bc7 100644 --- a/ufbx/__init__.py +++ b/ufbx/__init__.py @@ -18,6 +18,7 @@ Constraint, ConstraintType, CoordinateAxis, + CoordinateAxes, Element, ElementType, ErrorType, @@ -29,6 +30,7 @@ LightDecay, LightType, Material, + MaterialMap, Matrix, Mesh, MirrorAxis, @@ -77,6 +79,7 @@ "Constraint", "ConstraintType", "CoordinateAxis", + "CoordinateAxes", "Element", "ElementType", "ErrorType", @@ -88,6 +91,7 @@ "LightDecay", "LightType", "Material", + "MaterialMap", "Matrix", "Mesh", "MirrorAxis", diff --git a/ufbx/_ufbx.pyx b/ufbx/_ufbx.pyx index c2696b4..2bd91b7 100644 --- a/ufbx/_ufbx.pyx +++ b/ufbx/_ufbx.pyx @@ -12,15 +12,122 @@ cimport numpy as np np.import_array() # C declarations -cdef extern from "ufbx_wrapper.h": +cdef extern from "ufbx-c/ufbx.h": + ctypedef struct ufbx_vec4: + double x + double y + double z + double w + + ctypedef struct ufbx_material_map: + ufbx_vec4 value_vec4 + long long value_int + ufbx_texture* texture + bint has_value + bint texture_enabled + bint feature_disabled + unsigned int value_components + + ctypedef struct ufbx_material_fbx_maps: + ufbx_material_map diffuse_factor + ufbx_material_map diffuse_color + ufbx_material_map specular_factor + ufbx_material_map specular_color + ufbx_material_map specular_exponent + ufbx_material_map reflection_factor + ufbx_material_map reflection_color + ufbx_material_map transparency_factor + ufbx_material_map transparency_color + ufbx_material_map emission_factor + ufbx_material_map emission_color + ufbx_material_map ambient_factor + ufbx_material_map ambient_color + ufbx_material_map normal_map + ufbx_material_map bump + ufbx_material_map bump_factor + ufbx_material_map displacement_factor + ufbx_material_map displacement + ufbx_material_map vector_displacement_factor + ufbx_material_map vector_displacement + + ctypedef struct ufbx_material_pbr_maps: + ufbx_material_map base_factor + ufbx_material_map base_color + ufbx_material_map roughness + ufbx_material_map metalness + ufbx_material_map diffuse_roughness + ufbx_material_map specular_factor + ufbx_material_map specular_color + ufbx_material_map specular_ior + ufbx_material_map specular_anisotropy + ufbx_material_map specular_rotation + ufbx_material_map transmission_factor + ufbx_material_map transmission_color + ufbx_material_map transmission_depth + ufbx_material_map transmission_scatter + ufbx_material_map transmission_scatter_anisotropy + ufbx_material_map transmission_dispersion + ufbx_material_map transmission_roughness + ufbx_material_map transmission_extra_roughness + ufbx_material_map transmission_priority + ufbx_material_map transmission_enable_in_aov + ufbx_material_map subsurface_factor + ufbx_material_map subsurface_color + ufbx_material_map subsurface_radius + ufbx_material_map subsurface_scale + ufbx_material_map subsurface_anisotropy + ufbx_material_map subsurface_tint_color + ufbx_material_map subsurface_type + ufbx_material_map sheen_factor + ufbx_material_map sheen_color + ufbx_material_map sheen_roughness + ufbx_material_map coat_factor + ufbx_material_map coat_color + ufbx_material_map coat_roughness + ufbx_material_map coat_ior + ufbx_material_map coat_anisotropy + ufbx_material_map coat_rotation + ufbx_material_map coat_normal + ufbx_material_map coat_affect_base_color + ufbx_material_map coat_affect_base_roughness + ufbx_material_map thin_film_factor + ufbx_material_map thin_film_thickness + ufbx_material_map thin_film_ior + ufbx_material_map emission_factor + ufbx_material_map emission_color + ufbx_material_map opacity + ufbx_material_map indirect_diffuse + ufbx_material_map indirect_specular + ufbx_material_map normal_map + ufbx_material_map tangent_map + ufbx_material_map displacement_map + ufbx_material_map matte_factor + ufbx_material_map matte_color + ufbx_material_map ambient_occlusion + ufbx_material_map glossiness + ufbx_material_map coat_glossiness + ufbx_material_map transmission_glossiness + + ctypedef struct ufbx_string: + const char* data + size_t length + + ctypedef struct ufbx_element: + ufbx_string name + + ctypedef struct ufbx_material: + ufbx_element element + ufbx_material_fbx_maps fbx + ufbx_material_pbr_maps pbr + int shader_type + ufbx_string shading_model_name + ctypedef struct ufbx_scene: pass ctypedef struct ufbx_mesh: pass ctypedef struct ufbx_node: pass - ctypedef struct ufbx_material: - pass ctypedef struct ufbx_light: pass ctypedef struct ufbx_camera: @@ -28,7 +135,11 @@ cdef extern from "ufbx_wrapper.h": ctypedef struct ufbx_bone: pass ctypedef struct ufbx_texture: - pass + ufbx_element element + ufbx_string filename + ufbx_string relative_filename + ufbx_string absolute_filename + int type ctypedef struct ufbx_anim_stack: pass ctypedef struct ufbx_anim_layer: @@ -48,6 +159,8 @@ cdef extern from "ufbx_wrapper.h": ctypedef struct ufbx_constraint: pass +cdef extern from "ufbx_wrapper.h": + # Scene management ufbx_scene* ufbx_wrapper_load_file(const char *filename, char **error_msg) void ufbx_wrapper_free_scene(ufbx_scene *scene) @@ -90,7 +203,13 @@ cdef extern from "ufbx_wrapper.h": ufbx_material* ufbx_wrapper_scene_get_material(const ufbx_scene *scene, size_t index) size_t ufbx_wrapper_mesh_get_num_materials(const ufbx_mesh *mesh) ufbx_material* ufbx_wrapper_mesh_get_material(const ufbx_mesh *mesh, size_t index) - const char* ufbx_wrapper_material_get_name(const ufbx_material *material) + + # Texture properties + const char* ufbx_wrapper_texture_get_name(const ufbx_texture *texture) + const char* ufbx_wrapper_texture_get_filename(const ufbx_texture *texture) + const char* ufbx_wrapper_texture_get_relative_filename(const ufbx_texture *texture) + const char* ufbx_wrapper_texture_get_absolute_filename(const ufbx_texture *texture) + int ufbx_wrapper_texture_get_type(const ufbx_texture *texture) # Light access size_t ufbx_wrapper_scene_get_num_lights(const ufbx_scene *scene) @@ -341,8 +460,19 @@ class ApertureMode(IntEnum): class ShaderType(IntEnum): - SHADER_FBX_LAMBERT = 0 - SHADER_FBX_PHONG = 1 + SHADER_UNKNOWN = 0 + SHADER_FBX_LAMBERT = 1 + SHADER_FBX_PHONG = 2 + SHADER_OSL_STANDARD_SURFACE = 3 + SHADER_ARNOLD_STANDARD_SURFACE = 4 + SHADER_3DS_MAX_PHYSICAL_MATERIAL = 5 + SHADER_3DS_MAX_PBR_METAL_ROUGH = 6 + SHADER_3DS_MAX_PBR_SPEC_GLOSS = 7 + SHADER_GLTF_MATERIAL = 8 + SHADER_OPENPBR_MATERIAL = 9 + SHADER_SHADERFX_GRAPH = 10 + SHADER_BLENDER_PHONG = 11 + SHADER_WAVEFRONT_MTL = 12 class TextureType(IntEnum): @@ -780,6 +910,8 @@ cdef class Texture(Element): @staticmethod cdef Texture _create(Scene scene, ufbx_texture* texture): """Internal factory method""" + if texture == NULL: + return None cdef Texture obj = Texture.__new__(Texture) obj._scene = scene obj._texture = texture @@ -790,35 +922,39 @@ cdef class Texture(Element): """Texture name""" if self._scene._closed: raise RuntimeError("Scene is closed") - return ufbx_wrapper_texture_get_name(self._texture).decode('utf-8', errors='replace') + cdef bytes name_bytes = self._texture.element.name.data[:self._texture.element.name.length] + return name_bytes.decode('utf-8', errors='replace') @property def filename(self): """Texture filename""" if self._scene._closed: raise RuntimeError("Scene is closed") - return ufbx_wrapper_texture_get_filename(self._texture).decode('utf-8', errors='replace') + cdef bytes filename_bytes = self._texture.filename.data[:self._texture.filename.length] + return filename_bytes.decode('utf-8', errors='replace') @property def absolute_filename(self): """Absolute path to texture file""" if self._scene._closed: raise RuntimeError("Scene is closed") - return ufbx_wrapper_texture_get_absolute_filename(self._texture).decode('utf-8', errors='replace') + cdef bytes filename_bytes = self._texture.absolute_filename.data[:self._texture.absolute_filename.length] + return filename_bytes.decode('utf-8', errors='replace') @property def relative_filename(self): """Relative path to texture file""" if self._scene._closed: raise RuntimeError("Scene is closed") - return ufbx_wrapper_texture_get_relative_filename(self._texture).decode('utf-8', errors='replace') + cdef bytes filename_bytes = self._texture.relative_filename.data[:self._texture.relative_filename.length] + return filename_bytes.decode('utf-8', errors='replace') @property def type(self): """Texture type (TextureType enum)""" if self._scene._closed: raise RuntimeError("Scene is closed") - return TextureType(ufbx_wrapper_texture_get_type(self._texture)) + return TextureType(self._texture.type) cdef class AnimStack(Element): @@ -1680,8 +1816,74 @@ cdef class Mesh(Element): return result +cdef class MaterialMap: + """Material map (direct access to ufbx_material_map fields)""" + cdef Scene _scene + cdef const ufbx_material_map* _map + + @staticmethod + cdef MaterialMap _create(Scene scene, const ufbx_material_map* mat_map): + """Create MaterialMap from C struct""" + cdef MaterialMap obj = MaterialMap.__new__(MaterialMap) + obj._scene = scene + obj._map = mat_map + return obj + + @property + def value_vec4(self): + """Vec4 value (x, y, z, w)""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return (self._map.value_vec4.x, self._map.value_vec4.y, + self._map.value_vec4.z, self._map.value_vec4.w) + + @property + def value_int(self): + """Integer value""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._map.value_int + + @property + def texture(self): + """Texture (Texture object or None)""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + if self._map.texture == NULL: + return None + return Texture._create(self._scene, self._map.texture) + + @property + def has_value(self): + """Whether this map has a value""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._map.has_value + + @property + def texture_enabled(self): + """Whether texture is enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._map.texture_enabled + + @property + def feature_disabled(self): + """Whether this feature is disabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._map.feature_disabled + + @property + def value_components(self): + """Number of value components""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._map.value_components + + cdef class Material(Element): - """Material definition""" + """Material definition (Rust API style - direct struct access)""" cdef Scene _scene cdef ufbx_material* _material @@ -1698,7 +1900,557 @@ cdef class Material(Element): """Material name""" if self._scene._closed: raise RuntimeError("Scene is closed") - return ufbx_wrapper_material_get_name(self._material).decode('utf-8', errors='replace') + cdef bytes name_bytes = self._material.element.name.data[:self._material.element.name.length] + return name_bytes.decode('utf-8', errors='replace') + + @property + def shader_type(self): + """Shader type""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._material.shader_type + + @property + def shading_model_name(self): + """Shading model name (e.g., 'lambert', 'phong', 'unknown')""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + cdef bytes name_bytes = self._material.shading_model_name.data[:self._material.shading_model_name.length] + return name_bytes.decode('utf-8', errors='replace') + + # PBR Material Maps - 直接返回 MaterialMap + @property + def pbr_base_factor(self): + """PBR base factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.base_factor) + + @property + def pbr_base_color(self): + """PBR base color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.base_color) + + @property + def pbr_roughness(self): + """PBR roughness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.roughness) + + @property + def pbr_metalness(self): + """PBR metalness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.metalness) + + @property + def pbr_diffuse_roughness(self): + """PBR diffuse roughness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.diffuse_roughness) + + @property + def pbr_specular_factor(self): + """PBR specular factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.specular_factor) + + @property + def pbr_specular_color(self): + """PBR specular color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.specular_color) + + @property + def pbr_specular_ior(self): + """PBR specular IOR""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.specular_ior) + + @property + def pbr_specular_anisotropy(self): + """PBR specular anisotropy""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.specular_anisotropy) + + @property + def pbr_specular_rotation(self): + """PBR specular rotation""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.specular_rotation) + + @property + def pbr_transmission_factor(self): + """PBR transmission factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.transmission_factor) + + @property + def pbr_transmission_color(self): + """PBR transmission color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.transmission_color) + + @property + def pbr_transmission_depth(self): + """PBR transmission depth""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.transmission_depth) + + @property + def pbr_transmission_scatter(self): + """PBR transmission scatter""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.transmission_scatter) + + @property + def pbr_transmission_scatter_anisotropy(self): + """PBR transmission scatter anisotropy""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.transmission_scatter_anisotropy) + + @property + def pbr_transmission_dispersion(self): + """PBR transmission dispersion""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.transmission_dispersion) + + @property + def pbr_transmission_roughness(self): + """PBR transmission roughness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.transmission_roughness) + + @property + def pbr_transmission_extra_roughness(self): + """PBR transmission extra roughness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.transmission_extra_roughness) + + @property + def pbr_transmission_priority(self): + """PBR transmission priority""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.transmission_priority) + + @property + def pbr_transmission_enable_in_aov(self): + """PBR transmission enable in AOV""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.transmission_enable_in_aov) + + @property + def pbr_subsurface_factor(self): + """PBR subsurface factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.subsurface_factor) + + @property + def pbr_subsurface_color(self): + """PBR subsurface color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.subsurface_color) + + @property + def pbr_subsurface_radius(self): + """PBR subsurface radius""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.subsurface_radius) + + @property + def pbr_subsurface_scale(self): + """PBR subsurface scale""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.subsurface_scale) + + @property + def pbr_subsurface_anisotropy(self): + """PBR subsurface anisotropy""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.subsurface_anisotropy) + + @property + def pbr_subsurface_tint_color(self): + """PBR subsurface tint color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.subsurface_tint_color) + + @property + def pbr_subsurface_type(self): + """PBR subsurface type""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.subsurface_type) + + @property + def pbr_sheen_factor(self): + """PBR sheen factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.sheen_factor) + + @property + def pbr_sheen_color(self): + """PBR sheen color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.sheen_color) + + @property + def pbr_sheen_roughness(self): + """PBR sheen roughness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.sheen_roughness) + + @property + def pbr_coat_factor(self): + """PBR coat factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.coat_factor) + + @property + def pbr_coat_color(self): + """PBR coat color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.coat_color) + + @property + def pbr_coat_roughness(self): + """PBR coat roughness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.coat_roughness) + + @property + def pbr_coat_ior(self): + """PBR coat IOR""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.coat_ior) + + @property + def pbr_coat_anisotropy(self): + """PBR coat anisotropy""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.coat_anisotropy) + + @property + def pbr_coat_rotation(self): + """PBR coat rotation""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.coat_rotation) + + @property + def pbr_coat_normal(self): + """PBR coat normal""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.coat_normal) + + @property + def pbr_coat_affect_base_color(self): + """PBR coat affect base color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.coat_affect_base_color) + + @property + def pbr_coat_affect_base_roughness(self): + """PBR coat affect base roughness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.coat_affect_base_roughness) + + @property + def pbr_thin_film_factor(self): + """PBR thin film factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.thin_film_factor) + + @property + def pbr_thin_film_thickness(self): + """PBR thin film thickness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.thin_film_thickness) + + @property + def pbr_thin_film_ior(self): + """PBR thin film IOR""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.thin_film_ior) + + @property + def pbr_emission_factor(self): + """PBR emission factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.emission_factor) + + @property + def pbr_emission_color(self): + """PBR emission color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.emission_color) + + @property + def pbr_opacity(self): + """PBR opacity""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.opacity) + + @property + def pbr_indirect_diffuse(self): + """PBR indirect diffuse""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.indirect_diffuse) + + @property + def pbr_indirect_specular(self): + """PBR indirect specular""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.indirect_specular) + + @property + def pbr_normal_map(self): + """PBR normal map""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.normal_map) + + @property + def pbr_tangent_map(self): + """PBR tangent map""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.tangent_map) + + @property + def pbr_displacement_map(self): + """PBR displacement map""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.displacement_map) + + @property + def pbr_matte_factor(self): + """PBR matte factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.matte_factor) + + @property + def pbr_matte_color(self): + """PBR matte color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.matte_color) + + @property + def pbr_ambient_occlusion(self): + """PBR ambient occlusion""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.ambient_occlusion) + + @property + def pbr_glossiness(self): + """PBR glossiness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.glossiness) + + @property + def pbr_coat_glossiness(self): + """PBR coat glossiness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.coat_glossiness) + + @property + def pbr_transmission_glossiness(self): + """PBR transmission glossiness""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.pbr.transmission_glossiness) + + # FBX Material Maps + @property + def fbx_diffuse_factor(self): + """FBX diffuse factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.diffuse_factor) + + @property + def fbx_diffuse_color(self): + """FBX diffuse color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.diffuse_color) + + @property + def fbx_specular_factor(self): + """FBX specular factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.specular_factor) + + @property + def fbx_specular_color(self): + """FBX specular color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.specular_color) + + @property + def fbx_specular_exponent(self): + """FBX specular exponent""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.specular_exponent) + + @property + def fbx_reflection_factor(self): + """FBX reflection factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.reflection_factor) + + @property + def fbx_reflection_color(self): + """FBX reflection color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.reflection_color) + + @property + def fbx_transparency_factor(self): + """FBX transparency factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.transparency_factor) + + @property + def fbx_transparency_color(self): + """FBX transparency color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.transparency_color) + + @property + def fbx_emission_factor(self): + """FBX emission factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.emission_factor) + + @property + def fbx_emission_color(self): + """FBX emission color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.emission_color) + + @property + def fbx_ambient_factor(self): + """FBX ambient factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.ambient_factor) + + @property + def fbx_ambient_color(self): + """FBX ambient color""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.ambient_color) + + @property + def fbx_normal_map(self): + """FBX normal map""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.normal_map) + + @property + def fbx_bump(self): + """FBX bump""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.bump) + + @property + def fbx_bump_factor(self): + """FBX bump factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.bump_factor) + + @property + def fbx_displacement_factor(self): + """FBX displacement factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.displacement_factor) + + @property + def fbx_displacement(self): + """FBX displacement""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.displacement) + + @property + def fbx_vector_displacement_factor(self): + """FBX vector displacement factor""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.vector_displacement_factor) + + @property + def fbx_vector_displacement(self): + """FBX vector displacement""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialMap._create(self._scene, &self._material.fbx.vector_displacement) # Module-level functions diff --git a/ufbx/src/ufbx_wrapper.c b/ufbx/src/ufbx_wrapper.c index f2697b2..770deb5 100644 --- a/ufbx/src/ufbx_wrapper.c +++ b/ufbx/src/ufbx_wrapper.c @@ -199,6 +199,356 @@ const char* ufbx_wrapper_material_get_name(const ufbx_material *material) { return material->name.data ? material->name.data : ""; } +int ufbx_wrapper_material_get_shader_type(const ufbx_material *material) { + if (!material) return 0; + return (int)material->shader_type; +} + +const char* ufbx_wrapper_material_get_shading_model_name(const ufbx_material *material) { + if (!material) return ""; + return material->shading_model_name.data ? material->shading_model_name.data : ""; +} + +size_t ufbx_wrapper_material_get_num_textures(const ufbx_material *material) { + if (!material) return 0; + return material->textures.count; +} + +void ufbx_wrapper_material_get_pbr_base_color(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->pbr.base_color.value_vec3.x; + out_vec3[1] = material->pbr.base_color.value_vec3.y; + out_vec3[2] = material->pbr.base_color.value_vec3.z; + *has_texture = (material->pbr.base_color.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_roughness(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.roughness.value_real; + *has_texture = (material->pbr.roughness.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_metalness(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.metalness.value_real; + *has_texture = (material->pbr.metalness.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_normal(const ufbx_material *material, bool *has_texture) { + if (!material || !has_texture) return; + *has_texture = (material->pbr.normal_map.texture != NULL); +} + +void ufbx_wrapper_material_get_fbx_diffuse_color(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->fbx.diffuse_color.value_vec3.x; + out_vec3[1] = material->fbx.diffuse_color.value_vec3.y; + out_vec3[2] = material->fbx.diffuse_color.value_vec3.z; + *has_texture = (material->fbx.diffuse_color.texture != NULL); +} + +void ufbx_wrapper_material_get_fbx_specular_color(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->fbx.specular_color.value_vec3.x; + out_vec3[1] = material->fbx.specular_color.value_vec3.y; + out_vec3[2] = material->fbx.specular_color.value_vec3.z; + *has_texture = (material->fbx.specular_color.texture != NULL); +} + +void ufbx_wrapper_material_get_fbx_normal(const ufbx_material *material, bool *has_texture) { + if (!material || !has_texture) return; + *has_texture = (material->fbx.normal_map.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_emission_color(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->pbr.emission_color.value_vec3.x; + out_vec3[1] = material->pbr.emission_color.value_vec3.y; + out_vec3[2] = material->pbr.emission_color.value_vec3.z; + *has_texture = (material->pbr.emission_color.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_emission_factor(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.emission_factor.value_real; + *has_texture = (material->pbr.emission_factor.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_opacity(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.opacity.value_real; + *has_texture = (material->pbr.opacity.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_ambient_occlusion(const ufbx_material *material, bool *has_texture) { + if (!material || !has_texture) return; + *has_texture = (material->pbr.ambient_occlusion.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_specular_factor(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.specular_factor.value_real; + *has_texture = (material->pbr.specular_factor.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_specular_color(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->pbr.specular_color.value_vec3.x; + out_vec3[1] = material->pbr.specular_color.value_vec3.y; + out_vec3[2] = material->pbr.specular_color.value_vec3.z; + *has_texture = (material->pbr.specular_color.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_specular_ior(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.specular_ior.value_real; + *has_texture = (material->pbr.specular_ior.texture != NULL); +} + +void ufbx_wrapper_material_get_fbx_emission_color(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->fbx.emission_color.value_vec3.x; + out_vec3[1] = material->fbx.emission_color.value_vec3.y; + out_vec3[2] = material->fbx.emission_color.value_vec3.z; + *has_texture = (material->fbx.emission_color.texture != NULL); +} + +void ufbx_wrapper_material_get_fbx_emission_factor(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->fbx.emission_factor.value_real; + *has_texture = (material->fbx.emission_factor.texture != NULL); +} + +void ufbx_wrapper_material_get_fbx_ambient_color(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->fbx.ambient_color.value_vec3.x; + out_vec3[1] = material->fbx.ambient_color.value_vec3.y; + out_vec3[2] = material->fbx.ambient_color.value_vec3.z; + *has_texture = (material->fbx.ambient_color.texture != NULL); +} + +void ufbx_wrapper_material_get_fbx_bump(const ufbx_material *material, bool *has_texture) { + if (!material || !has_texture) return; + *has_texture = (material->fbx.bump.texture != NULL); +} + +void ufbx_wrapper_material_get_fbx_bump_factor(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->fbx.bump_factor.value_real; + *has_texture = (material->fbx.bump_factor.texture != NULL); +} + +void ufbx_wrapper_material_get_fbx_displacement(const ufbx_material *material, bool *has_texture) { + if (!material || !has_texture) return; + *has_texture = (material->fbx.displacement.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_transmission_factor(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.transmission_factor.value_real; + *has_texture = (material->pbr.transmission_factor.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_transmission_color(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->pbr.transmission_color.value_vec3.x; + out_vec3[1] = material->pbr.transmission_color.value_vec3.y; + out_vec3[2] = material->pbr.transmission_color.value_vec3.z; + *has_texture = (material->pbr.transmission_color.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_subsurface_factor(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.subsurface_factor.value_real; + *has_texture = (material->pbr.subsurface_factor.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_subsurface_color(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->pbr.subsurface_color.value_vec3.x; + out_vec3[1] = material->pbr.subsurface_color.value_vec3.y; + out_vec3[2] = material->pbr.subsurface_color.value_vec3.z; + *has_texture = (material->pbr.subsurface_color.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_subsurface_radius(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->pbr.subsurface_radius.value_vec3.x; + out_vec3[1] = material->pbr.subsurface_radius.value_vec3.y; + out_vec3[2] = material->pbr.subsurface_radius.value_vec3.z; + *has_texture = (material->pbr.subsurface_radius.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_coat_factor(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.coat_factor.value_real; + *has_texture = (material->pbr.coat_factor.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_coat_color(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->pbr.coat_color.value_vec3.x; + out_vec3[1] = material->pbr.coat_color.value_vec3.y; + out_vec3[2] = material->pbr.coat_color.value_vec3.z; + *has_texture = (material->pbr.coat_color.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_coat_roughness(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.coat_roughness.value_real; + *has_texture = (material->pbr.coat_roughness.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_coat_normal(const ufbx_material *material, bool *has_texture) { + if (!material || !has_texture) return; + *has_texture = (material->pbr.coat_normal.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_sheen_factor(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.sheen_factor.value_real; + *has_texture = (material->pbr.sheen_factor.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_sheen_color(const ufbx_material *material, double *out_vec3, bool *has_texture) { + if (!material || !out_vec3 || !has_texture) return; + out_vec3[0] = material->pbr.sheen_color.value_vec3.x; + out_vec3[1] = material->pbr.sheen_color.value_vec3.y; + out_vec3[2] = material->pbr.sheen_color.value_vec3.z; + *has_texture = (material->pbr.sheen_color.texture != NULL); +} + +void ufbx_wrapper_material_get_pbr_sheen_roughness(const ufbx_material *material, double *out_value, bool *has_texture) { + if (!material || !out_value || !has_texture) return; + *out_value = material->pbr.sheen_roughness.value_real; + *has_texture = (material->pbr.sheen_roughness.texture != NULL); +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_base_color_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.base_color.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_roughness_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.roughness.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_metalness_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.metalness.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_normal_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.normal_map.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_fbx_diffuse_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->fbx.diffuse_color.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_fbx_specular_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->fbx.specular_color.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_fbx_normal_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->fbx.normal_map.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_emission_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.emission_color.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_opacity_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.opacity.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_ambient_occlusion_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.ambient_occlusion.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_specular_color_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.specular_color.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_fbx_emission_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->fbx.emission_color.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_fbx_ambient_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->fbx.ambient_color.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_fbx_bump_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->fbx.bump.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_fbx_displacement_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->fbx.displacement.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_transmission_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.transmission_color.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_subsurface_color_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.subsurface_color.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_coat_color_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.coat_color.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_coat_normal_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.coat_normal.texture; +} + +ufbx_texture* ufbx_wrapper_material_get_pbr_sheen_color_texture(const ufbx_material *material) { + if (!material) return NULL; + return material->pbr.sheen_color.texture; +} + +const char* ufbx_wrapper_texture_get_name(const ufbx_texture *texture) { + if (!texture) return ""; + return texture->name.data ? texture->name.data : ""; +} + +const char* ufbx_wrapper_texture_get_filename(const ufbx_texture *texture) { + if (!texture) return ""; + return texture->filename.data ? texture->filename.data : ""; +} + +const char* ufbx_wrapper_texture_get_relative_filename(const ufbx_texture *texture) { + if (!texture) return ""; + return texture->relative_filename.data ? texture->relative_filename.data : ""; +} + +const char* ufbx_wrapper_texture_get_absolute_filename(const ufbx_texture *texture) { + if (!texture) return ""; + return texture->absolute_filename.data ? texture->absolute_filename.data : ""; +} + +int ufbx_wrapper_texture_get_type(const ufbx_texture *texture) { + if (!texture) return 0; + return (int)texture->type; +} + // Light access size_t ufbx_wrapper_scene_get_num_lights(const ufbx_scene *scene) { return scene ? scene->lights.count : 0; @@ -372,30 +722,6 @@ ufbx_texture* ufbx_wrapper_scene_get_texture(const ufbx_scene *scene, size_t ind return scene->textures.data[index]; } -const char* ufbx_wrapper_texture_get_name(const ufbx_texture *texture) { - if (!texture) return ""; - return texture->name.data ? texture->name.data : ""; -} - -const char* ufbx_wrapper_texture_get_filename(const ufbx_texture *texture) { - if (!texture) return ""; - return texture->filename.data ? texture->filename.data : ""; -} - -const char* ufbx_wrapper_texture_get_absolute_filename(const ufbx_texture *texture) { - if (!texture) return ""; - return texture->absolute_filename.data ? texture->absolute_filename.data : ""; -} - -const char* ufbx_wrapper_texture_get_relative_filename(const ufbx_texture *texture) { - if (!texture) return ""; - return texture->relative_filename.data ? texture->relative_filename.data : ""; -} - -int ufbx_wrapper_texture_get_type(const ufbx_texture *texture) { - return texture ? (int)texture->type : 0; -} - // AnimStack access size_t ufbx_wrapper_scene_get_num_anim_stacks(const ufbx_scene *scene) { return scene ? scene->anim_stacks.count : 0; diff --git a/ufbx/src/ufbx_wrapper.h b/ufbx/src/ufbx_wrapper.h index c652025..c336b20 100644 --- a/ufbx/src/ufbx_wrapper.h +++ b/ufbx/src/ufbx_wrapper.h @@ -75,6 +75,82 @@ ufbx_material* ufbx_wrapper_scene_get_material(const ufbx_scene *scene, size_t i size_t ufbx_wrapper_mesh_get_num_materials(const ufbx_mesh *mesh); ufbx_material* ufbx_wrapper_mesh_get_material(const ufbx_mesh *mesh, size_t index); const char* ufbx_wrapper_material_get_name(const ufbx_material *material); +int ufbx_wrapper_material_get_shader_type(const ufbx_material *material); +const char* ufbx_wrapper_material_get_shading_model_name(const ufbx_material *material); +size_t ufbx_wrapper_material_get_num_textures(const ufbx_material *material); + +// Material PBR properties (most common) +void ufbx_wrapper_material_get_pbr_base_color(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_pbr_roughness(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_pbr_metalness(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_pbr_normal(const ufbx_material *material, bool *has_texture); + +// Material PBR properties (extended) +void ufbx_wrapper_material_get_pbr_emission_color(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_pbr_emission_factor(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_pbr_opacity(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_pbr_ambient_occlusion(const ufbx_material *material, bool *has_texture); +void ufbx_wrapper_material_get_pbr_specular_factor(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_pbr_specular_color(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_pbr_specular_ior(const ufbx_material *material, double *out_value, bool *has_texture); + +// Material PBR properties (advanced - Phase 3) +void ufbx_wrapper_material_get_pbr_transmission_factor(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_pbr_transmission_color(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_pbr_subsurface_factor(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_pbr_subsurface_color(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_pbr_subsurface_radius(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_pbr_coat_factor(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_pbr_coat_color(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_pbr_coat_roughness(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_pbr_coat_normal(const ufbx_material *material, bool *has_texture); +void ufbx_wrapper_material_get_pbr_sheen_factor(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_pbr_sheen_color(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_pbr_sheen_roughness(const ufbx_material *material, double *out_value, bool *has_texture); + +// Material FBX properties (most common) +void ufbx_wrapper_material_get_fbx_diffuse_color(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_fbx_specular_color(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_fbx_normal(const ufbx_material *material, bool *has_texture); + +// Material FBX properties (extended) +void ufbx_wrapper_material_get_fbx_emission_color(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_fbx_emission_factor(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_fbx_ambient_color(const ufbx_material *material, double *out_vec3, bool *has_texture); +void ufbx_wrapper_material_get_fbx_bump(const ufbx_material *material, bool *has_texture); +void ufbx_wrapper_material_get_fbx_bump_factor(const ufbx_material *material, double *out_value, bool *has_texture); +void ufbx_wrapper_material_get_fbx_displacement(const ufbx_material *material, bool *has_texture); + +// Texture access from material maps - PBR +ufbx_texture* ufbx_wrapper_material_get_pbr_base_color_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_roughness_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_metalness_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_normal_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_emission_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_opacity_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_ambient_occlusion_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_specular_color_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_transmission_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_subsurface_color_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_coat_color_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_coat_normal_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_pbr_sheen_color_texture(const ufbx_material *material); + +// Texture access from material maps - FBX +ufbx_texture* ufbx_wrapper_material_get_fbx_diffuse_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_fbx_specular_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_fbx_normal_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_fbx_emission_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_fbx_ambient_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_fbx_bump_texture(const ufbx_material *material); +ufbx_texture* ufbx_wrapper_material_get_fbx_displacement_texture(const ufbx_material *material); + +// Texture properties +const char* ufbx_wrapper_texture_get_name(const ufbx_texture *texture); +const char* ufbx_wrapper_texture_get_filename(const ufbx_texture *texture); +const char* ufbx_wrapper_texture_get_relative_filename(const ufbx_texture *texture); +const char* ufbx_wrapper_texture_get_absolute_filename(const ufbx_texture *texture); +int ufbx_wrapper_texture_get_type(const ufbx_texture *texture); // Light access size_t ufbx_wrapper_scene_get_num_lights(const ufbx_scene *scene); @@ -120,11 +196,6 @@ bool ufbx_wrapper_bone_is_root(const ufbx_bone *bone); // Texture access size_t ufbx_wrapper_scene_get_num_textures(const ufbx_scene *scene); ufbx_texture* ufbx_wrapper_scene_get_texture(const ufbx_scene *scene, size_t index); -const char* ufbx_wrapper_texture_get_name(const ufbx_texture *texture); -const char* ufbx_wrapper_texture_get_filename(const ufbx_texture *texture); -const char* ufbx_wrapper_texture_get_absolute_filename(const ufbx_texture *texture); -const char* ufbx_wrapper_texture_get_relative_filename(const ufbx_texture *texture); -int ufbx_wrapper_texture_get_type(const ufbx_texture *texture); // AnimStack access size_t ufbx_wrapper_scene_get_num_anim_stacks(const ufbx_scene *scene); From 746e342cd5625684b48ca16922edbbd5aafb07b3 Mon Sep 17 00:00:00 2001 From: popomore Date: Sat, 24 Jan 2026 17:46:46 +0800 Subject: [PATCH 2/2] f --- examples/basic_usage.py | 21 ++- sfs.py | 8 +- tests/test_animation_deformers.py | 36 ++-- tests/test_new_api.py | 49 ++++-- ufbx/__init__.py | 6 +- ufbx/_ufbx.pyx | 280 ++++++++++++++++++++++++++++++ 6 files changed, 342 insertions(+), 58 deletions(-) diff --git a/examples/basic_usage.py b/examples/basic_usage.py index f3b78e6..3ebfc24 100755 --- a/examples/basic_usage.py +++ b/examples/basic_usage.py @@ -158,10 +158,10 @@ def extract_euler_angles(matrix, scale_x, scale_y, scale_z): r00 = matrix[0, 0] / scale_x r10 = matrix[1, 0] / scale_x r20 = matrix[2, 0] / scale_x - r01 = matrix[0, 1] / scale_y + matrix[0, 1] / scale_y r11 = matrix[1, 1] / scale_y r21 = matrix[2, 1] / scale_y - r02 = matrix[0, 2] / scale_z + matrix[0, 2] / scale_z r12 = matrix[1, 2] / scale_z r22 = matrix[2, 2] / scale_z @@ -202,10 +202,7 @@ def print_transform_info(node): # Column 2: Z-axis basis vector scale_z = math.sqrt(local_matrix[0, 2] ** 2 + local_matrix[1, 2] ** 2 + local_matrix[2, 2] ** 2) - if abs(scale_x - scale_y) < 1e-3 and abs(scale_y - scale_z) < 1e-3: - scale_note = "uniform" - else: - scale_note = "non-uniform" + scale_note = "uniform" if abs(scale_x - scale_y) < 0.001 and abs(scale_y - scale_z) < 0.001 else "non-uniform" print(f" Scale: ({scale_x:8.3f}, {scale_y:8.3f}, {scale_z:8.3f}) [{scale_note}]") @@ -410,11 +407,13 @@ def main(): shading_models[shading_model] = shading_models.get(shading_model, 0) + 1 # Count materials with textures (check common maps) - has_texture = any([ - material.pbr_base_color.texture_enabled and material.pbr_base_color.texture, - material.pbr_normal_map.texture_enabled and material.pbr_normal_map.texture, - material.fbx_diffuse_color.texture_enabled and material.fbx_diffuse_color.texture, - ]) + has_texture = any( + [ + material.pbr_base_color.texture_enabled and material.pbr_base_color.texture, + material.pbr_normal_map.texture_enabled and material.pbr_normal_map.texture, + material.fbx_diffuse_color.texture_enabled and material.fbx_diffuse_color.texture, + ] + ) if has_texture: materials_with_textures += 1 diff --git a/sfs.py b/sfs.py index 0a8ee0c..cb02d66 100644 --- a/sfs.py +++ b/sfs.py @@ -459,13 +459,7 @@ def do_update(argv, config: Config): old_dir = os.path.join(tmp_dir, "old") new_dir = os.path.join(tmp_dir, "new") - try: - os.mkdir(tmp_dir) - except FileExistsError: - raise TempExistsError( - f"Temporary directory {tmp_dir} exists, delete it or use '--remove-temp' to automatically remove it" - ) - + os.mkdir(tmp_dir) os.mkdir(old_dir) os.mkdir(new_dir) diff --git a/tests/test_animation_deformers.py b/tests/test_animation_deformers.py index 42a0715..2df3b30 100644 --- a/tests/test_animation_deformers.py +++ b/tests/test_animation_deformers.py @@ -7,83 +7,77 @@ def test_scene_has_animation_properties(): """Test that Scene has animation-related properties""" - assert hasattr(ufbx.Scene, 'anim_stacks') - assert hasattr(ufbx.Scene, 'anim_curves') + assert hasattr(ufbx.Scene, "anim_stacks") + assert hasattr(ufbx.Scene, "anim_curves") def test_scene_has_deformer_properties(): """Test that Scene has deformer-related properties""" - assert hasattr(ufbx.Scene, 'skin_deformers') - assert hasattr(ufbx.Scene, 'blend_deformers') - assert hasattr(ufbx.Scene, 'blend_shapes') - assert hasattr(ufbx.Scene, 'constraints') + assert hasattr(ufbx.Scene, "skin_deformers") + assert hasattr(ufbx.Scene, "blend_deformers") + assert hasattr(ufbx.Scene, "blend_shapes") + assert hasattr(ufbx.Scene, "constraints") def test_anim_stack_class_has_properties(): """Test that AnimStack class has expected properties""" - expected_properties = ['name', 'time_begin', 'time_end', 'layers'] + expected_properties = ["name", "time_begin", "time_end", "layers"] for prop in expected_properties: assert hasattr(ufbx.AnimStack, prop), f"AnimStack missing property: {prop}" def test_anim_layer_class_has_properties(): """Test that AnimLayer class has expected properties""" - expected_properties = [ - 'name', 'weight', 'weight_is_animated', 'blended', - 'additive', 'compose_rotation', 'compose_scale' - ] + expected_properties = ["name", "weight", "weight_is_animated", "blended", "additive", "compose_rotation", "compose_scale"] for prop in expected_properties: assert hasattr(ufbx.AnimLayer, prop), f"AnimLayer missing property: {prop}" def test_anim_curve_class_has_properties(): """Test that AnimCurve class has expected properties""" - expected_properties = [ - 'name', 'num_keyframes', 'min_value', 'max_value', - 'min_time', 'max_time' - ] + expected_properties = ["name", "num_keyframes", "min_value", "max_value", "min_time", "max_time"] for prop in expected_properties: assert hasattr(ufbx.AnimCurve, prop), f"AnimCurve missing property: {prop}" def test_skin_deformer_class_has_properties(): """Test that SkinDeformer class has expected properties""" - expected_properties = ['name', 'clusters'] + expected_properties = ["name", "clusters"] for prop in expected_properties: assert hasattr(ufbx.SkinDeformer, prop), f"SkinDeformer missing property: {prop}" def test_skin_cluster_class_has_properties(): """Test that SkinCluster class has expected properties""" - expected_properties = ['name', 'num_weights'] + expected_properties = ["name", "num_weights"] for prop in expected_properties: assert hasattr(ufbx.SkinCluster, prop), f"SkinCluster missing property: {prop}" def test_blend_deformer_class_has_properties(): """Test that BlendDeformer class has expected properties""" - expected_properties = ['name', 'channels'] + expected_properties = ["name", "channels"] for prop in expected_properties: assert hasattr(ufbx.BlendDeformer, prop), f"BlendDeformer missing property: {prop}" def test_blend_channel_class_has_properties(): """Test that BlendChannel class has expected properties""" - expected_properties = ['name', 'weight'] + expected_properties = ["name", "weight"] for prop in expected_properties: assert hasattr(ufbx.BlendChannel, prop), f"BlendChannel missing property: {prop}" def test_blend_shape_class_has_properties(): """Test that BlendShape class has expected properties""" - expected_properties = ['name', 'num_offsets'] + expected_properties = ["name", "num_offsets"] for prop in expected_properties: assert hasattr(ufbx.BlendShape, prop), f"BlendShape missing property: {prop}" def test_constraint_class_has_properties(): """Test that Constraint class has expected properties""" - expected_properties = ['name', 'type', 'weight', 'active'] + expected_properties = ["name", "type", "weight", "active"] for prop in expected_properties: assert hasattr(ufbx.Constraint, prop), f"Constraint missing property: {prop}" diff --git a/tests/test_new_api.py b/tests/test_new_api.py index 9492f24..6db7af1 100644 --- a/tests/test_new_api.py +++ b/tests/test_new_api.py @@ -9,25 +9,33 @@ def test_scene_has_new_properties(): """Test that Scene has the new properties""" # We can't test with real FBX data without files, but we can verify # the properties exist and are callable - assert hasattr(ufbx.Scene, 'lights') - assert hasattr(ufbx.Scene, 'cameras') - assert hasattr(ufbx.Scene, 'bones') - assert hasattr(ufbx.Scene, 'textures') + assert hasattr(ufbx.Scene, "lights") + assert hasattr(ufbx.Scene, "cameras") + assert hasattr(ufbx.Scene, "bones") + assert hasattr(ufbx.Scene, "textures") def test_node_has_new_properties(): """Test that Node has the new properties""" - assert hasattr(ufbx.Node, 'light') - assert hasattr(ufbx.Node, 'camera') - assert hasattr(ufbx.Node, 'bone') + assert hasattr(ufbx.Node, "light") + assert hasattr(ufbx.Node, "camera") + assert hasattr(ufbx.Node, "bone") def test_light_class_has_properties(): """Test that Light class has expected properties""" expected_properties = [ - 'name', 'color', 'intensity', 'local_direction', - 'type', 'decay', 'area_shape', 'inner_angle', - 'outer_angle', 'cast_light', 'cast_shadows' + "name", + "color", + "intensity", + "local_direction", + "type", + "decay", + "area_shape", + "inner_angle", + "outer_angle", + "cast_light", + "cast_shadows", ] for prop in expected_properties: assert hasattr(ufbx.Light, prop), f"Light missing property: {prop}" @@ -36,9 +44,17 @@ def test_light_class_has_properties(): def test_camera_class_has_properties(): """Test that Camera class has expected properties""" expected_properties = [ - 'name', 'projection_mode', 'resolution', 'resolution_is_pixels', - 'field_of_view_deg', 'field_of_view_tan', 'orthographic_extent', - 'orthographic_size', 'aspect_ratio', 'near_plane', 'far_plane' + "name", + "projection_mode", + "resolution", + "resolution_is_pixels", + "field_of_view_deg", + "field_of_view_tan", + "orthographic_extent", + "orthographic_size", + "aspect_ratio", + "near_plane", + "far_plane", ] for prop in expected_properties: assert hasattr(ufbx.Camera, prop), f"Camera missing property: {prop}" @@ -46,17 +62,14 @@ def test_camera_class_has_properties(): def test_bone_class_has_properties(): """Test that Bone class has expected properties""" - expected_properties = ['name', 'radius', 'relative_length', 'is_root'] + expected_properties = ["name", "radius", "relative_length", "is_root"] for prop in expected_properties: assert hasattr(ufbx.Bone, prop), f"Bone missing property: {prop}" def test_texture_class_has_properties(): """Test that Texture class has expected properties""" - expected_properties = [ - 'name', 'filename', 'absolute_filename', - 'relative_filename', 'type' - ] + expected_properties = ["name", "filename", "absolute_filename", "relative_filename", "type"] for prop in expected_properties: assert hasattr(ufbx.Texture, prop), f"Texture missing property: {prop}" diff --git a/ufbx/__init__.py b/ufbx/__init__.py index 4f22bc7..31586d0 100644 --- a/ufbx/__init__.py +++ b/ufbx/__init__.py @@ -17,8 +17,8 @@ Camera, Constraint, ConstraintType, - CoordinateAxis, CoordinateAxes, + CoordinateAxis, Element, ElementType, ErrorType, @@ -30,7 +30,9 @@ LightDecay, LightType, Material, + MaterialFeatures, MaterialMap, + MaterialTexture, Matrix, Mesh, MirrorAxis, @@ -91,7 +93,9 @@ "LightDecay", "LightType", "Material", + "MaterialFeatures", "MaterialMap", + "MaterialTexture", "Matrix", "Mesh", "MirrorAxis", diff --git a/ufbx/_ufbx.pyx b/ufbx/_ufbx.pyx index 2bd91b7..8ece87e 100644 --- a/ufbx/_ufbx.pyx +++ b/ufbx/_ufbx.pyx @@ -19,6 +19,51 @@ cdef extern from "ufbx-c/ufbx.h": double z double w + ctypedef struct ufbx_string: + const char* data + size_t length + + ctypedef struct ufbx_material_feature_info: + bint enabled + + ctypedef struct ufbx_material_features: + ufbx_material_feature_info pbr + ufbx_material_feature_info metalness + ufbx_material_feature_info diffuse + ufbx_material_feature_info specular + ufbx_material_feature_info emission + ufbx_material_feature_info transmission + ufbx_material_feature_info coat + ufbx_material_feature_info sheen + ufbx_material_feature_info opacity + ufbx_material_feature_info ambient_occlusion + ufbx_material_feature_info matte + ufbx_material_feature_info unlit + ufbx_material_feature_info ior + ufbx_material_feature_info diffuse_roughness + ufbx_material_feature_info transmission_roughness + ufbx_material_feature_info thin_walled + ufbx_material_feature_info caustics + ufbx_material_feature_info exit_to_background + ufbx_material_feature_info internal_reflections + ufbx_material_feature_info double_sided + ufbx_material_feature_info roughness_as_glossiness + ufbx_material_feature_info coat_roughness_as_glossiness + ufbx_material_feature_info transmission_roughness_as_glossiness + + # Forward declaration + ctypedef struct ufbx_texture + + ctypedef struct ufbx_material_texture: + ufbx_string material_prop + ufbx_string shader_prop + ufbx_texture* texture + + # Generic list structure + ctypedef struct ufbx_material_texture_list: + ufbx_material_texture* data + size_t count + ctypedef struct ufbx_material_map: ufbx_vec4 value_vec4 long long value_int @@ -119,8 +164,10 @@ cdef extern from "ufbx-c/ufbx.h": ufbx_element element ufbx_material_fbx_maps fbx ufbx_material_pbr_maps pbr + ufbx_material_features features int shader_type ufbx_string shading_model_name + ufbx_material_texture_list textures ctypedef struct ufbx_scene: pass @@ -1882,6 +1929,220 @@ cdef class MaterialMap: return self._map.value_components +cdef class MaterialFeatures: + """Material features (direct access to ufbx_material_features fields)""" + cdef Scene _scene + cdef const ufbx_material_features* _features + + @staticmethod + cdef MaterialFeatures _create(Scene scene, const ufbx_material_features* features): + """Create MaterialFeatures from C struct""" + cdef MaterialFeatures obj = MaterialFeatures.__new__(MaterialFeatures) + obj._scene = scene + obj._features = features + return obj + + @property + def pbr(self): + """PBR feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.pbr.enabled + + @property + def metalness(self): + """Metalness feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.metalness.enabled + + @property + def diffuse(self): + """Diffuse feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.diffuse.enabled + + @property + def specular(self): + """Specular feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.specular.enabled + + @property + def emission(self): + """Emission feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.emission.enabled + + @property + def transmission(self): + """Transmission feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.transmission.enabled + + @property + def coat(self): + """Coat feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.coat.enabled + + @property + def sheen(self): + """Sheen feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.sheen.enabled + + @property + def opacity(self): + """Opacity feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.opacity.enabled + + @property + def ambient_occlusion(self): + """Ambient occlusion feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.ambient_occlusion.enabled + + @property + def matte(self): + """Matte feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.matte.enabled + + @property + def unlit(self): + """Unlit feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.unlit.enabled + + @property + def ior(self): + """IOR feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.ior.enabled + + @property + def diffuse_roughness(self): + """Diffuse roughness feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.diffuse_roughness.enabled + + @property + def transmission_roughness(self): + """Transmission roughness feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.transmission_roughness.enabled + + @property + def thin_walled(self): + """Thin walled feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.thin_walled.enabled + + @property + def caustics(self): + """Caustics feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.caustics.enabled + + @property + def exit_to_background(self): + """Exit to background feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.exit_to_background.enabled + + @property + def internal_reflections(self): + """Internal reflections feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.internal_reflections.enabled + + @property + def double_sided(self): + """Double sided feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.double_sided.enabled + + @property + def roughness_as_glossiness(self): + """Roughness as glossiness feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.roughness_as_glossiness.enabled + + @property + def coat_roughness_as_glossiness(self): + """Coat roughness as glossiness feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.coat_roughness_as_glossiness.enabled + + @property + def transmission_roughness_as_glossiness(self): + """Transmission roughness as glossiness feature enabled""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return self._features.transmission_roughness_as_glossiness.enabled + + +cdef class MaterialTexture: + """Material texture mapping (direct access to ufbx_material_texture fields)""" + cdef Scene _scene + cdef const ufbx_material_texture* _mat_tex + + @staticmethod + cdef MaterialTexture _create(Scene scene, const ufbx_material_texture* mat_tex): + """Create MaterialTexture from C struct""" + cdef MaterialTexture obj = MaterialTexture.__new__(MaterialTexture) + obj._scene = scene + obj._mat_tex = mat_tex + return obj + + @property + def material_prop(self): + """Material property name""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + cdef bytes prop_bytes = self._mat_tex.material_prop.data[:self._mat_tex.material_prop.length] + return prop_bytes.decode('utf-8', errors='replace') + + @property + def shader_prop(self): + """Shader property name""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + cdef bytes prop_bytes = self._mat_tex.shader_prop.data[:self._mat_tex.shader_prop.length] + return prop_bytes.decode('utf-8', errors='replace') + + @property + def texture(self): + """Texture object""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + if self._mat_tex.texture == NULL: + return None + return Texture._create(self._scene, self._mat_tex.texture) + + cdef class Material(Element): """Material definition (Rust API style - direct struct access)""" cdef Scene _scene @@ -1918,6 +2179,25 @@ cdef class Material(Element): cdef bytes name_bytes = self._material.shading_model_name.data[:self._material.shading_model_name.length] return name_bytes.decode('utf-8', errors='replace') + @property + def features(self): + """Material features""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + return MaterialFeatures._create(self._scene, &self._material.features) + + @property + def textures(self): + """List of textures used by this material""" + if self._scene._closed: + raise RuntimeError("Scene is closed") + cdef size_t count = self._material.textures.count + cdef list result = [] + cdef size_t i + for i in range(count): + result.append(MaterialTexture._create(self._scene, &self._material.textures.data[i])) + return result + # PBR Material Maps - 直接返回 MaterialMap @property def pbr_base_factor(self):