Skip to content

Commit

Permalink
Merge pull request #110 from cadop/Fruity-Grebbles/issue101
Browse files Browse the repository at this point in the history
Fruity-Grebbles/issue101
  • Loading branch information
grebz-dev authored Jul 13, 2023
2 parents aae95b4 + f9ce574 commit fd3dffe
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 3 deletions.
61 changes: 61 additions & 0 deletions exts/siborg.create.human/API_EXAMPLE.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Human Generator API Example
# Author: Joshua Grebler | SiBORG Lab | 2023
# Description: This is an example of how to use the Human Generator API to create human models in NVIDIA Omniverse.
# The siborg.create.human extension must be installed and enabled for this to work.

# The script generates 10 humans, placing them throughout the stage. Random modifiers and clothing are applied to each.

import siborg.create.human as hg
from siborg.create.human.shared import data_path
import omni.usd
import random

# Get the stage
context = omni.usd.get_context()
stage = context.get_stage()

# Make a single Human to start with
human = hg.Human()
human.add_to_scene()

# Apply a modifier by name (you can find the names of all the available modifiers
# by using the `get_modifier_names()` method)
height = human.get_modifier_by_name("macrodetails-height/Height")
human.set_modifier_value(height, 1)
# Update the human in the scene
human.update_in_scene(human.prim_path)

# Gather some default clothing items (additional clothing can be downloaded from the extension UI)
clothes = ["nvidia_Casual/nvidia_casual.mhclo", "omni_casual/omni_casual.mhclo", "siborg_casual/siborg_casual.mhclo"]

# Convert the clothing names to their full paths.
clothes = [data_path(f"clothes/{c}") for c in clothes]

# Create 20 humans, placing them randomly throughout the scene, and applying random modifier values
for _ in range(10):
h = hg.Human()
h.add_to_scene()

# Apply a random translation and Y rotation to the human prim
translateOp = h.prim.AddTranslateOp()
translateOp.Set((random.uniform(-50, 50), 0, random.uniform(-50, 50)))
rotateOp = h.prim.AddRotateXYZOp()
rotateOp.Set((0, random.uniform(0, 360), 0))

# Apply a random value to the last 9 modifiers in the list.
# These modifiers are macros that affect the overall shape of the human more than any individual modifier.

# Get the last 9 modifiers
modifiers = h.get_modifiers()[-9:]

# Apply a random value to each modifier. Use the modifier's min/max values to ensure the value is within range.

for m in modifiers:
h.set_modifier_value(m, random.uniform(m.getMin(), m.getMax()))


# Update the human in the scene
h.update_in_scene(h.prim_path)

# Add a random clothing item to the human
h.add_item(random.choice(clothes))
1 change: 1 addition & 0 deletions exts/siborg.create.human/siborg/create/human/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .extension import *
from .human import Human
91 changes: 90 additions & 1 deletion exts/siborg.create.human/siborg/create/human/human.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Tuple, List
from typing import Tuple, List, Dict, Union
from .mhcaller import MHCaller
import numpy as np
import omni.kit
Expand All @@ -12,6 +12,29 @@

