diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 9acbc7f21..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: 2 -updates: -- package-ecosystem: pip - directory: "/" - schedule: - interval: weekly - time: "13:00" - groups: - python-packages: - patterns: - - "*" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index d9bb9da52..159fbf5ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,36 +6,23 @@ RUN if [ "$APP_IMAGE" = "nvidia/cuda:12.0.0-devel-ubuntu22.04" ]; then \ echo "Using CUDA image" && \ apt-get update && \ apt-get install -y unzip sudo git g++ libglm-dev libglew-dev libglfw3-dev libgles2-mesa-dev zlib1g-dev wget cmake vim libxi6 libgconf-2-4 && \ - wget \ - https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh \ - && mkdir /root/.conda \ - && bash Miniconda3-latest-Linux-x86_64.sh -b \ - && rm -f Miniconda3-latest-Linux-x86_64.sh; \ - && apt-get install libxkbcommon-x11-0 \ + wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && \ + mkdir /root/.conda && \ + bash Miniconda3-latest-Linux-x86_64.sh -b && \ + rm -f Miniconda3-latest-Linux-x86_64.sh && \ + apt-get install -y libxkbcommon-x11-0; \ else \ echo "Using Conda image" && \ - apt-get update -yq \ - && apt-get install -yq \ - cmake \ - g++ \ - libgconf-2-4 \ - libgles2-mesa-dev \ - libglew-dev \ - libglfw3-dev \ - libglm-dev \ - libxi6 \ - sudo \ - unzip \ - vim \ - zlib1g-dev; \ - && apt-get install libxkbcommon-x11-0 \ + apt-get update -yq && \ + apt-get install -yq cmake g++ libgconf-2-4 libgles2-mesa-dev libglew-dev libglfw3-dev libglm-dev libxi6 sudo unzip vim zlib1g-dev && \ + apt-get install -y libxkbcommon-x11-0; \ fi RUN mkdir /opt/infinigen WORKDIR /opt/infinigen COPY . . -RUN conda init bash \ - && . ~/.bashrc \ - && conda create --name infinigen python=3.10 \ - && conda activate infinigen \ - && pip install -e .[dev] \ No newline at end of file +RUN conda init bash && \ + . ~/.bashrc && \ + conda create --name infinigen python=3.10 -y && \ + conda activate infinigen && \ + pip install -e .[dev] diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 376f4ce11..e2c240dd6 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -68,3 +68,11 @@ v1.5.0 - minimize pip dependences: remove unused packages & move terrain/gt-vis packages into optional \[terrain,vis\] extras. - add parameters for object clutter, reduce excessively cluttered / slow indoors scenes - minorly improve infinigen-indoors performance via logging & asset hiding + +v1.5.1 +- Fix "base.gin" crash in generate_individual_assets +- Fix individual_export in export.py +- Fix Dockerfile +- Remove dependabot +- Add scatter unit tests and fix scatter imports +- Fix black renders due to non-hidden particle emitter diff --git a/docs/GeneratingIndividualAssets.md b/docs/GeneratingIndividualAssets.md index f0b2c2ffb..12556a62c 100644 --- a/docs/GeneratingIndividualAssets.md +++ b/docs/GeneratingIndividualAssets.md @@ -21,7 +21,23 @@ python -m infinigen_examples.generate_individual_assets --output_folder outputs/ Running the above commands will save images and .blend files into your `outputs` folder. You can customize what object is generated by changing the `-f` argument to the name of a different AssetFactory defined in the codebase (see the file `tests/test_meshes_basic.txt` for a partial list). -Please run `python -m infinigen.tools.generate_individual_assets --help` for a full list of commandline arguments. +Please run `python -m infinigen_examples.generate_individual_assets --help` for a full list of commandline arguments. + +### Generate many different assets in one command + +:warning: There are a small number of assets which are used in the main system but are not included in the tests/assets/*.txt lists for various reasons. You can read the commented lines in `tests/assets/list_nature_meshes.txt` for a partial list. There are also other types of procedural generators (lighting, colors, scatters, volumetrics, animations, etc) besides meshes/materials, which add diversity to the main rendering system but are not included in the commands below. + +Generate one of each mesh in the unit tests list: +```bash +python -m infinigen_examples.generate_individual_assets --output_folder outputs/corals -f tests/assets/list_nature_meshes.txt -n 1 --save_blend +python -m infinigen_examples.generate_individual_assets --output_folder outputs/corals -f tests/assets/list_indoor_meshes.txt -n 1 --save_blend +``` + +Generate one of each material in the unit tests list: +```bash +python -m infinigen_examples.generate_individual_assets --output_folder outputs/corals -f tests/assets/list_nature_materials.txt -n 1 --save_blend +python -m infinigen_examples.generate_individual_assets --output_folder outputs/corals -f tests/assets/list_indoor_materials.txt -n 1 --save_blend +``` ### Creating OBJ, USD, FBX and other file formats diff --git a/infinigen/.dockerignore b/infinigen/.dockerignore new file mode 100644 index 000000000..e69de29bb diff --git a/infinigen/__init__.py b/infinigen/__init__.py index 816527ea4..1a78b3ba8 100644 --- a/infinigen/__init__.py +++ b/infinigen/__init__.py @@ -1,7 +1,7 @@ import logging from pathlib import Path -__version__ = "1.5.0" +__version__ = "1.5.1" def repo_root(): diff --git a/infinigen/assets/objects/deformed_trees/fallen.py b/infinigen/assets/objects/deformed_trees/fallen.py index d32ff8058..6a6d57173 100644 --- a/infinigen/assets/objects/deformed_trees/fallen.py +++ b/infinigen/assets/objects/deformed_trees/fallen.py @@ -9,7 +9,7 @@ import numpy as np from numpy.random import uniform -from infinigen.assets.deformed_trees.base import BaseDeformedTreeFactory +from infinigen.assets.objects.deformed_trees.base import BaseDeformedTreeFactory from infinigen.assets.utils.decorate import remove_vertices from infinigen.assets.utils.draw import cut_plane from infinigen.assets.utils.misc import assign_material diff --git a/infinigen/assets/objects/deformed_trees/generate.py b/infinigen/assets/objects/deformed_trees/generate.py index 6ad37b6fa..b67ddb73b 100644 --- a/infinigen/assets/objects/deformed_trees/generate.py +++ b/infinigen/assets/objects/deformed_trees/generate.py @@ -6,12 +6,10 @@ import numpy as np -from infinigen.assets.deformed_trees import ( - FallenTreeFactory, - HollowTreeFactory, - RottenTreeFactory, -) -from infinigen.assets.deformed_trees.truncated import TruncatedTreeFactory +from infinigen.assets.objects.deformed_trees.fallen import FallenTreeFactory +from infinigen.assets.objects.deformed_trees.hollow import HollowTreeFactory +from infinigen.assets.objects.deformed_trees.rotten import RottenTreeFactory +from infinigen.assets.objects.deformed_trees.truncated import TruncatedTreeFactory from infinigen.core.placement.factory import AssetFactory from infinigen.core.util.math import FixedSeed diff --git a/infinigen/assets/objects/deformed_trees/hollow.py b/infinigen/assets/objects/deformed_trees/hollow.py index d25e71dbf..7b053f917 100644 --- a/infinigen/assets/objects/deformed_trees/hollow.py +++ b/infinigen/assets/objects/deformed_trees/hollow.py @@ -9,7 +9,7 @@ import numpy as np from numpy.random import uniform -from infinigen.assets.deformed_trees.base import BaseDeformedTreeFactory +from infinigen.assets.objects.deformed_trees.base import BaseDeformedTreeFactory from infinigen.assets.utils.decorate import ( read_co, read_material_index, diff --git a/infinigen/assets/objects/deformed_trees/rotten.py b/infinigen/assets/objects/deformed_trees/rotten.py index 2ccace381..ea82c3521 100644 --- a/infinigen/assets/objects/deformed_trees/rotten.py +++ b/infinigen/assets/objects/deformed_trees/rotten.py @@ -8,7 +8,7 @@ import numpy as np from numpy.random import uniform -from infinigen.assets.deformed_trees.base import BaseDeformedTreeFactory +from infinigen.assets.objects.deformed_trees.base import BaseDeformedTreeFactory from infinigen.assets.utils.decorate import ( read_material_index, remove_vertices, diff --git a/infinigen/assets/objects/deformed_trees/truncated.py b/infinigen/assets/objects/deformed_trees/truncated.py index 85a7bd72c..b6860f6e7 100644 --- a/infinigen/assets/objects/deformed_trees/truncated.py +++ b/infinigen/assets/objects/deformed_trees/truncated.py @@ -8,7 +8,7 @@ import numpy as np from numpy.random import uniform -from infinigen.assets.deformed_trees import FallenTreeFactory +from infinigen.assets.objects.deformed_trees import FallenTreeFactory from infinigen.assets.utils.decorate import read_co from infinigen.core import surface from infinigen.core.nodes.node_info import Nodes diff --git a/infinigen/assets/scatters/chopped_trees.py b/infinigen/assets/scatters/chopped_trees.py index 54261a51f..5ae717a20 100644 --- a/infinigen/assets/scatters/chopped_trees.py +++ b/infinigen/assets/scatters/chopped_trees.py @@ -21,6 +21,7 @@ ) from infinigen.core.placement.instance_scatter import scatter_instances from infinigen.core.util import blender as butil +from infinigen.core.util import math as mutil from infinigen.core.util.math import randomspacing, rotate_match_directions logger = logging.getLogger(__name__) @@ -65,7 +66,7 @@ def chop_object(obj, n, cutter_size, max_tilt=15, thickness=0.03): def cutter(t): butil.select_none() - z = butil.lerp(bbox[:, -1].min(), bbox[:, -1].max(), t) + z = mutil.lerp(bbox[:, -1].min(), bbox[:, -1].max(), t) loc = (*bbox[:, :-1].mean(axis=0), z) bpy.ops.mesh.primitive_plane_add(size=cutter_size, location=loc) cut = bpy.context.active_object diff --git a/infinigen/assets/scatters/clothes.py b/infinigen/assets/scatters/clothes.py index cf4966756..9e9dbf894 100644 --- a/infinigen/assets/scatters/clothes.py +++ b/infinigen/assets/scatters/clothes.py @@ -9,6 +9,7 @@ import numpy as np from numpy.random import uniform +from infinigen.assets.objects.clothes import blanket, pants, shirt from infinigen.assets.objects.creatures.util.cloth_sim import bake_cloth from infinigen.assets.utils.decorate import read_co, subsurf from infinigen.core.placement.factory import make_asset_collection @@ -49,8 +50,6 @@ class ClothesCover: def __init__( self, bbox=(0.3, 0.7, 0.3, 0.7), factory_fn=None, width=None, size=None ): - from infinigen.assets.clothes import blanket, pants, shirt - probs = np.array([2, 1, 1]) if factory_fn is None: factory_fn = np.random.choice( diff --git a/infinigen/assets/scatters/ground_mushroom.py b/infinigen/assets/scatters/ground_mushroom.py index 959c11da3..43f35cfed 100644 --- a/infinigen/assets/scatters/ground_mushroom.py +++ b/infinigen/assets/scatters/ground_mushroom.py @@ -39,3 +39,8 @@ def apply(self, obj, scale=0.3, density=1.0, selection=None): ) return scatter_obj + + +def apply(obj): + mushrooms = Mushrooms() + return mushrooms.apply(obj) diff --git a/infinigen/assets/scatters/mollusk.py b/infinigen/assets/scatters/mollusk.py index d56ad3134..3ad39616f 100644 --- a/infinigen/assets/scatters/mollusk.py +++ b/infinigen/assets/scatters/mollusk.py @@ -32,7 +32,6 @@ def scaling(nw): ) scatter_obj = scatter_instances( - "mollusk", base_obj=obj, collection=mollusk, density=density, diff --git a/infinigen/assets/scatters/moss.py b/infinigen/assets/scatters/moss.py index 75b6e086a..b4ef4c924 100644 --- a/infinigen/assets/scatters/moss.py +++ b/infinigen/assets/scatters/moss.py @@ -50,3 +50,8 @@ def instance_index(nw: NodeWrangler, n): ) return scatter_obj + + +def apply(obj): + moss_cover = MossCover() + return moss_cover.apply(obj) diff --git a/infinigen/assets/scatters/slime_mold.py b/infinigen/assets/scatters/slime_mold.py index c4342192a..ba77bfb68 100644 --- a/infinigen/assets/scatters/slime_mold.py +++ b/infinigen/assets/scatters/slime_mold.py @@ -86,3 +86,7 @@ def weight(nw): assign_material(scatter_obj, shaderfunc_to_material(shader_mold, base_hue)) return scatter_obj + + +def apply(obj, selection=None): + SlimeMold().apply(obj, selection) diff --git a/infinigen/assets/scatters/snow_layer.py b/infinigen/assets/scatters/snow_layer.py index 64ad02d21..653877605 100644 --- a/infinigen/assets/scatters/snow_layer.py +++ b/infinigen/assets/scatters/snow_layer.py @@ -12,6 +12,7 @@ class Snowlayer: def __init__(self): + bpy.ops.preferences.addon_enable(module="real_snow") pass def apply(self, obj, **kwargs): @@ -21,3 +22,9 @@ def apply(self, obj, **kwargs): snow = bpy.context.active_object tag_object(snow, "snow") tag_object(snow, "boulder") + + +def apply(obj): + snowlayer = Snowlayer() + snowlayer.apply(obj) + return snowlayer diff --git a/infinigen/assets/weather/__init__.py b/infinigen/assets/weather/__init__.py index 97d26424c..b865e6f04 100644 --- a/infinigen/assets/weather/__init__.py +++ b/infinigen/assets/weather/__init__.py @@ -6,5 +6,6 @@ marine_snow_param_distribution, rain_param_distribution, snow_param_distribution, + spawn_emitter, ) from .wind_effectors import TurbulenceEffector, WindEffector diff --git a/infinigen/assets/weather/particles.py b/infinigen/assets/weather/particles.py index 35c44f1f0..3541ddf0e 100644 --- a/infinigen/assets/weather/particles.py +++ b/infinigen/assets/weather/particles.py @@ -12,6 +12,27 @@ logger = logging.getLogger(__name__) +def spawn_emitter(follow_cam, mesh_type, size, offset, name=None): + match mesh_type: + case "plane": + emitter = butil.spawn_plane(location=offset, size=size) + case "cube": + emitter = butil.spawn_cube(location=offset, size=size) + case _: + raise ValueError(f"Unknown mesh type {mesh_type}") + + butil.constrain_object(emitter, "COPY_LOCATION", use_offset=True, target=follow_cam) + + if name is None: + name = follow_cam.name + emitter.name = f"emitter({name=}, {mesh_type=})" + + emitter.hide_viewport = True + emitter.hide_render = True + + return emitter + + def rain_param_distribution(): drops_per_sec_m2 = uniform(0.05, 1) velocity = uniform(9, 20) diff --git a/infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py b/infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py index bc3e6d793..a3e42b5e4 100644 --- a/infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py +++ b/infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py @@ -595,7 +595,7 @@ def angle_alignment_cost_base( b_edges = [] for b_name, b_mesh in zip(b, b_meshes): b_poly = iu.project_to_xy_poly(b_mesh) - if b_poly is not None: + if (b_poly is not None) and (not b_poly.is_empty): if isinstance(b_poly, Polygon): for i, coord in enumerate(b_poly.exterior.coords[:-1]): start, end = coord, b_poly.exterior.coords[i + 1] diff --git a/infinigen/core/placement/animation_policy.py b/infinigen/core/placement/animation_policy.py index d325c28e5..d13095bbd 100644 --- a/infinigen/core/placement/animation_policy.py +++ b/infinigen/core/placement/animation_policy.py @@ -19,7 +19,6 @@ from numpy.random import uniform as U from tqdm import tqdm -import infinigen.assets.utils.mesh from infinigen.assets.utils.geometry.curve import Curve from infinigen.core.placement.path_finding import path_finding from infinigen.core.util import blender as butil @@ -603,7 +602,7 @@ def animate_trajectory( obj.rotation_euler.z = U(0, 2 * np.pi) if hasattr(policy_func, "reset"): - infinigen.assets.utils.mesh.reset_preset() + policy_func.reset() keyframe(obj, obj.location, obj.rotation_euler, 0, interp="LINEAR") try_animate_trajectory_func = ( diff --git a/infinigen/core/placement/instance_scatter.py b/infinigen/core/placement/instance_scatter.py index 5c81dbd01..ae9627dcd 100644 --- a/infinigen/core/placement/instance_scatter.py +++ b/infinigen/core/placement/instance_scatter.py @@ -332,7 +332,10 @@ def scatter_instances( name = "scatter:" + collection.name.split(":")[-1] - avg_scale = scale * (1 - scale_rand / 2) * (1 - scale_rand_axi / 2) + if scale is not None: + avg_scale = scale * (1 - scale_rand / 2) * (1 - scale_rand_axi / 2) + else: + avg_scale = 1 if vol_density is not None: assert ( diff --git a/infinigen/datagen/configs/gt_options/opengl_gt_noshortrender.gin b/infinigen/datagen/configs/gt_options/opengl_gt_noshortrender.gin index fbaf1a66b..b185d55a2 100644 --- a/infinigen/datagen/configs/gt_options/opengl_gt_noshortrender.gin +++ b/infinigen/datagen/configs/gt_options/opengl_gt_noshortrender.gin @@ -1,7 +1,7 @@ -include 'infinigen/datagen/gt_options/opengl_gt.gin' # incase someone adds other settings to it +include 'infinigen/datagen/configs/gt_options/opengl_gt.gin' # incase someone adds other settings to it iterate_scene_tasks.camera_dependent_tasks = [ {'name': 'renderbackup', 'func': @renderbackup/queue_render}, # still call it "backup" since it is reusing the compute_platform's backup config. we are just skipping straight to the backup {'name': 'savemesh', 'func': @queue_mesh_save}, {'name': 'opengl', 'func': @queue_opengl} -] \ No newline at end of file +] diff --git a/infinigen/datagen/job_funcs.py b/infinigen/datagen/job_funcs.py index 32846529e..1a918abb2 100644 --- a/infinigen/datagen/job_funcs.py +++ b/infinigen/datagen/job_funcs.py @@ -425,13 +425,29 @@ def queue_mesh_save( output_folder.mkdir(parents=True, exist_ok=True) + input_folder_priority_options = [ + f"fine{input_suffix}", + "fine", + f"coarse{input_suffix}", + "coarse", + ] + + for option in input_folder_priority_options: + input_folder = f"{folder}/{option}" + if (Path(input_folder) / "scene.blend").exists(): + break + else: + raise ValueError( + f"No scene.blend found in {input_folder} for any of {input_folder_priority_options}" + ) + cmd = ( get_cmd( seed, "mesh_save", configs, taskname, - input_folder=f"{folder}/coarse{input_suffix}", + input_folder=input_folder, output_folder=f"{folder}/savemesh{output_suffix}", ) + f""" diff --git a/infinigen/tools/export.py b/infinigen/tools/export.py index f6c0fa5a5..de1c7bdc0 100644 --- a/infinigen/tools/export.py +++ b/infinigen/tools/export.py @@ -764,12 +764,12 @@ def export_scene( bpy.ops.wm.open_mainfile(filepath=str(input_blend)) folder = output_folder / f"export_{input_blend.name}" folder.mkdir(exist_ok=True, parents=True) - result = export_curr_scene(folder, **kwargs) + export_curr_scene(folder, **kwargs) if pipeline_folder is not None and task_uniqname is not None: (pipeline_folder / "logs" / f"FINISH_{task_uniqname}").touch() - return result + return folder # side effects: will remove parents of inputted obj and clean its name, hides viewport of all objects diff --git a/infinigen_examples/generate_individual_assets.py b/infinigen_examples/generate_individual_assets.py index 89a0c50a0..c31dcb63d 100644 --- a/infinigen_examples/generate_individual_assets.py +++ b/infinigen_examples/generate_individual_assets.py @@ -35,6 +35,7 @@ level=logging.INFO, ) +import infinigen from infinigen.assets.lighting import ( hdri_lighting, holdout_lighting, @@ -47,7 +48,7 @@ from infinigen.assets.utils.misc import assign_material, subclasses from infinigen.core import init, surface from infinigen.core.init import configure_cycles_devices -from infinigen.core.placement import density, factory +from infinigen.core.placement import AssetFactory, density from infinigen.core.tagging import tag_system # noinspection PyUnresolvedReferences @@ -63,30 +64,42 @@ level=logging.WARNING, ) +logger = logging.getLogger(__name__) + +OBJECTS_PATH = infinigen.repo_root() / "infinigen/assets/objects" +assert OBJECTS_PATH.exists(), OBJECTS_PATH + def build_scene_asset(args, factory_name, idx): - factory = None - for subdir in os.listdir("infinigen/assets"): + fac = None + for subdir in sorted(list(OBJECTS_PATH.iterdir())): + clsname = subdir.name.split(".")[0].strip() with gin.unlock_config(): - module = importlib.import_module(f'infinigen.assets.{subdir.split(".")[0]}') + module = importlib.import_module(f"infinigen.assets.objects.{clsname}") if hasattr(module, factory_name): - factory = getattr(module, factory_name) + fac = getattr(module, factory_name) + logger.info(f"Found {factory_name} in {subdir}") break - if factory is None: + logger.debug(f"{factory_name} not found in {subdir}") + if fac is None: raise ModuleNotFoundError(f"{factory_name} not Found.") + + if args.dryrun: + return + with FixedSeed(idx): - factory = factory(idx) + fac = fac(idx) try: if args.spawn_placeholder: - ph = factory.spawn_placeholder(idx, (0, 0, 0), (0, 0, 0)) - asset = factory.spawn_asset(idx, placeholder=ph) + ph = fac.spawn_placeholder(idx, (0, 0, 0), (0, 0, 0)) + asset = fac.spawn_asset(idx, placeholder=ph) else: - asset = factory.spawn_asset(idx) + asset = fac.spawn_asset(idx) except Exception as e: traceback.print_exc() - print(f"{factory}.spawn_asset({idx=}) FAILED!! {e}") + print(f"{fac}.spawn_asset({idx=}) FAILED!! {e}") raise e - factory.finalize_assets(asset) + fac.finalize_assets(asset) if args.fire: from infinigen.assets.fluid.fluid import set_obj_on_fire @@ -144,7 +157,7 @@ def build_scene_asset(args, factory_name, idx): return asset -def build_scene_surface(factory_name, idx): +def build_scene_surface(args, factory_name, idx): try: with gin.unlock_config(): scatter = importlib.import_module( @@ -154,6 +167,9 @@ def build_scene_surface(factory_name, idx): if not hasattr(scatter, "apply"): raise ValueError(f"{scatter} has no apply()") + if args.dryun: + return + bpy.ops.mesh.primitive_grid_add( size=10, x_subdivisions=400, y_subdivisions=400 ) @@ -190,6 +206,10 @@ def build_scene_surface(factory_name, idx): break else: raise Exception(f"{factory_name} not Found.") + + if args.dryrun: + return + if hasattr(template, "make_sphere"): asset = template.make_sphere() else: @@ -210,6 +230,8 @@ def build_and_save_asset(payload: dict): args = payload["args"] idx = payload["idx"] + logger.info(f"Building scene for {factory_name} {idx}") + if args.seed > 0: idx = args.seed @@ -226,7 +248,6 @@ def build_and_save_asset(payload: dict): ) scene.cycles.samples = args.samples butil.clear_scene() - configure_cycles_devices() if not args.fire: bpy.context.scene.render.film_transparent = args.film_transparent @@ -238,7 +259,12 @@ def build_and_save_asset(payload: dict): if "Factory" in factory_name: asset = build_scene_asset(args, factory_name, idx) else: - asset = build_scene_surface(factory_name, idx) + asset = build_scene_surface(args, factory_name, idx) + + if args.dryrun: + return + + configure_cycles_devices() with FixedSeed(args.lighting + idx): if args.hdri: @@ -424,22 +450,24 @@ def mapfunc(f, its, args): def main(args): bpy.context.window.workspace = bpy.data.workspaces["Geometry Nodes"] - init.apply_gin_configs("infinigen_examples/configs_indoor", skip_unknown=True) + init.apply_gin_configs( + ["infinigen_examples/configs_indoor", "infinigen_examples/configs_nature"], + skip_unknown=True, + ) surface.registry.initialize_from_gin() + if args.debug is not None: + for name in logging.root.manager.loggerDict: + if not name.startswith("infinigen"): + continue + if len(args.debug) == 0 or any(name.endswith(x) for x in args.debug): + logging.getLogger(name).setLevel(logging.DEBUG) + init.configure_blender() if args.gpu: init.configure_render_cycles() - extras = "[%(filename)s:%(lineno)d] " if args.loglevel == logging.DEBUG else "" - logging.basicConfig( - format=f"[%(asctime)s.%(msecs)03d] [%(name)s] [%(levelname)s] {extras}| %(message)s", - level=args.loglevel, - datefmt="%H:%M:%S", - ) - logging.getLogger("infinigen").setLevel(args.loglevel) - if ".txt" in args.factories[0]: name = args.factories[0].split(".")[-2].split("/")[-1] else: @@ -449,18 +477,26 @@ def main(args): args.output_folder = Path(os.getcwd()) / "outputs" path = Path(args.output_folder) / name - path.mkdir(exist_ok=True) + path.mkdir(exist_ok=True, parents=True) factories = list(args.factories) + if "ALL_ASSETS" in factories: - factories += [f.__name__ for f in subclasses(factory.AssetFactory)] + factories += [f.__name__ for f in subclasses(AssetFactory)] factories.remove("ALL_ASSETS") + logger.warning( + "ALL_ASSETS is deprecated. Use `-f tests/assets/list_nature_meshes.txt` and `-f tests/assets/list_indoor_meshes.txt` instead." + ) if "ALL_SCATTERS" in factories: - factories += [f.stem for f in Path("surfaces/scatters").iterdir()] + factories += [f.stem for f in Path("infinigen/assets/scatters").iterdir()] factories.remove("ALL_SCATTERS") if "ALL_MATERIALS" in factories: factories += [f.stem for f in Path("infinigen/assets/materials").iterdir()] factories.remove("ALL_MATERIALS") + logger.warning( + "ALL_MATERIALS is deprecated. Use `-f tests/assets/list_nature_materials.txt` and `-f tests/assets/list_indoor_materials.txt` instead." + ) + has_txt = ".txt" in factories[0] if has_txt: factories = [ @@ -474,9 +510,13 @@ def main(args): ] mapfunc(build_and_save_asset, targets, args) + if args.dryrun: + return + for j, fac in enumerate(factories): fac_path = args.output_folder / fac - assert fac_path.exists() + fac_path.mkdir(exist_ok=True, parents=True) + f"{fac_path} does not exist" if has_txt: for i in range(args.n_images): @@ -630,14 +670,6 @@ def make_args(): "-D", "--seed", type=int, default=-1, help="Run a specific seed." ) parser.add_argument("-N", "--no-mod", action="store_true", help="No modification") - parser.add_argument( - "-d", - "--debug", - action="store_const", - dest="loglevel", - const=logging.DEBUG, - default=logging.INFO, - ) parser.add_argument("-H", "--hdri", action="store_true", help="add_hdri") parser.add_argument( "-T", "--three_point", action="store_true", help="add three-point lighting" @@ -655,6 +687,12 @@ def make_args(): "--export", type=str, default=None, choices=export.FORMAT_CHOICES ) parser.add_argument("--export_texture_res", type=int, default=1024) + parser.add_argument("-d", "--debug", type=str, nargs="*", default=None) + parser.add_argument( + "--dryrun", + action="store_true", + help="Import assets but do not run them. Used for testing.", + ) return init.parse_args_blender(parser) diff --git a/infinigen_examples/generate_nature.py b/infinigen_examples/generate_nature.py index e1b164060..bf1a3934c 100644 --- a/infinigen_examples/generate_nature.py +++ b/infinigen_examples/generate_nature.py @@ -642,15 +642,16 @@ def add_corals(target): ), ) - p.run_stage("wind", lambda: weather.WindEffector(np.randint(1e7))(0)) - p.run_stage("turbulence", lambda: weather.TurbulenceEffector(np.randint(1e7))(0)) + p.run_stage("wind", lambda: weather.WindEffector(randint(1e7))(0)) + p.run_stage("turbulence", lambda: weather.TurbulenceEffector(randint(1e7))(0)) - overhead_emitter = butil.spawn_plane(location=Vector((0, 0, 5)), size=60) - butil.constrain_object( - overhead_emitter, "COPY_LOCATION", use_offset=True, target=camera_rigs[0] + overhead_emitter = weather.spawn_emitter( + camera_rigs[0], "plane", offset=Vector((0, 0, 5)), size=60 + ) + cube_emitter = weather.spawn_emitter( + camera_rigs[0], "cube", offset=Vector(), size=30 ) - cube_emitter = butil.spawn_cube(location=Vector(), size=30) butil.constrain_object( cube_emitter, "COPY_LOCATION", use_offset=True, target=camera_rigs[0] ) diff --git a/infinigen_examples/util/test_utils.py b/infinigen_examples/util/test_utils.py index 2bd11f589..8a80b883f 100644 --- a/infinigen_examples/util/test_utils.py +++ b/infinigen_examples/util/test_utils.py @@ -48,9 +48,15 @@ def load_txt_list(path: Path, skip_sharp=True): raise FileNotFoundError(f"{path=} resolved to {pathabs=} which does not exist") res = pathabs.read_text().splitlines() + res = [ - f.lstrip("#").lstrip(" ") + f.strip() for f in res if (not f.startswith("#") or not skip_sharp) and len(f) > 0 ] + + res = [f[: f.index("#")] if "#" in f else f for f in res] + + res = [r for r in res if len(r.strip()) > 0] + return res diff --git a/tests/assets/list_scatters.txt b/tests/assets/list_scatters.txt new file mode 100644 index 000000000..a84b9ed98 --- /dev/null +++ b/tests/assets/list_scatters.txt @@ -0,0 +1,25 @@ +# infinigen.assets.scatters.coral_reef # too slow for unit tests +# infinigen.assets.scatters.chopped_trees # too slow for unit tests +# infinigen.assets.scatters.ground_mushroom # too slow for unit tests +infinigen.assets.scatters.clothes +infinigen.assets.scatters.decorative_plants +infinigen.assets.scatters.fern +infinigen.assets.scatters.flowerplant +infinigen.assets.scatters.grass +infinigen.assets.scatters.ground_leaves +infinigen.assets.scatters.ground_twigs +infinigen.assets.scatters.ivy +infinigen.assets.scatters.jellyfish +infinigen.assets.scatters.lichen +infinigen.assets.scatters.mollusk +infinigen.assets.scatters.monocots +infinigen.assets.scatters.moss +infinigen.assets.scatters.mushroom +infinigen.assets.scatters.pebbles +infinigen.assets.scatters.pine_needle +infinigen.assets.scatters.pinecone +infinigen.assets.scatters.seashells +infinigen.assets.scatters.seaweed +infinigen.assets.scatters.slime_mold +infinigen.assets.scatters.snow_layer +infinigen.assets.scatters.urchin diff --git a/tests/assets/test_scatters_basic.py b/tests/assets/test_scatters_basic.py new file mode 100644 index 000000000..707e8d318 --- /dev/null +++ b/tests/assets/test_scatters_basic.py @@ -0,0 +1,24 @@ +# Copyright (c) Princeton University. +# This source code is licensed under the BSD 3-Clause license found in the LICENSE file in the root directory +# of this source tree. + +# Authors: Alexander Raistrick + +import pytest + +from infinigen.core.util import blender as butil +from infinigen_examples.util.test_utils import import_item, load_txt_list, setup_gin + + +def check_scatter_runs(pathspec): + butil.clear_scene() + base_cube = butil.spawn_cube() + + scatter = import_item(pathspec) + scatter.apply(base_cube) + + +@pytest.mark.parametrize("pathspec", load_txt_list("tests/assets/list_scatters.txt")) +def test_scatter_runs(pathspec, **kwargs): + setup_gin("infinigen_examples/configs_nature", ["base_nature.gin"]) + check_scatter_runs(pathspec)