Python API
Everything in the UI, from a script.
GroomLab has two Python modules:
groomlab: the Maya package. Every step in GroomLab's UI has a call here. It works on groom nodes in the Maya scene. Most of this page is about it.groomlab_core: the core, without Maya: groom files, graphs, sessions, strands. It runs in Maya's Python, inmayapyand on a farm. See Without Maya.
Import the package in Maya's Script Editor or mayapy:
from maya import cmds
import groomlabCreate a groom
scalp = "head"
groom = groomlab.create_groom(scalp)
cmds.setAttr(groom + ".previewDensity", 1.0)Guides
curves = cmds.ls("guideCurve*", type="transform")
groomlab.guides_from_curves(groom, curves, group="primary", tube_radius=0.15)
groomlab.generate_children(groom, "primary", name="children", count=8, twist=0.5)
groomlab.list_guide_groups(groom)Brush strokes can be scripted in world space too:
groomlab.brush_stroke(groom, "comb", [(-1, 1.6, 0), (1.2, 1.6, 0)], radius=1.2)Parameters and maps
groomlab.set_param(groom, "clump", "strength", groomlab.color_set_map("clumpMask"))
groomlab.set_param(groom, "noise", "amplitude", groomlab.attribute_map("frizzy", max=0.05))
groomlab.add_map(groom, "fur") # a painted map on the guides
groomlab.use_map(groom, "scatter", "density", "fur") # a Map node feeding density
groomlab.set_map_texture(groom, "fur", "C:/art/fur.jpg") # a texture map now, read from a PNG copy
groomlab.reload_map(groom, "fur") # after editing the image elsewhere
groomlab.missing_files(groom) # {"maps": ..., "params": ..., "attributes": ...}
groomlab.find_missing_files([groom], "D:/recovered") # relink by file name, one undo stepset_map_texture links a PNG in place and copies any other format into the masks folder as a PNG
(copy=True copies a PNG too). It returns the stored path.
Animating parameters
Key graph parameters as Maya animation (see Animating parameters):
groomlab.set_param_key(groom, "clump", "strength", value=0.2, time=1)
groomlab.set_param_key(groom, "clump", "strength", value=0.9, time=24)
groomlab.param_control(groom, "clump", "strength") # "groom.ctl_clump_strength"
groomlab.param_key_state(groom, "clump", "strength") # "keyed", "animated", "driven", ...
groomlab.delete_param_key(groom, "clump", "strength", time=24)
groomlab.remove_param_animation(groom, "clump", "strength") # keeps the value nowEditing the graph
from groomlab import graph, presets
model = graph.GraphModel.from_groom(groom)
model.add_node("Curl", name="curl")
model.insert_between("length", "width", "curl")
model.set_param("curl", "radius", 0.08)
model.save(groom) # validates, then writes the graph (undoable)
presets.save_preset(groom, "my_look")
presets.apply_preset(other_groom, "my_look")
text = groomlab.copy_graph_text(groom) # the whole look as text (a preset)
report = groomlab.paste_graph_text(other_groom, text) # fitted, one undo step
report["missing"] # maps, color sets, files, meshes... this groom lackspaste_graph_text raises ValueError and changes nothing when the text is not a GroomLab
graph. Pass fit=False to keep the sizes as they were.
Export
groomlab.export_alembic(groom, "C:/cache/groom.abc", start=1, end=48)
groomlab.export_usd(groom, "C:/cache/groom.usda", start=1, end=48)
groomlab.export_unreal_groom(groom, "C:/game/hair.abc")
groomlab.export_groom(groom, "C:/shots/010/hair.groom", scalp_cache=True, start=1, end=48)
# Scales: an instancer for engines, an FBX mesh for Unreal, maps baked into the body's uvs.
groomlab.export_usd(groom, "C:/game/dragon.usda", scales="instancer")
groomlab.export_scales(groom, "C:/game/dragon_scales.fbx")
maps = groomlab.bake_scale_maps(groom, resolution=4096) # {"ScaleNormal": [...], ..., "max_height": h}Simulation
The Simulate node (see Motion & simulation): added between the guides and Interpolate, stepped as the time moves, filled over a range, baked for renders:
from groomlab import sim
node = sim.add_simulate(groom, bend=0.6, damping=0.15)
sim.simulate(groom, end=120) # Simulate Range: the frames into the cache
sim.simulation_status(groom) # [{node, startFrame, ranges, frames, baked, ...}]
sim.bake_simulation(groom) # {node: path}: the bake renders and exports read
sim.clear_baked_simulation(groom) # simulate again rather than read the bakeOutside Maya, the core's groomlab_core.GroomSession does the same (simulate_to,
simulation_status, set_simulation_feed for the inputs at other times) and
groomlab_core.write_simulation_cache writes a bake.
Groom files back into Maya
A .groom file is how grooms travel between Maya and the standalone app. Import one as a new
groom, save a groom back to the file it is linked to, and reload the file's edits into the same
groom:
groom = groomlab.import_groom("C:/shots/010/hair.groom") # on the selected mesh, or a scalp made from the file
groomlab.save_groom(groom) # back to hair.groom, for the app to open
groomlab.reload_groom(groom) # the app's edits, one undo step
groomlab.groom_file(groom) # the file it is linked toA mesh given or selected must be the file's scalp (the same vertices and faces); otherwise
import_groom raises ValueError: import without it and use Transfer Groom to Scalp. Maps,
card bakes and caches come along; a Z-up file is turned Y-up.
Without Maya
groomlab_core evaluates groom files without the Maya scene. It is in the module's python
folder and is built for Maya 2022's Python (3.7): run it in mayapy, or any Python 3.7 with that
folder on sys.path.
import groomlab_core as gl
scene = gl.GroomScene("C:/shots/010/hair.groom") # opens the caches the file names
strands = scene.evaluate(seconds=12 / scene.frames_per_second, density=1.0)
print(strands.strand_count, strands.point_count)
points = strands.positions_view() # read-only float32 buffer, (point_count, 3)
offsets = strands.offsets_view() # strand i: points offsets[i] to offsets[i + 1] - 1groomlab_core follows GroomLab's own releases; its names may change between them. For a bridge
that must keep working across releases, use the stable C API (from Python, through
ctypes: see Writing a host bridge).
groomlab-cli evaluates groom files headless, for farms and other hosts:
groomlab-cli info hair.groom
groomlab-cli eval hair.groom -o hair.abc -f 1-48 --density 1The file itself is described in The .groom format.
Errors
groomlab.errors(groom) returns what went wrong on a groom: an unknown operator, a map that
can't be read, bad guide data.