Skip to content

Commit

Permalink
Merge pull request princeton-vl#122 from princeton-vl/rc_1.2.6
Browse files Browse the repository at this point in the history
Bugfix v1.2.6 - Fix duplicate configs, CUDA_VISIBLE_DEVICES & more
  • Loading branch information
araistrick authored Apr 25, 2024
2 parents 18be26c + 80ac24c commit 830891c
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 103 deletions.
12 changes: 11 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,14 @@ v1.2.0
- Integrate OcMesher terrain option - see https://github.com/princeton-vl/OcMesher

v1.2.4
- Fix TreeFactory crash for season='winter'
- Fix TreeFactory crash for season='winter'

v1.2.5
- Add Terrain.populated_bounds parameters
- Fix reinitalizing terrain

v1.2.6
- Fix bug where manage_jobs.py would pick a random scene_type config even if one was already loaded
- Fix bug where manage_jobs.py would ignore CUDA_VISIBLE_DEVICES that didnt start at 0
- Add NotImplementedError for dynamic hair.

2 changes: 1 addition & 1 deletion infinigen/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import logging

__version__ = "1.2.5"
__version__ = "1.2.6"
33 changes: 28 additions & 5 deletions infinigen/assets/creatures/carnivore.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import numpy as np
from numpy.random import uniform as U, normal as N
import gin
import bpy, mathutils

from infinigen.assets.creatures.util import genome
from infinigen.assets.creatures.util.genome import Joint
Expand Down Expand Up @@ -179,15 +180,32 @@ def tiger_genome():
@gin.configurable
class CarnivoreFactory(AssetFactory):

def __init__(self, factory_seed=None, bvh=None, coarse=False, animation_mode=None, **kwargs):
def __init__(
self,
factory_seed=None,
bvh: mathutils.bvhtree.BVHTree = None,
coarse: bool = False,
animation_mode: str = None,
hair: bool = True,
clothsim_skin: bool = False,
**kwargs
):
super().__init__(factory_seed, coarse)
self.bvh = bvh
self.animation_mode = animation_mode
self.hair = hair
self.clothsim_skin = clothsim_skin

if self.hair and (self.animation_mode is not None or self.clothsim_skin):
raise NotImplementedError(
'Dynamic hair is not yet fully working. '
'Please disable either hair or both of animation/clothsim'
)

def create_placeholder(self, **kwargs):
return butil.spawn_cube(size=4)

def create_asset(self, i, placeholder, hair=True, simulate=False, **kwargs):
def create_asset(self, i, placeholder, **kwargs):

genome = tiger_genome()
root, parts = creature.genome_to_creature(genome, name=f'carnivore({self.factory_seed}, {i})')
Expand All @@ -202,9 +220,14 @@ def create_asset(self, i, placeholder, hair=True, simulate=False, **kwargs):

butil.parent_to(root, placeholder, no_inverse=True)

if hair:
if self.hair:
creature_hair.configure_hair(
joined, root, genome.postprocess_params['hair'])
joined,
root,
genome.postprocess_params['hair'],
is_dynamic=dynamic
)

