Materials and Textures¶
Guide to reading, creating, and working with materials and their textures. The
reading snippets assume a model returned by skppy.load(); creation snippets
use a model created with skppy.new_model().
Reading materials¶
The material registry exposes stored color plus normalized PBR values:
for mat in model.materials:
r, g, b, a = mat.color.r, mat.color.g, mat.color.b, mat.color.a
print(f"{mat.name!r}")
print(f" RGBA: ({r}, {g}, {b}, {a})")
print(f" alpha: {mat.alpha:.2f}")
print(f" metallic: {mat.metallic:.2f}")
print(f" roughness: {mat.roughness:.2f}")
print(f" textured: {mat.has_texture}")
mat.color.a is the stored color-channel alpha, while mat.alpha is the
normalized material opacity used by importers. Faces reference mat.id.
Loading standalone SKM materials¶
Use load_material() for a material saved from SketchUp or downloaded from a
material library:
import skppy
material = skppy.load_material("stone.skm")
print(material.name, material.color)
if material.texture and material.texture.data:
print(material.texture.filename, len(material.texture.data))
The loader detects the material package from its ZIP contents rather than its
extension. This also handles downloads incorrectly named .skp when their
contents are actually an SKM package with document.xml and ref/ resources.
Malformed packages raise skppy.InvalidSkmError.
Standalone packages can contain V-Ray attribute dictionaries. SketchUp color
and texture values remain authoritative by default; pass
import_vray_materials=True to prefer supported V-Ray scalar PBR values:
material = skppy.load_material("stone.skm", import_vray_materials=True)
Accessing texture data¶
A texture combines physical tile size, its informational filename, and optional encoded image bytes:
for mat in model.materials:
if mat.has_texture and mat.texture:
tex = mat.texture
print(f" filename: {tex.filename!r}")
print(f" size: {tex.x_scale:.1f} x {tex.y_scale:.1f} inches/tile")
if tex.data:
print(f" bytes: {len(tex.data)}")
x_scale and y_scale represent the real-world size of one texture tile
in inches. A value of 100.0 means one tile spans 100 inches (~= 2.54 m).
PBR factors¶
SketchUp’s pbrMR XML block can provide metallic and roughness factors. skppy
applies these only when the matching enable_metalness or enable_roughness
flag is active; disabled factors fall back to Blender-friendly defaults
(metallic=0.0, roughness=1.0).
Saving textures to files¶
Write retained bytes without decoding them, while sanitizing the resource name before joining it to the output directory:
from pathlib import Path, PureWindowsPath
out = Path("extracted_textures")
out.mkdir(exist_ok=True)
for mat in model.materials:
if mat.has_texture and mat.texture and mat.texture.data:
# Discard any serialized source directories before creating a path.
safe_name = PureWindowsPath(mat.texture.filename).name or f"material-{mat.id}"
dest = out / safe_name
dest.write_bytes(mat.texture.data)
print(f"Saved {dest}")
Two materials can refer to the same filename. Production extractors should decide whether to deduplicate identical bytes or generate unique names.
Creating textured materials in memory¶
You can attach a Texture object to a material in memory and persist it through
the modern writer:
from pathlib import Path
import skppy
# Keep the encoded PNG/JPEG bytes; skppy does not transcode the image.
texture_bytes = Path("brick.jpg").read_bytes()
brick = model.add_material("Brick", color=skppy.Color(180, 80, 60))
brick.has_texture = True
brick.texture = skppy.Texture(
filename="brick.jpg",
x_scale=100.0, # 100 inches per tile
y_scale=100.0,
data=texture_bytes,
)
The physical scales control tiling independently of pixel dimensions. The
writer embeds texture.data; filename is the resource name stored in the
container.
UV coordinates¶
UV coordinates are computed per face from the FaceUVProjection object. See
format/uv_projection.md for the full formula.
Quick summary:
UV projection is active only when
face.front_uv(orback_uv) is notNone.Without a projection,
skppyfalls back to planar UV tiling.UV values are computed in local space (SketchUp inches), then divided by
texture.x_scale/texture.y_scaleto normalise.
def front_uv(face, vertex, material):
"""Return one local-space front UV, or None for an untextured face."""
projection = face.front_uv
texture = material.texture
if projection is None or texture is None:
return None
return projection.compute_uv(
px=vertex.position.x,
py=vertex.position.y,
pz=vertex.position.z,
x_scale=texture.x_scale,
y_scale=texture.y_scale,
normal=face.normal().to_tuple(),
)
The vertex must come from the same definition-local scope as the face. Do not apply an instance transform before computing stored UV projections.
Transparent materials¶
In SketchUp, alpha < 1.0 means the material is semi-transparent. In Blender
the addon sets:
Blender 4.2+:material.surface_render_method = "DITHERED"Older Blender:
material.blend_method = "HASHED"when supported, otherwiseBLEND
When a diffuse texture’s alpha channel contains transparent pixels, the Blender
addon connects the image texture’s Alpha output to the Principled BSDF
Alpha input and enables the same transparent material mode.
Material inheritance¶
A face with front_material_id = None inherits the material of its parent
component instance. Use Entities.prepare_mesh() to resolve this:
lookup = {m.id: m for m in model.materials}
mesh = defn.entities.prepare_mesh(
name="my_instance",
material_lookup=lookup,
inherited_material_id=instance.material_id, # may be None
)
for face in mesh.faces:
# None means neither the face nor the parent instance has a material.
print(face.material_name or "default material")
Pass the parent instance’s effective material ID at each nesting level. The method resolves both material names and the texture scale needed for UVs.