diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ba1baf0f7..9e85b9886 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -144,3 +144,12 @@ v1.10.1 v1.11.0 - Update to Blender == 4.2.0 + +v1.11.1 +- Fix failed camera search when canyon/cliff/cave loaded as nature background +- Fix scrambled GT maps in blender_gt due to incorrect OpenEXR pixel unpack ordering +- Fix save_mesh kwarg mismatch from v1.10.0 +- Remove `frozendict` dependency, make `geomdl` optional if not using creatures +- Make `submitit` optional if not using SLURM +- Make blender addons optional if not using relevant assets (rocks/terrain/snowlayer) +- Make `bnurbs` CPython module optional and not installed by default diff --git a/infinigen/__init__.py b/infinigen/__init__.py index f7218326c..31f8ab32b 100644 --- a/infinigen/__init__.py +++ b/infinigen/__init__.py @@ -6,7 +6,7 @@ import logging from pathlib import Path -__version__ = "1.11.0" +__version__ = "1.11.1" def repo_root(): diff --git a/infinigen/assets/fluid/fluid.py b/infinigen/assets/fluid/fluid.py index cb7ff93c3..4260f9d73 100644 --- a/infinigen/assets/fluid/fluid.py +++ b/infinigen/assets/fluid/fluid.py @@ -21,6 +21,7 @@ water, waterfall_material, ) +from infinigen.core.init import require_blender_addon from infinigen.core.nodes.node_wrangler import ( Nodes, NodeWrangler, @@ -34,6 +35,8 @@ FLUID_INITIALIZED = False +require_blender_addon("antlandscape", fail="warn") + def check_initalize_fluids(): if FLUID_INITIALIZED: @@ -621,6 +624,8 @@ def generate_waterfall( seed = np.random.randint(10000) + require_blender_addon("antlandscape") + bpy.ops.mesh.landscape_add( ant_terrain_name="Landscape", land_material="", diff --git a/infinigen/assets/objects/rocks/blender_rock.py b/infinigen/assets/objects/rocks/blender_rock.py index aee61f6e2..a7d3f92af 100644 --- a/infinigen/assets/objects/rocks/blender_rock.py +++ b/infinigen/assets/objects/rocks/blender_rock.py @@ -8,10 +8,13 @@ import numpy as np from numpy.random import uniform as U +from infinigen.core.init import require_blender_addon from infinigen.core.placement.factory import AssetFactory from infinigen.core.tagging import tag_object from infinigen.core.util import blender as butil +require_blender_addon("extra_mesh_objects", fail="warn") + class BlenderRockFactory(AssetFactory): def __init__(self, factory_seed, detail=1): @@ -21,6 +24,8 @@ def __init__(self, factory_seed, detail=1): __repr__ = AssetFactory.__repr__ def create_asset(self, **params): + require_blender_addon("extra_mesh_objects") + seed = np.random.randint(0, 99999) zscale = U(0.2, 0.8) diff --git a/infinigen/assets/scatters/snow_layer.py b/infinigen/assets/scatters/snow_layer.py index 9ad18a2a2..762b21a89 100644 --- a/infinigen/assets/scatters/snow_layer.py +++ b/infinigen/assets/scatters/snow_layer.py @@ -6,12 +6,17 @@ import bpy +from infinigen.core.init import require_blender_addon from infinigen.core.tagging import tag_object from infinigen.core.util import blender as butil +require_blender_addon("real_snow", fail="warn") + class Snowlayer: def apply(self, obj, **kwargs): + require_blender_addon("real_snow") + bpy.context.scene.snow.height = 0.1 with butil.SelectObjects(obj): bpy.ops.snow.create() diff --git a/infinigen/assets/utils/geometry/nurbs.py b/infinigen/assets/utils/geometry/nurbs.py index 66aec51b9..af16ad3da 100644 --- a/infinigen/assets/utils/geometry/nurbs.py +++ b/infinigen/assets/utils/geometry/nurbs.py @@ -10,18 +10,25 @@ import bmesh import bpy import numpy as np -from geomdl import NURBS from infinigen.core.util import blender as butil logger = logging.getLogger(__name__) try: - import bnurbs + from geomdl import NURBS except ImportError: logger.warning( - "Failed to import compiled `bnurbs` package, either installation failed or we are running a minimal install" + "Failed to import `geomdl` package, generation will crash if `geomdl` is used" ) + NURBS = None + +try: + import bnurbs +except ImportError: + # logger.warning( + # "Failed to import compiled `bnurbs` package, either installation failed or we are running a minimal install" + # ) bnurbs = None @@ -147,6 +154,12 @@ def blender_nurbs( spline.use_cyclic_v = cyclic_v spline.resolution_u, spline.resolution_v = resolution + if (kv_u is not None or kv_v is not None) and bnurbs is None: + logger.warning( + "Failed to import compiled `bnurbs` package. `bnurbs` extension is no longer compiled by default, " + f"please either install it via BUILD_BNURBS=True during install, or avoid calling {blender_nurbs.__name__}" + ) + if kv_u is not None: bnurbs.set_knotsu(spline, kv_u) if kv_v is not None: @@ -208,7 +221,12 @@ def blender_mesh_from_pydata(points, edges, faces, uvs=None, name="pydata_mesh") return obj -def blender_nurbs_to_geomdl(s: bpy.types.Spline) -> NURBS.Surface: +def blender_nurbs_to_geomdl(s: bpy.types.Spline): + if NURBS is None: + raise ImportError( + f"geomdl was not found at runtime, please either install `geomdl` or avoid calling {blender_nurbs_to_geomdl.__name__}" + ) + surf = NURBS.Surface(normalize_kv=False) surf.degree_u, surf.degree_v = (s.order_u - 1, s.order_v - 1) @@ -217,7 +235,8 @@ def blender_nurbs_to_geomdl(s: bpy.types.Spline) -> NURBS.Surface: if bnurbs is None: logger.warning( - "Failed to import compiled `bnurbs` package, either installation failed or we are running a minimal install" + "Failed to import compiled `bnurbs` package. `bnurbs` extension is no longer compiled by default, " + f"please either install it via BUILD_BNURBS=True during install, or avoid calling {blender_nurbs_to_geomdl.__name__}" ) surf.knotvector_u = bnurbs.get_knotsu(s) surf.knotvector_v = bnurbs.get_knotsv(s) @@ -241,7 +260,7 @@ def blender_nurbs_to_geomdl(s: bpy.types.Spline) -> NURBS.Surface: return surf -def geomdl_to_mesh(surf: NURBS.Surface, eval_delta, name="geomdl_mesh"): +def geomdl_to_mesh(surf, eval_delta, name="geomdl_mesh"): surf.delta = eval_delta points = np.array(surf.evalpts) @@ -277,6 +296,11 @@ def map_uv_to_valid_domain(s: bpy.types.Spline, uv: np.array): def geomdl_nurbs( ctrlpts, eval_delta, ws=None, kv_u=None, kv_v=None, name="loft_nurbs", cyclic_v=True ): + if NURBS is None: + raise ImportError( + f"geomdl was not found at runtime, please either install `geomdl` or avoid calling {geomdl_nurbs}" + ) + n, m, _ = ctrlpts.shape degree_u, degree_v = (3, 3) diff --git a/infinigen/core/execute_tasks.py b/infinigen/core/execute_tasks.py index a7d8e53d9..9f9c95bb3 100644 --- a/infinigen/core/execute_tasks.py +++ b/infinigen/core/execute_tasks.py @@ -16,7 +16,6 @@ import bpy import gin -from frozendict import frozendict import infinigen.assets.scatters from infinigen.core import init, surface @@ -102,7 +101,7 @@ def save_meshes( for col in bpy.data.collections: col.hide_viewport = col.hide_render - previous_frame_mesh_id_mapping = frozendict() + previous_frame_mesh_id_mapping = dict() current_frame_mesh_id_mapping = defaultdict(dict) # save static meshes @@ -118,7 +117,7 @@ def save_meshes( previous_frame_mesh_id_mapping, current_frame_mesh_id_mapping, ) - previous_frame_mesh_id_mapping = frozendict(current_frame_mesh_id_mapping) + previous_frame_mesh_id_mapping = dict(current_frame_mesh_id_mapping) current_frame_mesh_id_mapping.clear() for obj in bpy.data.objects: @@ -139,12 +138,13 @@ def save_meshes( previous_frame_mesh_id_mapping, current_frame_mesh_id_mapping, ) - cam_util.save_camera_parameters( - camera_ids=cameras, - output_folder=frame_info_folder / "cameras", - frame=frame_idx, - ) - previous_frame_mesh_id_mapping = frozendict(current_frame_mesh_id_mapping) + for cam in cameras: + cam_util.save_camera_parameters( + camera_obj=cam, + output_folder=frame_info_folder / "cameras", + frame=frame_idx, + ) + previous_frame_mesh_id_mapping = dict(current_frame_mesh_id_mapping) current_frame_mesh_id_mapping.clear() @@ -341,13 +341,14 @@ def execute_tasks( save_meshes( scene_seed, output_folder=output_folder, + cameras=[c for rig in camera_rigs for c in rig.children], frame_range=frame_range, point_trajectory_src_frame=point_trajectory_src_frame, ) def main(input_folder, output_folder, scene_seed, task, task_uniqname, **kwargs): - version_req = ["3.6.0", "4.2.0"] + version_req = ["4.2.0"] assert bpy.app.version_string in version_req, ( f"You are using blender={bpy.app.version_string} which is " f"not supported. Please use {version_req}" diff --git a/infinigen/core/init.py b/infinigen/core/init.py index dbe2c37c3..aff79258c 100644 --- a/infinigen/core/init.py +++ b/infinigen/core/init.py @@ -274,6 +274,53 @@ def configure_cycles_devices(use_gpu=True): return use_devices +def require_blender_addon(addon: str, fail: str = "fatal", allow_online=False): + def report_fail(msg): + if fail == "warn": + logger.warning( + msg + ". Generation may crash at runtime if certain assets are used." + ) + elif fail == "fatal": + raise ValueError(msg) + else: + raise ValueError( + f"{require_blender_addon.__name__} got unrecognized {fail=}" + ) + + long = f"bl_ext.blender_org.{addon}" + + addon_present = long in bpy.context.preferences.addons.keys() + + if addon_present: + logger.debug(f"Addon {addon} already present.") + return True + + builtin_local_addons = set(a.__name__ for a in addon_utils.modules(refresh=True)) + + if addon not in builtin_local_addons and not allow_online: + report_fail(f"{addon=} not found and online install is disabled") + + try: + if long in builtin_local_addons: + logger.info(f"Addon {addon} already in blender local addons, attempt to enable it.") + bpy.ops.preferences.addon_enable(module=long) + else: + bpy.ops.extensions.userpref_allow_online() + logger.info(f"Installing Add-on {addon}.") + bpy.ops.extensions.repo_sync(repo_index=0) + bpy.ops.extensions.package_install( + repo_index=0, pkg_id=addon, enable_on_install=True + ) + bpy.ops.preferences.addon_enable(module=long) + except Exception as e: + report_fail(f"Failed to install {addon=} due to {e=}") + + if long not in bpy.context.preferences.addons.keys(): + report_fail(f"Attempted to install {addon=} but wasnt found after install") + + return True + + @gin.configurable def configure_blender( render_engine="CYCLES", @@ -293,20 +340,3 @@ def configure_blender( if motion_blur: bpy.context.scene.cycles.motion_blur_position = "START" bpy.context.scene.render.motion_blur_shutter = motion_blur_shutter - - addons = ["extra_mesh_objects", "real_snow", "antlandscape"] - for addon in addons: - long = f"bl_ext.blender_org.{addon}" - all_addons = set(a.__name__ for a in addon_utils.modules(refresh=True)) - if long in all_addons: - bpy.ops.preferences.addon_enable(module=long) - else: - bpy.ops.extensions.userpref_allow_online() - logger.info(f"Installing Add-on {addon}.") - bpy.ops.extensions.repo_sync(repo_index=0) - bpy.ops.extensions.package_install( - repo_index=0, pkg_id=addon, enable_on_install=True - ) - bpy.ops.preferences.addon_enable(module=long) - assert long in bpy.context.preferences.addons.keys() - logger.info(f"{addon} enabled.") diff --git a/infinigen/core/placement/camera.py b/infinigen/core/placement/camera.py index 62a92f09c..17f909488 100644 --- a/infinigen/core/placement/camera.py +++ b/infinigen/core/placement/camera.py @@ -357,7 +357,7 @@ def keep_cam_pose_proposal( return None coverage = len(dists) / n_pix - if ( + if terrain_coverage_range is not None and ( coverage < terrain_coverage_range[0] or coverage > terrain_coverage_range[1] or coverage == 0 @@ -435,12 +435,10 @@ def compute_base_views( radius=None, bbox=None, placeholders_kd=None, - camera_selection_answers={}, - vertexwise_min_dist=None, - camera_selection_ratio=None, min_candidates_ratio=20, max_tries=30000, visualize=False, + **kwargs, ): potential_views = [] n_min_candidates = int(min_candidates_ratio * n_views) @@ -475,9 +473,7 @@ def compute_base_views( terrain, scene_bvh, placeholders_kd, - camera_selection_answers=camera_selection_answers, - vertexwise_min_dist=vertexwise_min_dist, - camera_selection_ratio=camera_selection_ratio, + **kwargs, ) all_scores.append(score) @@ -660,6 +656,7 @@ def configure_cameras( nonroom_objs=None, mvs_setting=False, mvs_radius=("uniform", 12, 18), + **kwargs, ): bpy.context.view_layer.update() @@ -716,6 +713,7 @@ def contain_keywords(name, keywords): radius=mvs_radius, bbox=init_bounding_box, **scene_preprocessed, + **kwargs, ) score, props, focus_dist = views[0] @@ -740,6 +738,7 @@ def animate_cameras( pois=None, follow_poi_chance=0.0, policy_registry=None, + **kwargs, ): animation_ratio = {} animation_answers = {} @@ -760,6 +759,7 @@ def anim_valid_camrig_pose_func(cam_rig: bpy.types.Object): vertexwise_min_dist=scene_preprocessed["vertexwise_min_dist"], camera_selection_answers=animation_answers, camera_selection_ratio=animation_ratio, + **kwargs, ) if score is None: @@ -793,7 +793,7 @@ def anim_valid_camrig_pose_func(cam_rig: bpy.types.Object): @gin.configurable def save_camera_parameters( - camera_obj: bpy.types.Object, output_folder, frame, use_dof=False + camera_obj: bpy.types.Object, output_folder: Path, frame: int, use_dof=False ): output_folder = Path(output_folder) output_folder.mkdir(exist_ok=True, parents=True) diff --git a/infinigen/core/rendering/post_render.py b/infinigen/core/rendering/post_render.py index 9a1f2aeed..8a960e6df 100644 --- a/infinigen/core/rendering/post_render.py +++ b/infinigen/core/rendering/post_render.py @@ -42,12 +42,12 @@ def load_single_channel(p): np_type = np.uint8 data = np.frombuffer(file.channel(channel, channel_type.type), np_type) dw = file.header()["dataWindow"] - sz = (dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1) + sz = (dw.max.y - dw.min.y + 1, dw.max.x - dw.min.x + 1) return data.reshape(sz) -load_depth = load_single_channel - +def load_depth(p): + return load_single_channel(p) def load_normals(p): return load_exr(p)[..., [2, 0, 1]] * np.array([-1.0, 1.0, 1.0]) diff --git a/infinigen/datagen/manage_jobs.py b/infinigen/datagen/manage_jobs.py index b6dd4a4c4..74605116e 100644 --- a/infinigen/datagen/manage_jobs.py +++ b/infinigen/datagen/manage_jobs.py @@ -28,10 +28,17 @@ import gin import numpy as np import pandas as pd -import submitit -import submitit.core.utils from jinja2 import Environment, FileSystemLoader, select_autoescape +try: + import submitit + import submitit.core.utils +except ImportError: + logging.warning( + f"Failed to import submitit, {Path(__file__).name} will crash if slurm job is requested" + ) + submitit = None + # ruff: noqa: E402 ORIG_SYS_PATH = list(sys.path) # Make a new instance of sys.path import infinigen.core.init @@ -114,6 +121,9 @@ def slurm_submit_cmd( slurm_niceness=None, **_, ): + if submitit is None: + raise ValueError("submitit is required for slurm_submit_cmd") + executor = submitit.AutoExecutor(folder=(folder / "logs")) executor.update_parameters( mem_gb=mem_gb, @@ -157,11 +167,13 @@ def slurm_submit_cmd( executor.update_parameters(slurm_additional_parameters=slurm_additional_params) + if callable(cmd[0]): + raise ValueError( + "Callable with submit_cmd is deprecated, please submit a commandline string" + ) + while True: try: - if callable(cmd[0]): - func, *arg = cmd - return executor.submit(func, *arg) render_fn = submitit.helpers.CommandFunction(cmd) return executor.submit(render_fn) except submitit.core.utils.FailedJobError as e: @@ -172,18 +184,19 @@ def slurm_submit_cmd( @gin.configurable def local_submit_cmd( - cmd, folder, name, use_scheduler=False, passthrough=False, **kwargs + cmd, folder: Path, name: str, use_scheduler=False, passthrough=False, **kwargs ): ExecutorClass = ScheduledLocalExecutor if use_scheduler else ImmediateLocalExecutor log_folder = (folder / "logs") if not passthrough else None executor = ExecutorClass(folder=log_folder) executor.update_parameters(name=name, **kwargs) + if callable(cmd[0]): - func, *arg = cmd - return executor.submit(func, *arg) - else: - func = submitit.helpers.CommandFunction(cmd) - return executor.submit(func) + raise ValueError( + "Callable with submit_cmd is deprecated, please submit a commandline string" + ) + + return executor.submit(cmd) def init_db_from_existing(output_folder: Path): diff --git a/infinigen/datagen/states.py b/infinigen/datagen/states.py index 45960297b..42cf18680 100644 --- a/infinigen/datagen/states.py +++ b/infinigen/datagen/states.py @@ -11,7 +11,6 @@ from pathlib import Path import gin -import submitit from infinigen.datagen.util.submitit_emulator import LocalJob @@ -73,7 +72,7 @@ def get_scene_state(scene: dict, taskname: str, scene_folder: Path): return JobState.Succeeded elif isinstance(job_obj, LocalJob): res = job_obj.status() - elif isinstance(job_obj, submitit.Job): + elif hasattr(job_obj, "job_id"): res = seff(job_obj) else: raise TypeError(f"Unrecognized {job_obj=}") @@ -95,7 +94,7 @@ def cancel_job(job_obj): return JobState.Succeeded elif isinstance(job_obj, LocalJob): job_obj.kill() - elif isinstance(job_obj, submitit.Job): + elif hasattr(job_obj, "job_id"): # TODO: does submitit have a cancel? subprocess.check_call(["/usr/bin/scancel", str(job_obj.job_id)]) else: diff --git a/infinigen/datagen/util/smb_client.py b/infinigen/datagen/util/smb_client.py index 398d33d34..39ff4afc9 100644 --- a/infinigen/datagen/util/smb_client.py +++ b/infinigen/datagen/util/smb_client.py @@ -17,7 +17,14 @@ from multiprocessing import Pool from pathlib import Path -import submitit +try: + import submitit +except ImportError: + logging.warning( + f"Failed to import submitit, {Path(__file__).name} will crash if slurm job is requested" + ) + submitit = None + from tqdm import tqdm logger = logging.getLogger(__file__) @@ -216,10 +223,10 @@ def list_files_recursive(base_path: Path): def mapfunc(f, its, args): if args.n_workers == 1: return [f(i) for i in its] - elif not args.slurm: - with Pool(args.n_workers) as p: - return list(tqdm(p.imap(f, its), total=len(its))) - else: + elif args.slurm: + if submitit is None: + raise ValueError("submitit not imported, cannot use --slurm") + executor = submitit.AutoExecutor(folder=args.local_path / "logs") executor.update_parameters( name=args.local_path.name, @@ -230,6 +237,9 @@ def mapfunc(f, its, args): slurm_array_parallelism=args.n_workers, ) executor.map_array(f, its) + else: + with Pool(args.n_workers) as p: + return list(tqdm(p.imap(f, its), total=len(its))) def resolve_globs(p: Path, args): diff --git a/infinigen/datagen/util/submitit_emulator.py b/infinigen/datagen/util/submitit_emulator.py index 13fa95451..bd8bbafb8 100644 --- a/infinigen/datagen/util/submitit_emulator.py +++ b/infinigen/datagen/util/submitit_emulator.py @@ -13,7 +13,6 @@ import os import re import subprocess -import sys from contextlib import nullcontext from dataclasses import dataclass from multiprocessing import Process @@ -67,9 +66,7 @@ def get_fake_job_id(): def job_wrapper( - func, - inner_args, - inner_kwargs, + command: list[str], stdout_file: Path = None, stderr_file: Path = None, cuda_devices=None, @@ -77,33 +74,36 @@ def job_wrapper( stdout_ctx = stdout_file.open("w") if stdout_file is not None else nullcontext() stderr_ctx = stderr_file.open("w") if stderr_file is not None else nullcontext() with stdout_ctx as stdout, stderr_ctx as stderr: - if stdout_file is not None: - sys.stdout = stdout - if stderr_file is not None: - sys.stderr = stderr if cuda_devices is not None: - os.environ[CUDA_VARNAME] = ",".join([str(i) for i in cuda_devices]) + env = os.environ.copy() + env[CUDA_VARNAME] = ",".join([str(i) for i in cuda_devices]) else: - os.environ[CUDA_VARNAME] = "" - return func(*inner_args, **inner_kwargs) + env = None + + subprocess.run( + command, + stdout=stdout if stdout_file is not None else subprocess.PIPE, + stderr=stderr if stderr_file is not None else subprocess.PIPE, + shell=False, + check=False, # dont throw CalledProcessError + env=env, + ) -def launch_local(func, args, kwargs, job_id, log_folder, name, cuda_devices=None): +def launch_local(command: str, job_id, log_folder: Path, name: str, cuda_devices=None): if log_folder is None: # pass input through to stdout if log_folder is None stderr_file = None stdout_file = None - print(f"{func} {args}") + print(command) else: stderr_file = log_folder / f"{job_id}_0_log.err" stdout_file = log_folder / f"{job_id}_0_log.out" with stdout_file.open("w") as f: - f.write(f"{func} {args}\n") + f.write(f"{command}\n") kwargs = dict( - func=func, - inner_args=args, - inner_kwargs=kwargs, + command=command, stdout_file=stdout_file, stderr_file=stderr_file, cuda_devices=cuda_devices, @@ -126,12 +126,10 @@ def __init__(self, folder: str | None): def update_parameters(self, **parameters): self.parameters.update(parameters) - def submit(self, func, *args, **kwargs): + def submit(self, command: str): job_id = get_fake_job_id() name = self.parameters.get("name", None) - proc = launch_local( - func, args, kwargs, job_id, log_folder=self.log_folder, name=name - ) + proc = launch_local(command, job_id, log_folder=self.log_folder, name=name) return LocalJob(job_id=job_id, process=proc) @@ -150,12 +148,10 @@ def __init__(self, jobs_per_gpu=1, use_gpu=True): self.jobs_per_gpu = jobs_per_gpu self.use_gpu = use_gpu - def enqueue(self, func, args, kwargs, params, log_folder): + def enqueue(self, command: str, params, log_folder): job = LocalJob(job_id=get_fake_job_id(), process=None) job_rec = dict( - func=func, - args=args, - kwargs=kwargs, + command=command, params=params, job=job, log_folder=log_folder, @@ -166,7 +162,7 @@ def enqueue(self, func, args, kwargs, params, log_folder): return job @gin.configurable - def total_resources(self): + def total_resources(self) -> set: resources = {} if self.use_gpu: @@ -192,7 +188,7 @@ def total_resources(self): return resources - def resources_available(self, total): + def resources_available(self, total) -> set: resources = copy.copy(total) for job_rec in self.queue: @@ -221,9 +217,7 @@ def dispatch(self, job_rec, resources): gpu_idxs = [g[0] for g in gpu_assignment] job_rec["job"].process = launch_local( - func=job_rec["func"], - args=job_rec["args"], - kwargs=job_rec["kwargs"], + command=job_rec["command"], job_id=job_rec["job"].job_id, log_folder=job_rec["log_folder"], name=job_rec["params"].get("name", None), @@ -231,7 +225,9 @@ def dispatch(self, job_rec, resources): ) job_rec["gpu_assignment"] = gpu_assignment - def attempt_dispatch_job(self, job_rec, available, total, select_gpus="first"): + def attempt_dispatch_job( + self, job_rec, available: set, total: set, select_gpus="first" + ): n_gpus = job_rec["params"].get("gpus", 0) or 0 if n_gpus == 0 or not self.use_gpu: @@ -260,9 +256,9 @@ def __init__(self, folder: str): def update_parameters(self, **parameters): self.parameters.update(parameters) - def submit(self, func, *args, **kwargs): + def submit(self, command): return LocalScheduleHandler.instance().enqueue( - func, args, kwargs, params=self.parameters, log_folder=self.log_folder + command, params=self.parameters, log_folder=self.log_folder ) diff --git a/infinigen/terrain/assets/landtiles/ant_landscape.py b/infinigen/terrain/assets/landtiles/ant_landscape.py index d96f0b426..55947180d 100644 --- a/infinigen/terrain/assets/landtiles/ant_landscape.py +++ b/infinigen/terrain/assets/landtiles/ant_landscape.py @@ -11,17 +11,22 @@ import gin import numpy as np +from infinigen.core.init import require_blender_addon from infinigen.core.util.organization import AssetFile, LandTile from infinigen.terrain.land_process.erosion import run_erosion from infinigen.terrain.land_process.snowfall import run_snowfall from infinigen.terrain.utils import random_nat, smooth +require_blender_addon("antlandscape", fail="warn") + def create( preset_name, subdivision_x, subdivision_y, ): + require_blender_addon("antlandscape") + def presets(**kwargs): bpy.ops.mesh.landscape_add( ant_terrain_name="Landscape", diff --git a/infinigen_examples/generate_individual_assets.py b/infinigen_examples/generate_individual_assets.py index 19f17b180..a6ecf74c4 100644 --- a/infinigen_examples/generate_individual_assets.py +++ b/infinigen_examples/generate_individual_assets.py @@ -23,9 +23,16 @@ import bpy import gin import numpy as np -import submitit from PIL import Image +try: + import submitit +except ImportError: + logging.warning( + f"Failed to import submitit, {Path(__file__).name} will crash if slurm job is requested" + ) + submitit = None + # ruff: noqa: E402 # NOTE: logging config has to be before imports that use logging logging.basicConfig( @@ -448,6 +455,9 @@ def mapfunc( with Pool(args.n_workers) as p: return list(p.imap(f, its)) else: + if submitit is None: + raise ValueError("submitit not imported, cannot use --slurm") + executor = submitit.AutoExecutor(folder=args.output_folder / "logs") slurm_additional_parameters = {} diff --git a/infinigen_examples/generate_indoors.py b/infinigen_examples/generate_indoors.py index cbf1f14bd..de2d94b45 100644 --- a/infinigen_examples/generate_indoors.py +++ b/infinigen_examples/generate_indoors.py @@ -253,6 +253,7 @@ def pose_cameras(): scene_preprocessed=scene_preprocessed, init_surfaces=solved_floor_surface, nonroom_objs=nonroom_objs, + terrain_coverage_range=None, # do not filter cameras by terrain visibility, even if nature scenetype configs request this ) butil.delete(solved_floor_surface) return scene_preprocessed @@ -260,7 +261,13 @@ def pose_cameras(): scene_preprocessed = p.run_stage("pose_cameras", pose_cameras, use_chance=False) def animate_cameras(): - cam_util.animate_cameras(camera_rigs, solved_bbox, scene_preprocessed, pois=[]) + cam_util.animate_cameras( + camera_rigs, + solved_bbox, + scene_preprocessed, + pois=[], + terrain_coverage_range=None, # same as above - do not filter by terrain visiblity when indoors + ) frames_folder = output_folder.parent / "frames" animated_cams = [cam for cam in camera_rigs if cam.animation_data is not None] diff --git a/pyproject.toml b/pyproject.toml index 33a43478c..add0c473a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,6 @@ classifiers = [ requires-python = "==3.11.*" dependencies = [ "bpy==4.2.0", - "frozendict", # TODO remove - "geomdl", # used only for creatures "gin_config>=0.5.0", "imageio", "matplotlib", @@ -36,11 +34,14 @@ dependencies = [ "scikit-learn", "scipy", "shapely", - "submitit", "tqdm", "trimesh", "OpenEXR", + "submitit", # can be removed so long as you dont use slurm.gin or --slurm args + + "geomdl", # can be removed so long as you do not generate any creatures + # used by trimesh, we could specify "trimesh[easy]" but this brings more packages "python-fcl", "rtree", diff --git a/setup.py b/setup.py index 0717d2f05..d7c7fb75e 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ MINIMAL_INSTALL = os.environ.get("INFINIGEN_MINIMAL_INSTALL") == str_true BUILD_TERRAIN = os.environ.get("INFINIGEN_INSTALL_TERRAIN", str_true) == str_true BUILD_OPENGL = os.environ.get("INFINIGEN_INSTALL_CUSTOMGT", "False") == str_true +BUILD_BNURBS = os.environ.get("INFINIGEN_INSTALL_BNURBS", "False") == str_true dont_build_steps = ["clean", "egg_info", "dist_info", "sdist", "--help"] is_build_step = not any(x in sys.argv[1] for x in dont_build_steps) @@ -56,13 +57,14 @@ def ensure_submodules(): cython_extensions = [] if not MINIMAL_INSTALL: - cython_extensions.append( - Extension( - name="bnurbs", - sources=["infinigen/assets/utils/geometry/cpp_utils/bnurbs.pyx"], - include_dirs=[numpy.get_include()], + if BUILD_BNURBS: + cython_extensions.append( + Extension( + name="bnurbs", + sources=["infinigen/assets/utils/geometry/cpp_utils/bnurbs.pyx"], + include_dirs=[numpy.get_include()], + ) ) - ) if BUILD_TERRAIN: cython_extensions.append( Extension(