if dynamic:
if self.animation_mode == 'run':
run_cycle.animate_run(root, arma, ik_targets)
Expand All @@ -215,7 +238,7 @@ def create_asset(self, i, placeholder, hair=True, simulate=False, **kwargs):
pass
else:
raise ValueError(f'Unrecognized mode {self.animation_mode=}')
if simulate:
if self.clothsim_skin:
rigidity = surface.write_vertex_group(
joined, cloth_sim.local_pos_rigity_mask, apply=True)
cloth_sim.bake_cloth(joined, genome.postprocess_params['skin'],
Expand Down
16 changes: 13 additions & 3 deletions infinigen/assets/creatures/fish.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,16 +224,26 @@ class FishFactory(AssetFactory):

max_distance = 40

def __init__(self, factory_seed=None, bvh=None, coarse=False, animation_mode=None, species_variety=None, **_):
def __init__(
self,
factory_seed=None,
bvh=None,
coarse=False,
animation_mode=None,
species_variety=None,
clothsim_skin: bool = False,
**_
):
super().__init__(factory_seed, coarse)
self.bvh = bvh
self.animation_mode = animation_mode
self.clothsim_skin = clothsim_skin

with FixedSeed(factory_seed):
self.species_genome = fish_genome()
self.species_variety = species_variety if species_variety is not None else clip_gaussian(0.2, 0.1, 0.05, 0.45)

def create_asset(self, i, simulate=False, **kwargs):
def create_asset(self, i, **kwargs):

instance_genome = genome.interp_genome(self.species_genome, fish_genome(), self.species_variety)

Expand All @@ -255,7 +265,7 @@ def seeded_fish_postprocess(*args, **kwargs):
else:
raise ValueError(f'Unrecognized {self.animation_mode=}')

if simulate:
if self.clothsim_skin:
joined = simulate_fish_cloth(joined, extras, instance_genome.postprocess_params['cloth'])
else:
joined = butil.join_objects([joined] + extras)
Expand Down
29 changes: 22 additions & 7 deletions infinigen/assets/creatures/herbivore.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


from collections import defaultdict
import bpy
import bpy, mathutils

import gin
import numpy as np
Expand Down Expand Up @@ -190,18 +190,33 @@ class HerbivoreFactory(AssetFactory):

max_distance = 40

def __init__(self,
factory_seed=None, bvh=None, coarse=False,
animation_mode=None, **kwargs
def __init__(
self,
factory_seed=None,
bvh: mathutils.bvhtree.BVHTree = None,
coarse: bool = False,
animation_mode: str = None,
hair: bool = True,
clothsim_skin: bool = False,
**kwargs
):
super().__init__(factory_seed, coarse)
self.bvh = bvh
self.animation_mode = animation_mode
self.hair = hair
self.clothsim_skin = clothsim_skin

if self.hair and (self.animation_mode is not None or self.clothsim_skin):
raise NotImplementedError(
'Dynamic hair is not yet fully working. '
'Please disable either hair or both of animation/clothsim'
)


def create_placeholder(self, **kwargs):
return butil.spawn_cube(size=4)

def create_asset(self, i, placeholder, hair=True, simulate=False, **kwargs):
def create_asset(self, i, placeholder, **kwargs):
genome = herbivore_genome()
root, parts = creature.genome_to_creature(genome, name=f'herbivore({self.factory_seed}, {i})')
tag_object(root, 'herbivore')
Expand All @@ -215,7 +230,7 @@ def create_asset(self, i, placeholder, hair=True, simulate=False, **kwargs):

butil.parent_to(root, placeholder, no_inverse=True)

if hair:
if self.hair:
creature_hair.configure_hair(
joined, root, genome.postprocess_params['hair'])
if dynamic:
Expand All @@ -226,7 +241,7 @@ def create_asset(self, i, placeholder, hair=True, simulate=False, **kwargs):
idle.idle_body_noise_drivers(ik_targets)
else:
raise ValueError(f'Unrecognized mode {self.animation_mode=}')
if simulate:
if self.clothsim_skin:
rigidity = surface.write_vertex_group(
joined, cloth_sim.local_pos_rigity_mask, apply=True)
cloth_sim.bake_cloth(joined, genome.postprocess_params['skin'],
Expand Down
42 changes: 30 additions & 12 deletions infinigen/assets/creatures/util/hair.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,27 +317,45 @@ def nodegroup_transfer_uvs_to_curves_vec3(nw: NodeWrangler):
attrs={'transform_space': 'RELATIVE'})
obj = object_info.outputs["Geometry"]

domain = 'POINT'
uvtype = 'FLOAT_VECTOR'

uv = nw.new_node(Nodes.NamedAttribute,
input_kwargs={'Name': group_input.outputs["from_uv"]},
attrs={'data_type': 'FLOAT_VECTOR'})
capture = nw.new_node(Nodes.CaptureAttribute,
input_kwargs={'Geometry': obj, 'Value': uv},
attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'FACE'})
attrs={'data_type': uvtype})

capture = nw.new_node(
Nodes.CaptureAttribute,
input_kwargs={'Geometry': obj, 'Value': uv},
attrs={'data_type': uvtype, 'domain': domain}
)

root_pos = nw.new_node(nodegroup_hair_position().name, [group_input.outputs['Geometry']])
transfer_attribute = nw.new_node(Nodes.TransferAttribute,
input_kwargs={
'Source': capture.outputs['Geometry'],
1: capture.outputs["Attribute"],
#'Source Position': root_pos
},
attrs={'data_type': 'FLOAT_VECTOR'})#, 'mapping': 'NEAREST'})

nearest_idx = nw.new_node(Nodes.SampleNearest, input_kwargs={
'Geometry': capture.outputs['Geometry'],
'Sample Position': root_pos
},
attrs={'domain': domain})
#transfer_attribute = nw.new_node(Nodes.SampleNearest,
# input_kwargs={
# 'Mesh': capture.outputs['Geometry'],
# 'Value': capture.outputs["Attribute"],
# 'Sample Position': root_pos
# },
# attrs={'data_type': 'FLOAT_VECTOR'})
transfer_attribute = nw.new_node(Nodes.SampleIndex, input_kwargs={
'Geometry': capture.outputs['Geometry'],
'Index': nearest_idx.outputs['Index'],
'Value': capture.outputs["Attribute"]
},
attrs={'data_type': uvtype, 'domain': domain})

store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute,
input_kwargs={
'Geometry': group_input.outputs["Geometry"],
'Name': group_input.outputs['to_attr'],
'Value': transfer_attribute.outputs["Attribute"]},
'Value': transfer_attribute},
attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CURVE'})