from .materials import get_mesh_texture, create_material, bind_material
class Human:
"""Class representing a human in the scene. This class is used to add a human to the scene,
and to update the human in the scene. The class also contains functions to add and remove
proxies (clothing, etc.) and apply modifiers, as well as a skeleton.
Attributes
----------
name : str
Name of the human
prim : UsdSkel.Root
Reference to the usd prim for the skelroot representing the human in the stage. Can be changed using set_prim()
prim_path : str
Path to the human prim
scale : float
Scale factor for the human. Defaults to 10 (Omniverse provided humans are 10 times larger than makehuman)
skeleton : Makehuman.Skeleton
Skeleton object for the human
usd_skel : UsdSkel.Skeleton
Skeleton object for the human in the USD stage. Imported from the skeleton object.
objects : List[Object3D]
List of objects attached to the human. Fetched from the makehuman app
mh_meshes : List[Object3D]
List of meshes attached to the human. Fetched from the makehuman app
"""
def __init__(self, name='human', **kwargs):
"""Constructs an instance of Human.
Expand All @@ -32,6 +55,9 @@ def __init__(self, name='human', **kwargs):
# Create a skeleton object for the human
self.skeleton = Skeleton(self.scale)

# usd_skel is none until the human is added to the stage
self.usd_skel = None

# Set the human in makehuman to default values
MHCaller.reset_human()

Expand Down Expand Up @@ -149,6 +175,7 @@ def update_in_scene(self, prim_path: str):
usd_context = omni.usd.get_context()
stage = usd_context.get_stage()
prim = stage.GetPrimAtPath(prim_path)
prim = stage.GetPrimAtPath(prim_path)

if prim and stage:
print(prim.GetPath().pathString)
Expand Down Expand Up @@ -355,6 +382,68 @@ def import_meshes(self, prim_path: str, stage: Usd.Stage, offset: List[float] =

return paths

def get_written_modifiers(self) -> Union[Dict[str, float], None]:
"""List of modifier names and values written to the human prim.
MAY BE STALE IF THE HUMAN HAS BEEN UPDATED IN MAKEHUMAN AND THE CHANGES HAVE NOT BEEN WRITTEN TO THE PRIM.
Returns
-------
Dict[str, float]
Dictionary of modifier names and values. Keys are modifier names, values are modifier values"""
return self.prim.GetCustomDataByKey("Modifiers") if self.prim else None

def get_changed_modifiers(self):
"""List of modifiers which have been changed in makehuman. Fetched from the human in makehuman.
MAY NOT MATCH `get_written_modifiers()` IF CHANGES HAVE NOT BEEN WRITTEN TO THE PRIM."""
return MHCaller.modifiers

def get_modifiers(self):
"""Retrieve the list of all modifiers available to the human, whether or not their values have changed."""
return MHCaller.default_modifiers

def set_modifier_value(self, modifier, value: float):
"""Sets the value of a modifier in makehuman. Validates the value before setting it.
Returns true if the value was set, false otherwise.
Parameters
----------
modifier : makehuman.humanmodifier.Modifier
Modifier to change
value : float
Value to set the modifier to
"""

# Get the range of the modifier
val_min = modifier.getMin()
val_max = modifier.getMax()

# Check if the value is within the range of the modifier
if value >= val_min and value <= val_max:
# Set the value of the modifier
modifier.setValue(value)
return True
else:
carb.log_warn(f"Value must be between {str(val_min)} and {str(val_max)}")
return False

def get_modifier_by_name(self, name: str):
"""Gets a modifier from the list of modifiers attached to the human by name
Parameters
----------
name : str
Name of the modifier to get
Returns
-------
makehuman.modifiers.Modifier
Modifier with the given name
"""
return MHCaller.human.getModifier(name)

def get_modifier_names(self):
return MHCaller.human.getModifierNames()

def write_properties(self, prim_path: str, stage: Usd.Stage):
"""Writes the properties of the human to the human prim. This includes modifiers and
proxies. This is called when the human is added to the scene, and when the human is
Expand Down
9 changes: 9 additions & 0 deletions exts/siborg.create.human/siborg/create/human/mhcaller.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ def modifiers(cls):
"""
return [m for m in cls.human.modifiers if m.getValue() or m.isMacro()]

@classproperty
def default_modifiers(cls):
"""List of all the loaded modifiers, whether or not their default values have been changed.
-------
list of: humanmodifier.Modifier
The macros and changed modifiers included in the human
"""
return cls.human.modifiers

@classproperty
def proxies(cls):
"""List of proxies attached to the human.
Expand Down
2 changes: 0 additions & 2 deletions exts/siborg.create.human/siborg/create/human/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,6 @@ def update_human(self):
# Collect changed values from the parameter panel
self.param_panel.update_models()

# Apply MakeHuman targets
MHCaller.human.applyAllTargets()

# Update the human in the scene
self._human.update_in_scene(self._human.prim_path)
Expand Down

0 comments on commit fd3dffe

Please sign in to comment.