from pxr import Usd, UsdGeom, Gf, Vt, Sdf
import numpy as np
import hou
def resolve_proto_indices_numpy(proto_names, prototype_lookup, fallback_index=0):
if not proto_names:
return []
proto_names_np = np.array(proto_names, dtype=object)
name_to_index = {k: prototype_lookup.get(k, fallback_index) for k in proto_names_np}
indices_np = np.vectorize(name_to_index.get)(proto_names_np)
return list(indices_np)
node = hou.pwd()
stage = node.editableStage()
instancer_path = node.evalParm("instancer_path")
points_path = node.evalParm("points_path")
collection_path = node.evalParm("collection_path")
collection_name = node.evalParm("collection_name")
collection_base = collection_path
collection_prim_path = Sdf.Path(collection_base)
collection_full_name = f"collection:{collection_name}"
collection_path = f"{collection_base}.{collection_full_name}"
collection_prim = stage.GetPrimAtPath(collection_prim_path)
if not collection_prim or not collection_prim.IsValid():
raise RuntimeError(f"Prim not found at path: {collection_prim_path}")
includes_rel = collection_prim.GetRelationship(f"{collection_full_name}:includes")
if not includes_rel or not includes_rel.IsValid():
raise RuntimeError(f"'includes' relationship not found on collection: {collection_path}")
prototype_paths = [target.pathString for target in includes_rel.GetTargets()]
if not prototype_paths:
raise RuntimeError(f"No prototype targets found in collection: {collection_path}")
prototype_lookup = {
path.split("/")[-1]: idx
for idx, path in enumerate(prototype_paths)
}
points_prim = stage.GetPrimAtPath(points_path)
if not points_prim or not points_prim.IsValid():
raise RuntimeError(f"Invalid points prim path: {points_path}")
points_geom = UsdGeom.Points(points_prim)
positions = points_geom.GetPointsAttr().Get() or []
orient_attr = points_prim.GetAttribute("orient")
if orient_attr and orient_attr.HasValue():
raw_orients = orient_attr.Get()
orientations = [
Gf.Quath(q.real, *q.imaginary) if hasattr(q, "real") else
Gf.Quath(q.GetReal(), *q.GetImaginary())
for q in raw_orients
]
else:
orientations = [Gf.Quath(1.0, 0.0, 0.0, 0.0) for _ in positions]
scale_attr = points_prim.GetAttribute("primvars:scale")
if scale_attr and scale_attr.HasValue():
raw_scales = scale_attr.Get()
scales = [Gf.Vec3f(*s) if hasattr(s, "__iter__") else s for s in raw_scales]
else:
scales = [Gf.Vec3f(1.0, 1.0, 1.0) for _ in positions]
proto_name_attr = points_prim.GetAttribute("primvars:protoName")
proto_names = proto_name_attr.Get() if proto_name_attr and proto_name_attr.HasValue() else []
proto_indices = resolve_proto_indices_numpy(proto_names, prototype_lookup)
proto_indices = [int(i) for i in proto_indices]
instancer_prim = stage.DefinePrim(instancer_path, "PointInstancer")
instancer = UsdGeom.PointInstancer(instancer_prim)
instancer.GetPrototypesRel().SetTargets(prototype_paths)
instancer.GetProtoIndicesAttr().Set(Vt.IntArray(proto_indices))
instancer.GetPositionsAttr().Set(positions)
instancer.GetOrientationsAttr().Set(orientations)
instancer.GetScalesAttr().Set(scales)