group_output = nw.new_node(Nodes.GroupOutput,
Expand Down
2 changes: 0 additions & 2 deletions infinigen/assets/creatures/util/rigging.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,6 @@ def compute_chain_length(parts_atts: tree.Tree, bones, part, ik: IKParams):
if ik.chain_length is not None:
chain_length += ik.chain_length

logger.debug(f'Com')

return chain_length


Expand Down
92 changes: 59 additions & 33 deletions infinigen/core/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,39 +83,58 @@ def sanitize_override(override: list):
def repo_root():
return Path(__file__).parent.parent.parent

def contains_any_stem(filenames, folder):
if not folder.exists():
return False
def contained_stems(filenames: list[str], folder: Path):
assert folder.exists()
names = [p.stem for p in folder.iterdir()]
return any(s.stem in names or s.name in names for s in map(Path, filenames))
return {s.stem in names or s.name in names for s in map(Path, filenames)}

def mandatory_config_dir_satisfied(mandatory_folder, root, configs):
mandatory_folder = Path(mandatory_folder)
mandatory_folder_rel = root/mandatory_folder

if not (mandatory_folder.exists() or mandatory_folder_rel.exists()):
raise FileNotFoundError(f'Could not find {mandatory_folder} or {mandatory_folder_rel}')

return (
contains_any_stem(configs, mandatory_folder) or
contains_any_stem(configs, mandatory_folder_rel)
)
def resolve_folder_maybe_relative(folder, root):
folder = Path(folder)
if folder.exists():
return folder
folder_rel = root/folder
if folder_rel.exists():
return folder_rel
raise FileNotFoundError(f'Could not find {folder} or {folder_rel}')

@gin.configurable
def apply_gin_configs(
configs_folder: Path,
configs: list[str] = None,
overrides: list[str] = None,
skip_unknown=False,
mandatory_folders: Path = None,
skip_unknown: bool = False,
mandatory_folders: list[Path] = None,
mutually_exclusive_folders: list[Path] = None
):

"""
Apply gin configuration files and bindings.
Parameters
----------
configs_folder : Path
The path to the toplevel folder containing the gin configuration files.
configs : list[str]
A list of filenames to find within the configs_folder.
overrides : list[str]
A list of gin-formatted pairs to override the configs with.
skip_unknown : bool
If True, ignore errors for configs that were set by the user but not used anywhere
mandatory_folders : list[Path]
For each folder in the list, at least one config file must be loaded from that folder.
mutually_exclusive_folders : list[Path]
For each folder in the list, at most one config file must be loaded from that folder.
"""

if configs is None:
configs = []
if overrides is None:
overrides = []
if mandatory_folders is None:
mandatory_folders = []
if mutually_exclusive_folders is None:
mutually_exclusive_folders = []
configs_folder = Path(configs_folder)

root = repo_root()
Expand All @@ -128,30 +147,37 @@ def apply_gin_configs(
gin.add_config_file_search_path(configs_folder)
else:
raise FileNotFoundError(f'Couldnt find {configs_folder} or {configs_folder_rel}')

for p in mandatory_folders:
if not mandatory_config_dir_satisfied(p, root, configs):
raise ValueError(
f"Please load one or more config from {p} to avoid unexpected behavior."
)

search_paths = [configs_folder, root, Path('.')]

def find_config(p):
p = Path(p)
try:
return next(
file
for folder in search_paths
for file in folder.glob('**/*.gin')
if file.stem == p.stem
)
except StopIteration:
raise FileNotFoundError(f'Could not find {p} or {p.stem} in any of {search_paths}')

for folder in search_paths:
for file in folder.glob('**/*.gin'):
if file.stem == p.stem:
return file
raise FileNotFoundError(f'Could not find {p} or {p.stem} in any of {search_paths}')

configs = [find_config(g) for g in ['base.gin'] + configs]
overrides = [sanitize_override(o) for o in overrides]

for mandatory_folder in mandatory_folders:
mandatory_folder = resolve_folder_maybe_relative(mandatory_folder, root)
if not contained_stems(configs, mandatory_folder):
raise FileNotFoundError(
f'At least one config file must be loaded from {mandatory_folder} to avoid unexpected behavior'
)

for mutex_folder in mutually_exclusive_folders:
mutex_folder = resolve_folder_maybe_relative(mutex_folder, root)
stems = {s.stem for s in mutex_folder.iterdir()}
config_stems = {s.stem for s in configs}
both = stems.intersection(config_stems)
if len(both) > 1:
raise ValueError(
f'At most one config file must be loaded from {mutex_folder} to avoid unexpected behavior, instead got {both=}'
)

with LogLevel(logger=logging.getLogger(), level=logging.CRITICAL):
gin.parse_config_files_and_bindings(
configs,
Expand Down
Loading

0 comments on commit 830891c

Please sign in to comment.