diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index a3283fe2b..ed99f6f1b 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -5,11 +5,11 @@ on: pull_request: branches: - main - - develop + - develop* push: branches: - main - - develop + - develop* concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -22,7 +22,7 @@ env: jobs: checks: - + if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: @@ -32,7 +32,7 @@ jobs: run: | pip install ruff # stop the build if there are Python syntax errors or undefined names - ruff --output-format=github --select=E9,F63,F7,F82 . + ruff check --output-format=github --select=E9,F63,F7,F82 . # default set of ruff rules with GitHub Annotations #ruff --format=github . # to be enabled in a future PR @@ -47,4 +47,4 @@ jobs: - name: Test with pytest run: | - pytest tests -m ci \ No newline at end of file + pytest tests -k 'not skip_for_ci' \ No newline at end of file diff --git a/README.md b/README.md index 9d9e88ce1..acc877e87 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,68 @@ +
+ +
-## [Infinigen: Infinite Photorealistic Worlds using Procedural Generation](https://infinigen.org) +# [Infinigen: Infinite Photorealistic Worlds Using Procedural Generation](https://infinigen.org) -Please visit our website, [https://infinigen.org](https://infinigen.org) +[**Getting Started**](#getting-started) +| [**Website**](https://infinigen.org/) +| [**Intro Video**](https://www.youtube.com/watch?v=6tgspeI-GHY) +| [**Papers**](#papers) +| [**Documentation**](#documentation) +| [**Contributing**](#contributing) -[![Infinigen Intro Video](docs/images/video_thumbnail.png)](https://youtu.be/6tgspeI-GHY) +
+ +
-If you use Infinigen in your work, please cite our [academic paper]([https://arxiv.org/abs/2306.09310](https://arxiv.org/abs/2306.09310)): +## Getting Started + +First, follow our [Installation Instructions](docs/Installation.md). + +### Hello Room: Getting Started with Infinigen Indoors + +

+ + + + +

+ +See instructions & example commands for Infinigen-Indoors in [HelloRoom.md](docs/HelloRoom.md) + +### Hello World: Getting Started with Infinigen Nature + +

+ + + + +

+ +See instructions & example commands for Infinigen-Nature in [HelloWorld.md](docs/HelloWorld.md) + +## Papers + +If you use Infinigen in your work, please cite our academic papers:

Infinite Photorealistic Worlds using Procedural Generation

-Alexander Raistrick*, Lahav Lipson*, Zeyu Ma* (*equal contribution, alphabetical order)
-Lingjie Mei, Mingzhe Wang, Yiming Zuo, Karhan Kayan, Hongyu Wen, Beining Han,
-Yihan Wang, Alejandro Newell, Hei Law, Ankit Goyal, Kaiyu Yang, Jia Deng
+Alexander Raistrick*, +Lahav Lipson*, +Zeyu Ma* (*equal contribution, alphabetical order)
+Lingjie Mei, +Mingzhe Wang, +Yiming Zuo, +Karhan Kayan, +Hongyu Wen, +Beining Han,
+Yihan Wang, +Alejandro Newell, +Hei Law, +Ankit Goyal, +Kaiyu Yang, +Jia Deng
Conference on Computer Vision and Pattern Recognition (CVPR) 2023

@@ -26,23 +76,39 @@ Conference on Computer Vision and Pattern Recognition (CVPR) 2023 } ``` -### Getting Started - -First, follow our [Installation Instructions](docs/Installation.md). - -Next, see our ["Hello World" example](docs/HelloWorld.md) to generate an image & ground truth similar to those shown below. - +

Infinigen Indoors: Photorealistic Indoor Scenes using Procedural Generation

- - - - +Alexander Raistrick*, +Lingjie Mei*, +Karhan Kayan*, (*equal contribution, random order)
+David Yan, +Yiming Zuo, +Beining Han, +Hongyu Wen, +Meenal Parakh,
+Stamatis Alexandropoulos, +Lahav Lipson, +Zeyu Ma, +Jia Deng
+Conference on Computer Vision and Pattern Recognition (CVPR) 2024

-### Documentation +``` +@inproceedings{infinigen2024indoors, + author = {Raistrick, Alexander and Mei, Lingjie and Kayan, Karhan and Yan, David and Zuo, Yiming and Han, Beining and Wen, Hongyu and Parakh, Meenal and Alexandropoulos, Stamatis and Lipson, Lahav and Ma, Zeyu and Deng, Jia}, + title = {Infinigen Indoors: Photorealistic Indoor Scenes using Procedural Generation}, + booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)}, + month = {June}, + year = {2024}, + pages = {21783-21794} +} +``` + +## Documentation - [Installation Guide](docs/Installation.md) -- ["Hello World": Generate your first Infinigen scene](docs/HelloWorld.md) +- ["Hello World": Generate your first Infinigen-Nature scene](docs/HelloWorld.md) +- ["Hello Room": Generate your first Infinigen-Indoors scene](docs/HelloRoom.md) - [Configuring Infinigen](docs/ConfiguringInfinigen.md) - [Downloading pre-generated data](docs/PreGeneratedData.md) - [Generating individual assets](docs/GeneratingIndividualAssets.md) @@ -51,10 +117,10 @@ Next, see our ["Hello World" example](docs/HelloWorld.md) to generate an image & - [Implementing new materials & assets](docs/ImplementingAssets.md) - [Generating fluid simulations](docs/GeneratingFluidSimulations.md) -### Coming Soon Please see our [project roadmap](https://infinigen.org/roadmap) and follow us at [https://twitter.com/PrincetonVL](https://twitter.com/PrincetonVL) for updates. -### Contributing +## Contributing + We welcome contributions! You can contribute in many ways: - **Contribute code to this repository** - We welcome code contributions. More guidelines coming soon. - **Contribute procedural generators** - `infinigen/nodes/node_transpiler/dev_script.py` provides tools to convert artist-friendly [Blender Nodes](https://docs.blender.org/manual/en/2.79/render/blender_render/materials/nodes/introduction.html) into python code. Tutorials and guidelines coming soon. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d72471b20..7b5d3fd15 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -51,3 +51,8 @@ v1.3.3 v1.3.4 - Fixed bug where individual export would fail on objects hidden from viewport - Fixed Terrain.populated_bounds bad merge + +v1.4.0 - Infinigen Indoors +- Add library of procedural generators for indoor objects & materials +- Add indoor scene generation system, including constraint language and solver +- Add HelloRoom.md & ExportingToSimulators.md diff --git a/docs/ConfiguringInfinigen.md b/docs/ConfiguringInfinigen.md index e739b1f58..1acc24d04 100644 --- a/docs/ConfiguringInfinigen.md +++ b/docs/ConfiguringInfinigen.md @@ -33,13 +33,13 @@ Infinigen is designed to run many independent scenes in paralell. This means tha Both `manage_jobs.py` and `infinigen_examples/generate_nature.py` can be configured via the commandline or config files, using [Google's "Gin Config"](https://github.com/google/gin-config). Gin allows you to insert new default keyword arguments ("kwargs") for any function decorated with `@gin.configurable`; many such functions exist in our codebase, and via gin overrides you can create datsets suiting many diverse applications, as is explained in the coming sections. -To use gin, simply add commandline arguments such as `-p compose_scene.rain_particles_chance = 1.0` to override the chance of rain, or `--pipeline_overrides iterate_scene_tasks.frame_range=[1,25]` to set a video's length to 24 frames. You can chain many statements together, separated by spaces, to configure many parts of the system at once. These statements depend on knowing the python names of the function and keyword argument you wish to override. To find parameters you wish to override, you should browse `infinigen_examples/configs/base.gin` and other configs, or `infinigen_examples/generate_nature.py` and the definitions of any functions it calls. Better documentation and organization of the available parameters will come in future versions. +To use gin, simply add commandline arguments such as `-p compose_nature.rain_particles_chance = 1.0` to override the chance of rain, or `--pipeline_overrides iterate_scene_tasks.frame_range=[1,25]` to set a video's length to 24 frames. You can chain many statements together, separated by spaces, to configure many parts of the system at once. These statements depend on knowing the python names of the function and keyword argument you wish to override. To find parameters you wish to override, you should browse `infinigen_examples/configs_nature/base.gin` and other configs, or `infinigen_examples/generate_nature.py` and the definitions of any functions it calls. Better documentation and organization of the available parameters will come in future versions. If you find a useful and related combination of these commandline overrides, you can write them into a `.gin` file in `infinigen_examples/configs`. Then, to load that config, just include the name of the file into the `--configs`. If your overrides target `manage_jobs` rather than `infinigen_examples/generate_nature.py` you should place the config file in `datagen/configs` and use `--pipeline_configs` rather than `--configs`. -Our `infinigen_examples/generate_nature.py` driver always loads [`infinigen_examples/configs/base.gin`][../infinigen_examples/configs/base.gin], and you can inspect / modify this file to see many common and useful gin override options. +Our `infinigen_examples/generate_nature.py` driver always loads [`infinigen_examples/configs_nature/base.gin`][../infinigen_examples/configs_nature/base.gin], and you can inspect / modify this file to see many common and useful gin override options. -`infinigen_examples/generate_nature.py` also expects that one file from (configs/scene_types/)[infinigen_examples/configs/scene_types] will be loaded. These scene_type configs contain gin overrides designed to encode the semantic constraints of real natural habitats (e.g. `infinigen_examples/configs/scene_types/desert.gin` causes sand to appear and cacti to be more likely). +`infinigen_examples/generate_nature.py` also expects that one file from (configs/scene_types/)[infinigen_examples/configs_nature/scene_types] will be loaded. These scene_type configs contain gin overrides designed to encode the semantic constraints of real natural habitats (e.g. `infinigen_examples/configs_nature/scene_types/desert.gin` causes sand to appear and cacti to be more likely). ### Moving beyond "Hello World" @@ -57,7 +57,7 @@ Here is a breakdown of what every commandline argument does, and ideas for how y - `--specific_seed 0` forces the system to use a random seed of your choice, rather than choosing one at random. Change this seed to get a different random variation, or remove it to have the program choose a seed at random - `--num_scenes` decides how many unique scenes the program will attempt to generate before terminating. Once you have removed `--specific_seed`, you can increase this to generate many scenes in sequence or in paralell. - `--configs desert.gin simple.gin` forces the command to generate a desert scene, and to do so with relatively low mesh detail, low render resolution, low render samples, and some asset types disabled. - - Do `--configs snowy_mountain.gin simple.gin` to try out a different scene type (`snowy_mountain.gin` can instead be any scene_type option from `infinigen_examples/configs/scene_types/`) + - Do `--configs snowy_mountain.gin simple.gin` to try out a different scene type (`snowy_mountain.gin` can instead be any scene_type option from `infinigen_examples/configs_nature/scene_types/`) - Remove the `desert.gin` and just specify `--configs simple.gin` to use random scene types according to the weighted list in `datagen/configs/base.gin`. - You have the option of removing `simple.gin` and specify neither of the original configs. This turns off the many detail-reduction options included in `simple.gin`, and will create scenes closer to those in our intro video, albeit at significant compute costs. Removing `simple.gin` will likely cause crashes unless using a workstation/server with large amounts of RAM and VRAM. You can find more details on optimizing scene content for performance [here](#config-overrides-for-mesh-detail-and-performance). - `--pipeline_configs local_16GB.gin monocular.gin blender_gt.gin` @@ -106,22 +106,22 @@ Generating a video, stereo or other dataset typically requires more render jobs, To create longer videos, modify `iterate_scene_tasks.frame_range` in `monocular_video.gin` (note: we use 24fps video by default). `iterate_scene_tasks.view_block_size` controls how many frames will be grouped into each `fine_terrain` and render / ground-truth task. -If you need more than two cameras, or want to customize their placement, see `infinigen_examples/configs/base.gin`'s `camera.spawn_camera_rigs.camera_rig_config` for advice on existing options, or write your own code to instantiate a custom camera setup. +If you need more than two cameras, or want to customize their placement, see `infinigen_examples/configs_nature/base.gin`'s `camera.spawn_camera_rigs.camera_rig_config` for advice on existing options, or write your own code to instantiate a custom camera setup. ### Config Overrides to Customize Scene Content :bulb: If you only care about few specific assets, or want to export Infinigen assets to another project, instead see [Generating individual assets](GeneratingIndividualAssets.md). -You can achieve a great deal of customization by browsing and editing `infinigen_examples/configs/base.gin` - e.g. modifying cameras, lighting, asset placement, etc. +You can achieve a great deal of customization by browsing and editing `infinigen_examples/configs_nature/base.gin` - e.g. modifying cameras, lighting, asset placement, etc. - `base.gin` only provides the default values of these configs, and may be overridden by scene_type configs. To apply a setting globally across all scene types, you should put them in a new config placed at the end of your `--configs` argument (so that it's overrides are applied last), or use commandline overrides. However, many options exist which are not present in base.gin. At present, you must browse `infinigen_examples/generate_nature.py` to find the part of the code you wish to customize, and look through the relevant code for what more advanced @gin.configurable functions are available. You can also add @gin.configurable to most functions to allow additional configuration. More documentation on available parameters is coming soon. -For most steps of `infinigen_examples/generate_nature.py`'s `compose_scene` function, we use our `RandomStageExecutor` wrapper to decide whether the stage is run, and handle other bookkeeping. This means that if you want to decide the probability with which some asset is included in a scene, you can use the gin override `compose_scene.trees_chance=1.0` or something similar depending on the name string provided as the first argument of the relevant run_stage calls in this way, e.g. `compose_scene.rain_particles_chance=0.9`to make most scenes rainy, or `compose_scene.flowers_chance=0.1` to make flowers rarer. +For most steps of `infinigen_examples/generate_nature.py`'s `compose_nature` function, we use our `RandomStageExecutor` wrapper to decide whether the stage is run, and handle other bookkeeping. This means that if you want to decide the probability with which some asset is included in a scene, you can use the gin override `compose_nature.trees_chance=1.0` or something similar depending on the name string provided as the first argument of the relevant run_stage calls in this way, e.g. `compose_nature.rain_particles_chance=0.9`to make most scenes rainy, or `compose_nature.flowers_chance=0.1` to make flowers rarer. -A common request is to just turn off things you don't want to see, which can be achieved by adding `compose_scene.trees_chance=0.0` or similar to your `-p` argument or a loaded config file. To conveniently turn off lots of things at the same time, we provide configs in `infinigen_examples/configs/disable_assets` to disable things like all creatures, or all particles. +A common request is to just turn off things you don't want to see, which can be achieved by adding `compose_nature.trees_chance=0.0` or similar to your `-p` argument or a loaded config file. To conveniently turn off lots of things at the same time, we provide configs in `infinigen_examples/configs_nature/disable_assets` to disable things like all creatures, or all particles. -You will also encounter configs using what we term a "registry pattern", e.g. `infinigen_examples/configs/base_surface_registry.gin`'s `ground_collection`. "Registries", in this project, are a list of discrete generators, with weights indicating how relatively likely they are to be chosen each time the registry is sampled. +You will also encounter configs using what we term a "registry pattern", e.g. `infinigen_examples/configs_nature/base_surface_registry.gin`'s `ground_collection`. "Registries", in this project, are a list of discrete generators, with weights indicating how relatively likely they are to be chosen each time the registry is sampled. - For example, in `base_surface_registry.gin`, `surface.registry.beach` specifies `("sand", 10)` to indicate that sand has high weight to be chosen to be assigned for the beach category. - Weights are normalized by their overall sum to obtain a probability distribution. - Name strings undergo lookup in the relevant source code folders, e.g. the name "sand" in a surface registry maps to `infinigen/assets/materials/sand.py`. @@ -131,27 +131,27 @@ You will also encounter configs using what we term a "registry pattern", e.g. `i The quantity, diversity and detail of assets in a scene drastically affects RAM/VRAM requirements and runtime. This section will highlight configurable parameters that may help tune Infinigen to run better on limited hardware, or that could be increased to create larger more detailed scenes. Infinigen can generate meshes at a wide range of resolutions. Many gin-configurable options exist to customize how detailed our meshes will be.
These options, as well as the choice of scene type configs, are the most effective ways to decrease compute costs.
-- All mesh resolutions are defined in terms of pixels-per-face. Increasing/decreasing the `compose_scene.generate_resolution` will have corresponding effects on geometry detail. If you wish to render the same mesh at a different resolution, override `render_image.render_resolution_override` +- All mesh resolutions are defined in terms of pixels-per-face. Increasing/decreasing the `compose_nature.generate_resolution` will have corresponding effects on geometry detail. If you wish to render the same mesh at a different resolution, override `render_image.render_resolution_override` - `OpaqueSphericalMesher.pixels_per_cube` and `TransparentSphericalMesher.pixels_per_cube`. You can increase them from their default, 2 pixels per marching cube, to 4 (as seen in `dev.gin`) or higher, in order to reduce terrain resolution and cost. Low resolution terrain will cause noticeable artifacts in videos, you must use `high_quality_terrain.gin` if rendering videos with moving cameras. - `target_face_size.global_multiplier` controls the resolution of all other assets. Increase it to 4 or higher to reduce the compute cost of non-terrain assets like plants and creatures. - All of these options have diminishing returns - a minimal amount of geometry or data is always generated to help define the shape of each asset, which may be larger than the final geometry if you set resolution very low. Infinigen curbs memory costs by only populating assets up to a certain distance away from the camera (except for terrain, which is essentially unbounded). `base.gin` contains many options to customize how far away, and thus how many, assets will be placed: - `placement.populate_all.dist_cull` controls the maximum distance to populate placeholders (for assets such as trees, cacti, creatures, etc). Reducing this will curb the number of assets and especially the number of trees, which can be quite expensive. - - `compose_scene.inview_distance`control the maximum distance to scatter plants/rocks/medium-sized-objects. Reducing this will reduce the number of poses for these assets, but will not reduce the number of unique underlying meshes, so may have diminishing returns. - - Similarly to the above, `compose_scene.near_distance` controls the maximum distance to scatter tiny particles like pine needles. + - `compose_nature.inview_distance`control the maximum distance to scatter plants/rocks/medium-sized-objects. Reducing this will reduce the number of poses for these assets, but will not reduce the number of unique underlying meshes, so may have diminishing returns. + - Similarly to the above, `compose_nature.near_distance` controls the maximum distance to scatter tiny particles like pine needles. - Infinigen does not populate assets which are far outside the camera frustrum. You may attempt to reduce camera FOV to minimize how many assets are in view, but be warned there will be minimal or significantly diminishing returns on performance, due to the need to keep out-of-view assets loaded to retain accurate lighting/shadows. -We also provide `infinigen_examples/configs/performance/dev.gin`, a config which sets many of the above performance parameters to achieve lower scenes. We often use this config to obtain previews for development purposes, but it may also be suitable for generating lower resolution images/scenes for some tasks. +We also provide `infinigen_examples/configs_nature/performance/dev.gin`, a config which sets many of the above performance parameters to achieve lower scenes. We often use this config to obtain previews for development purposes, but it may also be suitable for generating lower resolution images/scenes for some tasks. Our current system determines asset mesh resolution based on the _closest distance_ it comes to the camera during an entire trajectory. Therefore, longer videos are more expensive, as more assets will be closer to the camera at some point in the trajectory. Options exist to re-generate assets at new resolutions over the course of a video to curb these costs - please make a Github Issue for advice. If you find yourself bottlenecked by GPU time, you should consider the following options: - Render single images or stereo images, rather than video, such that less render jobs are required for each CPU-bound `coarse`/`populate` job - - Reduce `base.gin`'s `full/render_image.num_samples = 8192` or `compose_scene.generate_resolution = (1920, 1080)`. This proportionally reduces rendering FLOPS, with some diminishing returns due to BVH setup time. + - Reduce `base.gin`'s `full/render_image.num_samples = 8192` or `compose_nature.generate_resolution = (1920, 1080)`. This proportionally reduces rendering FLOPS, with some diminishing returns due to BVH setup time. - If your GPU(s) are _underutilized_, try the reverse of these tips. -Some scene type configs are also generally more expensive than others. `forest.gin` and `coral.gin` are very expensive due to dense detailed fauna, wheras `artic` and `snowy_mountain` are very cheap. Low-resource compute settings (<64GB) of RAM may only be able to handle a subset of our `infinigen_examples/configs/scene_type/` options, and you may wish to tune the ratios of scene_types by editing `datagen/configs/base.gin` or otherwise overriding `sample_scene_spec.config_distribution`. +Some scene type configs are also generally more expensive than others. `forest.gin` and `coral.gin` are very expensive due to dense detailed fauna, wheras `artic` and `snowy_mountain` are very cheap. Low-resource compute settings (<64GB) of RAM may only be able to handle a subset of our `infinigen_examples/configs_nature/scene_type/` options, and you may wish to tune the ratios of scene_types by editing `datagen/configs/base.gin` or otherwise overriding `sample_scene_spec.config_distribution`. ### Other `manage_jobs.py` commandline options @@ -200,7 +200,7 @@ These commands are intended as inspiration - please read docs above for more adv python -m infinigen.datagen.manage_jobs --output_folder outputs/my_videos --num_scenes 500 \ --pipeline_config slurm monocular cuda_terrain opengl_gt \ --cleanup big_files --warmup_sec 30000 \ - --overrides compose_scene.rain_particles_chance=1.0 + --overrides compose_nature.rain_particles_chance=1.0 ``` :bulb: You can substitute the `rain_particles` in `rain_particles_chance` for any `run_stage` name argument string in `infinigen_examples/generate_nature.py`, such as `trees` or `ground_creatures`. @@ -211,7 +211,7 @@ python -m infinigen.datagen.manage_jobs --output_folder outputs/my_videos --num_ --pipeline_config slurm monocular cuda_terrain opengl_gt \ --cleanup big_files --warmup_sec 30000 --config no_assets ``` -:bulb: You can substitute "no_assets" for `no_creatures` or `no_particles`, or the name of any file under `infinigen_examples/configs`. The command shown uses `infinigen_examples/configs/disable_assets/no_assets.gin`. +:bulb: You can substitute "no_assets" for `no_creatures` or `no_particles`, or the name of any file under `infinigen_examples/configs`. The command shown uses `infinigen_examples/configs_nature/disable_assets/no_assets.gin`. Create videos at birds-eye-view camera altitudes: @@ -222,7 +222,7 @@ python -m infinigen.datagen.manage_jobs --output_folder outputs/my_videos --num_ --overrides camera.camera_pose_proposal.altitude=["uniform", 20, 30] ``` -:bulb: The command shown is overriding `infinigen_examples/configs/base.gin`'s default setting of `camera.camera_pose_proposal.altitude`. You can use a similar syntax to override any number of .gin config entries. Separate multiple entries with spaces. +:bulb: The command shown is overriding `infinigen_examples/configs_nature/base.gin`'s default setting of `camera.camera_pose_proposal.altitude`. You can use a similar syntax to override any number of .gin config entries. Separate multiple entries with spaces. Create 1 second video clips: ``` diff --git a/docs/ExportingToExternalFileFormats.md b/docs/ExportingToExternalFileFormats.md index 9fdb128d2..1268e3b07 100644 --- a/docs/ExportingToExternalFileFormats.md +++ b/docs/ExportingToExternalFileFormats.md @@ -27,6 +27,7 @@ If you want a different output format, please use the "--help" flag or use one o - `-v` enables per-vertex colors (only compatible with .fbx and .ply formats). - `-r {INT}` controls the resolution of the baked texture maps. For instance, `-r 1024` will export 1024 x 1024 texture maps. - `--individual` will export each object in a scene in its own individual file. +- `--omniverse` will prepare the scene for import to IsaacSim or other NVIDIA Omniverse programs. See more in [Exporting to Simulators](./ExportingToSimulators.md). ## :warning: Exporting full Infinigen scenes is only supported for USDC files. @@ -54,21 +55,15 @@ If you require OBJ/FBX/PLY files for your research, you have a few options: ## Other Known Issues and Limitations -* Some material features used in Infinigen are not yet supported by this exporter. Specifically, this script only handles Albedo, Roughness and Metallicity maps. Any other procedural parameters of the material will be ignored. Many file formats also have limited support for spatially varying transmission, clearcoat, and sheen. Generally, you should not expect any materials (but especially skin, translucent leaves, glowing lava) to be perfectly reproduced outside of Blender. - +* Some material features used in Infinigen are not yet supported by this exporter. Specifically, this script only handles Albedo, Roughness, Normal, and Metallicity maps. Any other procedural parameters of the material will be ignored. Many file formats also have limited support for spatially varying transmission, clearcoat, and sheen. Generally, you should not expect any materials (but especially skin, translucent leaves, glowing lava) to be perfectly reproduced outside of Blender. * Exporting *animated* 3D files is generally untested and not officially supported. This includes exporting particles, articulated creatures, deforming plants, etc. These features are *in principle* supported by OpenUSD, but are untested by us and not officially supported by this export script. - * Assets with transparent materials (water, glass-like materials, etc.) may have incorrect textures for all material parameters after export. - * Large scenes and assets may take a long time to export and will crash Blender if you do not have enough RAM. The export results may also be unusably large. -* When exporting in .fbx format, the embedded roughness texture maps in the file may sometimes be too bright or too dark. The .png roughness map in the folder is accurate, however. - - * .fbx exports occasionally fail due to invalid UV values on complicated geometry. Adjusting the 'island_margin' value in bpy.ops.uv.smart_project() sometimes remedies this diff --git a/docs/ExportingToSimulators.md b/docs/ExportingToSimulators.md new file mode 100644 index 000000000..fe8fdadf0 --- /dev/null +++ b/docs/ExportingToSimulators.md @@ -0,0 +1,33 @@ +## Exporting an Indoors Scene for Robotics Simulation in NVIDIA IsaacSim + +This documentation details how to run a robotics simulation in an exported Infinigen scene using NVIDIA IsaacSim. For more information on scene generation or export, refer to [Hello Room](HelloRoom.md) or [Exporting to External File Formats](./ExportingToExternalFileFormats.md). + +:warning: Exported scenes can be imported to any simulator that supports .usd files. However, we have only extensively tested simulator import on **Indoor** scenes using **IsaacSim**, so quality is not guaranteed for Infinigen Nature scenes and/or other simulators. + +First, create and export a scene with the commands below: + +```bash +python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g overhead_singleroom.gin -p compose_indoors.terrain_enabled=False compose_indoors.solve_max_rooms=1 +``` + +```bash +python -m infinigen.tools.export --input_folder outputs/indoors/coarse --output_folder outputs/my_export -f usdc -r 1024 --omniverse +``` + +Download IsaacSim from [NVIDIA Omniverse](https://developer.nvidia.com/isaac/sim) and set up an IsaacSim conda environment by running the following commands in your IsaacSim Directory (typically ` ~/.local/share/ov/pkg/isaac_sim-2023.1.1`) + +```bash +conda env create -f environment.yml +conda activate isaac-sim +source setup_conda_env.sh +``` + +Import scene and run a simulation + +```bash +python {PATH_TO/isaac_sim.py} --scene-path outputs/my_export/export_scene.blend/export_scene.usdc --json-path outputs/my_export/export_scene.blend/solve_state.json +``` + +:warning: Physical properties are applied based on object relations specified in `solve_state.json`. Scenes can be imported without a `solve_state.json`, but all objects will be static colliders. + + diff --git a/docs/HelloRoom.md b/docs/HelloRoom.md new file mode 100644 index 000000000..bbace7ba0 --- /dev/null +++ b/docs/HelloRoom.md @@ -0,0 +1,126 @@ +# Hello Room: Generate your first Infinigen-Indoors scene + +

+ + + + +

+ +## Generate a scene step-by-step + +Infinigen has distinct scene generation & rendering stages. We typically run these automatically for you (skip to [Generate scenes automatically](#generating-scenes-automatically) + +#### Generate a blender file + +First, run ONE command of your choosing from the block below. This will generate a 3D blender file for use in the subsequent steps. + +NOTE: `fast_solve.gin` runs the system for fewer solving steps, which sacrifices quality for speed. Remove t +his to get a more complex & realistic arrangement. You can also remove `compose_indoors.terrain_enabled=False` to add a realistic terrain background (provided you [installed terrain](./Installation.md)) + +```bash +# Diningroom, single room only, first person view (~8min CPU runtime) +python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve.gin singleroom.gin -p compose_indoors.terrain_enabled=False restrict_solving.restrict_parent_rooms=\[\"DiningRoom\"\] + +# Bathroom, single room only, first person view (~13min CPU runtime) +python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve.gin singleroom.gin -p compose_indoors.terrain_enabled=False restrict_solving.restrict_parent_rooms=\[\"Bathroom\"\] + +# Bedroom, single room only, first person view (~10min CPU runtime) +python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve.gin singleroom.gin -p compose_indoors.terrain_enabled=False restrict_solving.restrict_parent_rooms=\[\"Bedroom\"\] + +# Kitchen, single room only, first person view (~10min runtime, CPU only) +python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve.gin singleroom.gin -p compose_indoors.terrain_enabled=False restrict_solving.restrict_parent_rooms=\[\"Kitchen\"\] + +# LivingRoom, single room only, first person view (~11min runtime, CPU only) +python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve.gin singleroom.gin -p compose_indoors.terrain_enabled=False restrict_solving.restrict_parent_rooms=\[\"LivingRoom\"\] + +# Floor layout, overhead view, no objects (~34 second runtime, CPU only): +python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g no_objects.gin overhead.gin -p compose_indoors.terrain_enabled=False + +# Single random room with objects, overhead view (~11min. runtime CPU only): +python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve.gin overhead.gin singleroom.gin -p compose_indoors.terrain_enabled=False compose_indoors.overhead_cam_enabled=True compose_indoors.solve_max_rooms=1 compose_indoors.invisible_room_ceilings_enabled=True compose_indoors.restrict_single_supported_roomtype=True + +# Whole apartment with objects, overhead view: +python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve.gin overhead.gin studio.gin -p compose_indoors.terrain_enabled=False +``` + +Once complete, you can inspect / fly around `outputs/indoors/coarse/scene.blend` in the blender UI: + +```bash +python -m infinigen.launch_blender outputs/indoors/coarse/scene.blend +``` + +You may be prompted to revisit our [Installation.md](./Installation.md#installing-infinigen-as-a-blender-python-script) if blender is not yet installed. + +#### Render image and ground truth + +Next, run the commands below to render an RGB image and ground truth: + +```bash +# Render RGB images +python -m infinigen_examples.generate_nature --seed 0 --task render --input_folder outputs/indoors/coarse --output_folder outputs/indoors/frames + +# Use blender to extract ground-truth (optional) +python -m infinigen_examples.generate_nature --seed 0 --task render --input_folder outputs/indoors/coarse --output_folder outputs/indoors/frames -p render.render_image_func=@flat/render_image +``` + +Once complete, you can open `outputs/indoors/frames` and navigate to view the results. + +#### Next Steps + +See [ExportingToExternalFileFormats](./ExportingToExternalFileFormats.md) and [ExportingToSimulators](./ExportingToSimulators.md) to export to OBJ/USD. + +We also provide an OpenGL-based ground truth extractor which offers additional ground truth channels, read more about using our ground truth [here](GroundTruthAnnotations.md). + +## Generating scenes automatically + +To generate a single scene in one command, you can run the following: +```bash +screen python -m infinigen.datagen.manage_jobs --output_folder outputs/my_dataset --num_scenes 1000 --pipeline_configs local_256.gin monocular.gin blender_gt.gin indoor_background_configs.gin --configs singleroom.gin --pipeline_overrides get_cmd.driver_script='infinigen_examples.generate_indoors' manage_datagen_jobs.num_concurrent=16 --overrides compose_indoors.restrict_single_supported_roomtype=True +``` + +To create a large dataset of many random rooms, we recommend: +```bash +screen python -m infinigen.datagen.manage_jobs --output_folder outputs/my_dataset --num_scenes 1000 --pipeline_configs local_256.gin monocular.gin blender_gt.gin indoor_background_configs.gin --configs singleroom.gin --pipeline_overrides get_cmd.driver_script='infinigen_examples.generate_indoors' manage_datagen_jobs.num_concurrent=16 --overrides compose_indoors.restrict_single_supported_roomtype=True +``` + +You can inspect `outputs/my_dataset/SEED/` to see the running logs of the subprocesses and output results. + +See [ConfiguringInfinigen.md](./ConfiguringInfinigen.md) for documentation on `manage_jobs` and commandline options. + +## Developer Guide + +More documentation coming soon. + +### Restricting the solver to certain rooms / objects + +Configuring `compose_indoors()` and ``restrict_solving()` via gin allows you to only solve sub-parts of the default constraint problem: + +``` +python -m infinigen_examples.generate_indoors --seed 0 --task coarse --output_folder outputs/indoors/coarse -g fast_solve -p \ + compose_indoors.terrain_enabled=False compose_indoors.solve_medium_enabled=False \ + restrict_solving.restrict_parent_rooms=[\"Kitchen\"] \ + restrict_solving.restrict_child_primary=[\"KitchenCounter\"] \ + restrict_solving.restrict_child_secondary=[\"Sink\"] \ + restrict_solving.solve_max_rooms=1 \ + restrict_solving.consgraph_filters=[\"counter\",\"sink\"] \ + compose_indoors.solve_steps_large=30 compose_indoors.solve_steps_small=30 +``` + +Each of these commandline args demonstrates a different way in which you can restrict what work the system does: +- `compose_indoors.terrain_enabled=False compose_indoors.solve_medium_enabled=False` turns off medium objects (paintings/chairs/ceilinglights) and also terrain. You can use this same pattern to disable any `p.run_stage(name, func, ...)` statement found in generate_indoors by specifying f`compose_indoors.{name}_enabled=False` +- `restrict_solving.solve_max_rooms=1` specifies to only solve objects in 1 room of the house +- `restrict_solving.restrict_parent_rooms=[\"Kitchen\"]` specifies to only solve objects in kitchen rooms. You can see `infinigen/core/tags.py` for available options. +- `restrict_solving.restrict_child_primary=[\"KitchenCounter\"]` specifies that when placing objects directly onto the room, we will only consider placing *KitchenCounter* objects, not other types of objects. You can see `infinigen/core/tags.py` for available options. +- `restrict_solving.restrict_child_secondary=[\"Sink\"]` specifies that when placing objects onto other objects, we will only consider placing *Sink* objects, not other types of objects. You can see `infinigen/core/tags.py` for available options. +- `restrict_solving.consgraph_filters=[\"counter\",\"sink\"]` says to throw out any `constraints` or `score_terms` keys from `home_constraints()` that do not contain `counter` or `sink` as substrings, producing a simpler constraint graph. +- `compose_indoors.solve_steps_large=30 compose_indoors.solve_steps_small=30` says to spend fewer optimization steps on large/small objects. You can also do the same for medium. These values override the defaults provided in `fast_solve.gin` and `infinigen_examples/configs_indoor/base.gin` + +These settings are intended for debugging or for generating tailored datasets. If you want more granular control over what assets are used for what purposes, please customize `infinigen_examples/indoor_asset_semantics.py` which defines this mapping. + +If you are using the commands from [Creating large datasets](#creating-large-datasets) you will instead add these configs as `--overrides` to the end of your command, rather than `-p` + +## Run unit tests +``` +pytest tests/ --disable-warnings +``` \ No newline at end of file diff --git a/docs/Installation.md b/docs/Installation.md index dae6a8a57..d5152facd 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -39,13 +39,13 @@ Please install anaconda or miniconda. Platform-specific instructions can be foun Then, install the following dependencies using the method of your choice. Examples are shown for Ubuntu, Mac ARM and Mac x86. ```bash # on Ubuntu / Debian / WSL / etc -sudo apt-get install wget cmake g++ libgles2-mesa-dev libglew-dev libglfw3-dev libglm-dev +sudo apt-get install wget cmake g++ libgles2-mesa-dev libglew-dev libglfw3-dev libglm-dev zlib1g-dev # on an Mac ARM (M1/M2/...) -arch -arm64 brew install wget cmake llvm open-mpi libomp glm glew +arch -arm64 brew install wget cmake llvm open-mpi libomp glm glew zlib # on Mac x86_64 (Intel) -brew install wget cmake llvm open-mpi libomp glm glew +brew install wget cmake llvm open-mpi libomp glm glew zlib # on Conda. Useful when you don't have sudo permissions conda install conda-forge::gxx=11.4.0 mesalib glew glm menpo::glfw3 @@ -57,9 +57,7 @@ export LD_LIBRARY_PATH=$CONDA_PREFIX/lib:$LD_LIBRARY_PATH ### Installation -First, download infinigen and set up your environment. - -On Linux / Mac / WSL: +First, download the repo and set up a conda environment (you may need to [install conda](https://conda.io/projects/conda/en/latest/user-guide/install/index.html)) ```bash git clone https://github.com/princeton-vl/infinigen.git cd infinigen @@ -69,18 +67,15 @@ conda activate infinigen Then, install the infinigen package using one of the options below: -:warning: Mac-ARM (M1/M2) users should prefix their installation command with `arch -arm64` - ```bash -# Default install (includes CPU Terrain, and CUDA Terrain if available) -pip install -e . - -# Minimal install (objects/materials only, no terrain or optional features) +# Minimal install (No terrain or opengl GT, ok for Infinigen-Indoors or single-object generation) INFINIGEN_MINIMAL_INSTALL=True pip install -e . -# Enable OpenGL GT -INFINIGEN_INSTALL_CUSTOMGT=True pip install -e . +# Full install (Terrain & OpenGL-GT enabled, needed for Infinigen-Nature HelloWorld) +pip install -e . +# Developer install (includes pytest, ruff, other recommended dev tools) +pip install -e ".[dev]" ``` :exclamation: If you encounter any issues with the above, please add `-vv > logs.txt 2>&1` to the end of your command and run again, then provide the resulting logs.txt file as an attachment when making a Github Issue. diff --git a/docs/images/hello_room/dining.png b/docs/images/hello_room/dining.png new file mode 100644 index 000000000..b0478f7cd Binary files /dev/null and b/docs/images/hello_room/dining.png differ diff --git a/docs/images/hello_room/dining_blender.png b/docs/images/hello_room/dining_blender.png new file mode 100644 index 000000000..846d6e054 Binary files /dev/null and b/docs/images/hello_room/dining_blender.png differ diff --git a/docs/images/hello_room/dining_depth.png b/docs/images/hello_room/dining_depth.png new file mode 100644 index 000000000..f43784fc3 Binary files /dev/null and b/docs/images/hello_room/dining_depth.png differ diff --git a/docs/images/hello_room/dining_obj.png b/docs/images/hello_room/dining_obj.png new file mode 100644 index 000000000..cc3f1d060 Binary files /dev/null and b/docs/images/hello_room/dining_obj.png differ diff --git a/docs/images/infinigen.png b/docs/images/infinigen.png new file mode 100644 index 000000000..3f0dcd938 Binary files /dev/null and b/docs/images/infinigen.png differ diff --git a/infinigen/OcMesher b/infinigen/OcMesher index 2cdcbacbe..4e5fad7b0 160000 --- a/infinigen/OcMesher +++ b/infinigen/OcMesher @@ -1 +1 @@ -Subproject commit 2cdcbacbe62ef79dc6031e0131f916266b7372e3 +Subproject commit 4e5fad7b0dd495444acf3ab2037bf08dd4b5d676 diff --git a/infinigen/__init__.py b/infinigen/__init__.py index ab5061d11..da6aaa4ca 100644 --- a/infinigen/__init__.py +++ b/infinigen/__init__.py @@ -1,3 +1,3 @@ import logging -__version__ = "1.3.4" +__version__ = "1.4.0" diff --git a/infinigen/assets/appliances/__init__.py b/infinigen/assets/appliances/__init__.py new file mode 100644 index 000000000..795254199 --- /dev/null +++ b/infinigen/assets/appliances/__init__.py @@ -0,0 +1,5 @@ +from .oven import OvenFactory +from .beverage_fridge import BeverageFridgeFactory +from .dishwasher import DishwasherFactory +from .microwave import MicrowaveFactory +from .tv import TVFactory, MonitorFactory diff --git a/infinigen/assets/appliances/beverage_fridge.py b/infinigen/assets/appliances/beverage_fridge.py new file mode 100644 index 000000000..9ab8b5278 --- /dev/null +++ b/infinigen/assets/appliances/beverage_fridge.py @@ -0,0 +1,735 @@ +# 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: Hongyu Wen + +import bpy +import random +import mathutils +import numpy as np +from numpy.random import uniform as U, normal as N, randint as RI + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core.util.blender import delete +from infinigen.core.util.bevelling import get_bevel_edges, add_bevel, complete_bevel, complete_no_bevel +from infinigen.core import surface +from infinigen.core.util import blender as butil + +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory +from infinigen.assets.material_assignments import AssetList + +class BeverageFridgeFactory(AssetFactory): + + def __init__(self, factory_seed, coarse=False, dimensions=[1., 1., 1.]): + super(BeverageFridgeFactory, self).__init__(factory_seed, coarse=coarse) + + self.dimensions = dimensions + with FixedSeed(factory_seed): + self.params = self.sample_parameters(dimensions) + self.material_params, self.scratch, self.edge_wear = self.get_material_params() + self.params.update(self.material_params) + + def get_material_params(self): + material_assignments = AssetList['BeverageFridgeFactory']() + params = { + "Surface": material_assignments['surface'].assign_material(), + "Front": material_assignments['front'].assign_material(), + "Handle": material_assignments['handle'].assign_material(), + "Back": material_assignments['back'].assign_material(), + } + wrapped_params = { + k: surface.shaderfunc_to_material(v) for k, v in params.items() + } + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = np.random.uniform() < scratch_prob + is_edge_wear = np.random.uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + @staticmethod + def sample_parameters(dimensions): + depth = 1 + N(0, 0.1) + width = 1 + N(0, 0.1) + height = 1 + N(0, 0.1) + # depth, width, height = dimensions + door_thickness = U(0.05, 0.1) * depth + door_rotation = 0 # Set to 0 for now + + rack_radius = U(0.01, 0.02) * depth + rack_h_amount = RI(2, 4) + rack_d_amount = RI(4, 6) + brand_name = "BrandName" + + params = { + "Depth": depth, + "Width": width, + "Height": height, + "DoorThickness": door_thickness, + "DoorRotation": door_rotation, + "RackRadius": rack_radius, + "RackHAmount": rack_h_amount, + "RackDAmount": rack_d_amount, + "BrandName": brand_name, + } + return params + + def create_asset(self, **params): + obj = butil.spawn_cube() + butil.modify_mesh(obj, 'NODES', node_group=nodegroup_beverage_fridge_geometry(preprocess=True), ng_inputs=self.params, apply=True) + bevel_edges = get_bevel_edges(obj) + delete(obj) + obj = butil.spawn_cube() + butil.modify_mesh(obj, 'NODES', node_group=nodegroup_beverage_fridge_geometry(), ng_inputs=self.params, apply=True) + obj = add_bevel(obj, bevel_edges, offset=0.01) + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + +@node_utils.to_nodegroup('nodegroup_oven_rack', singleton=False, type='GeometryNodeTree') +def nodegroup_oven_rack(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'Width', 2.0000), + ('NodeSocketFloatDistance', 'Height', 2.0000), + ('NodeSocketFloatDistance', 'Radius', 0.0200), + ('NodeSocketInt', 'Amount', 5)]) + + quadrilateral = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', + input_kwargs={'Width': group_input.outputs["Width"], 'Height': group_input.outputs["Height"]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"]}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_1}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz_3, 'End': combine_xyz_4}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': curve_line}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance, 'Amount': group_input.outputs["Amount"]}, + attrs={'domain': 'INSTANCE'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, attrs={'operation': 'MULTIPLY'}) + + divide = nw.new_node(Nodes.Math, + input_kwargs={0: multiply_2, 1: group_input.outputs["Amount"]}, + attrs={'operation': 'DIVIDE'}) + + multiply_3 = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: divide}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': duplicate_elements.outputs["Geometry"], 'Offset': combine_xyz}) + + duplicate_elements_1 = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance, 'Amount': group_input.outputs["Amount"]}, + attrs={'domain': 'INSTANCE'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + divide_1 = nw.new_node(Nodes.Math, + input_kwargs={0: multiply_4, 1: group_input.outputs["Amount"]}, + attrs={'operation': 'DIVIDE'}) + + multiply_5 = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements_1.outputs["Duplicate Index"], 1: divide_1}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_5}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': duplicate_elements_1.outputs["Geometry"], 'Offset': combine_xyz_1}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [quadrilateral, set_position, set_position_1]}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': group_input.outputs["Radius"]}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': join_geometry, 'Profile Curve': curve_circle.outputs["Curve"], 'Fill Caps': True}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': curve_to_mesh}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_text', singleton=False, type='GeometryNodeTree') +def nodegroup_text(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVectorTranslation', 'Translation', (1.5000, 0.0000, 0.0000)), + ('NodeSocketString', 'String', 'BrandName'), + ('NodeSocketFloatDistance', 'Size', 0.0500), + ('NodeSocketFloat', 'Offset Scale', 0.0020)]) + + string_to_curves = nw.new_node('GeometryNodeStringToCurves', + input_kwargs={'String': group_input.outputs["String"], 'Size': group_input.outputs["Size"]}, + attrs={'align_y': 'BOTTOM_BASELINE', 'align_x': 'CENTER'}) + + fill_curve = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': string_to_curves.outputs["Curve Instances"]}) + + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, + input_kwargs={'Mesh': fill_curve, 'Offset Scale': group_input.outputs["Offset Scale"]}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': extrude_mesh.outputs["Mesh"], 'Translation': group_input.outputs["Translation"], 'Rotation': (1.5708, 0.0000, 1.5708)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_1}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_handle', singleton=False, type='GeometryNodeTree') +def nodegroup_handle(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'width', 0.0000), + ('NodeSocketFloat', 'length', 0.0000), + ('NodeSocketFloat', 'thickness', 0.0200)]) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': group_input.outputs["width"]}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', 3: cube.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': group_input.outputs["width"]}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_1.outputs["Mesh"], 'Name': 'uv_map', 3: cube_1.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': group_input.outputs["length"]}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_1, 'Translation': combine_xyz}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [store_named_attribute, transform]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"]}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry_1, 'Translation': combine_xyz_3}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["length"], 1: group_input.outputs["width"]}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["width"], 'Y': add, 'Z': group_input.outputs["thickness"]}) + + cube_2 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_1}) + + store_named_attribute_2 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_2.outputs["Mesh"], 'Name': 'uv_map', 3: cube_2.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["length"]}, attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"]}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: multiply_2}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_1, 'Z': add_1}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_2, 'Translation': combine_xyz_2}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_2, transform_1]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_center', singleton=False, type='GeometryNodeTree') +def nodegroup_center(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Geometry', None), + ('NodeSocketVector', 'Vector', (0.0000, 0.0000, 0.0000)), + ('NodeSocketFloat', 'MarginX', 0.5000), + ('NodeSocketFloat', 'MarginY', 0.0000), + ('NodeSocketFloat', 'MarginZ', 0.0000)]) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': group_input.outputs["Geometry"]}) + + subtract = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Vector"], 1: bounding_box.outputs["Min"]}, + attrs={'operation': 'SUBTRACT'}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': subtract.outputs["Vector"]}) + + greater_than = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["X"], 1: group_input.outputs["MarginX"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + subtract_1 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: bounding_box.outputs["Max"], 1: group_input.outputs["Vector"]}, + attrs={'operation': 'SUBTRACT'}) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': subtract_1.outputs["Vector"]}) + + greater_than_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["X"], 1: group_input.outputs["MarginX"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than, 1: greater_than_1}) + + greater_than_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["Y"], 1: group_input.outputs["MarginY"]}, + attrs={'operation': 'GREATER_THAN'}) + + greater_than_3 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["Y"], 1: group_input.outputs["MarginY"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and_1 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than_2, 1: greater_than_3}) + + op_and_2 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and, 1: op_and_1}) + + greater_than_4 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["Z"], 1: group_input.outputs["MarginZ"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + greater_than_5 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["Z"], 1: group_input.outputs["MarginZ"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and_3 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than_4, 1: greater_than_5}) + + op_and_4 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and_2, 1: op_and_3}) + + op_not = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and_4}, attrs={'operation': 'NOT'}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'In': op_and_4, 'Out': op_not}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_cube', singleton=False, type='GeometryNodeTree') +def nodegroup_cube(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVectorTranslation', 'Size', (0.1000, 10.0000, 4.0000)), + ('NodeSocketVector', 'Pos', (0.0000, 0.0000, 0.0000)), + ('NodeSocketInt', 'Resolution', 2)]) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': group_input.outputs["Size"], 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', 3: cube.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': store_named_attribute_1, 'Name': 'uv_map'}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + multiply_add = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Size"], 1: (0.5000, 0.5000, 0.5000), 2: group_input.outputs["Pos"]}, + attrs={'operation': 'MULTIPLY_ADD'}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute, 'Translation': multiply_add.outputs["Vector"]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_hollow_cube', singleton=False, type='GeometryNodeTree') +def nodegroup_hollow_cube(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVectorTranslation', 'Size', (0.1000, 10.0000, 4.0000)), + ('NodeSocketVector', 'Pos', (0.0000, 0.0000, 0.0000)), + ('NodeSocketInt', 'Resolution', 2), + ('NodeSocketFloat', 'Thickness', 0.0000), + ('NodeSocketBool', 'Switch1', False), + ('NodeSocketBool', 'Switch2', False), + ('NodeSocketBool', 'Switch3', False), + ('NodeSocketBool', 'Switch4', False), + ('NodeSocketBool', 'Switch5', False), + ('NodeSocketBool', 'Switch6', False)]) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Size"]}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Thickness"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["Thickness"], 'Y': subtract, 'Z': subtract_1}) + + cube_2 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_4, 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_2.outputs["Mesh"], 'Name': 'uv_map', 3: cube_2.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Thickness"]}, attrs={'operation': 'MULTIPLY'}) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Pos"]}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: separate_xyz_1.outputs["X"]}) + + scale = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Size"], 'Scale': 0.5000}, + attrs={'operation': 'SCALE'}) + + separate_xyz_2 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': scale.outputs["Vector"]}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + subtract_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Z"], 1: separate_xyz_1.outputs["Z"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': subtract_2}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_1, 'Translation': combine_xyz_5}) + + switch_2 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch3"], 14: transform_2}) + + subtract_3 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz.outputs["X"], 'Y': subtract_3, 'Z': group_input.outputs["Thickness"]}) + + cube_1 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_2, 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}) + + store_named_attribute_4 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_1.outputs["Mesh"], 'Name': 'uv_map', 3: cube_1.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["X"], 1: separate_xyz_1.outputs["X"]}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + subtract_4 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_2, 'Y': add_3, 'Z': subtract_4}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_4, 'Translation': combine_xyz_3}) + + switch_1 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch2"], 14: transform_1}) + + subtract_5 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz.outputs["X"], 'Y': subtract_5, 'Z': group_input.outputs["Thickness"]}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', 3: cube.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["X"], 1: separate_xyz_1.outputs["X"]}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: separate_xyz_1.outputs["Z"]}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_4, 'Y': add_5, 'Z': add_6}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute, 'Translation': combine_xyz_1}) + + switch = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch1"], 14: transform}) + + subtract_6 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + subtract_7 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Thickness"], 'Y': subtract_6, 'Z': subtract_7}) + + cube_3 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_6, 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}) + + store_named_attribute_5 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_3.outputs["Mesh"], 'Name': 'uv_map', 3: cube_3.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + subtract_8 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["X"], 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + add_7 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + subtract_9 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Z"], 1: separate_xyz_1.outputs["Z"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_8, 'Y': add_7, 'Z': subtract_9}) + + transform_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_5, 'Translation': combine_xyz_7}) + + switch_3 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch4"], 14: transform_3}) + + combine_xyz_9 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz.outputs["X"], 'Y': group_input.outputs["Thickness"], 'Z': separate_xyz.outputs["Z"]}) + + cube_4 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_9, 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}) + + store_named_attribute_2 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_4.outputs["Mesh"], 'Name': 'uv_map', 3: cube_4.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_8 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_1.outputs["X"], 1: separate_xyz_2.outputs["X"]}) + + add_9 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_1.outputs["Y"], 1: multiply_1}) + + add_10 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_1.outputs["Z"], 1: separate_xyz_2.outputs["Z"]}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_8, 'Y': add_9, 'Z': add_10}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_2, 'Translation': combine_xyz_8}) + + switch_4 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch5"], 14: transform_4}) + + combine_xyz_10 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz.outputs["X"], 'Y': group_input.outputs["Thickness"], 'Z': separate_xyz.outputs["Z"]}) + + cube_5 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_10, 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}) + + store_named_attribute_3 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_5.outputs["Mesh"], 'Name': 'uv_map', 3: cube_5.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_11 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["X"], 1: separate_xyz_1.outputs["X"]}) + + subtract_10 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + add_12 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["Z"], 1: separate_xyz_1.outputs["Z"]}) + + combine_xyz_11 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_11, 'Y': subtract_10, 'Z': add_12}) + + transform_5 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_3, 'Translation': combine_xyz_11}) + + switch_5 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch6"], 14: transform_5}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [switch_2.outputs[6], switch_1.outputs[6], switch.outputs[6], switch_3.outputs[6], switch_4.outputs[6], switch_5.outputs[6]]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_beverage_fridge_geometry', singleton=False, type='GeometryNodeTree') +def nodegroup_beverage_fridge_geometry(nw: NodeWrangler, preprocess: bool = False): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Depth', 1.0000), + ('NodeSocketFloat', 'Width', 1.0000), + ('NodeSocketFloat', 'Height', 1.0000), + ('NodeSocketFloat', 'DoorThickness', 0.0700), + ('NodeSocketFloat', 'DoorRotation', 0.0000), + ('NodeSocketFloatDistance', 'RackRadius', 0.0100), + ('NodeSocketInt', 'RackDAmount', 5), + ('NodeSocketInt', 'RackHAmount', 2), + ('NodeSocketString', 'BrandName', 'BrandName'), + ('NodeSocketMaterial', 'Surface', None), + ('NodeSocketMaterial', 'Front', None), + ('NodeSocketMaterial', 'Handle', None), + ('NodeSocketMaterial', 'Back', None)]) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Depth"], 'Y': group_input.outputs["Width"], 'Z': group_input.outputs["Height"]}) + + hollowcube = nw.new_node(nodegroup_hollow_cube().name, + input_kwargs={'Size': combine_xyz, 'Thickness': group_input.outputs["DoorThickness"], 'Switch2': True, 'Switch4': True}) + + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': hollowcube, 'Material': group_input.outputs["Surface"]}) + + subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': set_material_1, 'Level': 0}) + + # set_shade_smooth_2 = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': subdivide_mesh}) + + body = nw.new_node(Nodes.Reroute, input_kwargs={'Input': subdivide_mesh}, label='Body') + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["DoorThickness"], 'Y': group_input.outputs["Width"], 'Z': group_input.outputs["Height"]}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["Depth"]}) + + cube = nw.new_node(nodegroup_cube().name, input_kwargs={'Size': combine_xyz_1, 'Pos': combine_xyz_2}) + + position = nw.new_node(Nodes.InputPosition) + + center = nw.new_node(nodegroup_center().name, + input_kwargs={'Geometry': cube, 'Vector': position, 'MarginX': -1.0000, 'MarginY': 0.1000, 'MarginZ': 0.1500}) + + set_material_2 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': cube, 'Selection': center.outputs["In"], 'Material': group_input.outputs["Front"]}) + + set_material_3 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': set_material_2, 'Selection': center.outputs["Out"], 'Material': group_input.outputs["Surface"]}) + + # set_shade_smooth = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': set_material_3}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: 0.0500}, attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: 0.8000}, attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply}, attrs={'operation': 'MULTIPLY'}) + + handle = nw.new_node(nodegroup_handle().name, + input_kwargs={'width': multiply, 'length': multiply_1, 'thickness': multiply_2}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["DoorThickness"]}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: 0.1000}, attrs={'operation': 'MULTIPLY'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: 0.9000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_13 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': multiply_3, 'Z': multiply_4}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': handle, 'Translation': combine_xyz_13, 'Rotation': (0.0000, 1.5708, 0.0000)}) + + set_material_8 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_1, 'Material': group_input.outputs["Handle"]}) + + geometry_to_instance_4 = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': set_material_8}) + + rotate_instances_2 = nw.new_node(Nodes.RotateInstances, + input_kwargs={'Instances': geometry_to_instance_4, 'Rotation': (-1.5708, 0.0000, 0.0000), 'Pivot Point': combine_xyz_13}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["DoorThickness"]}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_12 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_1, 'Y': multiply_5, 'Z': 0.0300}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: 0.0500}, attrs={'operation': 'MULTIPLY'}) + + text = nw.new_node(nodegroup_text().name, + input_kwargs={'Translation': combine_xyz_12, 'String': group_input.outputs["BrandName"], 'Size': multiply_6, 'Offset Scale': 0.0020}) + + text = complete_no_bevel(nw, text, preprocess) + + set_material_9 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': text, 'Material': group_input.outputs["Handle"]}) + + rotate_instances_2 = complete_bevel(nw, rotate_instances_2, preprocess) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material_3, rotate_instances_2, set_material_9]}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': join_geometry_3}) + + z = nw.scalar_multiply(group_input.outputs["DoorRotation"], 1 if not preprocess else 0) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': z}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["Depth"], 'Y': group_input.outputs["Width"]}) + + rotate_instances = nw.new_node(Nodes.RotateInstances, + input_kwargs={'Instances': geometry_to_instance, 'Rotation': combine_xyz_3, 'Pivot Point': combine_xyz_4}) + + door = nw.new_node(Nodes.Reroute, input_kwargs={'Input': nw.new_node(Nodes.RealizeInstances, [rotate_instances])}, label='door') + + multiply_7 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["DoorThickness"], 1: 2.1000}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Depth"], 1: multiply_7}, + attrs={'operation': 'SUBTRACT'}) + + multiply_8 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["DoorThickness"], 1: 2.1000}, + attrs={'operation': 'MULTIPLY'}) + + subtract_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Width"], 1: multiply_8}, + attrs={'operation': 'SUBTRACT'}) + + ovenrack = nw.new_node(nodegroup_oven_rack().name, + input_kwargs={'Width': subtract, 'Height': subtract_1, 'Radius': group_input.outputs["RackRadius"], 'Amount': group_input.outputs["RackDAmount"]}) + + geometry_to_instance_1 = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': ovenrack}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance_1, 'Amount': group_input.outputs["RackHAmount"]}, + attrs={'domain': 'INSTANCE'}) + + multiply_9 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"]}, attrs={'operation': 'MULTIPLY'}) + + multiply_10 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: 1.0000}) + + multiply_11 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["DoorThickness"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + subtract_2 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Height"], 1: multiply_11}, + attrs={'operation': 'SUBTRACT'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["RackHAmount"], 1: 1.0000}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: subtract_2, 1: add_3}, attrs={'operation': 'DIVIDE'}) + + multiply_12 = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: divide}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_9, 'Y': multiply_10, 'Z': multiply_12}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': duplicate_elements.outputs["Geometry"], 'Offset': combine_xyz_5}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': set_position, 'Material': group_input.outputs["Handle"]}) + + racks = nw.new_node(Nodes.Reroute, input_kwargs={'Input': nw.new_node(Nodes.RealizeInstances, [set_material])}, label='racks') + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["DoorThickness"]}) + + reroute_10 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': add_4}) + + reroute_11 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Width"]}) + + reroute_8 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["DoorThickness"]}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': reroute_10, 'Y': reroute_11, 'Z': reroute_8}) + + reroute_9 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Height"]}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': reroute_9}) + + cube_1 = nw.new_node(nodegroup_cube().name, input_kwargs={'Size': combine_xyz_6, 'Pos': combine_xyz_7}) + + set_material_5 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': cube_1, 'Material': group_input.outputs["Back"]}) + + # set_shade_smooth_1 = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': set_material_5}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': set_material_5}) + + heater = nw.new_node(Nodes.Reroute, input_kwargs={'Input': join_geometry_2}, label='heater') + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [body, door, racks, heater]}) + + geometry = nw.new_node(Nodes.RealizeInstances,[join_geometry]) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': geometry}) diff --git a/infinigen/assets/appliances/dishwasher.py b/infinigen/assets/appliances/dishwasher.py new file mode 100644 index 000000000..0b1694a3b --- /dev/null +++ b/infinigen/assets/appliances/dishwasher.py @@ -0,0 +1,929 @@ +# 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: Hongyu Wen + +import bpy +import random +import mathutils +import numpy as np +from numpy.random import uniform as U, normal as N, randint as RI + +from infinigen.assets.materials import metal +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core.util.blender import delete +from infinigen.core.util.bevelling import get_bevel_edges, add_bevel, complete_bevel, complete_no_bevel +from infinigen.core import surface +from infinigen.core.util import blender as butil + +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory +from infinigen.assets.material_assignments import AssetList + +class DishwasherFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=[1., 1., 1.]): + super(DishwasherFactory, self).__init__(factory_seed, coarse=coarse) + + self.dimensions = dimensions + with FixedSeed(factory_seed): + self.params = self.sample_parameters(dimensions) + self.material_params, self.scratch, self.edge_wear = self.get_material_params() + self.params.update(self.material_params) + + def get_material_params(self): + material_assignments = AssetList['DishwasherFactory']() + params = { + "Surface": material_assignments['surface'].assign_material(), + "Front": material_assignments['front'].assign_material(), + "WhiteMetal": material_assignments['white_metal'].assign_material(), + "Top": material_assignments['top'].assign_material(), + 'NameMaterial': material_assignments['name_material'].assign_material(), + } + wrapped_params = { + k: surface.shaderfunc_to_material(v) for k, v in params.items() + } + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = np.random.uniform() < scratch_prob + is_edge_wear = np.random.uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + @staticmethod + def sample_parameters(dimensions): + # depth, width, height = dimensions + depth = 1 + N(0, 0.1) + width = 1 + N(0, 0.1) + height = 1 + N(0, 0.1) + door_thickness = U(0.05, 0.1) * depth + door_rotation = 0 # Set to 0 for now + + rack_radius = U(0.01, 0.02) * depth + rack_h_amount = RI(2, 3) + brand_name = "BrandName" + + params = { + "Depth": depth, + "Width": width, + "Height": height, + "DoorThickness": door_thickness, + "DoorRotation": door_rotation, + "RackRadius": rack_radius, + "RackAmount": rack_h_amount, + "BrandName": brand_name, + } + return params + + def create_asset(self, **params): + obj = butil.spawn_cube() + butil.modify_mesh(obj, 'NODES', node_group=nodegroup_dishwasher_geometry(preprocess=True), ng_inputs=self.params, apply=True) + bevel_edges = get_bevel_edges(obj) + delete(obj) + obj = butil.spawn_cube() + butil.modify_mesh(obj, 'NODES', node_group=nodegroup_dishwasher_geometry(), ng_inputs=self.params, apply=True) + obj = add_bevel(obj, bevel_edges, offset=0.01) + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + +@node_utils.to_nodegroup('nodegroup_dish_rack', singleton=False, type='GeometryNodeTree') +def nodegroup_dish_rack(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + quadrilateral = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral') + + curve_line = nw.new_node(Nodes.CurveLine, + input_kwargs={'Start': (0.0000, -1.0000, 0.0000), 'End': (0.0000, 1.0000, 0.0000)}) + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloatDistance', 'Depth', 2.0000), + ('NodeSocketFloatDistance', 'Width', 2.0000), ('NodeSocketFloatDistance', 'Radius', 0.0200), + ('NodeSocketInt', 'Amount', 5), ('NodeSocketFloat', 'Height', 0.5000)]) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'Y': -1.0000, 'Z': group_input.outputs["Height"]}) + + curve_line_1 = nw.new_node(Nodes.CurveLine, + input_kwargs={'Start': (0.0000, -1.0000, 0.0000), 'End': combine_xyz_4}) + + geometry_to_instance_1 = nw.new_node('GeometryNodeGeometryToInstance', + input_kwargs={'Geometry': curve_line_1}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Amount"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + duplicate_elements_2 = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance_1, 'Amount': multiply}, + attrs={'domain': 'INSTANCE'}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: 1.0000, 1: group_input.outputs["Amount"]}, + attrs={'operation': 'DIVIDE'}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements_2.outputs["Duplicate Index"], 1: divide}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_1}) + + set_position_2 = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': duplicate_elements_2.outputs["Geometry"], + 'Offset': combine_xyz_3 + }) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [curve_line, set_position_2]}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', + input_kwargs={'Geometry': join_geometry_1}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance, 'Amount': multiply}, + attrs={'domain': 'INSTANCE'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={ + 0: duplicate_elements.outputs["Duplicate Index"], + 1: group_input.outputs["Amount"] + }, attrs={'operation': 'SUBTRACT'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: divide}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_2}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': duplicate_elements.outputs["Geometry"], + 'Offset': combine_xyz + }) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': set_position, 'Rotation': (0.0000, 0.0000, 1.5708)}) + + duplicate_elements_1 = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance, 'Amount': multiply}, + attrs={'domain': 'INSTANCE'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={ + 0: duplicate_elements_1.outputs["Duplicate Index"], + 1: group_input.outputs["Amount"] + }, attrs={'operation': 'SUBTRACT'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: divide}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3}) + + set_position_1 = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': duplicate_elements_1.outputs["Geometry"], + 'Offset': combine_xyz_1 + }) + + quadrilateral_1 = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral') + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: 0.8000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_4}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': quadrilateral_1, 'Translation': combine_xyz_5}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={ + 'Geometry': [quadrilateral, transform_1, set_position_1, transform_2] + }) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': group_input.outputs["Radius"]}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': join_geometry, + 'Profile Curve': curve_circle.outputs["Curve"], + 'Fill Caps': True + }) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_5, 'Y': multiply_6, 'Z': 0.5000}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': curve_to_mesh, + 'Rotation': (0.0000, 0.0000, 1.5708), + 'Scale': combine_xyz_2 + }) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': transform}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_text', singleton=False, type='GeometryNodeTree') +def nodegroup_text(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[ + ('NodeSocketVectorTranslation', 'Translation', (1.5000, 0.0000, 0.0000)), + ('NodeSocketString', 'String', 'BrandName'), ('NodeSocketFloatDistance', 'Size', 0.0500), + ('NodeSocketFloat', 'Offset Scale', 0.0020)]) + + string_to_curves = nw.new_node('GeometryNodeStringToCurves', input_kwargs={ + 'String': group_input.outputs["String"], + 'Size': group_input.outputs["Size"] + }, attrs={'align_y': 'BOTTOM_BASELINE', 'align_x': 'CENTER'}) + + fill_curve = nw.new_node(Nodes.FillCurve, + input_kwargs={'Curve': string_to_curves.outputs["Curve Instances"]}) + + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={ + 'Mesh': fill_curve, + 'Offset Scale': group_input.outputs["Offset Scale"] + }) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': extrude_mesh.outputs["Mesh"], + 'Translation': group_input.outputs["Translation"], + 'Rotation': (1.5708, 0.0000, 1.5708) + }) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_handle', singleton=False, type='GeometryNodeTree') +def nodegroup_handle(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'width', 0.0000), + ('NodeSocketFloat', 'length', 0.0000), ('NodeSocketFloat', 'thickness', 0.0200)]) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': group_input.outputs["width"]}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': cube.outputs["Mesh"], + 'Name': 'uv_map', + 3: cube.outputs["UV Map"] + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': group_input.outputs["width"]}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': cube_1.outputs["Mesh"], + 'Name': 'uv_map', + 3: cube_1.outputs["UV Map"] + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': group_input.outputs["length"]}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_1, 'Translation': combine_xyz}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [store_named_attribute, transform]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry_1, 'Translation': combine_xyz_3}) + + add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["length"], 1: group_input.outputs["width"]}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': group_input.outputs["width"], + 'Y': add, + 'Z': group_input.outputs["thickness"] + }) + + cube_2 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_1}) + + store_named_attribute_2 = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': cube_2.outputs["Mesh"], + 'Name': 'uv_map', + 3: cube_2.outputs["UV Map"] + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["length"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"]}, + attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: multiply_2}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_1, 'Z': add_1}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_2, 'Translation': combine_xyz_2}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_2, transform_1]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, + attrs={'is_active_output': True}) + + + +@node_utils.to_nodegroup('nodegroup_center', singleton=False, type='GeometryNodeTree') +def nodegroup_center(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None), + ('NodeSocketVector', 'Vector', (0.0000, 0.0000, 0.0000)), ('NodeSocketFloat', 'MarginX', 0.5000), + ('NodeSocketFloat', 'MarginY', 0.0000), ('NodeSocketFloat', 'MarginZ', 0.0000)]) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': group_input.outputs["Geometry"]}) + + subtract = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Vector"], 1: bounding_box.outputs["Min"]}, + attrs={'operation': 'SUBTRACT'}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': subtract.outputs["Vector"]}) + + greater_than = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["X"], 1: group_input.outputs["MarginX"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + subtract_1 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: bounding_box.outputs["Max"], 1: group_input.outputs["Vector"]}, + attrs={'operation': 'SUBTRACT'}) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': subtract_1.outputs["Vector"]}) + + greater_than_1 = nw.new_node(Nodes.Math, input_kwargs={ + 0: separate_xyz_1.outputs["X"], + 1: group_input.outputs["MarginX"] + }, attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than, 1: greater_than_1}) + + greater_than_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["Y"], 1: group_input.outputs["MarginY"]}, + attrs={'operation': 'GREATER_THAN'}) + + greater_than_3 = nw.new_node(Nodes.Math, input_kwargs={ + 0: separate_xyz_1.outputs["Y"], + 1: group_input.outputs["MarginY"] + }, attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and_1 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than_2, 1: greater_than_3}) + + op_and_2 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and, 1: op_and_1}) + + greater_than_4 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["Z"], 1: group_input.outputs["MarginZ"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + greater_than_5 = nw.new_node(Nodes.Math, input_kwargs={ + 0: separate_xyz_1.outputs["Z"], + 1: group_input.outputs["MarginZ"] + }, attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and_3 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than_4, 1: greater_than_5}) + + op_and_4 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and_2, 1: op_and_3}) + + op_not = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and_4}, attrs={'operation': 'NOT'}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'In': op_and_4, 'Out': op_not}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_cube', singleton=False, type='GeometryNodeTree') +def nodegroup_cube(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVectorTranslation', 'Size', (0.1000, 10.0000, 4.0000)), + ('NodeSocketVector', 'Pos', (0.0000, 0.0000, 0.0000)), + ('NodeSocketInt', 'Resolution', 2)]) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={ + 'Size': group_input.outputs["Size"], + 'Vertices X': group_input.outputs["Resolution"], + 'Vertices Y': group_input.outputs["Resolution"], + 'Vertices Z': group_input.outputs["Resolution"] + }) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': cube.outputs["Mesh"], + 'Name': 'uv_map', + 3: cube.outputs["UV Map"] + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': store_named_attribute_1, 'Name': 'uv_map'}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + multiply_add = nw.new_node(Nodes.VectorMath, input_kwargs={ + 0: group_input.outputs["Size"], + 1: (0.5000, 0.5000, 0.5000), + 2: group_input.outputs["Pos"] + }, attrs={'operation': 'MULTIPLY_ADD'}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': store_named_attribute, + 'Translation': multiply_add.outputs["Vector"] + }) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, + attrs={'is_active_output': True}) + + + +@node_utils.to_nodegroup('nodegroup_hollow_cube', singleton=False, type='GeometryNodeTree') +def nodegroup_hollow_cube(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVectorTranslation', 'Size', (0.1000, 10.0000, 4.0000)), + ('NodeSocketVector', 'Pos', (0.0000, 0.0000, 0.0000)), + ('NodeSocketInt', 'Resolution', 2), + ('NodeSocketFloat', 'Thickness', 0.0000), + ('NodeSocketBool', 'Switch1', False), + ('NodeSocketBool', 'Switch2', False), + ('NodeSocketBool', 'Switch3', False), + ('NodeSocketBool', 'Switch4', False), + ('NodeSocketBool', 'Switch5', False), + ('NodeSocketBool', 'Switch6', False)]) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Size"]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Thickness"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, + attrs={'operation': 'SUBTRACT'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: multiply}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': group_input.outputs["Thickness"], + 'Y': subtract, + 'Z': subtract_1 + }) + + cube_2 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_4, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': cube_2.outputs["Mesh"], + 'Name': 'uv_map', + 3: cube_2.outputs["UV Map"] + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Thickness"]}, + attrs={'operation': 'MULTIPLY'}) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Pos"]}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: separate_xyz_1.outputs["X"]}) + + scale = nw.new_node(Nodes.VectorMath, input_kwargs={0: group_input.outputs["Size"], 'Scale': 0.5000}, + attrs={'operation': 'SCALE'}) + + separate_xyz_2 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': scale.outputs["Vector"]}) + + add_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + subtract_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Z"], 1: separate_xyz_1.outputs["Z"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': subtract_2}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_1, 'Translation': combine_xyz_5}) + + switch_2 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch3"], 14: transform_2}) + + subtract_3 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': separate_xyz.outputs["X"], + 'Y': subtract_3, + 'Z': group_input.outputs["Thickness"] + }) + + cube_1 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_2, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_4 = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': cube_1.outputs["Mesh"], + 'Name': 'uv_map', + 3: cube_1.outputs["UV Map"] + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["X"], 1: separate_xyz_1.outputs["X"]}) + + add_3 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + subtract_4 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: multiply_1}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_2, 'Y': add_3, 'Z': subtract_4}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_4, 'Translation': combine_xyz_3}) + + switch_1 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch2"], 14: transform_1}) + + subtract_5 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': separate_xyz.outputs["X"], + 'Y': subtract_5, + 'Z': group_input.outputs["Thickness"] + }) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': cube.outputs["Mesh"], + 'Name': 'uv_map', + 3: cube.outputs["UV Map"] + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_4 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["X"], 1: separate_xyz_1.outputs["X"]}) + + add_5 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: separate_xyz_1.outputs["Z"]}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_4, 'Y': add_5, 'Z': add_6}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute, 'Translation': combine_xyz_1}) + + switch = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch1"], 14: transform}) + + subtract_6 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, + attrs={'operation': 'SUBTRACT'}) + + subtract_7 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: multiply}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': group_input.outputs["Thickness"], + 'Y': subtract_6, + 'Z': subtract_7 + }) + + cube_3 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_6, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_5 = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': cube_3.outputs["Mesh"], + 'Name': 'uv_map', + 3: cube_3.outputs["UV Map"] + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + subtract_8 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["X"], 1: multiply_1}, + attrs={'operation': 'SUBTRACT'}) + + add_7 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + subtract_9 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Z"], 1: separate_xyz_1.outputs["Z"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_8, 'Y': add_7, 'Z': subtract_9}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_5, 'Translation': combine_xyz_7}) + + switch_3 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch4"], 14: transform_3}) + + combine_xyz_9 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': separate_xyz.outputs["X"], + 'Y': group_input.outputs["Thickness"], + 'Z': separate_xyz.outputs["Z"] + }) + + cube_4 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_9, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_2 = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': cube_4.outputs["Mesh"], + 'Name': 'uv_map', + 3: cube_4.outputs["UV Map"] + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_8 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["X"], 1: separate_xyz_2.outputs["X"]}) + + add_9 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_1.outputs["Y"], 1: multiply_1}) + + add_10 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["Z"], 1: separate_xyz_2.outputs["Z"]}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_8, 'Y': add_9, 'Z': add_10}) + + transform_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_2, 'Translation': combine_xyz_8}) + + switch_4 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch5"], 14: transform_4}) + + combine_xyz_10 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': separate_xyz.outputs["X"], + 'Y': group_input.outputs["Thickness"], + 'Z': separate_xyz.outputs["Z"] + }) + + cube_5 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_10, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_3 = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': cube_5.outputs["Mesh"], + 'Name': 'uv_map', + 3: cube_5.outputs["UV Map"] + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_11 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["X"], 1: separate_xyz_1.outputs["X"]}) + + subtract_10 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply_1}, + attrs={'operation': 'SUBTRACT'}) + + add_12 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Z"], 1: separate_xyz_1.outputs["Z"]}) + + combine_xyz_11 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_11, 'Y': subtract_10, 'Z': add_12}) + + transform_5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_3, 'Translation': combine_xyz_11}) + + switch_5 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch6"], 14: transform_5}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={ + 'Geometry': [switch_2.outputs[6], switch_1.outputs[6], switch.outputs[6], switch_3.outputs[6], + switch_4.outputs[6], switch_5.outputs[6]] + }) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, + attrs={'is_active_output': True}) + + + +@node_utils.to_nodegroup('nodegroup_dishwasher_geometry', singleton=False, type='GeometryNodeTree') +def nodegroup_dishwasher_geometry(nw: NodeWrangler, preprocess: bool = False): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Depth', 1.0000), + ('NodeSocketFloat', 'Width', 1.0000), + ('NodeSocketFloat', 'Height', 1.0000), + ('NodeSocketFloat', 'DoorThickness', 0.0700), + ('NodeSocketFloat', 'DoorRotation', 0.0000), + ('NodeSocketFloatDistance', 'RackRadius', 0.0100), + ('NodeSocketInt', 'RackAmount', 2), + ('NodeSocketString', 'BrandName', 'BrandName'), + ('NodeSocketMaterial', 'Surface', None), + ('NodeSocketMaterial', 'Front', None), + ('NodeSocketMaterial', 'Top', None), + ('NodeSocketMaterial', 'WhiteMetal', None), + ('NodeSocketMaterial', 'NameMaterial', None)]) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': group_input.outputs["Depth"], + 'Y': group_input.outputs["Width"], + 'Z': group_input.outputs["Height"] + }) + + hollowcube = nw.new_node(nodegroup_hollow_cube().name, input_kwargs={ + 'Size': combine_xyz, + 'Thickness': group_input.outputs["DoorThickness"], + 'Switch2': True, + 'Switch4': True + }) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': hollowcube, 'Material': group_input.outputs["Surface"]}) + + subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': set_material_1, 'Level': 0}) + + # set_shade_smooth_2 = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': subdivide_mesh}) + + body = nw.new_node(Nodes.Reroute, input_kwargs={'Input': subdivide_mesh}, label='Body') + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': group_input.outputs["DoorThickness"], + 'Y': group_input.outputs["Width"], + 'Z': group_input.outputs["Height"] + }) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["Depth"]}) + + cube = nw.new_node(nodegroup_cube().name, input_kwargs={'Size': combine_xyz_1, 'Pos': combine_xyz_2}) + + position = nw.new_node(Nodes.InputPosition) + + center = nw.new_node(nodegroup_center().name, input_kwargs={ + 'Geometry': cube, + 'Vector': position, + 'MarginX': -1.0000, + 'MarginY': 0.1000, + 'MarginZ': 0.1500 + }) + + set_material_2 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': cube, 'Selection': center.outputs["In"], 'Material': group_input.outputs["Front"]}) + + set_material_3 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': set_material_2, 'Selection': center.outputs["Out"], 'Material': group_input.outputs["Surface"]}) + + + # set_shade_smooth = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': set_material_3}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: 0.0500}, + attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: 0.8000}, + attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply}, attrs={'operation': 'MULTIPLY'}) + + handle = nw.new_node(nodegroup_handle().name, + input_kwargs={'width': multiply, 'length': multiply_1, 'thickness': multiply_2}) + + add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["DoorThickness"]}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: 0.1000}, + attrs={'operation': 'MULTIPLY'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: 0.9500}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_13 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': multiply_3, 'Z': multiply_4}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': handle, + 'Translation': combine_xyz_13, + 'Rotation': (0.0000, 1.5708, 0.0000) + }) + + set_material_8 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_1, 'Material': group_input.outputs["WhiteMetal"]}) + + add_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["DoorThickness"]}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_12 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_1, 'Y': multiply_5, 'Z': 0.0300}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: 0.0500}, + attrs={'operation': 'MULTIPLY'}) + + text = nw.new_node(nodegroup_text().name, input_kwargs={ + 'Translation': combine_xyz_12, + 'String': group_input.outputs["BrandName"], + 'Size': multiply_6 + }) + + text = complete_no_bevel(nw, text, preprocess) + + set_material_9 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': text, 'Material': group_input.outputs["NameMaterial"]}) + + set_material_8 = complete_bevel(nw, set_material_8, preprocess) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material_3, set_material_8, set_material_9]}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', + input_kwargs={'Geometry': join_geometry_3}) + + y = nw.scalar_multiply(group_input.outputs["DoorRotation"], 1 if not preprocess else 0) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': y}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["Depth"]}) + + rotate_instances = nw.new_node(Nodes.RotateInstances, input_kwargs={ + 'Instances': geometry_to_instance, + 'Rotation': combine_xyz_3, + 'Pivot Point': combine_xyz_4 + }) + + rotate_instances = nw.new_node(Nodes.RealizeInstances, [rotate_instances]) + + door = nw.new_node(Nodes.Reroute, input_kwargs={'Input': rotate_instances}, label='door') + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["DoorThickness"], 1: 2.1000}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"], 1: multiply_7}, + attrs={'operation': 'SUBTRACT'}) + + multiply_8 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["DoorThickness"], 1: 2.1000}, + attrs={'operation': 'MULTIPLY'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: multiply_8}, + attrs={'operation': 'SUBTRACT'}) + + dishrack = nw.new_node(nodegroup_dish_rack().name, input_kwargs={ + 'Depth': subtract_1, + 'Width': subtract, + 'Radius': group_input.outputs["RackRadius"], + 'Amount': 4, + 'Height': 0.1000 + }) + + geometry_to_instance_1 = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': dishrack}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, input_kwargs={ + 'Geometry': geometry_to_instance_1, + 'Amount': group_input.outputs["RackAmount"] + }, attrs={'domain': 'INSTANCE'}) + + multiply_9 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_10 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, + attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: 1.0000}) + + multiply_11 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["DoorThickness"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + subtract_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: multiply_11}, + attrs={'operation': 'SUBTRACT'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["RackAmount"], 1: 1.0000}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: subtract_2, 1: add_3}, attrs={'operation': 'DIVIDE'}) + + multiply_12 = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: divide}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': multiply_9, 'Y': multiply_10, 'Z': multiply_12}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': duplicate_elements.outputs["Geometry"], + 'Offset': combine_xyz_5 + }) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': set_position, 'Material': group_input.outputs["Surface"]}) + + set_material = nw.new_node(Nodes.RealizeInstances, [set_material]) + + racks = nw.new_node(Nodes.Reroute, input_kwargs={'Input': set_material}, label='racks') + + add_4 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["DoorThickness"]}) + + reroute_10 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': add_4}) + + reroute_11 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Width"]}) + + reroute_8 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["DoorThickness"]}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': reroute_10, 'Y': reroute_11, 'Z': reroute_8}) + + reroute_9 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Height"]}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': reroute_9}) + + cube_1 = nw.new_node(nodegroup_cube().name, input_kwargs={'Size': combine_xyz_6, 'Pos': combine_xyz_7}) + + set_material_5 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': cube_1, 'Material': group_input.outputs["Top"]}) + + # set_shade_smooth_1 = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': set_material_5}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': set_material_5}) + + heater = nw.new_node(Nodes.Reroute, input_kwargs={'Input': join_geometry_2}, label='heater') + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [body, door, racks, heater]}) + + geometry = nw.new_node(Nodes.RealizeInstances, [join_geometry]) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) diff --git a/infinigen/assets/appliances/microwave.py b/infinigen/assets/appliances/microwave.py new file mode 100644 index 000000000..3e21353b3 --- /dev/null +++ b/infinigen/assets/appliances/microwave.py @@ -0,0 +1,447 @@ +# 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: Hongyu Wen + +import bpy +import random +import mathutils +import numpy as np +from numpy.random import uniform as U, normal as N, randint as RI + +from infinigen.assets.utils.misc import generate_text +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface +from infinigen.core.util import blender as butil +from infinigen.core.util.blender import delete +from infinigen.core.util.bevelling import get_bevel_edges, add_bevel, complete_bevel, complete_no_bevel +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory +from infinigen.assets.material_assignments import AssetList + + +class MicrowaveFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=[1., 1., 1.]): + super(MicrowaveFactory, self).__init__(factory_seed, coarse=coarse) + + self.dimensions = dimensions + with FixedSeed(factory_seed): + self.params = self.sample_parameters(dimensions) + self.material_params, self.scratch, self.edge_wear = self.get_material_params() + self.params.update(self.material_params) + + def get_material_params(self): + material_assignments = AssetList['MicrowaveFactory']() + params = { + "Surface": material_assignments['surface'].assign_material(), + "Back": material_assignments['back'].assign_material(), + "BlackGlass": material_assignments['black_glass'].assign_material(), + "Glass": material_assignments['glass'].assign_material(), + } + wrapped_params = { + k: surface.shaderfunc_to_material(v) for k, v in params.items() + } + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = np.random.uniform() < scratch_prob + is_edge_wear = np.random.uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + @staticmethod + def sample_parameters(dimensions): + depth = U(0.5, 0.7) + width = U(0.6, 1.0) + height = U(0.35, 0.45) + panel_width = U(0.2, 0.4) + margin_z = U(0.05, 0.1) + door_thickness = U(0.02, 0.04) + door_margin = U(0.03, 0.1) + door_rotation = 0 # Set to 0 for now + brand_name = generate_text() + params = { + "Depth": depth, + "Width": width, + "Height": height, + "PanelWidth": panel_width, + "MarginZ": margin_z, + "DoorThickness": door_thickness, + "DoorMargin": door_margin, + "DoorRotation": door_rotation, + "BrandName": brand_name, + } + return params + + def create_asset(self, **params): + obj = butil.spawn_cube() + butil.modify_mesh(obj, 'NODES', node_group=nodegroup_microwave_geometry(preprocess=True), ng_inputs=self.params, apply=True) + bevel_edges = get_bevel_edges(obj) + delete(obj) + obj = butil.spawn_cube() + butil.modify_mesh(obj, 'NODES', node_group=nodegroup_microwave_geometry(), ng_inputs=self.params, apply=True) + obj = add_bevel(obj, bevel_edges) + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + + +@node_utils.to_nodegroup('nodegroup_plate', singleton=False, type='GeometryNodeTree') +def nodegroup_plate(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': 128}) + + bezier_segment = nw.new_node(Nodes.CurveBezierSegment, + input_kwargs={'Start Handle': (0.0000, 0.0000, 0.0000), 'End': (1.0000, 0.0000, 0.4000)}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': bezier_segment, 'Rotation': (1.5708, 0.0000, 0.0000)}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={'Curve': curve_circle.outputs["Curve"], 'Profile Curve': transform}) + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketVectorXYZ', 'Scale', (1.0000, 1.0000, 1.0000))]) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': curve_to_mesh, 'Scale': group_input.outputs["Scale"]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': transform_1}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_text', singleton=False, type='GeometryNodeTree') +def nodegroup_text(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVectorTranslation', 'Translation', (1.5000, 0.0000, 0.0000)), + ('NodeSocketString', 'String', 'BrandName'), + ('NodeSocketFloatDistance', 'Size', 0.0500), + ('NodeSocketFloat', 'Offset Scale', 0.0020)]) + + string_to_curves = nw.new_node('GeometryNodeStringToCurves', + input_kwargs={'String': group_input.outputs["String"], 'Size': group_input.outputs["Size"]}, + attrs={'align_y': 'BOTTOM_BASELINE', 'align_x': 'CENTER'}) + + fill_curve = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': string_to_curves.outputs["Curve Instances"]}) + + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, + input_kwargs={'Mesh': fill_curve, 'Offset Scale': group_input.outputs["Offset Scale"]}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': extrude_mesh.outputs["Mesh"], 'Translation': group_input.outputs["Translation"], 'Rotation': (1.5708, 0.0000, 1.5708)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_1}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_center', singleton=False, type='GeometryNodeTree') +def nodegroup_center(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Geometry', None), + ('NodeSocketVector', 'Vector', (0.0000, 0.0000, 0.0000)), + ('NodeSocketFloat', 'MarginX', 0.5000), + ('NodeSocketFloat', 'MarginY', 0.0000), + ('NodeSocketFloat', 'MarginZ', 0.0000)]) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': group_input.outputs["Geometry"]}) + + subtract = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Vector"], 1: bounding_box.outputs["Min"]}, + attrs={'operation': 'SUBTRACT'}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': subtract.outputs["Vector"]}) + + greater_than = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["X"], 1: group_input.outputs["MarginX"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + subtract_1 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: bounding_box.outputs["Max"], 1: group_input.outputs["Vector"]}, + attrs={'operation': 'SUBTRACT'}) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': subtract_1.outputs["Vector"]}) + + greater_than_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["X"], 1: group_input.outputs["MarginX"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than, 1: greater_than_1}) + + greater_than_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["Y"], 1: group_input.outputs["MarginY"]}, + attrs={'operation': 'GREATER_THAN'}) + + greater_than_3 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["Y"], 1: group_input.outputs["MarginY"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and_1 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than_2, 1: greater_than_3}) + + op_and_2 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and, 1: op_and_1}) + + greater_than_4 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["Z"], 1: group_input.outputs["MarginZ"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + greater_than_5 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["Z"], 1: group_input.outputs["MarginZ"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and_3 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than_4, 1: greater_than_5}) + + op_and_4 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and_2, 1: op_and_3}) + + op_not = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and_4}, attrs={'operation': 'NOT'}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'In': op_and_4, 'Out': op_not}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_cube', singleton=False, type='GeometryNodeTree') +def nodegroup_cube(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVectorTranslation', 'Size', (0.1000, 10.0000, 4.0000)), + ('NodeSocketVector', 'Pos', (0.0000, 0.0000, 0.0000)), + ('NodeSocketInt', 'Resolution', 10)]) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': group_input.outputs["Size"], 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', 3: cube.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': store_named_attribute_1, 'Name': 'uv_map'}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + multiply_add = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Size"], 1: (0.5000, 0.5000, 0.5000), 2: group_input.outputs["Pos"]}, + attrs={'operation': 'MULTIPLY_ADD'}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute, 'Translation': multiply_add.outputs["Vector"]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_microwave_geometry', singleton=False, type='GeometryNodeTree') +def nodegroup_microwave_geometry(nw: NodeWrangler, preprocess: bool=False): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Depth', 0.0000), + ('NodeSocketFloat', 'Width', 0.0000), + ('NodeSocketFloat', 'Height', 0.0000), + ('NodeSocketFloat', 'PanelWidth', 0.5000), + ('NodeSocketFloat', 'MarginZ', 0.0000), + ('NodeSocketFloat', 'DoorThickness', 0.0000), + ('NodeSocketFloat', 'DoorMargin', 0.0500), + ('NodeSocketFloat', 'DoorRotation', 0.0000), + ('NodeSocketString', 'BrandName', 'BrandName'), + ('NodeSocketMaterial', 'Surface', None), + ('NodeSocketMaterial', 'Back', None), + ('NodeSocketMaterial', 'BlackGlass', None), + ('NodeSocketMaterial', 'Glass', None), + ]) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Depth"], 'Y': group_input.outputs["Width"], 'Z': group_input.outputs["Height"]}) + + cube = nw.new_node(nodegroup_cube().name, input_kwargs={'Size': combine_xyz}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Width"], 1: group_input.outputs["PanelWidth"]}, + attrs={'operation': 'SUBTRACT'}) + + subtract_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Height"], 1: group_input.outputs["MarginZ"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["Depth"], 'Y': subtract, 'Z': subtract_1}) + + scale = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["MarginZ"], 'Scale': 0.5000}, + attrs={'operation': 'SCALE'}) + + cube_1 = nw.new_node(nodegroup_cube().name, input_kwargs={'Size': combine_xyz_1, 'Pos': scale.outputs["Vector"]}) + + difference = nw.new_node(Nodes.MeshBoolean, input_kwargs={'Mesh 1': cube, 'Mesh 2': cube_1}) + + cube_2 = nw.new_node(nodegroup_cube().name, + input_kwargs={'Size': (0.0300, 0.0300, 0.0100), 'Pos': (0.1000, 0.0000, 0.0500), 'Resolution': 2}) + + geometry_to_instance_1 = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': cube_2}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance_1, 'Amount': 10}, + attrs={'domain': 'INSTANCE'}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: 0.0400}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': duplicate_elements.outputs["Geometry"], 'Offset': combine_xyz_7}) + + duplicate_elements_1 = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': set_position_1, 'Amount': 7}, + attrs={'domain': 'INSTANCE'}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements_1.outputs["Duplicate Index"], 1: 0.0200}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_1}) + + set_position_2 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': duplicate_elements_1.outputs["Geometry"], 'Offset': combine_xyz_8}) + + difference_1 = nw.new_node(Nodes.MeshBoolean, + input_kwargs={'Mesh 1': difference.outputs["Mesh"], 'Mesh 2': [duplicate_elements_1.outputs["Geometry"], set_position_2]}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': difference_1.outputs["Mesh"], 'Material': group_input.outputs["Back"]}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["DoorThickness"], 'Y': group_input.outputs["Width"], 'Z': group_input.outputs["Height"]}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["Depth"]}) + + cube_3 = nw.new_node(nodegroup_cube().name, input_kwargs={'Size': combine_xyz_2, 'Pos': combine_xyz_3, 'Resolution': 10}) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + subtract_2 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Width"], 1: group_input.outputs["PanelWidth"]}, + attrs={'operation': 'SUBTRACT'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["MarginZ"]}, attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: subtract_2, 1: multiply_2}) + + less_than = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: add}, attrs={'operation': 'LESS_THAN'}) + + separate_geometry = nw.new_node(Nodes.SeparateGeometry, + input_kwargs={'Geometry': cube_3, 'Selection': less_than}, + attrs={'domain': 'FACE'}) + + convex_hull = nw.new_node(Nodes.ConvexHull, input_kwargs={'Geometry': separate_geometry.outputs["Selection"]}) + + subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': convex_hull, 'Level': 0}) + + position_1 = nw.new_node(Nodes.InputPosition) + + center = nw.new_node(nodegroup_center().name, + input_kwargs={'Geometry': subdivide_mesh, 'Vector': position_1, 'MarginX': -1.0000, 'MarginZ': group_input.outputs["DoorMargin"]}) + + set_material_3 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': subdivide_mesh, 'Selection': center.outputs["In"], 'Material': group_input.outputs["BlackGlass"]}) + + set_material_2 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': set_material_3, 'Selection': center.outputs["Out"], 'Material': group_input.outputs["Surface"]}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["DoorThickness"]}) + + bounding_box_1 = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': subdivide_mesh}) + + add_2 = nw.new_node(Nodes.VectorMath, input_kwargs={0: bounding_box_1.outputs["Min"], 1: bounding_box_1.outputs["Max"]}) + + scale_1 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: add_2.outputs["Vector"], 'Scale': 0.5000}, + attrs={'operation': 'SCALE'}) + + separate_xyz_3 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': scale_1.outputs["Vector"]}) + + separate_xyz_4 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': bounding_box_1.outputs["Min"]}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_4.outputs["Z"], 1: group_input.outputs["DoorMargin"]}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_1, 'Y': separate_xyz_3.outputs["Y"], 'Z': add_3}) + + text = nw.new_node(nodegroup_text().name, + input_kwargs={'Translation': combine_xyz_5, 'String': group_input.outputs["BrandName"], 'Size': 0.0300, 'Offset Scale': 0.0020}) + + text = complete_no_bevel(nw, text, preprocess) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material_2, text]}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': join_geometry_1}) + + z = nw.scalar_multiply(group_input.outputs["DoorRotation"], 1 if not preprocess else 0) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': z}) + + rotate_instances = nw.new_node(Nodes.RotateInstances, + input_kwargs={'Instances': geometry_to_instance, 'Rotation': combine_xyz_6, 'Pivot Point': combine_xyz_3}) + + plate = nw.new_node(nodegroup_plate().name, input_kwargs={'Scale': (0.1000, 0.1000, 0.1000)}) + + multiply_add = nw.new_node(Nodes.VectorMath, + input_kwargs={0: combine_xyz_1, 1: (0.5000, 0.5000, 0.0000), 2: scale.outputs["Vector"]}, + attrs={'operation': 'MULTIPLY_ADD'}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': plate, 'Offset': multiply_add.outputs["Vector"]}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': set_position, 'Material': group_input.outputs["Glass"]}) + + convex_hull_1 = nw.new_node(Nodes.ConvexHull, input_kwargs={'Geometry': separate_geometry.outputs["Inverted"]}) + + subdivide_mesh_1 = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': convex_hull_1, 'Level': 0}) + + position_2 = nw.new_node(Nodes.InputPosition) + + center_1 = nw.new_node(nodegroup_center().name, + input_kwargs={'Geometry': subdivide_mesh_1, 'Vector': position_2, 'MarginX': -1.0000, 'MarginY': 0.0010, 'MarginZ': group_input.outputs["DoorMargin"]}) + + set_material_4 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': subdivide_mesh_1, 'Selection': center_1.outputs["In"], 'Material': group_input.outputs["BlackGlass"]}) + + set_material_5 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': set_material_4, 'Selection': center_1.outputs["Out"], 'Material': group_input.outputs["Surface"]}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["DoorThickness"]}) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': subdivide_mesh_1}) + + add_5 = nw.new_node(Nodes.VectorMath, input_kwargs={0: bounding_box.outputs["Min"], 1: bounding_box.outputs["Max"]}) + + scale_2 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: add_5.outputs["Vector"], 'Scale': 0.5000}, + attrs={'operation': 'SCALE'}) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': scale_2.outputs["Vector"]}) + + separate_xyz_2 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': bounding_box.outputs["Max"]}) + + subtract_3 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Z"], 1: group_input.outputs["DoorMargin"]}, + attrs={'operation': 'SUBTRACT'}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_3, 1: -0.1000}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_4, 'Y': separate_xyz_1.outputs["Y"], 'Z': add_6}) + + text_1 = nw.new_node(nodegroup_text().name, + input_kwargs={'Translation': combine_xyz_4, 'String': '12:01', 'Offset Scale': 0.0050}) + + text_1 = complete_no_bevel(nw, text_1, preprocess) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [set_material_1, rotate_instances, set_material, set_material_5, text_1]}) + geometry =nw.new_node(Nodes.RealizeInstances,[join_geometry]) + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}, attrs={'is_active_output': True}) diff --git a/infinigen/assets/appliances/oven.py b/infinigen/assets/appliances/oven.py new file mode 100644 index 000000000..16eeff2da --- /dev/null +++ b/infinigen/assets/appliances/oven.py @@ -0,0 +1,1215 @@ +# 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: Hongyu Wen + +import bpy +import random +import mathutils +import numpy as np +from numpy.random import uniform as U, normal as N, randint as RI + +from infinigen.assets.utils.misc import generate_text +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core.util.blender import delete +from infinigen.core.util.bevelling import get_bevel_edges, add_bevel, complete_bevel, complete_no_bevel +from infinigen.core import surface +from infinigen.core.util import blender as butil + +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory +from infinigen.assets.material_assignments import AssetList +from infinigen.assets.utils.object import new_bbox +from infinigen.core.surface import write_attr_data +from infinigen.assets.utils.decorate import read_normal +from infinigen.core.tagging import PREFIX +from infinigen.core import tagging, tags as t + +class OvenFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=[1., 1., 1.]): + super(OvenFactory, self).__init__(factory_seed, coarse=coarse) + + self.dimensions = dimensions + with FixedSeed(factory_seed): + self.params, self.geometry_node_params = self.sample_parameters(dimensions) + self.material_params, self.scratch, self.edge_wear = self.get_material_params() + self.geometry_node_params.update(self.material_params) + + def get_material_params(self): + material_assignments = AssetList['OvenFactory']() + params = { + "Surface": material_assignments['surface'].assign_material(), + "Back": material_assignments['back'].assign_material(), + "WhiteMetal": material_assignments['white_metal'].assign_material(), + "SuperBlackGlass": material_assignments['black_glass'].assign_material(), + "Glass": material_assignments['glass'].assign_material(), + } + wrapped_params = { + k: surface.shaderfunc_to_material(v) for k, v in params.items() + } + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = np.random.uniform() < scratch_prob + is_edge_wear = np.random.uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + @staticmethod + def sample_parameters(dimensions): + # depth, width, height = dimensions + depth = 1 + N(0, 0.1) + width = 1 + N(0, 0.1) + height = 1 + N(0, 0.1) + door_thickness = U(0.05, 0.1) * depth + door_rotation = 0 # Set to 0 for now + + rack_radius = U(0.01, 0.02) * depth + rack_h_amount = RI(2, 4) + rack_d_amount = RI(4, 6) + + panel_height = U(0.2, 0.4) * height + panel_thickness = U(0.15, 0.25) * depth + botton_amount = RI(1, 3) * 2 + botton_radius = U(0.05, 0.1) * width + botton_thickness = U(0.02, 0.04) * depth + heat_radius_ratio = U(0.1, 0.2) + brand_name = generate_text() + + use_gas = RI(2) + n_grids = RI(2, 5) + grids = [RI(1, 4) for i in range(n_grids)] + branches = 2 * RI(2, 9) + grate_thickness = U(0.01, 0.03) + center_ratio = U(0.05, 0.15) + middle_ratio = U(0.5, 0.7) + + params = { + "UseGas": use_gas, + "Grids": grids, + "Branches": branches, + "GrateThickness": grate_thickness, + "CenterRatio": center_ratio, + "MiddleRatio": middle_ratio, + "Depth": depth, + "Width": width, + "Height": height, + "DoorThickness": door_thickness, + "DoorRotation": door_rotation, + "RackRadius": rack_radius, + "RackHAmount": rack_h_amount, + "RackDAmount": rack_d_amount, + "PanelHeight": panel_height, + "PanelThickness": panel_thickness, + "BottonAmount": botton_amount, + "BottonRadius": botton_radius, + "BottonThickness": botton_thickness, + "HeaterRadiusRatio": heat_radius_ratio, + "BrandName": brand_name, + } + geometry_node_params = {k: params[k] for k in params.keys() if k not in [ + "UseGas", + "Grids", + "Branches", + "GrateThickness", + "CenterRatio", + "MiddleRatio", + ]} + return params, geometry_node_params + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + # x, y, z = self.params["Depth"], self.params["Width"], self.params["Height"] + # box = new_bbox(-x/2 - 0.05, x/2 + self.params["DoorThickness"] + 0.1, -y/2, y/2, 0, z + 0.1) + # tagging.tag_object(box, f'{PREFIX}{t.Subpart.SupportSurface.value}', read_normal(box)[:, -1] > .5) + # box_top = new_bbox(-x/2 - 0.05, -x/2 - 0.05 + self.params["PanelThickness"], -y/2, y/2, z + 0.1, z+ 0.1 + 0.5) + # box_top.rotation_euler[1] = -0.1 + #box = butil.join_objects([box, box_top]) + obj = butil.spawn_cube() + return butil.modify_mesh(obj, 'NODES', node_group=nodegroup_oven_geometry(use_gas=self.params["UseGas"], is_placeholder=True), ng_inputs=self.geometry_node_params, apply=True) + + def create_asset(self, **params): + obj = butil.spawn_cube() + butil.modify_mesh(obj, 'NODES', node_group=nodegroup_oven_geometry(preprocess=True, use_gas=self.params["UseGas"]), ng_inputs=self.geometry_node_params, apply=True) + bevel_edges = get_bevel_edges(obj) + delete(obj) + obj = butil.spawn_cube() + butil.modify_mesh(obj, 'NODES', node_group=nodegroup_oven_geometry(use_gas=self.params["UseGas"]), ng_inputs=self.geometry_node_params, apply=True) + obj = add_bevel(obj, bevel_edges, offset=0.01) + if not self.params["UseGas"]: return obj + width, depth = self.params["Width"], self.params["Depth"] + 2 * self.params["DoorThickness"] + grate_width, grate_depth = width * 0.8, depth * 0.6 + grate_thickness = self.params["GrateThickness"] + grates = gas_grates(width, depth, grate_width, grate_depth, self.params["Height"] + self.params["DoorThickness"] - grate_thickness, grate_thickness, self.params["Grids"], self.params["Branches"], self.params["CenterRatio"], self.params["MiddleRatio"]) + grates.data.materials.append(self.geometry_node_params["WhiteMetal"]) + obj.data.materials.append(self.geometry_node_params["Back"]) + with butil.SelectObjects(obj): + obj.active_material_index = len(obj.material_slots) - 1 + for i in range(len(obj.material_slots)): bpy.ops.object.material_slot_move(direction='UP') + hollow= butil.spawn_cube( + size=1, + location=(depth / 2, width / 2, self.params["Height"] + self.params["DoorThickness"]), + scale=(grate_depth + grate_thickness, grate_width + grate_thickness, grate_thickness * 2), + ) + with butil.SelectObjects(hollow): + bpy.ops.object.modifier_add(type='BEVEL') + bpy.context.object.modifiers["Bevel"].segments = 8 + bpy.context.object.modifiers["Bevel"].width = grate_thickness + bpy.ops.object.modifier_apply(modifier="Bevel") + with butil.SelectObjects(obj): + bpy.ops.object.modifier_add(type='BOOLEAN') + bpy.context.object.modifiers["Boolean"].object = hollow + bpy.context.object.modifiers["Boolean"].use_hole_tolerant = True + bpy.ops.object.modifier_apply(modifier="Boolean") + butil.delete(hollow) + butil.join_objects([obj, grates], check_attributes=True) + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + +def gas_grates(width, depth, grate_width, grate_depth, height, thickness, grids, branches, center_ratio, middle_ratio): + high_height = height + thickness * 0.9 + grates = [] + for i, n in enumerate(grids): + cubes = [ + butil.spawn_cube(size=1, location=(depth / 2, grate_width / len(grids) * i + (width - grate_width) / 2 + thickness / 2, height), scale=(grate_depth + thickness, thickness, thickness), name=None), + butil.spawn_cube(size=1, location=(depth / 2, grate_width / len(grids) * (i+1) + (width - grate_width) / 2 - thickness / 2, height), scale=(grate_depth + thickness, thickness, thickness), name=None), + ] + for j in range(n+1): + cubes.append(butil.spawn_cube( + size=1, + location=(grate_depth / n * j + (depth - grate_depth) / 2, grate_width / len(grids) * (i+0.5) + (width - grate_width) / 2, high_height), + scale=(thickness, grate_width / len(grids), thickness), + )) + for j in range(n): + min_dist = min(grate_width / len(grids) / 2, grate_depth / n / 2) + line_len = max(grate_width / len(grids) / 2, grate_depth / n / 2) - min_dist + center_dist = min_dist * center_ratio + middle_dist = min_dist * middle_ratio + if grate_width / len(grids) / 2 > grate_depth / n / 2: + x_center, y_center = center_dist, line_len + center_dist + x_middle, y_middle = middle_dist, line_len + middle_dist + x_full, y_full = min_dist, line_len + min_dist + else: + x_center, y_center = center_dist + line_len, center_dist + x_middle, y_middle = middle_dist + line_len, middle_dist + x_full, y_full = min_dist + line_len, min_dist + center = (grate_depth / n * (j+0.5) + (depth - grate_depth) / 2), grate_width / len(grids) * (i+0.5) + (width - grate_width) / 2 + for k in range(branches): + angle = 2 * np.pi / branches * k + x0, y0 = x_center * np.cos(angle), y_center * np.sin(angle) + x1, y1 = x_middle * np.cos(angle), y_middle * np.sin(angle) + location = center[0] + (x0 + x1) / 2, center[1] + (y0 + y1) / 2, high_height + scale = ((x0 - x1) ** 2 + (y0 - y1) ** 2) ** 0.5, thickness, thickness + actual_angle = np.arctan2(y1-y0, x1-x0) + obj = butil.spawn_cube(size=1, location=location, scale=scale) + bpy.context.object.rotation_euler[2] = actual_angle + cubes.append(obj) + x0, y0 = x1, y1 + if x_full - abs(x0) < y_full - abs(y0): + x1, y1 = x_full * np.sign(x0), y0 + else: + x1, y1 = x0, y_full * np.sign(y0) + location = center[0] + (x0 + x1) / 2, center[1] + (y0 + y1) / 2, high_height + scale = ((x0 - x1) ** 2 + (y0 - y1) ** 2) ** 0.5, thickness, thickness + actual_angle = np.arctan2(y1-y0, x1-x0) + obj = butil.spawn_cube(size=1, location=location, scale=scale) + bpy.context.object.rotation_euler[2] = actual_angle + cubes.append(obj) + grates.append(butil.spawn_cylinder(center_dist + thickness, thickness / 2, location=(center[0], center[1], height))) + obj = butil.boolean(cubes) + for i in range(1, len(cubes)): + butil.delete(cubes[i]) + with butil.SelectObjects(obj): + bpy.ops.object.modifier_add(type='REMESH') + remesh_type = "VOXEL" + bpy.context.object.modifiers["Remesh"].mode = remesh_type + bpy.context.object.modifiers["Remesh"].voxel_size = 0.004 + bpy.ops.object.modifier_apply(modifier="Remesh") + bpy.ops.object.modifier_add(type='SMOOTH') + bpy.context.object.modifiers["Smooth"].iterations = 8 + bpy.context.object.modifiers["Smooth"].factor = 1 + bpy.ops.object.modifier_apply(modifier="Smooth") + grates.append(obj) + obj = butil.boolean(grates) + for i in range(1, len(grates)): + butil.delete(grates[i]) + return obj + +@node_utils.to_nodegroup('nodegroup_hollow_cube', singleton=False, type='GeometryNodeTree') +def nodegroup_hollow_cube(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVectorTranslation', 'Size', (0.1000, 10.0000, 4.0000)), + ('NodeSocketVector', 'Pos', (0.0000, 0.0000, 0.0000)), + ('NodeSocketInt', 'Resolution', 2), + ('NodeSocketFloat', 'Thickness', 0.0000), + ('NodeSocketBool', 'Switch1', False), + ('NodeSocketBool', 'Switch2', False), + ('NodeSocketBool', 'Switch3', False), + ('NodeSocketBool', 'Switch4', False), + ('NodeSocketBool', 'Switch5', False), + ('NodeSocketBool', 'Switch6', False)]) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Size"]}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Thickness"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["Thickness"], 'Y': subtract, 'Z': subtract_1}) + + cube_2 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_4, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_2.outputs["Mesh"], 'Name': 'uv_map', 3: cube_2.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Thickness"]}, attrs={'operation': 'MULTIPLY'}) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Pos"]}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: separate_xyz_1.outputs["X"]}) + + scale = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Size"], 'Scale': 0.5000}, + attrs={'operation': 'SCALE'}) + + separate_xyz_2 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': scale.outputs["Vector"]}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + subtract_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Z"], 1: separate_xyz_1.outputs["Z"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': subtract_2}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_1, 'Translation': combine_xyz_5}) + + switch_2 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch3"], 14: transform_2}) + + subtract_3 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz.outputs["X"], 'Y': subtract_3, 'Z': group_input.outputs["Thickness"]}) + + cube_1 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_2, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_4 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_1.outputs["Mesh"], 'Name': 'uv_map', 3: cube_1.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["X"], 1: separate_xyz_1.outputs["X"]}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + subtract_4 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_2, 'Y': add_3, 'Z': subtract_4}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_4, 'Translation': combine_xyz_3}) + + switch_1 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch2"], 14: transform_1}) + + subtract_5 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz.outputs["X"], 'Y': subtract_5, 'Z': group_input.outputs["Thickness"]}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', 3: cube.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["X"], 1: separate_xyz_1.outputs["X"]}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: separate_xyz_1.outputs["Z"]}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_4, 'Y': add_5, 'Z': add_6}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute, 'Translation': combine_xyz_1}) + + switch = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch1"], 14: transform}) + + subtract_6 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + subtract_7 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Thickness"], 'Y': subtract_6, 'Z': subtract_7}) + + cube_3 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_6, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_5 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_3.outputs["Mesh"], 'Name': 'uv_map', 3: cube_3.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + subtract_8 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["X"], 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + add_7 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + subtract_9 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Z"], 1: separate_xyz_1.outputs["Z"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_8, 'Y': add_7, 'Z': subtract_9}) + + transform_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_5, 'Translation': combine_xyz_7}) + + switch_3 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch4"], 14: transform_3}) + + combine_xyz_9 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz.outputs["X"], 'Y': group_input.outputs["Thickness"], 'Z': separate_xyz.outputs["Z"]}) + + cube_4 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_9, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_2 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_4.outputs["Mesh"], 'Name': 'uv_map', 3: cube_4.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_8 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_1.outputs["X"], 1: separate_xyz_2.outputs["X"]}) + + add_9 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_1.outputs["Y"], 1: multiply_1}) + + add_10 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_1.outputs["Z"], 1: separate_xyz_2.outputs["Z"]}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_8, 'Y': add_9, 'Z': add_10}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_2, 'Translation': combine_xyz_8}) + + switch_4 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch5"], 14: transform_4}) + + combine_xyz_10 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz.outputs["X"], 'Y': group_input.outputs["Thickness"], 'Z': separate_xyz.outputs["Z"]}) + + cube_5 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_10, 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_3 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_5.outputs["Mesh"], 'Name': 'uv_map', 3: cube_5.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + add_11 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["X"], 1: separate_xyz_1.outputs["X"]}) + + subtract_10 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + add_12 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["Z"], 1: separate_xyz_1.outputs["Z"]}) + + combine_xyz_11 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_11, 'Y': subtract_10, 'Z': add_12}) + + transform_5 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_3, 'Translation': combine_xyz_11}) + + switch_5 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Switch6"], 14: transform_5}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [switch_2.outputs[6], switch_1.outputs[6], switch.outputs[6], switch_3.outputs[6], switch_4.outputs[6], switch_5.outputs[6]]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_o', singleton=False, type='GeometryNodeTree') +def nodegroup_o(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': (0.0000, 0.0000, 0.0020)}) + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloatDistance', 'Size', 1.0000)]) + + curve_circle_1 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': group_input.outputs["Size"]}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': curve_line, 'Profile Curve': curve_circle_1.outputs["Curve"]}) + + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={'Mesh': curve_to_mesh, 'Offset Scale': 0.0030}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': extrude_mesh.outputs["Mesh"]}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_heater', singleton=False, type='GeometryNodeTree') +def nodegroup_heater(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + curve_line_1 = nw.new_node(Nodes.CurveLine, input_kwargs={'End': (0.0000, 0.0000, 0.0010)}) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'width', 0.5000), + ('NodeSocketFloat', 'depth', 0.0000), + ('NodeSocketFloat', 'radius_ratio', 0.2000), + ('NodeSocketFloat', 'arrangement_ratio', 0.5000), + ('NodeSocketShader', 'SuperBlackGlass', None)]) + + minimum = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["width"], 1: group_input.outputs["depth"]}, + attrs={'operation': 'MINIMUM'}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: minimum, 1: group_input.outputs["radius_ratio"]}, + label='Multiply', + attrs={'operation': 'MULTIPLY'}) + + curve_circle_1 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': multiply}) + + curve_to_mesh_1 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': curve_line_1, 'Profile Curve': curve_circle_1.outputs["Curve"], 'Fill Caps': True}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': curve_to_mesh_1, 'Material': group_input.outputs["SuperBlackGlass"]}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': set_material}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: minimum, 1: group_input.outputs["arrangement_ratio"]}, + label='Multiply', + attrs={'operation': 'MULTIPLY'}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: multiply_1}, attrs={'operation': 'DIVIDE'}) + + floor = nw.new_node(Nodes.Math, input_kwargs={0: divide}, attrs={'operation': 'FLOOR'}) + + divide_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: multiply_1}, attrs={'operation': 'DIVIDE'}) + + floor_1 = nw.new_node(Nodes.Math, input_kwargs={0: divide_1}, attrs={'operation': 'FLOOR'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: floor, 1: floor_1}, attrs={'operation': 'MULTIPLY'}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance, 'Amount': multiply_2}, + attrs={'domain': 'INSTANCE'}) + + divide_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: floor_1}, attrs={'operation': 'DIVIDE'}) + + divide_3 = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: floor}, + attrs={'operation': 'DIVIDE'}) + + floor_2 = nw.new_node(Nodes.Math, input_kwargs={0: divide_3}, attrs={'operation': 'FLOOR'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: floor_2, 1: divide_2}, attrs={'operation': 'MULTIPLY'}) + + multiply_add = nw.new_node(Nodes.Math, input_kwargs={0: divide_2, 2: multiply_3}, attrs={'operation': 'MULTIPLY_ADD'}) + + divide_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: floor}, attrs={'operation': 'DIVIDE'}) + + modulo = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: floor}, + attrs={'operation': 'MODULO'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: modulo, 1: divide_4}, attrs={'operation': 'MULTIPLY'}) + + multiply_add_1 = nw.new_node(Nodes.Math, input_kwargs={0: divide_4, 2: multiply_4}, attrs={'operation': 'MULTIPLY_ADD'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_add, 'Y': multiply_add_1}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': duplicate_elements.outputs["Geometry"], 'Offset': combine_xyz}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': set_position}, attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_oven_rack', singleton=False, type='GeometryNodeTree') +def nodegroup_oven_rack(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'Width', 2.0000), + ('NodeSocketFloatDistance', 'Height', 2.0000), + ('NodeSocketFloatDistance', 'Radius', 0.0200), + ('NodeSocketInt', 'Amount', 5)]) + + quadrilateral = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', + input_kwargs={'Width': group_input.outputs["Width"], 'Height': group_input.outputs["Height"]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"]}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_1}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz_3, 'End': combine_xyz_4}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': curve_line}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance, 'Amount': group_input.outputs["Amount"]}, + attrs={'domain': 'INSTANCE'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, attrs={'operation': 'MULTIPLY'}) + + divide = nw.new_node(Nodes.Math, + input_kwargs={0: multiply_2, 1: group_input.outputs["Amount"]}, + attrs={'operation': 'DIVIDE'}) + + multiply_3 = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: divide}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': duplicate_elements.outputs["Geometry"], 'Offset': combine_xyz}) + + duplicate_elements_1 = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance, 'Amount': group_input.outputs["Amount"]}, + attrs={'domain': 'INSTANCE'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + divide_1 = nw.new_node(Nodes.Math, + input_kwargs={0: multiply_4, 1: group_input.outputs["Amount"]}, + attrs={'operation': 'DIVIDE'}) + + multiply_5 = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements_1.outputs["Duplicate Index"], 1: divide_1}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_5}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': duplicate_elements_1.outputs["Geometry"], 'Offset': combine_xyz_1}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [quadrilateral, set_position, set_position_1]}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': group_input.outputs["Radius"]}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': join_geometry, 'Profile Curve': curve_circle.outputs["Curve"], 'Fill Caps': True}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': curve_to_mesh}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_text', singleton=False, type='GeometryNodeTree') +def nodegroup_text(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVectorTranslation', 'Translation', (1.5000, 0.0000, 0.0000)), + ('NodeSocketString', 'String', 'BrandName'), + ('NodeSocketFloatDistance', 'Size', 0.0500), + ('NodeSocketFloat', 'Offset Scale', 0.0020)]) + + string_to_curves = nw.new_node('GeometryNodeStringToCurves', + input_kwargs={'String': group_input.outputs["String"], 'Size': group_input.outputs["Size"]}, + attrs={'align_y': 'BOTTOM_BASELINE', 'align_x': 'CENTER'}) + + fill_curve = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': string_to_curves.outputs["Curve Instances"]}) + + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, + input_kwargs={'Mesh': fill_curve, 'Offset Scale': group_input.outputs["Offset Scale"]}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': extrude_mesh.outputs["Mesh"], 'Translation': group_input.outputs["Translation"], 'Rotation': (1.5708, 0.0000, 1.5708)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_1}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_handle', singleton=False, type='GeometryNodeTree') +def nodegroup_handle(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'width', 0.0000), + ('NodeSocketFloat', 'length', 0.0000), + ('NodeSocketFloat', 'thickness', 0.0200)]) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': group_input.outputs["width"]}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', 3: cube.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': group_input.outputs["width"]}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_1.outputs["Mesh"], 'Name': 'uv_map', 3: cube_1.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': group_input.outputs["length"]}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_1, 'Translation': combine_xyz}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [store_named_attribute, transform]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"]}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry_1, 'Translation': combine_xyz_3}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["length"], 1: group_input.outputs["width"]}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["width"], 'Y': add, 'Z': group_input.outputs["thickness"]}) + + cube_2 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_1}) + + store_named_attribute_2 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_2.outputs["Mesh"], 'Name': 'uv_map', 3: cube_2.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["length"]}, attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"]}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: multiply_2}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_1, 'Z': add_1}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_2, 'Translation': combine_xyz_2}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_2, transform_1]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_center', singleton=False, type='GeometryNodeTree') +def nodegroup_center(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Geometry', None), + ('NodeSocketVector', 'Vector', (0.0000, 0.0000, 0.0000)), + ('NodeSocketFloat', 'MarginX', 0.5000), + ('NodeSocketFloat', 'MarginY', 0.0000), + ('NodeSocketFloat', 'MarginZ', 0.0000)]) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': group_input.outputs["Geometry"]}) + + subtract = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Vector"], 1: bounding_box.outputs["Min"]}, + attrs={'operation': 'SUBTRACT'}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': subtract.outputs["Vector"]}) + + greater_than = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["X"], 1: group_input.outputs["MarginX"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + subtract_1 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: bounding_box.outputs["Max"], 1: group_input.outputs["Vector"]}, + attrs={'operation': 'SUBTRACT'}) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': subtract_1.outputs["Vector"]}) + + greater_than_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["X"], 1: group_input.outputs["MarginX"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than, 1: greater_than_1}) + + greater_than_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["Y"], 1: group_input.outputs["MarginY"]}, + attrs={'operation': 'GREATER_THAN'}) + + greater_than_3 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["Y"], 1: group_input.outputs["MarginY"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and_1 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than_2, 1: greater_than_3}) + + op_and_2 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and, 1: op_and_1}) + + greater_than_4 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["Z"], 1: group_input.outputs["MarginZ"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + greater_than_5 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["Z"], 1: group_input.outputs["MarginZ"]}, + attrs={'operation': 'GREATER_THAN', 'use_clamp': True}) + + op_and_3 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: greater_than_4, 1: greater_than_5}) + + op_and_4 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and_2, 1: op_and_3}) + + op_not = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and_4}, attrs={'operation': 'NOT'}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'In': op_and_4, 'Out': op_not}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_cube', singleton=False, type='GeometryNodeTree') +def nodegroup_cube(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVectorTranslation', 'Size', (0.1000, 10.0000, 4.0000)), + ('NodeSocketVector', 'Pos', (0.0000, 0.0000, 0.0000)), + ('NodeSocketInt', 'Resolution', 2)]) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': group_input.outputs["Size"], 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"], 'Vertices Z': group_input.outputs["Resolution"]}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', 3: cube.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': store_named_attribute_1, 'Name': 'uv_map'}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + multiply_add = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Size"], 1: (0.5000, 0.5000, 0.5000), 2: group_input.outputs["Pos"]}, + attrs={'operation': 'MULTIPLY_ADD'}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute, 'Translation': multiply_add.outputs["Vector"]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_oven_geometry', singleton=False, type='GeometryNodeTree') +def nodegroup_oven_geometry(nw: NodeWrangler, preprocess: bool=False, use_gas: bool=False, is_placeholder: bool=False): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Depth', 1.0000), + ('NodeSocketFloat', 'Width', 1.0000), + ('NodeSocketFloat', 'Height', 1.0000), + ('NodeSocketFloat', 'DoorThickness', 0.0700), + ('NodeSocketFloat', 'DoorRotation', 0.0000), + ('NodeSocketFloatDistance', 'RackRadius', 0.0100), + ('NodeSocketInt', 'RackHAmount', 2), + ('NodeSocketInt', 'RackDAmount', 5), + ('NodeSocketFloat', 'PanelHeight', 0.3000), + ('NodeSocketFloat', 'PanelThickness', 0.2000), + ('NodeSocketInt', 'BottonAmount', 4), + ('NodeSocketFloatDistance', 'BottonRadius', 0.0500), + ('NodeSocketFloat', 'BottonThickness', 0.0300), + ('NodeSocketFloat', 'HeaterRadiusRatio', 0.1500), + ('NodeSocketString', 'BrandName', 'BrandName'), + ('NodeSocketMaterial', 'Glass', None), + ('NodeSocketMaterial', 'Surface', None), + ('NodeSocketMaterial', 'WhiteMetal', None), + ('NodeSocketMaterial', 'SuperBlackGlass', None), + ('NodeSocketMaterial', 'Back', None), + ('NodeSocketBool', 'is_placeholder', is_placeholder)]) + + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["DoorThickness"], 'Y': group_input.outputs["Width"], 'Z': group_input.outputs["Height"]}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["Depth"]}) + + cube = nw.new_node(nodegroup_cube().name, input_kwargs={'Size': combine_xyz_1, 'Pos': combine_xyz_2}) + + position = nw.new_node(Nodes.InputPosition) + + center = nw.new_node(nodegroup_center().name, + input_kwargs={'Geometry': cube, 'Vector': position, 'MarginX': -1.0000, 'MarginY': 0.1000, 'MarginZ': 0.1500}) + + set_material_2 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': cube, 'Selection': center.outputs["In"], 'Material': group_input.outputs["Glass"]}) + + set_material_3 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': set_material_2, 'Selection': center.outputs["Out"], 'Material': group_input.outputs["Surface"]}) + + # set_shade_smooth = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': set_material_3}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: 0.0500}, attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: 0.8000}, attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply}, attrs={'operation': 'MULTIPLY'}) + + handle = nw.new_node(nodegroup_handle().name, + input_kwargs={'width': multiply, 'length': multiply_1, 'thickness': multiply_2}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["DoorThickness"]}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, attrs={'operation': 'MULTIPLY'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_3, 1: multiply_4}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: 0.9200}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_13 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': multiply_5}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': handle, 'Translation': combine_xyz_13, 'Rotation': (0.0000, 1.5708, 0.0000)}) + + set_material_8 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_1, 'Material': group_input.outputs["WhiteMetal"]}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["DoorThickness"]}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_12 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_2, 'Y': multiply_6, 'Z': 0.0300}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: 0.0500}, attrs={'operation': 'MULTIPLY'}) + + text = nw.new_node(nodegroup_text().name, + input_kwargs={'Translation': combine_xyz_12, 'String': group_input.outputs["BrandName"], 'Size': multiply_7}) + + text = complete_no_bevel(nw, text, preprocess) + + set_material_9 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': text, 'Material': group_input.outputs["WhiteMetal"]}) + + set_material_8 = complete_bevel(nw, set_material_8, preprocess) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material_3, set_material_8, set_material_9]}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': join_geometry_3}) + + y = nw.scalar_multiply(group_input.outputs["DoorRotation"], 1 if not preprocess else 0) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': y}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["Depth"]}) + + rotate_instances = nw.new_node(Nodes.RotateInstances, + input_kwargs={'Instances': geometry_to_instance, 'Rotation': combine_xyz_3, 'Pivot Point': combine_xyz_4}) + + rotate_instances = nw.new_node(Nodes.RealizeInstances, [rotate_instances]) + + door = nw.new_node(Nodes.Reroute, input_kwargs={'Input': rotate_instances}, label='door') + + multiply_8 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["DoorThickness"], 1: 2.1000}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Depth"], 1: multiply_8}, + attrs={'operation': 'SUBTRACT'}) + + multiply_9 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["DoorThickness"], 1: 2.1000}, + attrs={'operation': 'MULTIPLY'}) + + subtract_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Width"], 1: multiply_9}, + attrs={'operation': 'SUBTRACT'}) + + ovenrack = nw.new_node(nodegroup_oven_rack().name, + input_kwargs={'Width': subtract, 'Height': subtract_1, 'Radius': group_input.outputs["RackRadius"], 'Amount': group_input.outputs["RackDAmount"]}) + + geometry_to_instance_1 = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': ovenrack}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance_1, 'Amount': group_input.outputs["RackHAmount"]}, + attrs={'domain': 'INSTANCE'}) + + multiply_10 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"]}, attrs={'operation': 'MULTIPLY'}) + + multiply_11 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, attrs={'operation': 'MULTIPLY'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: 1.0000}) + + multiply_12 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["DoorThickness"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + subtract_2 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Height"], 1: multiply_12}, + attrs={'operation': 'SUBTRACT'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["RackHAmount"], 1: 1.0000}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: subtract_2, 1: add_4}, attrs={'operation': 'DIVIDE'}) + + multiply_13 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: divide}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_10, 'Y': multiply_11, 'Z': multiply_13}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': duplicate_elements.outputs["Geometry"], 'Offset': combine_xyz_5}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': set_position, 'Material': group_input.outputs["Surface"]}) + + set_material = nw.new_node(Nodes.RealizeInstances, [set_material]) + + racks = nw.new_node(Nodes.Reroute, input_kwargs={'Input': set_material}, label='racks') + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["DoorThickness"]}) + + reroute_10 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': add_5}) + + reroute_11 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Width"]}) + + reroute_8 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["DoorThickness"]}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': reroute_10, 'Y': reroute_11, 'Z': reroute_8}) + + reroute_9 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Height"]}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': reroute_9}) + + cube_1 = nw.new_node(nodegroup_cube().name, input_kwargs={'Size': combine_xyz_6, 'Pos': combine_xyz_7}) + + set_material_5 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': cube_1, 'Material': group_input.outputs["Back"]}) + + # set_shade_smooth_1 = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': set_material_5}) + + subtract_3 = nw.new_node(Nodes.Math, + input_kwargs={0: reroute_10, 1: group_input.outputs["PanelThickness"]}, + attrs={'operation': 'SUBTRACT'}) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["HeaterRadiusRatio"], 1: 2.0000, 2: 0.1000}, + attrs={'operation': 'MULTIPLY_ADD'}) + + heater = nw.new_node(nodegroup_heater().name, + input_kwargs={'width': reroute_11, 'depth': subtract_3, 'radius_ratio': group_input.outputs["HeaterRadiusRatio"], 'arrangement_ratio': multiply_add}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_8, 1: reroute_9}) + + combine_xyz_15 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["PanelThickness"], 'Z': add_6}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': heater, 'Translation': combine_xyz_15}) + + transform_2 = complete_no_bevel(nw, transform_2, preprocess) + + if use_gas: + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material_5]}) + else: + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material_5, transform_2]}) + + heater_1 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': join_geometry_2}, label='heater') + + reroute_14 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Width"]}) + + combine_xyz_9 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["PanelThickness"], 'Y': reroute_14, 'Z': group_input.outputs["PanelHeight"]}) + + add_7 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: group_input.outputs["DoorThickness"]}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add_7}) + + cube_2 = nw.new_node(nodegroup_cube().name, input_kwargs={'Size': combine_xyz_9, 'Pos': combine_xyz_8}) + + position_1 = nw.new_node(Nodes.InputPosition) + + center_1 = nw.new_node(nodegroup_center().name, + input_kwargs={'Geometry': cube_2, 'Vector': position_1, 'MarginX': -1.0000, 'MarginY': 0.0500, 'MarginZ': 0.0500}) + + set_material_4 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': cube_2, 'Selection': center_1.outputs["In"], 'Material': group_input.outputs["Back"]}) + + set_material_7 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': set_material_4, 'Selection': center_1.outputs["Out"], 'Material': group_input.outputs["Surface"]}) + + # set_shade_smooth_3 = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': set_material_7}) + + reroute_13 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["PanelThickness"]}) + + multiply_14 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_14}, attrs={'operation': 'MULTIPLY'}) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': cube_2}) + + add_8 = nw.new_node(Nodes.VectorMath, input_kwargs={0: bounding_box.outputs["Min"], 1: bounding_box.outputs["Max"]}) + + scale = nw.new_node(Nodes.VectorMath, + input_kwargs={0: add_8.outputs["Vector"], 'Scale': 0.5000}, + attrs={'operation': 'SCALE'}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': scale.outputs["Vector"]}) + + combine_xyz_16 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': reroute_13, 'Y': multiply_14, 'Z': separate_xyz.outputs["Z"]}) + + multiply_15 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["PanelHeight"], 1: 0.2000}, + attrs={'operation': 'MULTIPLY'}) + + text_1 = nw.new_node(nodegroup_text().name, + input_kwargs={'Translation': combine_xyz_16, 'String': '12:01', 'Size': multiply_15}) + + set_material_7 = complete_bevel(nw, set_material_7, preprocess) + text_1 = complete_no_bevel(nw, text_1, preprocess) + + join_geometry_5 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material_7, text_1]}) + + combine_xyz_21 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["BottonThickness"]}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_21}) + + reroute_12 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["BottonRadius"]}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': reroute_12}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': curve_line, 'Profile Curve': curve_circle.outputs["Curve"], 'Fill Caps': True}) + + add_9 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_12, 1: 0.0050}) + + o = nw.new_node(nodegroup_o().name, input_kwargs={'Size': add_9}) + + join_geometry_4 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [curve_to_mesh, o]}) + + combine_xyz_10 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': reroute_13, 'Z': separate_xyz.outputs["Z"]}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry_4, 'Translation': combine_xyz_10, 'Rotation': (0.0000, 1.5708, 0.0000)}) + + reroute_16 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': separate_xyz.outputs["Z"]}) + + reroute_15 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["BottonRadius"]}) + + multiply_16 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["PanelHeight"], 1: 0.0500}, + attrs={'operation': 'MULTIPLY'}) + + multiply_add_1 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_15, 1: 1.0000, 2: multiply_16}, attrs={'operation': 'MULTIPLY_ADD'}) + + add_10 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_16, 1: multiply_add_1}) + + combine_xyz_17 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': reroute_13, 'Z': add_10}) + + multiply_17 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["BottonRadius"], 1: 0.2500}, + attrs={'operation': 'MULTIPLY'}) + + text_2 = nw.new_node(nodegroup_text().name, + input_kwargs={'Translation': combine_xyz_17, 'String': 'Off', 'Size': multiply_17}) + + multiply_add_2 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_15, 1: 0.7000, 2: multiply_16}, attrs={'operation': 'MULTIPLY_ADD'}) + + add_11 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_16, 1: multiply_add_2}) + + combine_xyz_18 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': reroute_13, 'Y': multiply_add_2, 'Z': add_11}) + + text_3 = nw.new_node(nodegroup_text().name, + input_kwargs={'Translation': combine_xyz_18, 'String': 'High', 'Size': multiply_17}) + + multiply_18 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_16, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + multiply_add_3 = nw.new_node(Nodes.Math, + input_kwargs={0: reroute_15, 1: -0.7000, 2: multiply_18}, + attrs={'operation': 'MULTIPLY_ADD'}) + + combine_xyz_19 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': reroute_13, 'Y': multiply_add_3, 'Z': add_11}) + + text_4 = nw.new_node(nodegroup_text().name, + input_kwargs={'Translation': combine_xyz_19, 'String': 'Low', 'Size': multiply_17}) + + add_12 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_13, 1: group_input.outputs["BottonThickness"]}) + + combine_xyz_20 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_12, 'Z': separate_xyz.outputs["Z"]}) + + multiply_19 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["BottonThickness"], 1: 0.1000}, + attrs={'operation': 'MULTIPLY'}) + + text_5 = nw.new_node(nodegroup_text().name, + input_kwargs={'Translation': combine_xyz_20, 'String': '1', 'Size': group_input.outputs["BottonRadius"], 'Offset Scale': multiply_19}) + + join_geometry_6 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, text_2, text_3, text_4, text_5]}) + + geometry_to_instance_2 = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': join_geometry_6}) + + add_13 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["BottonAmount"], 1: 2.0000}) + + reroute_6 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': add_13}) + + duplicate_elements_1 = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance_2, 'Amount': reroute_6}, + attrs={'domain': 'INSTANCE'}) + + add_14 = nw.new_node(Nodes.Math, input_kwargs={0: duplicate_elements_1.outputs["Duplicate Index"], 1: 1.0000}) + + add_15 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_6, 1: 1.0000}) + + divide_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: add_15}, attrs={'operation': 'DIVIDE'}) + + multiply_20 = nw.new_node(Nodes.Math, input_kwargs={0: add_14, 1: divide_1}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_11 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_20}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': duplicate_elements_1.outputs["Geometry"], 'Offset': combine_xyz_11}) + + multiply_21 = nw.new_node(Nodes.Math, input_kwargs={0: add_13}, attrs={'operation': 'MULTIPLY'}) + + add_16 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_21, 1: -1.0100}) + + greater_than = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements_1.outputs["Duplicate Index"], 1: add_16}, + attrs={'operation': 'GREATER_THAN'}) + + add_17 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_21, 1: 0.9900}) + + less_than = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements_1.outputs["Duplicate Index"], 1: add_17}, + attrs={'operation': 'LESS_THAN'}) + + minimum = nw.new_node(Nodes.Math, input_kwargs={0: greater_than, 1: less_than}, attrs={'operation': 'MINIMUM'}) + + delete_geometry = nw.new_node(Nodes.DeleteGeometry, + input_kwargs={'Geometry': set_position_1, 'Selection': minimum}, + attrs={'domain': 'INSTANCE'}) + + set_material_6 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': delete_geometry, 'Material': group_input.outputs["WhiteMetal"]}) + + botton = nw.new_node(Nodes.Reroute, input_kwargs={'Input': set_material_6}, label='botton') + + botton = complete_no_bevel(nw, botton, preprocess) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [join_geometry_5, botton]}) + + geometry_to_instance_3 = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': join_geometry_1}) + + combine_xyz_14 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Height"]}) + + panel_bbox = nw.new_node(Nodes.BoundingBox,input_kwargs={'Geometry': geometry_to_instance_3}) + + switch_1 = nw.new_node(Nodes.Switch, input_kwargs={'Switch': group_input.outputs["is_placeholder"], 'False': geometry_to_instance_3, 'True': panel_bbox}) + + rotate_instances_1 = nw.new_node(Nodes.RotateInstances, + input_kwargs={'Instances': switch_1, 'Rotation': (0.0000, -0.1745, 0.0000), 'Pivot Point': combine_xyz_14}) + + rotate_instances_1 = nw.new_node(Nodes.RealizeInstances, [rotate_instances_1]) + + panel = nw.new_node(Nodes.Reroute, input_kwargs={'Input': rotate_instances_1}, label='panel') + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Depth"], 'Y': group_input.outputs["Width"], 'Z': group_input.outputs["Height"]}) + + hollowcube = nw.new_node(nodegroup_hollow_cube().name, + input_kwargs={'Size': combine_xyz, 'Thickness': group_input.outputs["DoorThickness"], 'Switch2': True, 'Switch4': True}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': hollowcube, 'Material': group_input.outputs["Surface"]}) + + subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': set_material_1, 'Level': 0}) + + # set_shade_smooth_2 = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': subdivide_mesh}) + + body = nw.new_node(Nodes.Reroute, input_kwargs={'Input': subdivide_mesh}, label='Body') + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [door, racks, heater_1, panel, body]}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [door, racks, heater_1, body]}) + body_bbox = nw.new_node(Nodes.BoundingBox,input_kwargs={'Geometry': join_geometry_2}) + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [body_bbox, panel]}) + + + switch_2 = nw.new_node(Nodes.Switch, input_kwargs={'Switch': group_input.outputs["is_placeholder"], 'False': join_geometry, 'True': join_geometry_3}) + geometry = nw.new_node(Nodes.RealizeInstances,[switch_2]) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': geometry}) diff --git a/infinigen/assets/appliances/tv.py b/infinigen/assets/appliances/tv.py new file mode 100644 index 000000000..2a1dcebc6 --- /dev/null +++ b/infinigen/assets/appliances/tv.py @@ -0,0 +1,221 @@ +# 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: +# - Lingjie Mei: primary author +# - Karhan Kayan: fix rotation + +import bpy +import bmesh +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import read_co, write_attribute, write_co, read_area, mirror, read_normal +from infinigen.assets.utils.nodegroup import geo_radius +from infinigen.assets.utils.object import data2mesh, join_objects, mesh2obj, new_bbox, new_cube, new_plane +from infinigen.assets.utils.uv import compute_uv_direction, face_corner2faces, unwrap_faces +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.surface import read_attr_data, write_attr_data +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList +from infinigen.assets.materials.text import Text + + +class TVFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(TVFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.aspect_ratio = np.random.choice([9 / 16, 3 / 4]) + self.width = uniform(0.6, 2.1) + self.screen_bevel_width = uniform(0, .01) + self.side_margin = log_uniform(.005, .01) + self.bottom_margin = uniform(.005, .03) + self.depth = uniform(.02, .04) + self.has_depth_extrude = uniform() < .4 + if self.has_depth_extrude: + self.depth_extrude = self.depth * uniform(2, 5) + else: + self.depth_extrude = self.depth * 1.5 + self.leg_type = np.random.choice(['two-legged', 'single-legged']) # 'none', + self.leg_length = uniform(.1, .2) + self.leg_length_y = uniform(.1, .15) + self.leg_radius = uniform(.008, .015) + self.leg_width = uniform(.5, .8) + self.leg_bevel_width = uniform(.01, .02) + + materials = self.get_material_params() + self.surface = materials['surface'] + self.scratch = materials['scratch'] + self.edge_wear = materials['edge_wear'] + self.screen_surface = materials['screen_surface'] + self.support_surface = materials['support'] + + def get_material_params(self): + material_assignments = AssetList['TVFactory']() + surface = material_assignments['surface'].assign_material() + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = np.random.uniform() < scratch_prob + is_edge_wear = np.random.uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + args = (self.factory_seed, False) + kwargs = {'emission': 0.01 if uniform() < 0.1 else uniform(2, 3)} + screen_surface = material_assignments['screen_surface'].assign_material() + if screen_surface == Text: + screen_surface = screen_surface(*args, **kwargs) + support = material_assignments['support'].assign_material() + return { + 'surface': surface, 'scratch': scratch, 'edge_wear': edge_wear, 'screen_surface': screen_surface, + 'support': support + } + + @property + def height(self): + return self.aspect_ratio * self.width + + @property + def total_width(self): + return self.width + 2 * self.side_margin + + @property + def total_height(self): + return self.height + self.side_margin + self.bottom_margin + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + match self.leg_type: + case 'two-legged': + max_x = self.leg_length_y / 2 - (1 - self.leg_width) * self.depth_extrude + case _: + max_x = self.leg_length_y / 2 - self.depth_extrude / 2 + return new_bbox( + - self.depth_extrude - self.depth, max_x, -self.total_width / 2, + self.total_width / 2, -self.leg_length - self.leg_radius / 2, self.total_height + ) + + def create_asset(self, **params) -> bpy.types.Object: + obj = self.make_base() + self.make_screen(obj) + parts = [obj] + match self.leg_type: + case 'two-legged': + legs = self.add_two_legs() + case _: + legs = self.add_single_leg() + for l in legs: + write_attribute(l, 1, 'leg', 'FACE', 'INT') + parts.extend(legs) + obj = join_objects(parts) + obj.rotation_euler[2] = np.pi / 2 + butil.apply_transform(obj) + return obj + + def make_screen(self, obj): + cutter = new_cube() + cutter.location = 0, -1, 1 + butil.apply_transform(cutter, True) + cutter.scale = self.width / 2, 1, self.height / 2 + cutter.location = 0, 1e-3, self.bottom_margin + butil.apply_transform(cutter, True) + butil.modify_mesh(obj, 'BOOLEAN', object=cutter, operation='DIFFERENCE') + butil.delete(cutter) + areas = read_area(obj) + screen = np.zeros(len(areas), int) + y = read_normal(obj)[:, 1] < 0 + screen[np.argmax(areas + 1e5 * y)] = 1 + fc2f = face_corner2faces(obj) + unwrap_faces(obj, screen) + bbox = compute_uv_direction(obj, 'x', 'z', screen[fc2f]) + write_attr_data(obj, 'screen', screen, domain='FACE', type='INT') + self.screen_surface.apply(obj, 'screen', bbox) + + def make_base(self): + obj = new_cube() + obj.location = 0, 1, 1 + butil.apply_transform(obj, True) + obj.scale = self.total_width / 2, self.depth / 2, self.total_height / 2 + butil.apply_transform(obj) + butil.modify_mesh(obj, 'BEVEL', width=self.screen_bevel_width, segments=8) + if not self.has_depth_extrude: + return obj + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + geom = [f for f in bm.faces if f.normal[1] > .5] + bmesh.ops.delete(bm, geom=geom, context='FACES_KEEP_BOUNDARY') + bmesh.update_edit_mesh(obj.data) + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.region_to_loop() + height_min, height_max = self.total_height * uniform(.1, .3), self.total_height * uniform(.5, .7) + width = self.total_width * uniform(.3, .6) + extra = new_plane() + extra.scale = width / 2, (height_max - height_min) / 2, 1 + extra.rotation_euler[0] = -np.pi / 2 + extra.location = 0, self.depth_extrude + self.depth, self.total_height / 2 + obj = join_objects([obj, extra]) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.bridge_edge_loops(number_cuts=32, profile_shape_factor=-uniform(.0, .4)) + x, y, z = read_co(obj).T + z += (height_max + height_min - self.total_height) / 2 * np.clip( + y - self.depth, 0, + None + ) / self.depth_extrude + write_co(obj, np.stack([x, y, z], -1)) + return obj + + def add_two_legs(self): + vertices = (-self.total_width / 2 * self.leg_width * uniform(0, .6), 0, self.total_height * uniform(.3, .5)), ( + 0, 0, -self.leg_length), ( + 0, self.leg_length_y / 2, -self.leg_length), (0, -self.leg_length_y / 2, -self.leg_length) + edges = (0, 1), (1, 2), (1, 3) + leg = mesh2obj(data2mesh(vertices, edges)) + surface.add_geomod(leg, geo_radius, apply=True, input_args=[self.leg_radius, 16]) + x, y, z = read_co(leg).T + write_co(leg, np.stack([x, y, np.maximum(z, -self.leg_length - self.leg_radius * uniform(.0, .6))], -1)) + leg_ = deep_clone_obj(leg) + butil.select_none() + leg.location = self.total_width / 2 * self.leg_width, (1 - self.leg_width) * self.depth_extrude, 0 + butil.apply_transform(leg, True) + mirror(leg_) + leg_.location = -self.total_width / 2 * self.leg_width, (1 - self.leg_width) * self.depth_extrude, 0 + butil.apply_transform(leg_, True) + return [leg, leg_] + + def add_single_leg(self): + leg = new_cube() + leg.location = 0, 1, 1 + butil.apply_transform(leg, True) + leg.location = 0, self.depth_extrude / 2, -self.leg_length + leg.scale = [self.total_width * uniform(.05, .1), self.leg_radius, + (self.leg_length + self.total_height * uniform(.3, .5)) / 2] + butil.apply_transform(leg, True) + butil.modify_mesh(leg, 'BEVEL', width=self.leg_bevel_width, segments=8) + base = new_cube() + base.location = 0, self.depth_extrude / 2, -self.leg_length + base.scale = [self.total_width * uniform(.15, .3), self.leg_length_y / 2, self.leg_radius] + butil.apply_transform(base, True) + butil.modify_mesh(base, 'BEVEL', width=self.leg_bevel_width, segments=8) + return [leg, base] + + def finalize_assets(self, assets): + self.surface.apply(assets, selection='!screen', rough=True, metal_color='bw') + self.support_surface.apply(assets, selection='leg', rough=True, metal_color='bw') + + +class MonitorFactory(TVFactory): + def __init__(self, factory_seed, coarse=False): + super(MonitorFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.width = log_uniform(.4, .8) + self.leg_type = 'single-legged' diff --git a/infinigen/assets/bathroom/__init__.py b/infinigen/assets/bathroom/__init__.py new file mode 100644 index 000000000..ab167ac6d --- /dev/null +++ b/infinigen/assets/bathroom/__init__.py @@ -0,0 +1,8 @@ +# 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: Lingjie Mei +from .bathtub import BathtubFactory +from .bathroom_sink import BathroomSinkFactory, StandingSinkFactory +from .hardware import HardwareFactory +from .toilet import ToiletFactory diff --git a/infinigen/assets/bathroom/bathroom_sink.py b/infinigen/assets/bathroom/bathroom_sink.py new file mode 100644 index 000000000..c1474d254 --- /dev/null +++ b/infinigen/assets/bathroom/bathroom_sink.py @@ -0,0 +1,143 @@ +# 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: Lingjie Mei +import bpy +import bmesh +import numpy as np +from numpy.random import uniform + +from infinigen.assets.bathroom import BathtubFactory +from infinigen.assets.table_decorations import TapFactory +from infinigen.assets.utils.decorate import read_co, subdivide_edge_ring, subsurf +from infinigen.assets.utils.object import join_objects, new_base_cylinder, new_bbox, new_cube, origin2lowest +from infinigen.core.util import blender as butil +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.assets.material_assignments import AssetList + + +class BathroomSinkFactory(BathtubFactory): + + def __init__(self, factory_seed, coarse=False): + super(BathroomSinkFactory, self).__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.width = uniform(.6, .9) + self.size = self.width * log_uniform(.55, .8) + self.depth = self.width * log_uniform(.2, .4) + self.contour_fn = self.make_box_contour + self.sink_types = np.random.choice(['undermount', 'drop-in', 'vessel']) + self.has_stand = False + match self.sink_types: + case 'undermount': + self.bathtub_type = 'freestanding' + self.has_extrude = uniform() < .7 + case 'drop-in': + self.bathtub_type = 'alcove' + self.has_extrude = True + case _: + self.bathtub_type = np.random.choice(['alcove', 'freestanding']) + self.has_extrude = uniform() < .7 + self.has_stand = True + self.tap_factory = TapFactory(self.factory_seed) + self.disp_x = [self.disp_x[0], self.disp_x[0]] + self.alcove_levels = 0 if uniform() < .5 else np.random.randint(2, 4) + self.thickness = .01 if self.has_base else uniform(.01, .03) + self.size_extrude = uniform(.2, .35) + self.tap_offset = uniform(.0, .05) + self.stand_radius = self.width / 2 * log_uniform(.15, .2) + self.stand_bottom = self.width * log_uniform(.2, .3) if uniform() < .6 else self.stand_radius + self.stand_height = uniform(.7, .9) - self.depth + self.is_stand_circular = uniform() < .5 + self.is_hole_centered = True + material_assignments = AssetList['BathroomSinkFactory']() + self.surface = material_assignments["surface"].assign_material() + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + return new_bbox( + -(self.size_extrude + 1) * self.size, 0, 0, self.width, + -self.stand_height if self.has_stand else 0, self.depth + ) + + def create_asset(self, **params) -> bpy.types.Object: + if self.has_base: + obj = self.make_base() + cutter = self.make_cutter() + butil.modify_mesh(obj, 'BOOLEAN', object=cutter, operation='DIFFERENCE') + butil.delete(cutter) + else: + obj = self.make_bowl() + self.remove_top(obj) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness) + subsurf(obj, self.side_levels) + obj.location = np.array(obj.location) - np.min(read_co(obj), 0) + butil.apply_transform(obj, True) + obj.scale = np.array([self.width, self.size, self.depth]) / np.array(obj.dimensions) + butil.apply_transform(obj, True) + if self.has_extrude: + self.extrude_back(obj) + if self.has_stand: + self.add_stand(obj) + hole = self.add_hole(obj) + obj = join_objects([obj, hole]) + obj.rotation_euler[-1] = np.pi / 2 + butil.apply_transform(obj, True) + self.surface.apply(obj, clear=True, metal_color='plain') + if self.has_extrude: + tap = self.tap_factory(np.random.randint(1e7)) + min_x = np.min(read_co(tap)[:, 0]) + tap.location = (-1 - self.size_extrude + self.tap_offset) * self.size - min_x, self.width / 2, self.depth + butil.apply_transform(tap, True) + obj = join_objects([obj, tap]) + return obj + + def extrude_back(self, obj): + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='FACE') + bpy.ops.mesh.select_all(action='DESELECT') + bm = bmesh.from_edit_mesh(obj.data) + for f in bm.faces: + f.select_set(f.calc_center_median()[1] > self.size / 2 and f.normal[1] > .1) + bm.select_flush(False) + bmesh.update_edit_mesh(obj.data) + bpy.ops.mesh.extrude_region_move( + TRANSFORM_OT_translate={'value': (0, self.size_extrude * self.size, 0)} + ) + + def add_stand(self, obj): + if self.is_stand_circular: + stand = new_base_cylinder(vertices=16) + else: + stand = new_cube() + stand.scale = self.stand_radius, self.stand_radius, self.stand_height / 2 + stand.location = self.width / 2, self.size / 2, -self.stand_height / 2 + butil.apply_transform(stand, True) + subdivide_edge_ring(stand, np.random.randint(3, 6)) + with butil.ViewportMode(stand, 'EDIT'): + bpy.ops.mesh.select_mode(type='FACE') + bm = bmesh.from_edit_mesh(stand.data) + for f in bm.faces: + f.select_set(f.normal[-1] < -.1) + bm.select_flush(False) + bmesh.update_edit_mesh(stand.data) + bpy.ops.transform.resize( + value=(self.stand_bottom / self.stand_radius, self.stand_bottom / self.stand_radius, 1) + ) + subsurf(stand, 2, True) + subsurf(stand, 1) + obj = join_objects([obj, stand]) + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + + +class StandingSinkFactory(BathroomSinkFactory): + def __init__(self, factory_seed, coarse=False): + super(StandingSinkFactory, self).__init__(factory_seed, coarse) + self.bathtub_type = 'freestanding' + self.has_extrude = True + self.has_stand = True diff --git a/infinigen/assets/bathroom/bathtub.py b/infinigen/assets/bathroom/bathtub.py new file mode 100644 index 000000000..1d6df5012 --- /dev/null +++ b/infinigen/assets/bathroom/bathtub.py @@ -0,0 +1,289 @@ +# 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: Lingjie Mei +import bpy +import bmesh +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import ( + read_center, read_co, read_normal, subsurf, write_attribute, + write_co, +) +from infinigen.assets.utils.nodegroup import geo_radius +from infinigen.assets.utils.object import join_objects, new_bbox, new_cube, new_cylinder, new_line +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util import blender as butil +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.math import FixedSeed + +from infinigen.assets.utils.autobevel import BevelSharp +from infinigen.assets.material_assignments import AssetList + + +class BathtubFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(BathtubFactory, self).__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.width = uniform(1.5, 2) + self.size = uniform(.8, 1) + self.depth = uniform(.55, .7) + prob = np.array([2, 2]) + self.bathtub_type = np.random.choice(['alcove', 'freestanding'], p=prob / prob.sum()) # , 'corner' + self.contour_fn = self.make_corner_contour if self.has_corner else self.make_box_contour + self.has_curve = uniform() < .5 + self.has_legs = uniform() < .5 + + self.thickness = uniform(.04, .08) if self.has_base else uniform(.02, .04) + self.disp_x = uniform(0, .2, 2) + self.disp_y = uniform(0, .1) + + self.leg_height = uniform(.2, .3) * self.depth + self.leg_side = uniform(.05, .1) + self.leg_radius = uniform(.02, .03) + self.leg_y_scale = uniform() + self.leg_subsurf_level = np.random.randint(3) + + self.taper_factor = uniform(-.1, .1) + self.stretch_factor = uniform(-.2, .2) + + self.alcove_levels = np.random.randint(1, 3) if self.has_base else 1 + self.levels = 5 + self.side_levels = 2 + + self.is_hole_centered = False + self.hole_radius = uniform(.015, .02) + + # /////////////////// assign materials /////////////////// + material_assignments = AssetList['BathtubFactory']() + self.surface = material_assignments["surface"].assign_material() + self.leg_surface = material_assignments["leg"].assign_material() + self.hole_surface = material_assignments["hole"].assign_material() + is_scratch = uniform() < material_assignments["wear_tear_prob"][0] + is_edge_wear = uniform() < material_assignments["wear_tear_prob"][1] + self.scratch = material_assignments["wear_tear"][0] if is_scratch else None + self.edge_wear = material_assignments["wear_tear"][1] if is_edge_wear else None + # //////////////////////////////////////////////////////// + + self.beveler = BevelSharp(mult=5, segments=5) + + @property + def has_base(self): + return self.bathtub_type != 'freestanding' + + @property + def has_corner(self): + return self.bathtub_type == 'corner' + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + return new_bbox(-self.size, 0, 0, self.width, 0, self.depth) + + def create_asset(self, **params) -> bpy.types.Object: + if self.has_base: + obj = self.make_base() + cutter = self.make_cutter() + butil.modify_mesh(obj, 'BOOLEAN', object=cutter, operation='DIFFERENCE') + butil.delete(cutter) + else: + obj = self.make_freestanding() + parts = [obj] + if self.has_legs: + parts.extend(self.make_legs(obj)) + else: + parts.append(self.add_base(obj)) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness) + subsurf(obj, self.side_levels) + obj = join_objects(parts) + hole = self.add_hole(obj) + obj = join_objects([obj, hole]) + obj.rotation_euler[-1] = np.pi / 2 + butil.apply_transform(obj, True) + + if self.bathtub_type == 'freestanding': + butil.modify_mesh(obj, 'SUBSURF', levels=1, apply=True) + else: + self.beveler(obj) + + return obj + + def make_freestanding(self): + obj = self.make_bowl() + self.remove_top(obj) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.region_to_loop() + bpy.ops.mesh.extrude_edges_move() + bpy.ops.transform.resize( + value=(1 + self.thickness * 2 / self.width, 1 + self.thickness / self.size, 1) + ) + obj.location[1] -= self.size / 2 + butil.apply_transform(obj, True) + butil.modify_mesh(obj, 'SIMPLE_DEFORM', deform_method='TAPER', angle=self.taper_factor) + butil.modify_mesh(obj, 'SIMPLE_DEFORM', deform_method='STRETCH', angle=self.taper_factor) + obj.location = 0, self.size / 2, -np.min(read_co(obj)[:, -1]) * uniform(.5, .7) + butil.apply_transform(obj, True) + return obj + + def remove_top(self, obj): + butil.select_none() + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + geom = [f for f in bm.faces if f.calc_center_median()[-1] > self.depth] + bmesh.ops.delete(bm, geom=geom, context='FACES_KEEP_BOUNDARY') + bmesh.update_edit_mesh(obj.data) + + def make_legs(self, obj): + legs = [] + co, normal = read_center(obj), read_normal(obj) + x, y, z = co.T + leg_height = np.min(z) + self.leg_height + for u in [1, -1]: + for v in [1, -1]: + metric = np.where(z < leg_height, u * x + v * y, -np.inf) + i = np.argmax(metric) + p = co[i] + n = normal[i] + q = co[i] + self.leg_side * np.array([n[0], n[1] * self.leg_y_scale, n[2]]) + r = np.array([q[0], q[1], 0]) + leg = new_line(2) + write_co(leg, np.stack([p, q, r])) + subsurf(leg, self.leg_subsurf_level) + surface.add_geomod( + leg, geo_radius, apply=True, input_args=[self.leg_radius, 32], + input_kwargs={'to_align_tilt': False} + ) + butil.modify_mesh(leg, 'BEVEL', width=self.leg_radius * uniform(.3, .7)) + leg.location[-1] = self.leg_radius + butil.apply_transform(leg, True) + write_attribute(leg, 1, 'leg', 'FACE') + legs.append(leg) + return legs + + def add_base(self, obj): + obj = deep_clone_obj(obj) + cutter = new_cube() + x, y, z_ = read_co(obj).T + cutter.scale = 10, 10, np.min(z_) + self.leg_height + butil.apply_transform(cutter, True) + butil.modify_mesh(obj, 'BOOLEAN', object=cutter, operation='INTERSECT') + butil.delete(cutter) + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + geom = [f for f in bm.faces if len(f.verts) > 10] + bmesh.ops.delete(bm, geom=geom, context='FACES_KEEP_BOUNDARY') + bmesh.update_edit_mesh(obj.data) + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.region_to_loop() + bpy.ops.mesh.select_all(action='INVERT') + bpy.ops.mesh.delete(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={'value': (0, 0, -self.depth)}) + x, y, z = read_co(obj).T + z = np.clip(z, 0, None) + write_co(obj, np.stack([x, y, z], -1)) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.normals_make_consistent(inside=False) + subsurf(obj, 2) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness) + return obj + + def make_box_contour(self, t, i): + return [(t + self.disp_x[0] * i, t + self.disp_y * i), + (self.width - t - self.disp_x[1] * i, t + self.disp_y * i), + (self.width - t - self.disp_x[1] * i, self.size - t - self.disp_y * i), + (t + self.disp_x[0] * i, self.size - t - self.disp_y * i)] + + def make_corner_contour(self, t, i): + return [(t + self.disp_y * i, t + self.disp_y * i), + (self.width - t - self.disp_x[1] * i, t + self.disp_y * i), + (self.width - t - self.disp_x[1] * i, self.size - (t + self.disp_y * i) / np.sqrt(2)), + (self.size - (t + self.disp_y * i) / np.sqrt(2), self.width - t - self.disp_x[0] * i), + (t + self.disp_y * i, self.width - t - self.disp_x[0] * i)] + + # noinspection PyArgumentList + def make_base(self): + contour = self.contour_fn(0, 0) + obj = new_cylinder(vertices=len(contour)) + co = np.concatenate([np.array([[x, y, 0], [x, y, self.depth]]) for x, y in contour]) + write_co(obj, co) + return obj + + # noinspection PyArgumentList + def make_bowl(self): + if self.has_curve: + lower = self.contour_fn(0, 1) + upper = self.contour_fn(0, -1) + else: + lower = self.contour_fn(0, 0) + upper = self.contour_fn(0, 0) + obj = new_cylinder(vertices=len(lower)) + co = np.concatenate( + [np.array([[x, y, 0], [z, w, self.depth * 2]]) for (x, y), (z, w) in zip(lower[::-1], upper[::-1])] + ) + write_co(obj, co) + subsurf(obj, self.alcove_levels, True) + levels = self.levels - self.alcove_levels - self.side_levels + subsurf(obj, levels) + return obj + + # noinspection PyArgumentList + def make_cutter(self): + if self.has_curve: + lower = self.contour_fn(self.thickness, 1) + upper = self.contour_fn(self.thickness, -1) + else: + lower = self.contour_fn(self.thickness, 0) + upper = self.contour_fn(self.thickness, 0) + obj = new_cylinder(vertices=len(lower)) + co = np.concatenate( + [np.array([[x, y, self.thickness], [z, w, self.depth * 2 - self.thickness]]) for (x, y), (z, w) in + zip(lower[::-1], upper[::-1])] + ) + write_co(obj, co) + subsurf(obj, self.alcove_levels, True) + levels = self.levels - self.alcove_levels + subsurf(obj, levels) + return obj + + def find_hole(self, obj, x=None, y=None): + if x is None: + x = self.width / 2 + if y is None: + y = self.size / 2 + up_facing = read_normal(obj)[:, -1] > 0 + center = read_center(obj) + i = np.argmin(np.abs(center[:, :2] - np.array([[x, y]])).sum(1) - up_facing) + return center[i] + + def add_hole(self, obj): + match self.bathtub_type: + case 'alcove': + location = self.find_hole(obj) + case 'freestanding': + location = self.find_hole(obj, uniform(.35, .4) * self.width) + case _: + location = self.find_hole(obj, self.size / 2, self.size / 2) + if self.is_hole_centered: + location = self.find_hole(obj) + obj = new_cylinder() + obj.scale = self.hole_radius, self.hole_radius, .005 + obj.location = location + butil.apply_transform(obj, True) + write_attribute(obj, 1, 'hole', 'FACE') + return obj + + def finalize_assets(self, assets): + self.surface.apply(assets, clear=True) + if self.has_legs and not self.has_base: + self.leg_surface.apply(assets, 'leg', metal_color='bw+natural') + self.hole_surface.apply(assets, 'hole', metal_color='bw+natural') + + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) diff --git a/infinigen/assets/bathroom/hardware.py b/infinigen/assets/bathroom/hardware.py new file mode 100644 index 000000000..411dcbbda --- /dev/null +++ b/infinigen/assets/bathroom/hardware.py @@ -0,0 +1,121 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import subsurf +from infinigen.assets.utils.object import join_objects, new_base_cylinder, new_cube +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.core.util.random import log_uniform +from infinigen.assets.material_assignments import AssetList + + +class HardwareFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(HardwareFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.attachment_radius = uniform(.02, .03) + self.attachment_depth = uniform(.01, .015) + self.radius = uniform(.01, .015) + self.depth = uniform(.06, .1) + self.is_circular = uniform() < .5 + self.hardware_type = np.random.choice(['hook', 'holder', 'bar', 'ring']) + self.hook_length = self.attachment_radius * uniform(2, 4) + self.holder_length = uniform(.15, .25) + self.bar_length = uniform(.4, .8) + self.extension_length = self.attachment_radius * uniform(2, 3) + self.ring_radius = log_uniform(2, 6) * self.attachment_radius + + material_assignments = AssetList['HardwareFactory']() + self.surface = material_assignments['surface'].assign_material() + is_scratch = uniform() < material_assignments['wear_tear_prob'][0] + is_edge_wear = uniform() < material_assignments['wear_tear_prob'][1] + self.scratch = material_assignments['wear_tear'][0] if is_scratch else None + self.edge_wear = material_assignments['wear_tear'][1] if is_edge_wear else None + + def make_attachment(self): + base = new_base_cylinder() if self.is_circular else new_cube() + base.scale = self.attachment_radius, self.attachment_radius, self.attachment_depth / 2 + base.rotation_euler[0] = np.pi / 2 + base.location[1] = -self.attachment_depth / 2 + butil.apply_transform(base, True) + + rod = new_base_cylinder() if self.is_circular else new_cube() + rod.scale = self.radius, self.radius, self.depth / 2 + rod.rotation_euler[0] = np.pi / 2 + rod.location[1] = -self.depth / 2 + butil.apply_transform(rod, True) + obj = join_objects([base, rod]) + return obj + + def make_hook(self): + obj = new_base_cylinder() if self.is_circular else new_cube() + obj.scale = self.radius, self.radius, self.hook_length / 2 + butil.apply_transform(obj) + return obj + + def make_holder(self): + obj = new_base_cylinder() if self.is_circular else new_cube() + obj.scale = self.radius, self.radius, (self.holder_length + self.extension_length) / 2 + obj.rotation_euler[1] = np.pi / 2 + obj.location[0] = (self.holder_length - self.extension_length) / 2 + butil.apply_transform(obj, True) + return obj + + def make_bar(self): + obj = new_base_cylinder() if self.is_circular else new_cube() + obj.scale = self.radius, self.radius, self.bar_length / 2 + self.extension_length + obj.rotation_euler[1] = np.pi / 2 + obj.location[0] = self.bar_length / 2 + butil.apply_transform(obj, True) + return obj + + def make_ring(self): + bpy.ops.mesh.primitive_torus_add( + major_segments=128, major_radius=self.ring_radius, + minor_radius=self.radius * uniform(.4, .7) + ) + obj = bpy.context.active_object + obj.rotation_euler[0] = np.pi / 2 + obj.location = 0, self.attachment_depth, -self.ring_radius + butil.apply_transform(obj, True) + subsurf(obj, 2) + return obj + + def create_asset(self, **params) -> bpy.types.Object: + match self.hardware_type: + case 'hook': + extra = self.make_hook() + case 'holder': + extra = self.make_holder() + case 'bar': + extra = self.make_bar() + case 'ring': + extra = self.make_ring() + case _: + return self.make_attachment() + extra.scale = [1 + 1e-3] * 3 + extra.location[1] = -self.depth + butil.apply_transform(extra, True) + parts = [self.make_attachment(), extra] + if self.hardware_type == 'bar': + attachment_ = self.make_attachment() + attachment_.location[0] = self.bar_length + butil.apply_transform(attachment_, True) + parts.append(attachment_) + obj = join_objects(parts) + obj.rotation_euler[-1] = np.pi / 2 + butil.apply_transform(obj) + return obj + + def finalize_assets(self, assets): + self.surface.apply(assets, metal_color='plain') + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) diff --git a/infinigen/assets/bathroom/toilet.py b/infinigen/assets/bathroom/toilet.py new file mode 100644 index 000000000..2e82f638e --- /dev/null +++ b/infinigen/assets/bathroom/toilet.py @@ -0,0 +1,292 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import ( + read_center, read_co, read_edge_center, read_edges, read_normal, + select_edges, select_faces, select_vertices, subsurf, write_attribute, write_co, +) +from infinigen.assets.utils.draw import align_bezier +from infinigen.assets.utils.object import join_objects, new_bbox, new_cube, new_cylinder +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util import blender as butil +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.math import normalize, FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.assets.material_assignments import AssetList + + +class ToiletFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.size = uniform(.4, .5) + self.width = self.size * uniform(.7, .8) + self.height = self.size * uniform(.8, .9) + self.size_mid = uniform(.6, .65) + self.curve_scale = log_uniform(.8, 1.2, 4) + self.depth = self.size * uniform(.5, .6) + self.tube_scale = uniform(.25, .3) + self.thickness = uniform(.05, .06) + self.extrude_height = uniform(.015, .02) + self.stand_depth = self.depth * uniform(.85, .95) + self.stand_scale = uniform(.7, .85) + self.bottom_offset = uniform(.5, 1.5) + self.back_thickness = self.thickness * uniform(0, .8) + self.back_size = self.size * uniform(.55, .65) + self.back_scale = uniform(.8, 1.) + self.seat_thickness = uniform(.1, .3) * self.thickness + self.seat_size = self.thickness * uniform(1.2, 1.6) + self.has_seat_cut = uniform() < .1 + self.tank_width = self.width * uniform(1., 1.2) + self.tank_height = self.height * uniform(.6, 1.) + self.tank_size = self.back_size - self.seat_size - uniform(.02, .03) + self.tank_cap_height = uniform(.03, .04) + self.tank_cap_extrude = 0 if uniform() < .5 else uniform(.005, .01) + self.cover_rotation = - uniform(0, np.pi / 2) + self.hardware_type = np.random.choice(['button', 'handle']) + self.hardware_cap = uniform(.01, .015) + self.hardware_radius = uniform(.015, .02) + self.hardware_length = uniform(.04, .05) + self.hardware_on_side = uniform() < .5 + material_assignments = AssetList['ToiletFactory']() + self.surface = material_assignments['surface'].assign_material() + self.hardware_surface = material_assignments['hardware_surface'].assign_material() + + is_scratch = uniform() < material_assignments['wear_tear_prob'][0] + is_edge_wear = uniform() < material_assignments['wear_tear_prob'][1] + self.scratch = material_assignments['wear_tear'][0] if is_scratch else None + self.edge_wear = material_assignments['wear_tear'][1] if is_edge_wear else None + + @property + def mid_offset(self): + return (1 - self.size_mid) * self.size + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + return new_bbox( + -self.mid_offset - self.back_size - self.tank_cap_extrude, + self.size_mid * self.size + self.thickness + self.thickness, + -self.width / 2 - self.thickness * 1.1, self.width / 2 + self.thickness * 1.1, -self.height, + max( + self.tank_height, + -np.sin(self.cover_rotation) * (self.seat_size + self.size + self.thickness + self.thickness) + ) + ) + + def create_asset(self, **params) -> bpy.types.Object: + upper = self.build_curve() + lower = deep_clone_obj(upper) + lower.scale = [self.tube_scale] * 3 + lower.location = 0, self.tube_scale * self.mid_offset / 2, -self.depth + butil.apply_transform(lower, True) + bottom = deep_clone_obj(upper) + bottom.scale = [self.stand_scale] * 3 + bottom.location = 0, self.tube_scale * ( + 1 - self.size_mid) * self.size / 2 * self.bottom_offset, -self.height + butil.apply_transform(bottom, True) + + obj = self.make_tube(lower, upper) + seat, cover = self.make_seat(obj) + stand = self.make_stand(obj, bottom) + back = self.make_back(obj) + tank = self.make_tank() + butil.modify_mesh(obj, 'BEVEL', segments=2) + match self.hardware_type: + case 'button': + hardware = self.add_button() + case _: + hardware = self.add_handle() + write_attribute(hardware, 1, 'hardware', 'FACE') + obj = join_objects([obj, seat, cover, stand, back, tank, hardware]) + obj.rotation_euler[-1] = np.pi / 2 + butil.apply_transform(obj) + return obj + + def build_curve(self): + x_anchors = [0, self.width / 2, 0] + y_anchors = [-self.size_mid * self.size, 0, self.mid_offset] + axes = [np.array([1, 0, 0]), np.array([0, 1, 0]), np.array([1, 0, 0])] + obj = align_bezier([x_anchors, y_anchors, 0], axes, self.curve_scale) + butil.modify_mesh(obj, 'MIRROR', use_axis=(True, False, False)) + return obj + + def make_tube(self, lower, upper): + obj = join_objects([upper, lower]) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bridge_edge_loops( + number_cuts=np.random.randint(12, 16), + profile_shape_factor=uniform(.1, .2), interpolation='SURFACE' + ) + butil.modify_mesh( + obj, 'SOLIDIFY', thickness=self.thickness, offset=1, solidify_mode='NON_MANIFOLD', + nonmanifold_boundary_mode='FLAT' + ) + normal = read_normal(obj) + select_faces(obj, normal[:, -1] > .9) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.extrude_region_move( + TRANSFORM_OT_translate={'value': (0, 0, self.thickness + self.extrude_height)} + ) + x, y, z = read_co(obj).T + write_co(obj, np.stack([x, y, np.clip(z, None, self.extrude_height)], -1)) + return obj + + def make_seat(self, obj): + seat = self.make_plane(obj) + cover = deep_clone_obj(seat) + butil.modify_mesh(seat, 'SOLIDIFY', thickness=self.extrude_height, offset=1) + if self.has_seat_cut: + cutter = new_cube() + cutter.scale = [self.thickness] * 3 + cutter.location = 0, -self.thickness / 2 - self.size_mid * self.size, 0 + butil.apply_transform(cutter, True) + butil.select_none() + butil.modify_mesh(seat, 'BOOLEAN', object=cutter, operation='DIFFERENCE') + butil.delete(cutter) + butil.modify_mesh(seat, 'BEVEL', segments=2) + + x, y, _ = read_edge_center(cover).T + i = np.argmin(np.abs(x) + np.abs(y)) + selection = np.full(len(x), False) + selection[i] = True + select_edges(cover, selection) + with butil.ViewportMode(cover, 'EDIT'): + bpy.ops.mesh.loop_multi_select() + bpy.ops.mesh.fill_grid() + butil.modify_mesh(cover, 'SOLIDIFY', thickness=self.extrude_height, offset=1) + cover.location = [0, -self.mid_offset - self.seat_size + self.extrude_height / 2, + -self.extrude_height / 2] + butil.apply_transform(cover, True) + cover.rotation_euler[0] = self.cover_rotation + cover.location = [0, self.mid_offset + self.seat_size - self.extrude_height / 2, + self.extrude_height * 1.5] + butil.apply_transform(cover, True) + butil.modify_mesh(cover, 'BEVEL', segments=2) + return seat, cover + + def make_plane(self, obj): + select_faces(obj, lambda x, y, z: z > self.extrude_height * 2 / 3) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.duplicate_move() + bpy.ops.mesh.separate(type='SELECTED') + seat = next(o for o in bpy.context.selected_objects if o != obj) + butil.select_none() + select_vertices(seat, lambda x, y, z: y > self.mid_offset + self.seat_thickness) + with butil.ViewportMode(seat, 'EDIT'): + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={'value': (0, self.seat_size + self.thickness * 2, 0)} + ) + x, y, z = read_co(seat).T + write_co(seat, np.stack([x, np.clip(y, None, self.mid_offset + self.seat_size), z], -1)) + return seat + + def make_stand(self, obj, bottom): + co = read_co(obj)[read_edges(obj).reshape(-1)].reshape(-1, 2, 3) + horizontal = np.abs(normalize(co[:, 0] - co[:, 1])[:, -1]) < .1 + x, y, z = read_edge_center(obj).T + under_depth = z < -self.stand_depth + i = np.argmin(y - horizontal - under_depth) + selection = np.full(len(co), False) + selection[i] = True + select_edges(obj, selection) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.loop_multi_select() + bpy.ops.mesh.duplicate_move() + bpy.ops.mesh.separate(type='SELECTED') + stand = next(o for o in bpy.context.selected_objects if o != obj) + stand = join_objects([stand, bottom]) + with butil.ViewportMode(stand, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bridge_edge_loops( + number_cuts=np.random.randint(12, 16), + profile_shape_factor=uniform(.0, .15) + ) + return stand + + def make_back(self, obj): + back = read_center(obj)[:, 1] > self.mid_offset - self.back_thickness + back_facing = read_normal(obj)[:, 1] > .1 + butil.select_none() + select_faces(obj, back & back_facing) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.region_to_loop() + bpy.ops.mesh.duplicate_move() + bpy.ops.mesh.separate(type='SELECTED') + back = next(o for o in bpy.context.selected_objects if o != obj) + butil.modify_mesh(back, 'CORRECTIVE_SMOOTH') + butil.select_none() + with butil.ViewportMode(back, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={'value': (0, self.back_size + self.thickness * 2, 0)} + ) + bpy.ops.transform.resize(value=(self.back_scale, 1, 1)) + bpy.ops.mesh.edge_face_add() + back.location[1] -= .01 + butil.apply_transform(back, True) + x, y, z = read_co(back).T + write_co(back, np.stack([x, np.clip(y, None, self.mid_offset + self.back_size), z], -1)) + return back + + def make_tank(self): + tank = new_cube() + tank.scale = self.tank_width / 2, self.tank_size / 2, self.tank_height / 2 + tank.location = 0, self.mid_offset + self.back_size - self.tank_size / 2, self.tank_height / 2 + butil.apply_transform(tank, True) + subsurf(tank, 2, True) + butil.modify_mesh(tank, 'BEVEL', segments=2) + cap = new_cube() + cap.scale = self.tank_width / 2 + self.tank_cap_extrude, self.tank_size / 2 + self.tank_cap_extrude, \ + self.tank_cap_height / 2 + cap.location = 0, self.mid_offset + self.back_size - self.tank_size / 2, self.tank_height + butil.apply_transform(cap, True) + butil.modify_mesh(cap, 'BEVEL', width=uniform(0, self.extrude_height), segments=4) + tank = join_objects([tank, cap]) + return tank + + def add_button(self): + obj = new_cylinder() + obj.scale = self.hardware_radius, self.hardware_radius, self.tank_cap_height / 2 + 1e-3 + obj.location = 0, self.mid_offset + self.back_size - self.tank_size / 2, self.tank_height + butil.apply_transform(obj, True) + return obj + + def add_handle(self): + obj = new_cylinder() + obj.scale = self.hardware_radius, self.hardware_radius, self.hardware_cap + obj.rotation_euler[0] = np.pi / 2 + butil.apply_transform(obj, True) + lever = new_cylinder() + lever.scale = self.hardware_radius / 2, self.hardware_radius / 2, self.hardware_length + lever.rotation_euler[1] = np.pi / 2 + lever.location = [-self.hardware_radius * uniform(0, .5), -self.hardware_cap, + -self.hardware_radius * uniform(0, .5)] + butil.apply_transform(lever, True) + obj = join_objects([obj, lever]) + if self.hardware_on_side: + obj.location = [-self.tank_width / 2 + self.hardware_radius + uniform(.01, .02), + self.mid_offset + self.back_size - self.tank_size, + self.tank_height - self.hardware_radius - uniform(.02, .03)] + else: + obj.location = [-self.tank_width / 2, + self.mid_offset + self.back_size - self.tank_size + self.hardware_radius + uniform(.01, .02), + self.tank_height - self.hardware_radius - uniform(.02, .03)] + obj.rotation_euler[-1] = -np.pi / 2 + butil.apply_transform(obj, True) + butil.modify_mesh(obj, 'BEVEL', width=uniform(.005, .01), segments=2) + return obj + + def finalize_assets(self, assets): + self.surface.apply(assets, clear=True, metal_color='plain') + self.hardware_surface.apply(assets, 'hardware', metal_color='natural') + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) diff --git a/infinigen/assets/cactus/columnar.py b/infinigen/assets/cactus/columnar.py index 177105eaa..84480da89 100644 --- a/infinigen/assets/cactus/columnar.py +++ b/infinigen/assets/cactus/columnar.py @@ -15,7 +15,7 @@ from infinigen.core import surface from infinigen.assets.cactus.base import BaseCactusFactory from infinigen.assets.trees.tree import build_radius_tree -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core import tagging class ColumnarBaseCactusFactory(BaseCactusFactory): @@ -75,7 +75,6 @@ def create_asset(self, face_size=.01, **params) -> bpy.types.Object: surface.add_geomod(obj, self.geo_star, apply=True, input_attributes=[None, 'radius'], attributes=['selection']) surface.add_geomod(obj, geo_extension, apply=True, input_kwargs={'musgrave_dimensions': '2D'}) - tag_object(obj, 'columnar_cactus') return obj @staticmethod diff --git a/infinigen/assets/cactus/generate.py b/infinigen/assets/cactus/generate.py index 2ef7022cc..674afb418 100644 --- a/infinigen/assets/cactus/generate.py +++ b/infinigen/assets/cactus/generate.py @@ -16,23 +16,28 @@ from .columnar import ColumnarBaseCactusFactory from .pricky_pear import PrickyPearBaseCactusFactory from .kalidium import KalidiumBaseCactusFactory + from infinigen.assets.cactus import spike -from infinigen.assets.utils.misc import build_color_ramp, log_uniform -from infinigen.assets.utils.decorate import assign_material, join_objects +from infinigen.assets.utils.misc import assign_material +from infinigen.assets.utils.object import join_objects + +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_wrangler import NodeWrangler, Nodes from infinigen.core.placement.detail import remesh_with_attrs from infinigen.core import surface from infinigen.core.placement.factory import AssetFactory from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object +from infinigen.core import tagging +from infinigen.core.nodes.node_utils import build_color_ramp class CactusFactory(AssetFactory): - + def __init__(self, factory_seed, coarse=False, factory_method=None): super(CactusFactory, self).__init__(factory_seed, coarse) with FixedSeed(factory_seed): self.factory_methods = [GlobularBaseCactusFactory, ColumnarBaseCactusFactory, - PrickyPearBaseCactusFactory, KalidiumBaseCactusFactory] + PrickyPearBaseCactusFactory]#, KalidiumBaseCactusFactory] weights = np.array([1] * len(self.factory_methods)) self.weights = weights / weights.sum() if factory_method is None: @@ -44,6 +49,7 @@ def __init__(self, factory_seed, coarse=False, factory_method=None): def create_asset(self, face_size=0.01, realize=True, **params): obj = self.factory.create_asset(**params) + remesh_with_attrs(obj, face_size) if self.factory.noise_strength > 0: @@ -52,20 +58,25 @@ def create_asset(self, face_size=0.01, realize=True, **params): texture.noise_scale = log_uniform(.1, .15) butil.modify_mesh(obj, 'DISPLACE', True, strength=self.factory.noise_strength, mid_level=0, texture=texture) + assign_material(obj, self.material) + if face_size <= .05 and self.factory.density > 0: t = spike.apply(obj, self.factory.points_fn, self.factory.base_radius, realize) + + tagging.tag_object(obj, "cactus_spike") obj = join_objects([obj, t]) - tag_object(obj, 'cactus') + tagging.tag_object(obj, 'cactus') + return obj @staticmethod def shader_cactus(nw: NodeWrangler, base_hue): shift = uniform(-.15, .15) - bright_color = *colorsys.hsv_to_rgb((base_hue + shift) % 1, 1., .02), 1 - dark_color = *colorsys.hsv_to_rgb(base_hue, .8, .01), 1 - fresnel_color = *colorsys.hsv_to_rgb((base_hue - uniform(.05, .1)) % 1, .9, uniform(.3, .5)), 1 + bright_color = hsv2rgba((base_hue + shift) % 1, 1., .02) + dark_color = hsv2rgba(base_hue, .8, .01) + fresnel_color = hsv2rgba((base_hue - uniform(.05, .1)) % 1, .9, uniform(.3, .5)) specular = .25 fresnel = nw.scalar_multiply(nw.new_node(Nodes.Fresnel), log_uniform(.6, 1.)) diff --git a/infinigen/assets/cactus/globular.py b/infinigen/assets/cactus/globular.py index 0ccc5c516..b78785f93 100644 --- a/infinigen/assets/cactus/globular.py +++ b/infinigen/assets/cactus/globular.py @@ -12,12 +12,12 @@ from infinigen.assets.utils.object import new_cube from infinigen.assets.utils.decorate import geo_extension -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core import tagging class GlobularBaseCactusFactory(BaseCactusFactory): @@ -46,11 +46,13 @@ def geo_globular(nw: NodeWrangler): nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry, 'Selection': selection}) def create_asset(self, face_size=.01, **params) -> bpy.types.Object: + obj = new_cube() surface.add_geomod(obj, self.geo_globular, apply=True, attributes=['selection']) surface.add_geomod(obj, geo_extension, apply=True, input_kwargs={'musgrave_dimensions': '2D'}) + obj.scale = uniform(.8, 1.5, 3) obj.rotation_euler[-1] = uniform(0, np.pi * 2) butil.apply_transform(obj) - tag_object(obj, 'globular_cactus') + return obj diff --git a/infinigen/assets/cactus/kalidium.py b/infinigen/assets/cactus/kalidium.py index 995e5225e..e0dc9c0e9 100644 --- a/infinigen/assets/cactus/kalidium.py +++ b/infinigen/assets/cactus/kalidium.py @@ -10,8 +10,8 @@ from infinigen.assets.trees.tree import TreeVertices, build_radius_tree, recursive_path from infinigen.assets.utils.nodegroup import geo_radius -from infinigen.assets.utils.object import data2mesh, mesh2obj, new_cube, origin2lowest -from infinigen.assets.utils.decorate import displace_vertices, geo_extension, read_co, remove_vertices, separate_loose, \ +from infinigen.assets.utils.object import data2mesh, mesh2obj, new_cube, origin2lowest, separate_loose +from infinigen.assets.utils.decorate import displace_vertices, geo_extension, read_co, remove_vertices, \ subsurface2face_size from infinigen.assets.utils.shortest_path import geo_shortest_path from infinigen.core.nodes.node_info import Nodes @@ -21,7 +21,7 @@ from infinigen.core import surface from infinigen.assets.cactus.base import BaseCactusFactory from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class KalidiumBaseCactusFactory(BaseCactusFactory): diff --git a/infinigen/assets/cactus/pricky_pear.py b/infinigen/assets/cactus/pricky_pear.py index c7194cdee..8a74ae34f 100644 --- a/infinigen/assets/cactus/pricky_pear.py +++ b/infinigen/assets/cactus/pricky_pear.py @@ -8,16 +8,16 @@ import numpy as np from numpy.random import uniform -from infinigen.assets.utils.object import new_cube -from infinigen.assets.utils.decorate import geo_extension, join_objects -from infinigen.assets.utils.misc import log_uniform +from infinigen.assets.utils.object import join_objects, new_cube +from infinigen.assets.utils.decorate import geo_extension +from infinigen.core.util.random import log_uniform from infinigen.core.surface import write_attr_data from infinigen.core.util import blender as butil from infinigen.assets.cactus.base import BaseCactusFactory from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class PrickyPearBaseCactusFactory(BaseCactusFactory): spike_distance = .08 diff --git a/infinigen/assets/cactus/spike.py b/infinigen/assets/cactus/spike.py index 055f952da..527d9a584 100644 --- a/infinigen/assets/cactus/spike.py +++ b/infinigen/assets/cactus/spike.py @@ -8,9 +8,9 @@ import numpy as np from numpy.random import uniform +from infinigen.core.util.color import hsv2rgba from infinigen.core.util import blender as butil -from infinigen.assets.utils.misc import sample_direction -from infinigen.assets.utils.decorate import assign_material +from infinigen.assets.utils.misc import assign_material, sample_direction, toggle_show, toggle_hide from infinigen.assets.utils.nodegroup import geo_radius from infinigen.core.placement.factory import AssetFactory, make_asset_collection from infinigen.core.nodes.node_wrangler import NodeWrangler, Nodes @@ -18,7 +18,7 @@ from infinigen.assets.trees.tree import build_radius_tree import infinigen.core.util.blender as butil from infinigen.core.util.blender import deep_clone_obj -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup, COMBINED_ATTR_NAME def build_spikes(base_radius=.002, **kwargs): n_branch = 4 @@ -33,7 +33,6 @@ def build_spikes(base_radius=.002, **kwargs): np.arange(size * resolution) / (size * resolution)) obj = build_radius_tree(radius_fn, branch_config, base_radius) surface.add_geomod(obj, geo_radius, apply=True, input_args=['radius', None, .001]) - tag_object(obj, 'spike') return obj @@ -58,8 +57,15 @@ def selection(nw: NodeWrangler, selected, geometry): def geo_spikes(nw: NodeWrangler, spikes, points_fn=None, realize=True): - geometry, selection = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None), - ('NodeSocketFloat', 'Selection', None)]).outputs[:2] + + geometry, selection = nw.new_node( + Nodes.GroupInput, + expose_input=[ + ('NodeSocketGeometry', 'Geometry', None), + ('NodeSocketFloat', 'Selection', None) + ] + ).outputs[:2] + capture = nw.new_node(Nodes.CaptureAttribute, input_kwargs={'Geometry': geometry, 'Value': nw.new_node(Nodes.InputNormal)}) @@ -86,7 +92,7 @@ def geo_spikes(nw: NodeWrangler, spikes, points_fn=None, realize=True): realize_instances = nw.new_node(Nodes.RealizeInstances, [spikes]) else: realize_instances = spikes - + nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': realize_instances}) @@ -94,7 +100,7 @@ def shader_spikes(nw: NodeWrangler): roughness = .8 specular = .25 mix_ratio = .9 - color = *colorsys.hsv_to_rgb(uniform(.2, .4), uniform(.1, .3), .8), 1 + color = hsv2rgba(uniform(.2, .4), uniform(.1, .3), .8) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={ 'Base Color': color, 'Roughness': roughness, @@ -108,10 +114,16 @@ def shader_spikes(nw: NodeWrangler): def apply(obj, points_fn, base_radius=.002, realize=True): spikes = deep_clone_obj(obj) + + if COMBINED_ATTR_NAME in spikes.data.attributes: + spikes.data.attributes.remove(spikes.data.attributes[COMBINED_ATTR_NAME]) + instances = make_asset_collection(build_spikes, 5, 'spikes', verbose=False, base_radius=base_radius) mat = surface.shaderfunc_to_material(shader_spikes) + toggle_show(instances) for o in instances.objects: - assign_material(o, mat) + assign_material(o, mat) + toggle_hide(instances) surface.add_geomod(spikes, geo_spikes, apply=realize, input_args=[instances, points_fn, realize], input_attributes=[None, 'selection']) butil.delete_collection(instances) diff --git a/infinigen/assets/clothes/__init__.py b/infinigen/assets/clothes/__init__.py new file mode 100644 index 000000000..a1b27f179 --- /dev/null +++ b/infinigen/assets/clothes/__init__.py @@ -0,0 +1,9 @@ +# 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: Lingjie Mei +from .blanket import BlanketFactory +from .shirt import ShirtFactory +from .pants import PantsFactory +from .towel import TowelFactory + diff --git a/infinigen/assets/clothes/blanket.py b/infinigen/assets/clothes/blanket.py new file mode 100644 index 000000000..2d5d018b7 --- /dev/null +++ b/infinigen/assets/clothes/blanket.py @@ -0,0 +1,79 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import art, fabrics +from infinigen.assets.utils.decorate import read_co, select_vertices, write_co +from infinigen.assets.utils.object import new_grid +from infinigen.assets.utils.uv import unwrap_faces +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.assets.materials.art import ArtFabric +from infinigen.assets.material_assignments import AssetList + +class BlanketFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(BlanketFactory, self).__init__(factory_seed, coarse) + self.width = log_uniform(.9, 1.2) + self.size = self.width * log_uniform(.4, .7) + self.thickness = log_uniform(.004, .008) + + materials = AssetList['BlanketFactory']() + self.surface = materials['surface'].assign_material() + if self.surface == ArtFabric: + self.surface = self.surface(self.factory_seed) + + + def create_asset(self, **params) -> bpy.types.Object: + obj = new_grid(x_subdivisions=64, y_subdivisions=int(self.size / self.width * 64)) + obj.scale = self.width / 2, self.size / 2, 1 + butil.apply_transform(obj) + unwrap_faces(obj) + self.surface.apply(obj) + return obj + + def fold(self, obj): + theta = uniform(-np.pi / 6, np.pi / 6) + y_margin = self.size * (.5 - uniform(.1, .3)) + obj.rotation_euler[-1] = theta + obj.location[1] -= y_margin + butil.apply_transform(obj, True) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bisect(plane_co=(0, 0, 0), plane_no=(0, 1, 0)) + x, y, z = read_co(obj).T + co = np.stack([x, np.where(y > 0, -y, y), np.where(y > 0, .05 - z, z)], -1) + write_co(obj, co) + obj.location[1] += y_margin + butil.apply_transform(obj, True) + obj.rotation_euler[-1] = -theta + butil.apply_transform(obj) + + +class ComforterFactory(BlanketFactory): + + def create_asset(self, **params) -> bpy.types.Object: + obj = super().create_asset(**params) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=.01) + return obj + + +class BoxComforterFactory(ComforterFactory): + def __init__(self, factory_seed, coarse=False): + super(BoxComforterFactory, self).__init__(factory_seed, coarse) + self.margin = uniform(.3, .4) + + def create_asset(self, **params) -> bpy.types.Object: + obj = super().create_asset(**params) + x, y, _ = read_co(obj).T + _x = np.abs(x / self.margin - np.round(x / self.margin)) * self.margin < self.width / 64 / 2 + _y = np.abs(y / self.margin - np.round(y / self.margin)) * self.margin < self.width / 64 / 2 + with butil.ViewportMode(obj, 'EDIT'): + select_vertices(obj, _x | _y) + bpy.ops.mesh.remove_doubles(threshold=.02) + return obj diff --git a/infinigen/assets/clothes/pants.py b/infinigen/assets/clothes/pants.py new file mode 100644 index 000000000..848f94980 --- /dev/null +++ b/infinigen/assets/clothes/pants.py @@ -0,0 +1,66 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import art, fabrics +from infinigen.assets.utils.decorate import distance2boundary, read_normal, remove_faces, subsurf, write_co +from infinigen.assets.utils.draw import remesh_fill +from infinigen.assets.utils.object import new_circle +from infinigen.assets.utils.uv import unwrap_faces, wrap_front_back, wrap_top_bottom +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList +from infinigen.assets.materials.art import ArtFabric + + +class PantsFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(PantsFactory, self).__init__(factory_seed, coarse) + self.width = log_uniform(.45, .55) + self.size = self.width / 2 + uniform(0, .05) + self.type = np.random.choice(['underwear', 'shorts', 'pants']) + match self.type: + case 'underwear': + self.length = self.size + uniform(-.02, .02) + case 'shorts': + self.length = self.size + uniform(.05, .1) + case _: + self.length = self.size + uniform(.5, .7) + self.neck_shrink = uniform(.1, .15) + self.thickness = log_uniform(.02, .03) + materials = AssetList['PantsFactory']() + self.surface = materials['surface'].assign_material() + if self.surface == ArtFabric: + self.surface = self.surface(self.factory_seed) + + def create_asset(self, **params) -> bpy.types.Object: + x_anchors = 0, self.width / 2, self.width / 2 * ( + 1 + self.neck_shrink), self.width / 2 * self.neck_shrink * 2, 0 + y_anchors = 0, 0, -self.length, -self.length, -self.size + + obj = new_circle(vertices=len(x_anchors)) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.edge_face_add() + write_co(obj, np.stack([x_anchors, y_anchors, np.zeros_like(x_anchors)], -1)) + butil.modify_mesh(obj, 'MIRROR', use_axis=(True, False, False)) + remesh_fill(obj, .02) + distance2boundary(obj) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness, offset=0) + x_, y_, z_ = read_normal(obj).T + remove_faces(obj, (y_ < -.99) | (y_ > .99)) + with butil.ViewportMode(obj, 'EDIT'), butil.Suppress(): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.remove_doubles(threshold=1e-3) + bpy.ops.mesh.normals_make_consistent(inside=False) + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_loose() + bpy.ops.mesh.delete(type='EDGE') + wrap_top_bottom(obj, self.surface) + subsurf(obj, 1) + return obj diff --git a/infinigen/assets/clothes/shirt.py b/infinigen/assets/clothes/shirt.py new file mode 100644 index 000000000..9c5b1c7ec --- /dev/null +++ b/infinigen/assets/clothes/shirt.py @@ -0,0 +1,71 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import read_center, read_normal, remove_faces, subsurf, write_co +from infinigen.assets.utils.draw import remesh_fill +from infinigen.assets.utils.object import new_circle +from infinigen.assets.utils.uv import wrap_front_back +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList +from infinigen.assets.materials.art import ArtFabric + +class ShirtFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(ShirtFactory, self).__init__(factory_seed, coarse) + self.width = log_uniform(.45, .55) + self.size = self.width + uniform(.25, .3) + self.size_neck = uniform(.1, .15) * self.size + self.type = np.random.choice(['short', 'long']) + match self.type: + case 'short': + self.sleeve_length = self.size / 2 + uniform(-.35, -.3) + case _: + self.sleeve_length = self.size / 2 + uniform(-.05, .0) + self.sleeve_width = uniform(.14, .18) + self.sleeve_angle = uniform(np.pi / 6, np.pi / 4) + self.thickness = log_uniform(.02, .03) + materials = AssetList['ShirtFactory']() + self.surface = materials['surface'].assign_material() + if self.surface == ArtFabric: + self.surface = self.surface(self.factory_seed) + + def create_asset(self, **params) -> bpy.types.Object: + x_anchors = 0, self.width / 2, self.width / 2, self.width / 2 + self.sleeve_length * np.sin( + self.sleeve_angle), self.width / 2 + self.sleeve_length * np.sin( + self.sleeve_angle) + self.sleeve_width * np.cos( + self.sleeve_angle), self.width / 2, self.width / 4, 0 + + y_anchors = 0, 0, self.size - self.sleeve_width / np.sin( + self.sleeve_angle), self.size - self.sleeve_width / np.sin( + self.sleeve_angle) - self.sleeve_length * np.cos( + self.sleeve_angle), self.size - self.sleeve_width / np.sin( + self.sleeve_angle) - self.sleeve_length * np.cos(self.sleeve_angle) + self.sleeve_width * np.sin( + self.sleeve_angle), self.size, self.size + self.size_neck, self.size + self.size_neck * uniform(.3, + .7) + + obj = new_circle(vertices=len(x_anchors)) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.edge_face_add() + bpy.ops.mesh.flip_normals() + write_co(obj, np.stack([x_anchors, y_anchors, np.zeros_like(x_anchors)], -1)) + butil.modify_mesh(obj, 'MIRROR', use_axis=(True, False, False)) + remesh_fill(obj, .02) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness) + x, y, z = read_center(obj).T + x_, y_, z_ = read_normal(obj).T + remove_faces(obj, (y_ < -.5) | ((y_ > .5) & (x_ * x < 0))) + with butil.ViewportMode(obj, 'EDIT'), butil.Suppress(): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.remove_doubles(threshold=1e-3) + butil.modify_mesh(obj, 'BEVEL', width=self.sleeve_width * uniform(.1, .15)) + subsurf(obj, 1) + wrap_front_back(obj, self.surface) + return obj diff --git a/infinigen/assets/clothes/towel.py b/infinigen/assets/clothes/towel.py new file mode 100644 index 000000000..964191c44 --- /dev/null +++ b/infinigen/assets/clothes/towel.py @@ -0,0 +1,123 @@ +# 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: Lingjie Mei +import bpy +import bmesh +import numpy as np +from numpy.random import uniform +from scipy.optimize import fsolve + +from infinigen.assets.elements.rug import ArtRug +from infinigen.assets.materials import rug +from infinigen.assets.utils.decorate import geo_extension, mirror, read_co, read_edge_direction, \ + subdivide_edge_ring, subsurf, write_co +from infinigen.assets.utils.object import center, new_plane +from infinigen.assets.utils.uv import unwrap_faces, wrap_sides +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import normalize +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList + + +class TowelFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(TowelFactory, self).__init__(factory_seed, coarse) + self.width = log_uniform(.3, .6) + self.length = self.width * log_uniform(1, 1.5) + self.thickness = log_uniform(.003, .01) + prob = np.array([2, 1]) + self.fold_type = np.random.choice(['fold', 'roll'], p=prob / prob.sum()) + self.folds = np.random.randint(2, 4) + self.extra_thickness = self.thickness * uniform(.2, .3) + self.fold_count = 15 + self.roll_count = 256 + self.roll_total = self.compute_roll_total() + materials = AssetList['TowelFactory']() + self.surface = materials['surface'].assign_material() + if self.surface == ArtRug: + self.surface = self.surface(self.factory_seed) + + def fold(self, obj): + x, y, z = read_co(obj).T + if np.max(x) - np.min(x) > np.max(y) - np.min(y): + obj.rotation_euler[-1] = np.pi * (uniform() < .5) + else: + obj.rotation_euler[-1] = np.pi * (uniform() < .5) + np.pi / 2 + butil.apply_transform(obj, True) + obj.location = *(-center(obj))[:-1], 0 + obj.location[0] += uniform(-self.thickness, self.thickness) + butil.apply_transform(obj, True) + n = len(obj.data.vertices) + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + bm.edges.ensure_lookup_table() + selected = np.abs(read_edge_direction(obj)[:, 0]) > 1 - 1e-3 + edges = [bm.edges[i] for i in np.nonzero(selected)[0]] + bmesh.ops.subdivide_edgering(bm, edges=edges, cuts=self.fold_count, smooth=2) + bmesh.update_edit_mesh(obj.data) + co = read_co(obj) + order = np.where(co[n::self.fold_count, 0] < co[n + 1::self.fold_count, 0], 1, -1) + x_ = np.linspace(-self.thickness * order, self.thickness * order, self.fold_count).T.ravel() + co[n:, 0] = x_ + x, y, z = co.T + max_z = np.max(z) + self.extra_thickness + theta = x / self.thickness * np.pi / 2 + x__ = np.where(x < -self.thickness, x, + np.where(x > self.thickness, -x, -self.thickness + (max_z - z) * np.cos(theta))) + z_ = np.where(x < -self.thickness, z, + np.where(x > self.thickness, max_z * 2 - z, max_z + (max_z - z) * np.sin(theta))) + write_co(obj, np.stack([x__, y, z_], -1)) + if uniform() < .5: + mirror(obj) + return obj + + def compute_roll_total(self): + c = self.length / (self.thickness + self.extra_thickness) * (4 * np.pi) + f = lambda t: t * np.sqrt(1 + t * t) + np.log(t + np.sqrt(1 + t * t)) - c + return fsolve(f, np.zeros(1))[0] + + def pre_roll(self, obj): + subdivide_edge_ring(obj, self.roll_count, (1, 0, 0)) + x, y, z = read_co(obj).T + i = np.round((x / self.length + .5) * self.roll_count).astype(int) + t = np.linspace(0, self.roll_total, self.roll_count + 1)[i] + length = (t * np.sqrt(1 + t * t) + np.log(t + np.sqrt(1 + t * t))) * ( + self.thickness + self.extra_thickness) / (4 * np.pi) + write_co(obj, np.stack([length, y, z], -1)) + return i + + def roll(self, obj, i): + t = np.linspace(0, self.roll_total, self.roll_count + 1)[np.concatenate([i, i])] + x, y, z = read_co(obj).T + r = (self.thickness + self.extra_thickness) / (2 * np.pi) * t + np.where(z > self.thickness / 2, + -self.thickness / 2, + self.thickness / 2) + write_co(obj, np.stack([r * np.cos(t), y, r * np.sin((t))], -1)) + + def create_asset(self, **params) -> bpy.types.Object: + obj = new_plane() + if self.fold_type == 'roll': + obj.scale = self.length / 2, self.width / 2, 1 + else: + obj.scale = self.width / 2, self.length / 2, 1 + butil.apply_transform(obj, True) + i = None + if self.fold_type == 'roll': + i = self.pre_roll(obj) + wrap_sides(obj, self.surface, 'z', 'x', 'y', strength=uniform(.2, .4)) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness, offset=1) + if self.fold_type == 'roll': + self.roll(obj, i) + subdivide_edge_ring(obj, 16, (0, 1, 0)) + else: + for _ in range(self.folds): + self.fold(obj) + subdivide_edge_ring(obj, 16, (1, 0, 0)) + subdivide_edge_ring(obj, 16, (0, 1, 0)) + butil.modify_mesh(obj, 'BEVEL', width=self.thickness * uniform(.4, .8), segments=2) + surface.add_geomod(obj, geo_extension, apply=True, input_args=[uniform(.05, .1)]) + subsurf(obj, 1) + return obj diff --git a/infinigen/assets/color_fits.py b/infinigen/assets/color_fits.py new file mode 100644 index 000000000..a04a3b40f --- /dev/null +++ b/infinigen/assets/color_fits.py @@ -0,0 +1,98 @@ +# 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: Stamatis Alexandropoulos, Meenal Parakh + +import os +import numpy as np +from matplotlib import pyplot as plt +import time + +from infinigen.core.util.color import hsv2rgba + +manual_fits = { + 'sofa_fabric': { + 'means': np.array([[0.1, 0.25], [0.5, 0.7], [0.65, 0.15]]), + 'covariances': [ + 0.7*np.array([[0.01, 0], [0, 0.04]]), + 0.7*np.array([[0.02, 0], [0, 0.02]]), + 0.7*np.array([[0.03, 0], [-0.01, 0.012]]) + ], + 'probabilities': [0.5, 0.3, 0.2], + 'n_components': 3 + }, + 'sofa_leather': { + 'means': np.array([[0.07, 0.45], [0.6, 0.3]]), + 'covariances': [ + 0.7*np.array([[0.005, 0], [0, 0.09]]), + 0.7*np.array([[0.015, 0], [0, 0.04]]) + ], + 'min_val': [0.04, 0.04], + 'max_val': [0.75, 0.85], + 'probabilities': [0.7, 0.3], + 'n_components': 2 + }, + 'sofa_linen': { + 'means': np.array([[0.12, 0.5], [0.6, 0.4], [0.9, 0.2]]), + 'covariances': [ + 0.7*np.array([[0.01, 0], [0, 0.12]]), + 0.7*np.array([[0.01, 0], [0, 0.09]]), + 0.7*np.array([[0.01, 0], [0, 0.02]]) + ], + 'probabilities': [0.8, 0.15, 0.05], + 'n_components': 3 + }, + 'sofa_velvet': { + 'means': np.array([[0.52, 0.45]]), + 'covariances': [ + np.array([[0.2, 0], [0, 0.2]]) + ], + 'probabilities': [1.0], + 'n_components': 1 + }, + 'bedding_sheet': { + 'means': np.array([[0.1, 0.4], [0.6, 0.2]]), + 'covariances': [ + 0.7*np.array([[0.01, 0], [0, 0.1]]), + 0.7*np.array([[0.03, 0], [-0.01, 0.02]]) + ], + 'probabilities': [0.9, 0.1], + 'n_components': 2 + } +} + +val_params = { + 'bedding_sheet': {'min_val': 0.15, 'max_val': 0.94, 'mu': 0.66, 'std': 0.17}, + 'sofa_fabric': {'min_val': 0.10, 'max_val': 0.88, 'mu': 0.47, 'std': 0.23}, + 'sofa_leather': {'min_val': 0.06, 'max_val': 0.93, 'mu': 0.40, 'std': 0.2}, + 'sofa_linen': {'min_val': 0.15, 'max_val': 0.86, 'mu': 0.55, 'std': 0.2}, + 'sofa_velvet': {'min_val': 0.11, 'max_val': 0.70, 'mu': 0.35, 'std': 0.18}, +} + + + +def get_val(mu=0.5, std=0.2, min_val=0.1, max_val=0.9): + val = np.random.normal(mu, std) + val = np.clip(val, min_val, max_val) + return val + + +def real_color_distribution(name): + params = manual_fits[name] + + num_gaussians = params['n_components'] + idx = np.random.choice(num_gaussians, p=params['probabilities']) + + mu = params['means'][idx] + cov = params['covariances'][idx] + + h, s = np.random.multivariate_normal(mu, cov) + min_val = params.get('min_val', 0.0) + max_val = params.get('max_val', 1.0) + + h, s = np.clip([h, s], min_val, max_val) + + v = get_val(**(val_params[name])) * 0.1 + rgba = hsv2rgba([h, s, v]) + + return rgba diff --git a/infinigen/assets/corals/diff_growth.py b/infinigen/assets/corals/diff_growth.py index cfd799951..036f104ec 100644 --- a/infinigen/assets/corals/diff_growth.py +++ b/infinigen/assets/corals/diff_growth.py @@ -16,7 +16,7 @@ import infinigen.core.util.blender as butil from infinigen.core import surface from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class DiffGrowthBaseCoralFactory(BaseCoralFactory): default_scale = [1] * 3 diff --git a/infinigen/assets/corals/elkhorn.py b/infinigen/assets/corals/elkhorn.py index 90485844d..57302fdf0 100644 --- a/infinigen/assets/corals/elkhorn.py +++ b/infinigen/assets/corals/elkhorn.py @@ -4,25 +4,24 @@ # Authors: Lingjie Mei -import bmesh import bpy +import bmesh import numpy as np from mathutils import kdtree from numpy.random import uniform from infinigen.assets.corals.base import BaseCoralFactory from infinigen.assets.corals.tentacles import make_radius_points_fn -from infinigen.assets.utils.decorate import displace_vertices, geo_extension, read_co, remove_vertices, \ - separate_loose, write_co +from infinigen.assets.utils.decorate import displace_vertices, geo_extension, read_co, remove_vertices, write_co from infinigen.assets.utils.draw import make_circular_interp -from infinigen.assets.utils.misc import log_uniform -from infinigen.assets.utils.object import new_circle, origin2lowest +from infinigen.core.util.random import log_uniform +from infinigen.assets.utils.object import new_circle, origin2lowest, separate_loose from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface from infinigen.core.util.blender import deep_clone_obj from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class ElkhornBaseCoralFactory(BaseCoralFactory): tentacle_prob = 0. diff --git a/infinigen/assets/corals/fan.py b/infinigen/assets/corals/fan.py index c553f773d..58fe2ca76 100644 --- a/infinigen/assets/corals/fan.py +++ b/infinigen/assets/corals/fan.py @@ -4,14 +4,15 @@ # Authors: Lingjie Mei -import bmesh import bpy +import bmesh import numpy as np from numpy.random import uniform import infinigen.core.util.blender as butil from infinigen.assets.corals.base import BaseCoralFactory -from infinigen.assets.utils.decorate import displace_vertices, geo_extension, read_co, subsurface2face_size, treeify +from infinigen.assets.utils.decorate import displace_vertices, geo_extension, read_co, subsurface2face_size +from infinigen.assets.utils.mesh import treeify from infinigen.assets.utils.draw import shape_by_angles from infinigen.assets.utils.nodegroup import geo_radius from infinigen.assets.utils.object import new_circle, origin2lowest @@ -19,7 +20,7 @@ from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class FanBaseCoralFactory(BaseCoralFactory): diff --git a/infinigen/assets/corals/generate.py b/infinigen/assets/corals/generate.py index 0b99dbab4..aa476581a 100644 --- a/infinigen/assets/corals/generate.py +++ b/infinigen/assets/corals/generate.py @@ -11,9 +11,11 @@ from numpy.random import uniform import infinigen.core.util.blender as butil -from infinigen.assets.utils.misc import build_color_ramp, log_uniform +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform from .fan import FanBaseCoralFactory -from ..utils.decorate import assign_material, join_objects +from infinigen.assets.utils.misc import assign_material +from infinigen.assets.utils.object import join_objects from infinigen.core.util.math import FixedSeed from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler @@ -30,7 +32,8 @@ from .tube import TubeBaseCoralFactory from .star import StarBaseCoralFactory from . import tentacles -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup +from infinigen.core.nodes.node_utils import build_color_ramp class CoralFactory(AssetFactory): @@ -63,19 +66,19 @@ def create_asset(self, face_size=0.01, realize=True, **params): else: self.apply_bump(obj) + tag_object(obj, 'coral') + if uniform(0, 1) < self.factory.tentacle_prob and not has_bump: t = tentacles.apply(obj, self.factory.points_fn, self.factory.density, realize, self.base_hue) obj = join_objects([obj, t]) - tag_object(obj, 'coral') - return obj def apply_noise_texture(self, obj): t = np.random.choice(['STUCCI', 'MARBLE']) texture = bpy.data.textures.new(name='coral', type=t) texture.noise_scale = log_uniform(.01, .02) - butil.modify_mesh(obj, 'DISPLACE', True, strength=self.factory.noise_strength * uniform(.8, 1.5), + butil.modify_mesh(obj, 'DISPLACE', True, strength=self.factory.noise_strength * uniform(.9, 1.2), mid_level=0, texture=texture) def apply_bump(self, obj): @@ -98,10 +101,10 @@ def build_base_hue(): @staticmethod def shader_coral(nw: NodeWrangler, base_hue): shift = uniform(.05, .1) * (-1) ** np.random.randint(2) - subsurface_color = *colorsys.hsv_to_rgb(uniform(0, 1), uniform(0, 1), 1.), 1 - bright_color = *colorsys.hsv_to_rgb((base_hue + shift) % 1, uniform(.7, .9), .2), 1 - dark_color = *colorsys.hsv_to_rgb(base_hue, uniform(.5, .7), .1), 1 - light_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.2, .2)) % 1, uniform(.2, .4), .4), 1 + subsurface_color = hsv2rgba(uniform(0, 1), uniform(0, 1), 1.) + bright_color = hsv2rgba((base_hue + shift) % 1, uniform(.7, .9), .2) + dark_color = hsv2rgba(base_hue, uniform(.5, .7), .1) + light_color = hsv2rgba((base_hue + uniform(-.2, .2)) % 1, uniform(.2, .4), .4) specular = uniform(.25, .5) color = build_color_ramp(nw, nw.musgrave(uniform(10, 20)), [.0, .3, .7, 1.], diff --git a/infinigen/assets/corals/laplacian.py b/infinigen/assets/corals/laplacian.py index 261e5cd96..dc10d769a 100644 --- a/infinigen/assets/corals/laplacian.py +++ b/infinigen/assets/corals/laplacian.py @@ -11,7 +11,7 @@ from infinigen.assets.utils.decorate import geo_extension import infinigen.core.util.blender as butil from infinigen.core import surface -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class CauliflowerBaseCoralFactory(BaseCoralFactory): tentacle_prob = 0.4 diff --git a/infinigen/assets/corals/reaction_diffusion.py b/infinigen/assets/corals/reaction_diffusion.py index f966ba9bf..3acb7eb77 100644 --- a/infinigen/assets/corals/reaction_diffusion.py +++ b/infinigen/assets/corals/reaction_diffusion.py @@ -15,7 +15,7 @@ import infinigen.core.util.blender as butil from infinigen.core.util.math import FixedSeed from infinigen.core import surface -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class ReactionDiffusionBaseCoralFactory(BaseCoralFactory): tentacle_prob = 0. diff --git a/infinigen/assets/corals/star.py b/infinigen/assets/corals/star.py index 825db8d1e..9f5c791f1 100644 --- a/infinigen/assets/corals/star.py +++ b/infinigen/assets/corals/star.py @@ -4,21 +4,21 @@ # Authors: Lingjie Mei -import bmesh import bpy +import bmesh import numpy as np from mathutils import Vector from numpy.random import uniform import infinigen.core.util.blender as butil from infinigen.assets.corals.base import BaseCoralFactory -from infinigen.assets.utils.decorate import displace_vertices, geo_extension, join_objects -from infinigen.assets.utils.object import new_empty, new_icosphere +from infinigen.assets.utils.decorate import displace_vertices, geo_extension +from infinigen.assets.utils.object import join_objects, new_empty, new_icosphere from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface from infinigen.core.util.blender import deep_clone_obj -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class StarBaseCoralFactory(BaseCoralFactory): tentacle_prob = 1. @@ -65,7 +65,7 @@ def geo_flower(nw: NodeWrangler, size, resolution, anchor): normal = nw.new_node(Nodes.NamedAttribute, ['custom_normal'], attrs={'data_type': 'FLOAT_VECTOR'}) geometry = nw.new_node(Nodes.SetPosition, [geometry, None, None, nw.scale(offset, normal)]) outer = nw.boolean_math('AND', nw.compare('GREATER_THAN', t, .4), nw.compare('LESS_THAN', t, .6)) - geometry = nw.new_node(Nodes.StoreNamedAttribute, + geometry = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={'Geometry': geometry, 'Name': 'outermost', 'Value': outer}) nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) diff --git a/infinigen/assets/corals/tentacles.py b/infinigen/assets/corals/tentacles.py index af02c3ae6..f18bb54a3 100644 --- a/infinigen/assets/corals/tentacles.py +++ b/infinigen/assets/corals/tentacles.py @@ -8,16 +8,16 @@ import numpy as np from numpy.random import uniform -from infinigen.assets.utils.misc import sample_direction -from infinigen.assets.utils.decorate import assign_material +from infinigen.assets.utils.misc import assign_material, sample_direction from infinigen.assets.utils.nodegroup import geo_radius +from infinigen.core.util.color import hsv2rgba from infinigen.core.placement.factory import make_asset_collection from infinigen.core.nodes.node_wrangler import NodeWrangler, Nodes from infinigen.core import surface from infinigen.assets.trees.tree import build_radius_tree import infinigen.core.util.blender as butil from infinigen.core.util.blender import deep_clone_obj -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup, COMBINED_ATTR_NAME def build_tentacles(**kwargs): n_branch = 5 @@ -29,7 +29,6 @@ def build_tentacles(**kwargs): obj = build_radius_tree(None, branch_config, uniform(.002, .004)) surface.add_geomod(obj, geo_radius, apply=True, input_args=['radius']) - tag_object(obj, 'tentacle') return obj @@ -85,20 +84,20 @@ def geo_tentacles(nw: NodeWrangler, tentacles, points_fn=None, density=500, real realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': tentacles}) else: realize_instances = tentacles - + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': realize_instances}) def shader_tentacles(nw: NodeWrangler, base_hue=.3): roughness = .8 specular = .25 - color = *colorsys.hsv_to_rgb((base_hue + uniform(-0.1, 0.1)) % 1, uniform(.4, .6), .5), 1 + color = hsv2rgba((base_hue + uniform(-0.1, 0.1)) % 1, uniform(.4, .6), .5) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={ 'Base Color': color, 'Roughness': roughness, 'Specular': specular, 'Subsurface': .01}) - fresnel_color = *colorsys.hsv_to_rgb(uniform(0, 1), .6, .6), 1 + fresnel_color = hsv2rgba(uniform(0, 1), .6, .6) fresnel_bdsf = nw.new_node(Nodes.PrincipledBSDF, [fresnel_color]) mixed_shader = nw.new_node(Nodes.MixShader, [nw.new_node(Nodes.Fresnel), principled_bsdf, fresnel_bdsf]) return mixed_shader @@ -106,6 +105,9 @@ def shader_tentacles(nw: NodeWrangler, base_hue=.3): def apply(obj, points_fn, density, realize=True, base_hue=.3): tentacles = deep_clone_obj(obj) + if COMBINED_ATTR_NAME in tentacles.data.attributes: + tentacles.data.attributes.remove(tentacles.data.attributes[COMBINED_ATTR_NAME]) + instances = make_asset_collection(build_tentacles, 5, 'spikes', verbose=False) surface.add_geomod(tentacles, geo_tentacles, apply=realize, input_args=[instances, points_fn, density, realize]) diff --git a/infinigen/assets/corals/tree.py b/infinigen/assets/corals/tree.py index 0df8febc3..09a9bbbff 100644 --- a/infinigen/assets/corals/tree.py +++ b/infinigen/assets/corals/tree.py @@ -11,15 +11,14 @@ from infinigen.assets.corals.base import BaseCoralFactory from infinigen.assets.corals.tentacles import make_radius_points_fn -from infinigen.assets.utils.decorate import separate_loose -from infinigen.assets.utils.object import mesh2obj, data2mesh +from infinigen.assets.utils.object import mesh2obj, data2mesh, separate_loose from infinigen.assets.utils.nodegroup import geo_radius import infinigen.core.util.blender as butil from infinigen.core.placement.detail import remesh_with_attrs from infinigen.core.util.math import FixedSeed from infinigen.core import surface from infinigen.assets.trees.tree import build_radius_tree, recursive_path, FineTreeVertices -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class TreeBaseCoralFactory(BaseCoralFactory): default_scale = [1] * 3 diff --git a/infinigen/assets/corals/tube.py b/infinigen/assets/corals/tube.py index 34b2aa146..07115eee4 100644 --- a/infinigen/assets/corals/tube.py +++ b/infinigen/assets/corals/tube.py @@ -14,7 +14,7 @@ from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class TubeBaseCoralFactory(BaseCoralFactory): default_scale = [.7] * 3 diff --git a/infinigen/assets/creatures/beetle.py b/infinigen/assets/creatures/beetle.py index f5a9bf5bb..0eb5a1225 100644 --- a/infinigen/assets/creatures/beetle.py +++ b/infinigen/assets/creatures/beetle.py @@ -24,7 +24,7 @@ from infinigen.core.util.math import lerp, clip_gaussian, FixedSeed from infinigen.core import surface import infinigen.assets.materials.chitin -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup logger = logging.getLogger(__name__) diff --git a/infinigen/assets/creatures/bird.py b/infinigen/assets/creatures/bird.py index 52377fd55..885d2b11f 100644 --- a/infinigen/assets/creatures/bird.py +++ b/infinigen/assets/creatures/bird.py @@ -37,7 +37,7 @@ from infinigen.assets.creatures.util import creature, hair as creature_hair, joining from infinigen.assets.creatures.util.animation.driver_wiggle import animate_wiggle_bones from infinigen.assets.creatures.util.animation import idle, run_cycle -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup from infinigen.core.placement import animation_policy diff --git a/infinigen/assets/creatures/carnivore.py b/infinigen/assets/creatures/carnivore.py index c5ed8e360..7b84135c1 100644 --- a/infinigen/assets/creatures/carnivore.py +++ b/infinigen/assets/creatures/carnivore.py @@ -3,7 +3,6 @@ # Authors: Alexander Raistrick - import numpy as np from numpy.random import uniform as U, normal as N import gin @@ -27,7 +26,7 @@ from infinigen.assets.creatures.util import hair as creature_hair, cloth_sim from infinigen.assets.creatures.util.animation import idle, run_cycle -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup from infinigen.core.util import blender as butil @@ -209,7 +208,7 @@ def create_asset(self, i, placeholder, **kwargs): genome = tiger_genome() root, parts = creature.genome_to_creature(genome, name=f'carnivore({self.factory_seed}, {i})') - tag_object(root, 'carnivore') + # tag_object(root, 'carnivore') offset_center(root) dynamic = self.animation_mode is not None diff --git a/infinigen/assets/creatures/crustacean.py b/infinigen/assets/creatures/crustacean.py index 9435532fd..09f9e7f1b 100644 --- a/infinigen/assets/creatures/crustacean.py +++ b/infinigen/assets/creatures/crustacean.py @@ -22,14 +22,17 @@ from infinigen.assets.creatures.parts.crustacean.leg import CrabLegFactory, LobsterLegFactory from infinigen.assets.creatures.parts.crustacean.body import CrabBodyFactory, LobsterBodyFactory from infinigen.assets.creatures.parts.crustacean.tail import CrustaceanTailFactory -from infinigen.assets.utils.decorate import assign_material, read_material_index, write_material_index -from infinigen.assets.utils.misc import build_color_ramp, log_uniform +from infinigen.assets.utils.decorate import read_material_index, write_material_index +from infinigen.assets.utils.misc import assign_material +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core.placement.factory import AssetFactory from infinigen.core.surface import read_attr_data, shaderfunc_to_material from infinigen.core.util import blender as butil from infinigen.core.util.math import FixedSeed +from infinigen.core.nodes.node_utils import build_color_ramp n_legs = 4 n_limbs = 5 @@ -120,10 +123,10 @@ def build_base_hue(): def shader_crustacean(nw: NodeWrangler, params): value_shift = log_uniform(2, 10) base_hue = params['base_hue'] - bright_color = *colorsys.hsv_to_rgb(base_hue, uniform(.8, 1.), log_uniform(.02, .05) * value_shift), 1 - dark_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.05, .05)) % 1, uniform(.8, 1.), - log_uniform(.01, .02) * value_shift), 1 - light_color = *colorsys.hsv_to_rgb(base_hue, uniform(.0, .4), log_uniform(.2, 1.)), 1 + bright_color = hsv2rgba(base_hue, uniform(.8, 1.), log_uniform(.02, .05) * value_shift) + dark_color = hsv2rgba((base_hue + uniform(-.05, .05)) % 1, uniform(.8, 1.), + log_uniform(.01, .02) * value_shift) + light_color = hsv2rgba(base_hue, uniform(.0, .4), log_uniform(.2, 1.)) specular = uniform(.6, .8) specular_tint = uniform(0, 1) clearcoat = uniform(.2, .8) diff --git a/infinigen/assets/creatures/fish.py b/infinigen/assets/creatures/fish.py index d97798bbc..de37a396f 100644 --- a/infinigen/assets/creatures/fish.py +++ b/infinigen/assets/creatures/fish.py @@ -33,7 +33,7 @@ from infinigen.assets.creatures.util.animation.driver_wiggle import animate_wiggle_bones from infinigen.assets.creatures.util.creature_util import offset_center -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup from infinigen.assets.materials import fish_eye_shader diff --git a/infinigen/assets/creatures/herbivore.py b/infinigen/assets/creatures/herbivore.py index 8eac14d13..074220f3c 100644 --- a/infinigen/assets/creatures/herbivore.py +++ b/infinigen/assets/creatures/herbivore.py @@ -31,7 +31,7 @@ from infinigen.assets.creatures.util.animation import idle, run_cycle from infinigen.assets.materials import bone, tongue, eyeball, nose, horn -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup from infinigen.core.util import blender as butil @@ -219,7 +219,7 @@ def create_placeholder(self, **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') + # tag_object(root, 'herbivore') offset_center(root) dynamic = self.animation_mode is not None diff --git a/infinigen/assets/creatures/insects/dragonfly.py b/infinigen/assets/creatures/insects/dragonfly.py index e5d3b2936..8afdf0e56 100644 --- a/infinigen/assets/creatures/insects/dragonfly.py +++ b/infinigen/assets/creatures/insects/dragonfly.py @@ -222,8 +222,16 @@ def geometry_dragonfly(nw: NodeWrangler, **kwargs): realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_3}) + # TODO replace this hacky postprocess transform + result = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': realize_instances, + 'Translation': (0.6, 0, 0), # position origin at ~center of dragonfly + 'Rotation': (0, 0, -np.pi / 2), + 'Scale': (kwargs['PostprocessScale'],) * 3 + }) + group_output = nw.new_node(Nodes.GroupOutput, - input_kwargs={'Geometry': realize_instances}) + input_kwargs={'Geometry': result}) @gin.configurable @@ -236,7 +244,6 @@ def __init__(self, factory_seed, coarse=False, bvh=None, **_): with FixedSeed(factory_seed): self.genome = self.sample_geo_genome() y = U(20, 60) - self.scale = 0.015 * N(1, 0.1) self.policy = animation_policy.AnimPolicyRandomForwardWalk( forward_vec=(1, 0, 0), speed=U(7, 10), step_range=(0.2, 7), yaw_dist=("uniform", -y, y), rot_vars=[0,0,0]) @@ -283,6 +290,7 @@ def sample_geo_genome(): 'Eye Color': eye_color_rgba, 'V': U(0.0, 0.5), 'Ring Length': U(0.0, 0.3), + 'PostprocessScale': 0.015 * N(1, 0.1), } def create_placeholder(self, i, loc, rot): @@ -306,12 +314,6 @@ def create_asset(self, placeholder, **params): phenome = self.genome.copy() surface.add_geomod(obj, geometry_dragonfly, apply=False, input_kwargs=phenome) - - obj = bpy.context.object - obj.scale *= N(1, 0.1) * self.scale - obj.parent = placeholder - obj.location.x += 0.6 - obj.rotation_euler.z = -np.pi / 2 # TODO: dragonfly should have been defined facing +X return obj \ No newline at end of file diff --git a/infinigen/assets/creatures/jellyfish.py b/infinigen/assets/creatures/jellyfish.py index 5630d3ae7..a9db482e5 100644 --- a/infinigen/assets/creatures/jellyfish.py +++ b/infinigen/assets/creatures/jellyfish.py @@ -7,7 +7,6 @@ import colorsys -import bmesh import numpy as np import bpy from mathutils import Vector @@ -17,10 +16,13 @@ from infinigen.assets.creatures.util.animation.driver_repeated import repeated_driver from infinigen.assets.utils.mesh import polygon_angles from infinigen.assets.utils.nodegroup import geo_base_selection -from infinigen.assets.utils.object import data2mesh, mesh2obj, new_circle, new_empty, new_icosphere, origin2highest -from infinigen.assets.utils.decorate import assign_material, geo_extension, join_objects, read_co, remove_vertices, \ +from infinigen.assets.utils.object import data2mesh, join_objects, mesh2obj, new_circle, new_empty, \ + new_icosphere, origin2highest +from infinigen.assets.utils.decorate import geo_extension, read_co, remove_vertices, \ subsurface2face_size, write_attribute, write_co -from infinigen.assets.utils.misc import log_uniform, make_circular, make_circular_angle +from infinigen.assets.utils.misc import assign_material +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core.placement.factory import AssetFactory @@ -30,7 +32,7 @@ from infinigen.core.util.blender import deep_clone_obj from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class JellyfishFactory(AssetFactory): @@ -284,10 +286,10 @@ def build_arm(self, radius, length, bend_angle): def shader_jellyfish(nw: NodeWrangler, base_hue, saturation, transparency): layerweight = nw.build_float_curve(nw.new_node(Nodes.LayerWeight, input_kwargs={'Blend': 0.3}), [(0, 0), (.4, 0), (uniform(.6, .9), 1), (1, 1)]) - emission_color = *colorsys.hsv_to_rgb(base_hue, uniform(.4, .6), 1), 1 - transparent_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.1, .1)) % 1, saturation, 1), 1 + emission_color = hsv2rgba(base_hue, uniform(.4, .6), 1) + transparent_color = hsv2rgba((base_hue + uniform(-.1, .1)) % 1, saturation, 1) emission = nw.new_node(Nodes.Emission, [emission_color]) - glossy = nw.new_node(Nodes.GlossyBSDF, + glossy = nw.new_node(Nodes.GlossyBSDF, input_kwargs={'Color': transparent_color, 'Roughness': uniform(0.8, 1)}) transparent = nw.new_node(Nodes.TransparentBSDF, [transparent_color]) mix_shader = nw.new_node(Nodes.MixShader, [0.5, glossy, transparent]) diff --git a/infinigen/assets/creatures/parts/beak.py b/infinigen/assets/creatures/parts/beak.py index da77a65da..31a29044c 100644 --- a/infinigen/assets/creatures/parts/beak.py +++ b/infinigen/assets/creatures/parts/beak.py @@ -14,7 +14,7 @@ from infinigen.core.util import blender as butil from infinigen.assets.creatures.util.geometry import nurbs as nurbs_util -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup def square(x): return x * x diff --git a/infinigen/assets/creatures/parts/body.py b/infinigen/assets/creatures/parts/body.py index c8cd5d039..90b72ec1a 100644 --- a/infinigen/assets/creatures/parts/body.py +++ b/infinigen/assets/creatures/parts/body.py @@ -21,7 +21,7 @@ from infinigen.assets.creatures.util import part_util from infinigen.assets.creatures.util.geometry import lofting, nurbs -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_quadruped_body', singleton=False, type='GeometryNodeTree') def nodegroup_quadruped_body(nw: NodeWrangler): diff --git a/infinigen/assets/creatures/parts/crustacean/antenna.py b/infinigen/assets/creatures/parts/crustacean/antenna.py index 6e0efce51..c63a0b544 100644 --- a/infinigen/assets/creatures/parts/crustacean/antenna.py +++ b/infinigen/assets/creatures/parts/crustacean/antenna.py @@ -11,8 +11,9 @@ from infinigen.assets.creatures.util.creature import Part from infinigen.assets.creatures.util.genome import Joint from infinigen.assets.creatures.parts.crustacean.leg import CrabLegFactory -from infinigen.assets.utils.decorate import displace_vertices, join_objects -from infinigen.assets.utils.misc import log_uniform +from infinigen.assets.utils.decorate import displace_vertices +from infinigen.assets.utils.object import join_objects +from infinigen.core.util.random import log_uniform class LobsterAntennaFactory(CrabLegFactory): diff --git a/infinigen/assets/creatures/parts/crustacean/body.py b/infinigen/assets/creatures/parts/crustacean/body.py index 93fda284c..56be383ab 100644 --- a/infinigen/assets/creatures/parts/crustacean/body.py +++ b/infinigen/assets/creatures/parts/crustacean/body.py @@ -1,5 +1,6 @@ # 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. +# 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: Lingjie Mei @@ -11,15 +12,15 @@ from infinigen.assets.creatures.util.creature import Part, PartFactory from infinigen.assets.creatures.util.genome import Joint from infinigen.assets.creatures.parts.utils.draw import geo_symmetric_texture -from infinigen.assets.utils.decorate import add_distance_to_boundary, displace_vertices, join_objects, read_co, write_co +from infinigen.assets.utils.decorate import distance2boundary, displace_vertices, read_co, write_co from infinigen.assets.utils.draw import leaf, spin from infinigen.assets.utils.misc import log_uniform -from infinigen.assets.utils.object import new_line +from infinigen.assets.utils.object import join_objects, new_line from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core.placement.placement import placeholder_locs from infinigen.core import surface -from infinigen.core.surface import write_attr_data +from infinigen.core.surface import read_attr_data, write_attr_data from infinigen.core.util import blender as butil @@ -103,12 +104,11 @@ def make_surface(params): vector_locations = [] obj = leaf(x_anchors, y_anchors, vector_locations) butil.modify_mesh(obj, 'SUBSURF', levels=1, render_levels=1) - add_distance_to_boundary(obj) + distance2boundary(obj) return obj def make_surface_side(self, obj, params, prefix="upper"): - vg = obj.vertex_groups['distance'] - distance = np.array([vg.weight(i) for i in range(len(obj.data.vertices))]) + distance = read_attr_data(obj, 'distance') height_scale = interp1d([0, .5, 1], [0, params[f'{prefix}_alpha'], 1], 'quadratic') displace_vertices(obj, lambda x, y, z: ( 0, 0, (1 if prefix == 'upper' else -1) * height_scale(distance) * params[f'{prefix}_z'])) diff --git a/infinigen/assets/creatures/parts/crustacean/claw.py b/infinigen/assets/creatures/parts/crustacean/claw.py index f042ce85a..9773e69d4 100644 --- a/infinigen/assets/creatures/parts/crustacean/claw.py +++ b/infinigen/assets/creatures/parts/crustacean/claw.py @@ -13,9 +13,10 @@ from infinigen.assets.creatures.util.genome import Joint from infinigen.assets.creatures.parts.crustacean.leg import CrabLegFactory from infinigen.assets.creatures.parts.utils.draw import decorate_segment -from infinigen.assets.utils.decorate import displace_vertices, join_objects, read_co, remove_vertices +from infinigen.assets.utils.decorate import displace_vertices, read_co, remove_vertices +from infinigen.assets.utils.object import join_objects from infinigen.assets.utils.draw import spin -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.random import log_uniform from infinigen.assets.utils.nodegroup import geo_base_selection from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler diff --git a/infinigen/assets/creatures/parts/crustacean/eye.py b/infinigen/assets/creatures/parts/crustacean/eye.py index 87f048a53..c07eac775 100644 --- a/infinigen/assets/creatures/parts/crustacean/eye.py +++ b/infinigen/assets/creatures/parts/crustacean/eye.py @@ -9,8 +9,7 @@ from numpy.random import uniform from infinigen.assets.creatures.util.creature import Part, PartFactory -from infinigen.assets.utils.decorate import join_objects -from infinigen.assets.utils.object import new_icosphere, origin2leftmost +from infinigen.assets.utils.object import join_objects, new_icosphere, origin2leftmost from infinigen.core.placement.detail import remesh_with_attrs diff --git a/infinigen/assets/creatures/parts/crustacean/leg.py b/infinigen/assets/creatures/parts/crustacean/leg.py index 82136f3d1..b16e359c7 100644 --- a/infinigen/assets/creatures/parts/crustacean/leg.py +++ b/infinigen/assets/creatures/parts/crustacean/leg.py @@ -11,8 +11,9 @@ from infinigen.assets.creatures.util.creature import Part, PartFactory from infinigen.assets.creatures.util.genome import Joint from infinigen.assets.creatures.parts.utils.draw import make_segments -from infinigen.assets.utils.decorate import join_objects, read_co -from infinigen.assets.utils.misc import log_uniform +from infinigen.assets.utils.decorate import read_co +from infinigen.assets.utils.object import join_objects +from infinigen.core.util.random import log_uniform from infinigen.core.surface import write_attr_data diff --git a/infinigen/assets/creatures/parts/crustacean/tail.py b/infinigen/assets/creatures/parts/crustacean/tail.py index 99fc2971c..b961cae23 100644 --- a/infinigen/assets/creatures/parts/crustacean/tail.py +++ b/infinigen/assets/creatures/parts/crustacean/tail.py @@ -12,8 +12,9 @@ from infinigen.assets.creatures.util.creature import Part, PartFactory from infinigen.assets.creatures.util.genome import Joint from infinigen.assets.creatures.parts.utils.draw import make_segments -from infinigen.assets.utils.decorate import join_objects, read_co -from infinigen.assets.utils.misc import log_uniform +from infinigen.assets.utils.decorate import read_co +from infinigen.assets.utils.object import join_objects +from infinigen.core.util.random import log_uniform from infinigen.core.surface import write_attr_data diff --git a/infinigen/assets/creatures/parts/eye.py b/infinigen/assets/creatures/parts/eye.py index 741e60c89..e3ee25d3f 100644 --- a/infinigen/assets/creatures/parts/eye.py +++ b/infinigen/assets/creatures/parts/eye.py @@ -23,7 +23,7 @@ from infinigen.assets.creatures.util.creature import PartFactory from infinigen.assets.creatures.util import part_util -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_eyelid', singleton=True, type='GeometryNodeTree') def nodegroup_eyelid(nw: NodeWrangler): diff --git a/infinigen/assets/creatures/parts/fin_old.py b/infinigen/assets/creatures/parts/fin_old.py index 28913c1a1..510e5bf77 100644 --- a/infinigen/assets/creatures/parts/fin_old.py +++ b/infinigen/assets/creatures/parts/fin_old.py @@ -19,7 +19,7 @@ from infinigen.assets.creatures.util.creature import PartFactory from infinigen.assets.creatures.util.part_util import nodegroup_to_part -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_fish_fin', singleton=False, type='GeometryNodeTree') def nodegroup_fish_fin(nw: NodeWrangler): diff --git a/infinigen/assets/creatures/parts/foot.py b/infinigen/assets/creatures/parts/foot.py index e5df80357..724f6159b 100644 --- a/infinigen/assets/creatures/parts/foot.py +++ b/infinigen/assets/creatures/parts/foot.py @@ -19,7 +19,7 @@ from infinigen.assets.creatures.util.creature import Part, PartFactory from infinigen.assets.creatures.util.part_util import nodegroup_to_part -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_tiger_toe', singleton=False, type='GeometryNodeTree') def nodegroup_tiger_toe(nw: NodeWrangler): diff --git a/infinigen/assets/creatures/parts/generic_nurbs.py b/infinigen/assets/creatures/parts/generic_nurbs.py index c5f3f8a92..9e92d917d 100644 --- a/infinigen/assets/creatures/parts/generic_nurbs.py +++ b/infinigen/assets/creatures/parts/generic_nurbs.py @@ -20,7 +20,7 @@ from infinigen.core.util import blender as butil from infinigen.core.util.logging import Suppress -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup NURBS_BASE_PATH = Path(__file__).parent/'nurbs_data' NURBS_KEYS = [p.stem for p in NURBS_BASE_PATH.iterdir()] diff --git a/infinigen/assets/creatures/parts/head.py b/infinigen/assets/creatures/parts/head.py index 341ed7078..caf2b7af6 100644 --- a/infinigen/assets/creatures/parts/head.py +++ b/infinigen/assets/creatures/parts/head.py @@ -23,7 +23,7 @@ from infinigen.assets.creatures.util.creature import PartFactory from infinigen.assets.creatures.util import part_util from infinigen.assets.creatures.parts.eye import nodegroup_mammal_eye -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_carnivore_jaw', singleton=True, type='GeometryNodeTree') def nodegroup_carnivore_jaw(nw: NodeWrangler): diff --git a/infinigen/assets/creatures/parts/head_detail.py b/infinigen/assets/creatures/parts/head_detail.py index b342d7d6d..f1fb163fe 100644 --- a/infinigen/assets/creatures/parts/head_detail.py +++ b/infinigen/assets/creatures/parts/head_detail.py @@ -21,7 +21,7 @@ from infinigen.assets.creatures.util.nodegroups.geometry import nodegroup_solidify, nodegroup_symmetric_clone, nodegroup_taper from infinigen.core.util.math import clip_gaussian from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_cat_ear', singleton=False, type='GeometryNodeTree') def nodegroup_cat_ear(nw: NodeWrangler): diff --git a/infinigen/assets/creatures/parts/hoof.py b/infinigen/assets/creatures/parts/hoof.py index 16b379883..9662c085c 100644 --- a/infinigen/assets/creatures/parts/hoof.py +++ b/infinigen/assets/creatures/parts/hoof.py @@ -25,7 +25,7 @@ from infinigen.core.nodes import node_utils from infinigen.assets.creatures.util.geometry import nurbs as nurbs_util -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup def square(x): return x * x diff --git a/infinigen/assets/creatures/parts/horn.py b/infinigen/assets/creatures/parts/horn.py index 615c8d0c9..271d76e6b 100644 --- a/infinigen/assets/creatures/parts/horn.py +++ b/infinigen/assets/creatures/parts/horn.py @@ -21,7 +21,7 @@ from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler from infinigen.core.nodes import node_utils from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_noise', singleton=False, type='GeometryNodeTree') def nodegroup_noise(nw: NodeWrangler): diff --git a/infinigen/assets/creatures/parts/leg.py b/infinigen/assets/creatures/parts/leg.py index 2cf9a5d4a..afe40be65 100644 --- a/infinigen/assets/creatures/parts/leg.py +++ b/infinigen/assets/creatures/parts/leg.py @@ -21,7 +21,7 @@ from infinigen.assets.creatures.util.creature import PartFactory from infinigen.assets.creatures.util.part_util import nodegroup_to_part -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_quadruped_back_leg', singleton=False, type='GeometryNodeTree') def nodegroup_quadruped_back_leg(nw: NodeWrangler): diff --git a/infinigen/assets/creatures/parts/ridged_fin.py b/infinigen/assets/creatures/parts/ridged_fin.py index df2ee929d..6c8cb5171 100644 --- a/infinigen/assets/creatures/parts/ridged_fin.py +++ b/infinigen/assets/creatures/parts/ridged_fin.py @@ -22,7 +22,7 @@ from infinigen.assets.creatures.util.creature import PartFactory, Part from infinigen.assets.creatures.util.part_util import nodegroup_to_part from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_mix2_values', singleton=True, type='GeometryNodeTree') def nodegroup_mix2_values(nw: NodeWrangler): diff --git a/infinigen/assets/creatures/parts/tail.py b/infinigen/assets/creatures/parts/tail.py index 78c088a4f..26e6d0c82 100644 --- a/infinigen/assets/creatures/parts/tail.py +++ b/infinigen/assets/creatures/parts/tail.py @@ -16,7 +16,7 @@ from infinigen.assets.creatures.util.creature import PartFactory from infinigen.assets.creatures.util.part_util import nodegroup_to_part -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_tail', singleton=False, type='GeometryNodeTree') def nodegroup_tail(nw: NodeWrangler): @@ -39,7 +39,7 @@ class Tail(PartFactory): def sample_params(self): return { - 'length_rad1_rad2': (N(1.5, 0.5), 0.05, 0.02), + 'length_rad1_rad2': (N(0.5, 0.1), 0.05, 0.02), 'angles_deg': np.array((31.39, 65.81, -106.93)) * N(1, 0.1), 'aspect': N(1, 0.05) } diff --git a/infinigen/assets/creatures/parts/wings.py b/infinigen/assets/creatures/parts/wings.py index 779048f8d..4331db191 100644 --- a/infinigen/assets/creatures/parts/wings.py +++ b/infinigen/assets/creatures/parts/wings.py @@ -24,7 +24,7 @@ from infinigen.assets.creatures.util.creature import PartFactory from infinigen.assets.creatures.util.part_util import nodegroup_to_part -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_feather', singleton=False, type='GeometryNodeTree') def nodegroup_feather(nw: NodeWrangler): diff --git a/infinigen/assets/creatures/util/cloth_sim.py b/infinigen/assets/creatures/util/cloth_sim.py index b7151d614..c68915a90 100644 --- a/infinigen/assets/creatures/util/cloth_sim.py +++ b/infinigen/assets/creatures/util/cloth_sim.py @@ -28,46 +28,46 @@ def local_pos_rigity_mask(nw: NodeWrangler): expose_input=[('NodeSocketGeometry', 'Geometry', None), ('NodeSocketFloat', 'To Min', 0.4), ('NodeSocketFloat', 'To Max', 0.9)]) - + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': nw.expose_input('Local Pos', attribute='local_pos')}) - + clamp = nw.new_node(Nodes.Clamp, input_kwargs={'Value': nw.expose_input("Radius", attribute='skeleton_rad'), 'Min': 0.03, 'Max': 0.49}) - + multiply = nw.new_node(Nodes.Math, input_kwargs={0: clamp, 1: -1.0}, attrs={'operation': 'MULTIPLY'}) - + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: clamp, 1: 1.5}, attrs={'operation': 'MULTIPLY'}) - + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': separate_xyz.outputs["Z"], 1: multiply, 2: multiply_1}) - + musgrave_texture = nw.new_node(Nodes.MusgraveTexture, input_kwargs={'W': uniform(1e3), 'Scale': normal(10, 1)}, attrs={'musgrave_dimensions': '4D'}) - + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: musgrave_texture, 1: normal(0.07, 0.007)}, attrs={'operation': 'MULTIPLY'}) - + musgrave_texture_1 = nw.new_node(Nodes.MusgraveTexture, input_kwargs={'Scale': normal(5, 0.5), 'W': uniform(1e3)}, attrs={'musgrave_dimensions': '4D'}) - + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: musgrave_texture_1, 1: normal(0.12, 0.01)}, attrs={'operation': 'MULTIPLY'}) - + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: multiply_3}) - + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: map_range.outputs["Result"], 1: add}) - + colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': add_1}) colorramp.color_ramp.elements.new(1) @@ -77,29 +77,35 @@ def local_pos_rigity_mask(nw: NodeWrangler): colorramp.color_ramp.elements[1].color = (1.0, 1.0, 1.0, 1.0) colorramp.color_ramp.elements[2].position = 1.0 colorramp.color_ramp.elements[2].color = (0.0, 0.0, 0.0, 1.0) - + map_range_1 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': colorramp.outputs["Color"], 3: group_input.outputs["To Min"], 4: group_input.outputs["To Max"]}) - + musgrave_texture_2 = nw.new_node(Nodes.MusgraveTexture) - + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: musgrave_texture_2, 1: normal(0.1, 0.02)}, attrs={'operation': 'MULTIPLY'}) - + return nw.new_node(Nodes.Math, input_kwargs={0: map_range_1.outputs["Result"], 1: multiply_4}) -def bake_cloth(obj, settings, attributes, frame_start=None, frame_end=None): +def bake_cloth(obj, settings=None, attributes=None, frame_start=None, frame_end=None): if frame_start is None: frame_start = bpy.context.scene.frame_start if frame_end is None: frame_end = bpy.context.scene.frame_end + if settings is None: + settings = {} + if attributes is None: + attributes = {} mod = obj.modifiers.new('bake_cloth', 'CLOTH') mod.settings.effector_weights.gravity = settings.pop('gravity', 1) + mod.collision_settings.distance_min = settings.pop('distance_min', .015) + mod.collision_settings.use_self_collision = settings.pop('use_self_collision', False) for k, v in settings.items(): setattr(mod.settings, k, v) @@ -111,7 +117,6 @@ def bake_cloth(obj, settings, attributes, frame_start=None, frame_end=None): mod.point_cache.frame_start = frame_start mod.point_cache.frame_end = frame_end - with butil.ViewportMode(obj, mode='OBJECT'), butil.SelectObjects(obj), Timer('Baking fish cloth'): override = {'scene': bpy.context.scene, 'active_object': obj, 'point_cache': mod.point_cache} bpy.ops.ptcache.bake(override, bake=True) diff --git a/infinigen/assets/creatures/util/geometry/curve.py b/infinigen/assets/creatures/util/geometry/curve.py index 8c2f8fa2f..893086a74 100644 --- a/infinigen/assets/creatures/util/geometry/curve.py +++ b/infinigen/assets/creatures/util/geometry/curve.py @@ -11,9 +11,9 @@ class Curve: def __init__( - self, points, - profile=None, taper=None, - closed=False, sharp=None, + self, points, + profile=None, taper=None, + closed=False, sharp=None, scale=None ): self.points = points @@ -23,7 +23,7 @@ def __init__( self.sharp = sharp self.scale = scale - def to_curve_obj(self, name='curve', + def to_curve_obj(self, name='curve', resu=4, curvetype='NURBS', extrude=0, fill_caps = True, to_mesh=False, cleanup=True ): @@ -33,7 +33,7 @@ def to_curve_obj(self, name='curve', curveData.resolution_u = resu curveData.use_fill_caps = fill_caps curveData.twist_mode = 'MINIMUM' - curveData.extrude = extrude + curveData.bevel_depth = extrude polyline = curveData.splines.new(curvetype) @@ -89,7 +89,7 @@ def get_pos(p): if o is not None: o.select_set(True) bpy.ops.object.delete(use_global=False, confirm=False) - + self.profile = None self.taper = None diff --git a/infinigen/assets/creatures/util/geonode_part.py b/infinigen/assets/creatures/util/geonode_part.py index 9c420aa79..ad7b91e4b 100644 --- a/infinigen/assets/creatures/util/geonode_part.py +++ b/infinigen/assets/creatures/util/geonode_part.py @@ -3,48 +3,13 @@ # Authors: Alexander Raistrick - -import pdb -import bpy - import numpy as np from infinigen.assets.creatures.util.creature import Part, Joint, infer_skeleton_from_mesh from infinigen.core.util import blender as butil from infinigen.core.nodes.node_wrangler import NodeWrangler, Nodes, geometry_node_group_empty_new - -def extract_nodegroup_geo(target_obj, nodegroup, k, ng_params=None): - - assert k in nodegroup.outputs - assert target_obj.type == 'MESH' - - vert = butil.spawn_vert('extract_nodegroup_geo.temp') - - butil.modify_mesh(vert, type='NODES', apply=False) - if vert.modifiers[0].node_group == None: - group = geometry_node_group_empty_new() - vert.modifiers[0].node_group = group - ng = vert.modifiers[0].node_group - nw = NodeWrangler(ng) - obj_inp = nw.new_node(Nodes.ObjectInfo, [target_obj]) - - group_input_kwargs = {**ng_params} - if 'Geometry' in nodegroup.inputs: - group_input_kwargs['Geometry'] = obj_inp.outputs['Geometry'] - group = nw.new_node(nodegroup.name, input_kwargs=group_input_kwargs) - - geo = group.outputs[k] - - if k.endswith('Curve'): - # curves dont export from geonodes well, convert it to a mesh - geo = nw.new_node(Nodes.CurveToMesh, [geo]) - - output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geo}) - - butil.apply_modifiers(vert) - bpy.data.node_groups.remove(ng) - return vert +from infinigen.assets.utils.extract_nodegroup_parts import extract_nodegroup_geo class GeonodePartFactory: @@ -69,7 +34,12 @@ def _extract_geo_results(self): with butil.TemporaryObject(self.base_obj()) as base_obj: ng = self.nodegroup_func() geo_outputs = [o for o in ng.outputs if o.bl_socket_idname == 'NodeSocketGeometry'] - results = {o.name: extract_nodegroup_geo(base_obj, ng, o.name, ng_params=ng_params) for o in geo_outputs} + results = { + o.name: extract_nodegroup_geo( + base_obj, ng, o.name, ng_params=ng_params + ) + for o in geo_outputs + } return results diff --git a/infinigen/assets/creatures/util/part_util.py b/infinigen/assets/creatures/util/part_util.py index 6192e4b85..e42805929 100644 --- a/infinigen/assets/creatures/util/part_util.py +++ b/infinigen/assets/creatures/util/part_util.py @@ -17,39 +17,7 @@ from infinigen.core.nodes.node_wrangler import NodeWrangler, Nodes -def extract_nodegroup_geo(target_obj, nodegroup, k, ng_params=None): - - assert k in nodegroup.outputs - assert target_obj.type == 'MESH' - - vert = butil.spawn_vert('extract_nodegroup_geo.temp') - - butil.modify_mesh(vert, type='NODES', apply=False) - mod = vert.modifiers[0] - mod.node_group = bpy.data.node_groups.new('extract_nodegroup_geo', 'GeometryNodeTree') - nw = NodeWrangler(mod.node_group) - obj_inp = nw.new_node(Nodes.ObjectInfo, [target_obj]) - - group_input_kwargs = {**ng_params} - if 'Geometry' in nodegroup.inputs: - group_input_kwargs['Geometry'] = obj_inp.outputs['Geometry'] - - try: - group = nw.new_node(nodegroup.name, input_kwargs=group_input_kwargs) - except KeyError as e: - print(f"Error while performing extract_nodegroup_geo for {nodegroup=} on {target_obj=}, output_key={k}") - raise e - - geo = group.outputs[k] - - if k.endswith('Curve'): - # curves dont export from geonodes well, convert it to a mesh - geo = nw.new_node(Nodes.CurveToMesh, [geo]) - - output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geo}) - - butil.apply_modifiers(vert) - return vert +from infinigen.assets.utils.extract_nodegroup_parts import extract_nodegroup_geo def nodegroup_to_part(nodegroup_func, params, kwargs=None, base_obj=None, split_extras=False): diff --git a/infinigen/assets/debris/lichen.py b/infinigen/assets/debris/lichen.py index 890a05df0..0e190a598 100644 --- a/infinigen/assets/debris/lichen.py +++ b/infinigen/assets/debris/lichen.py @@ -11,15 +11,18 @@ import numpy as np from numpy.random import uniform, normal as N -from infinigen.assets.utils.decorate import assign_material +from infinigen.assets.utils.misc import assign_material +from infinigen.core.util.color import hsv2rgba from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.placement.factory import AssetFactory, make_asset_collection +from infinigen.core.placement.instance_scatter import scatter_instances from infinigen.core import surface from infinigen.core.placement.factory import AssetFactory from infinigen.infinigen_gpl.extras.diff_growth import build_diff_growth from infinigen.assets.utils.object import data2mesh from infinigen.assets.utils.mesh import polygon_angles from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class LichenFactory(AssetFactory): @@ -46,7 +49,7 @@ def shader_lichen(nw: NodeWrangler, base_hue=.2, **params): v_perturb = uniform(1., 1.5) def map_perturb(h, s, v): - return *colorsys.hsv_to_rgb(h + h_perturb, s + s_perturb, v / v_perturb), 1. + return hsv2rgba(h + h_perturb, s + s_perturb, v / v_perturb) subsurface_ratio = .02 roughness = 1. @@ -93,6 +96,6 @@ def create_asset(self, **kwargs): butil.apply_transform(obj) assign_material(obj, surface.shaderfunc_to_material(LichenFactory.shader_lichen, (self.base_hue + uniform(-.04, .04)) % 1)) - + tag_object(obj, 'lichen') - return obj \ No newline at end of file + return obj diff --git a/infinigen/assets/debris/moss.py b/infinigen/assets/debris/moss.py index 63df60737..b0ab11542 100644 --- a/infinigen/assets/debris/moss.py +++ b/infinigen/assets/debris/moss.py @@ -1,17 +1,26 @@ +# 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: Lingjie Mei import math import colorsys from numpy.random import uniform as U +from infinigen.core.placement.instance_scatter import scatter_instances from infinigen.assets.utils.object import new_cube -from infinigen.assets.utils.misc import build_color_ramp -from infinigen.assets.utils.decorate import assign_material -from infinigen.assets.utils.tag import tag_object - -from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.color import hsv2rgba +from infinigen.assets.utils.misc import assign_material +from infinigen.core.placement.factory import AssetFactory, make_asset_collection from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils from infinigen.core import surface +from infinigen.core.tagging import tag_object, tag_nodegroup +from infinigen.core.placement.instance_scatter import scatter_instances + +from infinigen.core.nodes.node_utils import build_color_ramp class MossFactory(AssetFactory): @@ -27,14 +36,14 @@ def shader_moss(nw: NodeWrangler, base_hue=.3): v_perturb = U(1., 1.5) def map_perturb(h, s, v): - return *colorsys.hsv_to_rgb(h + h_perturb, s + s_perturb, v / v_perturb), 1. + return hsv2rgba(h + h_perturb, s + s_perturb, v / v_perturb) subsurface_ratio = .05 roughness = 1. mix_ratio = .2 - cr = build_color_ramp(nw, - nw.new_node(Nodes.NoiseTexture, input_kwargs={'Scale': 5.}).outputs["Fac"], + cr = build_color_ramp(nw, + nw.new_node(Nodes.NoiseTexture, input_kwargs={'Scale': 5.}).outputs["Fac"], [0, .5, 1], [map_perturb(base_hue, .8, .1), map_perturb(base_hue - 0.05, .8, .1), (0., 0., 0., 1.)] ) @@ -82,4 +91,4 @@ def geo_moss_instance(nw: NodeWrangler, face_size): circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': 4, 'Radius': radius}).outputs[ "Curve"] mesh = nw.curve2mesh(bezier, circle) - nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': mesh}) \ No newline at end of file + nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': mesh}) diff --git a/infinigen/assets/debris/pine_needle.py b/infinigen/assets/debris/pine_needle.py index b030e7eeb..57049e887 100644 --- a/infinigen/assets/debris/pine_needle.py +++ b/infinigen/assets/debris/pine_needle.py @@ -13,7 +13,7 @@ from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object +from infinigen.core.tagging import tag_object def shader_material(nw: NodeWrangler): # Code generated using version 2.6.3 of the node_transpiler diff --git a/infinigen/assets/decor/__init__.py b/infinigen/assets/decor/__init__.py new file mode 100644 index 000000000..c24bee2f6 --- /dev/null +++ b/infinigen/assets/decor/__init__.py @@ -0,0 +1 @@ +from .aquarium_tank import AquariumTankFactory \ No newline at end of file diff --git a/infinigen/assets/decor/aquarium_tank.py b/infinigen/assets/decor/aquarium_tank.py new file mode 100644 index 000000000..627c5db35 --- /dev/null +++ b/infinigen/assets/decor/aquarium_tank.py @@ -0,0 +1,114 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.rocks.boulder import BoulderFactory +from infinigen.assets.cactus import CactusFactory +from infinigen.assets.corals import CoralFactory +from infinigen.assets.mollusk import MolluskFactory +from infinigen.assets.mushroom import MushroomFactory +from infinigen.assets.underwater.seaweed import SeaweedFactory +from infinigen.assets.materials import metal, water +from infinigen.assets.materials import glass +from infinigen.assets.utils.decorate import read_co, write_attribute +from infinigen.assets.utils.object import join_objects, new_bbox, new_cube, new_plane +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList + + +class AquariumTankFactory(AssetFactory): + dry_factories = [MushroomFactory, CactusFactory, BoulderFactory] + wet_factories = [MolluskFactory, CoralFactory, SeaweedFactory] + + def __init__(self, factory_seed, coarse=False): + super(AquariumTankFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.is_wet = uniform() < .5 + base_factory_fn = np.random.choice(self.wet_factories if self.is_wet else self.dry_factories) + self.base_factory = base_factory_fn(self.factory_seed) + self.width = log_uniform(.5, 1) + self.depth = log_uniform(.5, .8) + self.height = log_uniform(.5, 1) + self.thickness = uniform(.01, .02) + self.belt_thickness = log_uniform(.02, .05) + + materials = AssetList['AquariumTankFactory']() + self.glass_surface = materials['glass_surface'].assign_material() + self.belt_surface = materials['belt_surface'].assign_material() + self.water_surface = materials['water_surface'].assign_material() + + scratch_prob, edge_wear_prob = materials['wear_tear_prob'] + self.scratch, self.edge_wear = materials['wear_tear'] + is_scratch = uniform() < scratch_prob + is_edge_wear = uniform() < edge_wear_prob + if not is_scratch: + self.scratch = None + if not is_edge_wear: + self.edge_wear = None + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + return new_bbox( + -self.thickness - self.depth, self.thickness, -self.thickness, self.width + self.thickness, 0, self.height + ) + + def create_asset(self, **params) -> bpy.types.Object: + tank = new_cube(location=(1, 1, 1)) + butil.apply_transform(tank, loc=True) + tank.scale = self.width / 2, self.depth / 2, self.height / 2 + butil.apply_transform(tank) + butil.modify_mesh(tank, 'SOLIDIFY', thickness=self.thickness) + write_attribute(tank, 1, 'glass', 'FACE') + parts = [tank] + parts.extend(self.make_belts()) + base_obj = self.base_factory.create_asset(**params) + co = read_co(base_obj) + x_min, x_max = np.amin(co, 0), np.amax(co, 0) + scale = uniform(.7, .9) / np.max((x_max - x_min) / np.array([self.width, self.depth, self.height])) + base_obj.location = -(x_min + x_max) * np.array(base_obj.scale) / 2 + base_obj.location[-1] = -(x_min * base_obj.scale)[-1] + butil.apply_transform(base_obj, True) + base_obj.location = self.width / 2, self.depth / 2, self.thickness + base_obj.scale = [scale] * 3 + butil.apply_transform(base_obj) + parts.append(base_obj) + obj = join_objects(parts) + obj.rotation_euler[-1] = np.pi / 2 + butil.apply_transform(obj) + return obj + + def make_belts(self): + belt = new_plane() + with butil.ViewportMode(belt, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.delete(type='ONLY_FACE') + belt.location = self.width / 2, self.depth / 2, 0 + belt.scale = self.width / 2, self.depth / 2, 0 + butil.apply_transform(belt, loc=True) + with butil.ViewportMode(belt, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={'value': (0, 0, self.belt_thickness)}) + butil.modify_mesh(belt, 'SOLIDIFY', thickness=self.thickness) + write_attribute(belt, 1, 'belt', 'FACE') + + belt_ = deep_clone_obj(belt) + belt_.location[-1] = self.height - self.belt_thickness + butil.apply_transform(belt_, True) + return [belt, belt_] + + def finalize_assets(self, assets): + self.glass_surface.apply(assets, selection='glass') + self.belt_surface.apply(assets, selection='belt') + + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) diff --git a/infinigen/assets/deformed_trees/base.py b/infinigen/assets/deformed_trees/base.py index cd56066f6..f0c610659 100644 --- a/infinigen/assets/deformed_trees/base.py +++ b/infinigen/assets/deformed_trees/base.py @@ -11,7 +11,8 @@ from infinigen.assets.trees import TreeFactory from infinigen.assets.trees.generate import GenericTreeFactory, random_species -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core.placement.factory import AssetFactory @@ -49,7 +50,7 @@ def shader_rings(nw: NodeWrangler, base_hue): ratio = nw.new_node(Nodes.WaveTexture, [position], input_kwargs={'Scale': uniform(10, 20), 'Distortion': uniform(4, 10)}, attrs={'wave_type': 'RINGS', 'rings_direction': 'Z', 'wave_profile': 'SAW'}) - bright_color = *colorsys.hsv_to_rgb(base_hue, uniform(.4, .8), log_uniform(.2, .8)), 1. + bright_color = hsv2rgba(base_hue, uniform(.4, .8), log_uniform(.2, .8)) dark_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.02, .02)) % 1, uniform(.4, .8), log_uniform(.02, .05)), 1. color = nw.new_node(Nodes.MixRGB, [ratio, dark_color, bright_color]) diff --git a/infinigen/assets/deformed_trees/fallen.py b/infinigen/assets/deformed_trees/fallen.py index a522010bd..c84970d81 100644 --- a/infinigen/assets/deformed_trees/fallen.py +++ b/infinigen/assets/deformed_trees/fallen.py @@ -5,20 +5,23 @@ # Authors: Lingjie Mei -import bmesh + import bpy +import bmesh import numpy as np from numpy.random import uniform from infinigen.assets.deformed_trees.base import BaseDeformedTreeFactory -from infinigen.assets.utils.decorate import assign_material, join_objects, remove_vertices, separate_loose +from infinigen.assets.utils.decorate import remove_vertices +from infinigen.assets.utils.misc import assign_material +from infinigen.assets.utils.object import join_objects, separate_loose from infinigen.assets.utils.draw import cut_plane from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface from infinigen.core.util.blender import deep_clone_obj from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class FallenTreeFactory(BaseDeformedTreeFactory): diff --git a/infinigen/assets/deformed_trees/generate.py b/infinigen/assets/deformed_trees/generate.py index 9e0313856..24ac17a63 100644 --- a/infinigen/assets/deformed_trees/generate.py +++ b/infinigen/assets/deformed_trees/generate.py @@ -11,7 +11,7 @@ from infinigen.assets.deformed_trees.truncated import TruncatedTreeFactory from infinigen.core.placement.factory import AssetFactory from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class DeformedTreeFactory(AssetFactory): diff --git a/infinigen/assets/deformed_trees/hollow.py b/infinigen/assets/deformed_trees/hollow.py index 9defe98c5..3487057eb 100644 --- a/infinigen/assets/deformed_trees/hollow.py +++ b/infinigen/assets/deformed_trees/hollow.py @@ -10,15 +10,16 @@ from numpy.random import uniform from infinigen.assets.deformed_trees.base import BaseDeformedTreeFactory -from infinigen.assets.utils.decorate import assign_material, join_objects, read_co, read_material_index, separate_loose, \ - write_material_index +from infinigen.assets.utils.decorate import read_co, read_material_index, write_material_index +from infinigen.assets.utils.misc import assign_material +from infinigen.assets.utils.object import join_objects, separate_loose from infinigen.assets.utils.nodegroup import geo_selection from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface from infinigen.core.util.blender import deep_clone_obj, select_none from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class HollowTreeFactory(BaseDeformedTreeFactory): diff --git a/infinigen/assets/deformed_trees/rotten.py b/infinigen/assets/deformed_trees/rotten.py index fabf3d8b6..88b68c77d 100644 --- a/infinigen/assets/deformed_trees/rotten.py +++ b/infinigen/assets/deformed_trees/rotten.py @@ -9,16 +9,16 @@ from numpy.random import uniform from infinigen.assets.deformed_trees.base import BaseDeformedTreeFactory -from infinigen.assets.utils.decorate import assign_material, join_objects, read_material_index, remove_vertices, \ - separate_loose, write_material_index -from infinigen.assets.utils.misc import log_uniform -from infinigen.assets.utils.object import new_icosphere +from infinigen.assets.utils.decorate import read_material_index, remove_vertices, write_material_index +from infinigen.assets.utils.misc import assign_material +from infinigen.core.util.random import log_uniform +from infinigen.assets.utils.object import join_objects, new_icosphere, separate_loose from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface from infinigen.core.util.blender import deep_clone_obj from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class RottenTreeFactory(BaseDeformedTreeFactory): @staticmethod diff --git a/infinigen/assets/deformed_trees/truncated.py b/infinigen/assets/deformed_trees/truncated.py index 15803e704..1a398b6fc 100644 --- a/infinigen/assets/deformed_trees/truncated.py +++ b/infinigen/assets/deformed_trees/truncated.py @@ -14,7 +14,7 @@ from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class TruncatedTreeFactory(FallenTreeFactory): diff --git a/infinigen/assets/elements/__init__.py b/infinigen/assets/elements/__init__.py new file mode 100644 index 000000000..765c5d999 --- /dev/null +++ b/infinigen/assets/elements/__init__.py @@ -0,0 +1,10 @@ +# 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: Lingjie Mei +from .staircases import * +from .doors import * +from .rug import RugFactory +from .warehouses import * +from .nature_shelf_trinkets.generate import NatureShelfTrinketsFactory +from .pillars import PillarFactory diff --git a/infinigen/assets/elements/doors/__init__.py b/infinigen/assets/elements/doors/__init__.py new file mode 100644 index 000000000..e0d675979 --- /dev/null +++ b/infinigen/assets/elements/doors/__init__.py @@ -0,0 +1,33 @@ +# 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: Lingjie Mei +import bpy +import numpy as np + +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from .panel import PanelDoorFactory, GlassPanelDoorFactory +from .lite import LiteDoorFactory +from .louver import LouverDoorFactory +from .casing import DoorCasingFactory + +from infinigen.core.util import blender as butil + +def random_door_factory(): + door_factories = [PanelDoorFactory, GlassPanelDoorFactory, LouverDoorFactory, LiteDoorFactory] + door_probs = np.array([4, 2, 3, 3]) + return np.random.choice(door_factories, p=door_probs / door_probs.sum()) + + +class DoorFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(DoorFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.base_factory = random_door_factory()(factory_seed, coarse) + + def create_asset(self, **params) -> bpy.types.Object: + return self.base_factory.create_asset(**params) + + def finalize_assets(self, assets): + self.base_factory.finalize_assets(assets) diff --git a/infinigen/assets/elements/doors/base.py b/infinigen/assets/elements/doors/base.py new file mode 100644 index 000000000..dd44e0e8e --- /dev/null +++ b/infinigen/assets/elements/doors/base.py @@ -0,0 +1,217 @@ +# 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: +# - Lingjie Mei: primary author + +import bpy +import gin +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials.common import unique_surface +from infinigen.assets.utils.decorate import mirror, read_co, write_attribute, write_co +from infinigen.assets.utils.draw import spin +from infinigen.assets.utils.nodegroup import geo_radius +from infinigen.assets.utils.object import data2mesh, join_objects, mesh2obj, new_cube, new_line +from infinigen.core.constraints.example_solver.room import constants +from infinigen.core.placement.factory import AssetFactory +from infinigen.core import surface +from infinigen.assets.materials import glass, metal, wood +from infinigen.core.util.bevelling import add_bevel, get_bevel_edges +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil + +from infinigen.assets.utils.autobevel import BevelSharp + +class BaseDoorFactory(AssetFactory): + + def __init__(self, factory_seed, coarse=False): + super(BaseDoorFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.width = constants.DOOR_WIDTH + self.height = constants.DOOR_SIZE + self.depth = uniform(.04, .06) + self.panel_margin = log_uniform(.08, .12) + self.bevel_width = uniform(.005, .01) + self.out_bevel = uniform() < .7 + self.shrink_width = log_uniform(.005, .06) + + surface_fn = np.random.choice([metal, wood], p=[.2, .8]) + self.surface = unique_surface(surface_fn, self.factory_seed) + self.has_glass = False + self.glass_surface = glass + self.has_louver = False + self.louver_surface = np.random.choice([metal, wood], p=[.2, .8]) + + self.handle_type = np.random.choice(['knob', 'lever', 'pull']) + self.handle_surface = np.random.choice([metal, wood], p=[.2, .8]) + self.handle_offset = self.panel_margin * .5 + self.handle_height = self.height * uniform(.45, .5) + + self.knob_radius = uniform(.03, .04) + base_radius = uniform(1.1, 1.2) + mid_radius = uniform(.4, .5) + self.knob_radius_mid = base_radius, base_radius, mid_radius, mid_radius, 1, uniform(.6, .8), 0 + self.knob_depth = uniform(.08, .1) + self.knob_depth_mid = [0, uniform(.1, .15), uniform(.25, .3), uniform(.35, .45), uniform(.6, .8), 1, + 1 + 1e-3] + + self.lever_radius = uniform(.03, .04) + self.lever_mid_radius = uniform(.01, .02) + self.lever_depth = uniform(.05, .08) + self.lever_mid_depth = uniform(.15, .25) + self.lever_length = log_uniform(.15, .2) + self.level_type = np.random.choice(['wave', 'cylinder', 'bent']) + + self.pull_size = log_uniform(.1, .4) + self.pull_depth = uniform(.05, .08) + self.pull_width = log_uniform(.08, .15) + self.pull_extension = uniform(.05, .15) + self.to_pull_bevel = uniform() < .5 + self.pull_bevel_width = uniform(.02, .04) + self.pull_radius = uniform(.01, .02) + self.pull_type = np.random.choice(['u', 'tee', 'zed']) + self.is_pull_circular = uniform() < .5 or self.pull_type == 'zed' + self.panel_surface = unique_surface(surface_fn, np.random.randint(1e5)) + self.auto_bevel = BevelSharp() + self.side_bevel = log_uniform(.005,.015) + + self.metal_color = metal.sample_metal_color() + + def create_asset(self, **params) -> bpy.types.Object: + for _ in range(100): + obj = self._create_asset() + if max(obj.dimensions) < 5: + return obj + else: + raise ValueError('Bad door booleaning') + + def _create_asset(self): + obj = new_cube(location=(1, 1, 1)) + butil.apply_transform(obj, loc=True) + obj.scale = self.width / 2, self.depth / 2, self.height / 2 + butil.apply_transform(obj) + panels = self.make_panels() + extras = [] + for panel in panels: + extras.extend(panel['func'](obj, panel)) + match self.handle_type: + case 'knob': + extras.extend(self.make_knobs()) + case 'lever': + extras.extend(self.make_levers()) + case 'pull': + extras.extend(self.make_pulls()) + obj = join_objects([obj] + extras) + self.auto_bevel(obj) + obj.location = -self.width, -self.depth, 0 + butil.apply_transform(obj, True) + obj = add_bevel(obj, get_bevel_edges(obj), offset=self.side_bevel) + return obj + + def make_panels(self): + return [] + + def finalize_assets(self, assets): + self.surface.apply(assets, metal_color=self.metal_color, vertical=True) + if self.has_glass: + self.glass_surface.apply(assets, selection='glass', clear=True) + if self.has_louver: + self.louver_surface.apply(assets, selection='louver', metal_color=self.metal_color) + self.handle_surface.apply(assets, selection='handle', metal_color='natural') + + def make_knobs(self): + x_anchors = np.array(self.knob_radius_mid) * self.knob_radius + y_anchors = np.array(self.knob_depth_mid) * self.knob_depth + obj = spin([x_anchors, y_anchors, 0], [0, 2, 3], axis=(0, 1, 0)) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.region_to_loop() + bpy.ops.mesh.edge_face_add() + return self.make_handles(obj) + + def make_handles(self, obj): + write_attribute(obj, 1, 'handle', 'FACE') + obj.location = self.handle_offset, 0, self.handle_height + butil.apply_transform(obj, loc=True) + other = deep_clone_obj(obj) + obj.location[1] += self.depth + butil.apply_transform(obj, loc=True) + mirror(other, 1) + return [obj, other] + + def make_levers(self): + x_anchors = self.lever_radius, self.lever_radius, self.lever_mid_radius, self.lever_mid_radius, 0 + y_anchors = np.array([0, self.lever_mid_depth, self.lever_mid_depth, 1, 1 + 1e-3]) * self.lever_depth + obj = spin([x_anchors, y_anchors, 0], [0, 1, 2, 3], axis=(0, 1, 0)) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.region_to_loop() + bpy.ops.mesh.fill() + lever = new_line(4) + if self.level_type == 'wave': + co = read_co(lever) + co[1, -1] = -uniform(.2, .3) + co[3, -1] = uniform(.1, .15) + write_co(lever, co) + elif self.level_type == 'bent': + co = read_co(lever) + co[4, 1] = -uniform(.2, .3) + write_co(lever, co) + lever.scale = [self.lever_length] * 3 + butil.apply_transform(lever) + butil.select_none() + with butil.ViewportMode(lever, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={'value': (0, 0, self.lever_mid_radius * 2)}) + butil.modify_mesh(lever, 'SOLIDIFY', lever, thickness=self.lever_mid_radius, offset=0) + butil.modify_mesh(lever, 'SUBSURF', render_levels=1, levels=1) + lever.location = -self.lever_mid_radius, self.lever_depth, -self.lever_mid_radius + butil.apply_transform(lever, loc=True) + obj = join_objects([obj, lever]) + return self.make_handles(obj) + + def make_pulls(self): + if self.pull_type == 'u': + vertices = (0, 0, self.pull_size), (0, self.pull_depth, self.pull_size), (0, self.pull_depth, 0) + edges = (0, 1), (1, 2) + elif self.pull_type == 'tee': + vertices = (0, 0, self.pull_size), (0, self.pull_depth, self.pull_size), (0, self.pull_depth, 0), ( + 0, self.pull_depth, self.pull_size + self.pull_extension) + edges = (0, 1), (1, 2), (1, 3) + else: + vertices = (0, 0, self.pull_size), (0, self.pull_depth, self.pull_size), ( + self.pull_width, self.pull_depth, self.pull_size), (self.pull_width, self.pull_depth, 0), + edges = (0, 1), (1, 2), (2, 3) + obj = mesh2obj(data2mesh(vertices, edges)) + butil.modify_mesh(obj, 'MIRROR', use_axis=(False, False, True)) + if self.to_pull_bevel: + butil.modify_mesh(obj, 'BEVEL', width=self.pull_bevel_width, segments=4, affect='VERTICES') + if self.is_pull_circular: + surface.add_geomod( + obj, geo_radius, apply=True, input_args=[self.pull_radius, 32], input_kwargs={'to_align_tilt': False} + ) + else: + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={'value': (self.pull_radius * 2, 0, 0)}) + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.normals_make_consistent(inside=False) + obj.location = -self.pull_radius, -self.pull_radius, -self.pull_radius + butil.apply_transform(obj, loc=True) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.pull_radius * 2, offset=0) + return self.make_handles(obj) + + @property + def casing_factory(self): + from infinigen.assets.elements import DoorCasingFactory + factory = DoorCasingFactory(self.factory_seed, self.coarse) + factory.surface = self.surface + factory.metal_color = self.metal_color + return factory diff --git a/infinigen/assets/elements/doors/casing.py b/infinigen/assets/elements/doors/casing.py new file mode 100644 index 000000000..32317fe93 --- /dev/null +++ b/infinigen/assets/elements/doors/casing.py @@ -0,0 +1,62 @@ +# 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: Lingjie Mei + +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import wood, metal +from infinigen.assets.utils.decorate import read_edge_center, read_edge_direction +from infinigen.assets.utils.mesh import bevel +from infinigen.assets.utils.object import new_cube +from infinigen.core.constraints.example_solver.room import constants +from infinigen.core.placement.factory import AssetFactory + +from infinigen.core.util import blender as butil +from infinigen.core.util.math import FixedSeed + + +class DoorCasingFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(DoorCasingFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.margin = constants.DOOR_SIZE * uniform(.05, .1) + self.extrude = uniform(.02, .08) + self.bevel_all_sides = uniform() < .3 + self.surface = np.random.choice([metal, wood]) + self.metal_color = metal.sample_metal_color() + + def create_asset(self, **params) -> bpy.types.Object: + obj = new_cube() + obj.location = 0, 0, 1 + butil.apply_transform(obj, True) + w = constants.DOOR_WIDTH + s = constants.DOOR_SIZE + obj.scale = w / 2 + self.margin, constants.WALL_THICKNESS / 2 + self.extrude, \ + s / 2 + self.margin / 2 + butil.apply_transform(obj) + cutter = new_cube() + cutter.location = 0, 0, 1 - 1e-3 + butil.apply_transform(cutter, True) + cutter.scale = w / 2 - 1e-3, constants.WALL_THICKNESS + self.extrude, s / 2 + butil.apply_transform(cutter) + butil.modify_mesh(obj, 'BOOLEAN', object=cutter, operation='DIFFERENCE') + butil.delete(cutter) + + x, y, z = read_edge_center(obj).T + x_, y_, z_ = read_edge_direction(obj).T + + if self.bevel_all_sides: + selection = (np.abs(z_) > .5) | (np.abs(x_) > .5) + else: + selection = ((np.abs(z_) > .5) & (np.abs(x) < w / 2 + self.margin / 2)) | ( + (np.abs(x_) > .5) & (z < s + self.margin / 2)) + obj.data.edges.foreach_set('bevel_weight', selection) + bevel(obj, self.extrude, limit_method='WEIGHT') + return obj + + def finalize_assets(self, assets): + self.surface.apply(assets, metal_color=self.metal_color) diff --git a/infinigen/assets/elements/doors/lite.py b/infinigen/assets/elements/doors/lite.py new file mode 100644 index 000000000..e96e0a663 --- /dev/null +++ b/infinigen/assets/elements/doors/lite.py @@ -0,0 +1,55 @@ +# 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: Lingjie Mei +import numpy as np +from numpy.random import uniform + +from infinigen.core.util.math import FixedSeed +from .panel import PanelDoorFactory + + +class LiteDoorFactory(PanelDoorFactory): + def __init__(self, factory_seed, coarse=False): + super(LiteDoorFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + r = uniform() + subdivide_glass = False + if r <= 1 / 6: + dimension = 0, 1, uniform(.4, .6), 1 + subdivide_glass = True + elif r <= 1 / 3: + dimension = 0, 1, 0, 1 + subdivide_glass = True + elif r <= 1 / 2: + dimension = 0, uniform(.3, .4), uniform(.4, .6), 1 + elif r <= 2 / 3: + dimension = 0, uniform(.3, .4), uniform(.4, .6), 1 + elif r <= 5 / 6: + dimension = 0, 1, 0, 1 + else: + x = uniform(.3, .35) + dimension = x, 1 - x, uniform(.7, .8), 1 + self.x_min, self.x_max, self.y_min, self.y_max = dimension + if subdivide_glass: + self.x_subdivisions = np.random.choice([1, 3]) + self.y_subdivisions = int(self.height / self.width * self.x_subdivisions) + np.random.randint( + -1, 2 + ) + else: + self.x_subdivisions = 1 + self.y_subdivisions = 1 + self.has_glass = True + + def make_panels(self): + x_range = np.linspace(self.x_min, self.x_max, self.x_subdivisions + 1) * ( + self.width - self.panel_margin * 2) + self.panel_margin + y_range = np.linspace(self.y_min, self.y_max, self.y_subdivisions + 1) * ( + self.height - self.panel_margin * 2) + self.panel_margin + panels = [] + for x_min, x_max in zip(x_range[:-1], x_range[1:]): + for y_min, y_max in zip(y_range[:-1], y_range[1:]): + panels.append( + {'dimension': (x_min, x_max, y_min, y_max), 'func': self.bevel, 'attribute_name': 'glass'} + ) + return panels diff --git a/infinigen/assets/elements/doors/louver.py b/infinigen/assets/elements/doors/louver.py new file mode 100644 index 000000000..48f8bee6e --- /dev/null +++ b/infinigen/assets/elements/doors/louver.py @@ -0,0 +1,80 @@ +# 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: Lingjie Mei +import numpy as np +from numpy.random import uniform + +from .panel import PanelDoorFactory +from infinigen.assets.utils.decorate import write_attribute, write_co +from infinigen.assets.utils.object import new_cube, new_plane +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil + + +class LouverDoorFactory(PanelDoorFactory): + def __init__(self, factory_seed, coarse=False): + super(LouverDoorFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.x_subdivisions = 1 + self.y_subdivisions = np.clip(np.random.binomial(5, .4), 1, None) + self.has_panel = uniform() < .7 + self.has_upper_panel = uniform() < .5 + self.louver_width = uniform(.002, .004) + self.louver_margin = uniform(.02, .03) + self.louver_size = log_uniform(.05, .1) + self.louver_angle = uniform(np.pi / 4.5, np.pi / 3.5) + self.has_louver = True + + def louver(self, obj, panel): + x_min, x_max, y_min, y_max = panel['dimension'] + cutter = new_cube(location=(1, 1, 1)) + butil.apply_transform(cutter, loc=True) + write_attribute(cutter, 1, 'louver', 'FACE') + cutter.location = x_min - self.louver_margin, -self.louver_width, y_min - self.louver_margin + cutter.scale = [(x_max - x_min) / 2 + self.louver_margin, self.depth / 2 + self.louver_width, + (y_max - y_min) / 2 + self.louver_margin] + butil.apply_transform(cutter, loc=True) + butil.modify_mesh(obj, 'BOOLEAN', object=cutter, operation='DIFFERENCE') + + hole = new_cube(location=(1, 1, 1)) + butil.apply_transform(hole, loc=True) + write_attribute(hole, 1, 'louver', 'FACE') + hole.location = x_min, -self.louver_width * 2, y_min + hole.scale = (x_max - x_min) / 2, self.depth / 2 + self.louver_width * 2, (y_max - y_min) / 2 + butil.apply_transform(hole, loc=True) + butil.modify_mesh(cutter, 'BOOLEAN', object=hole, operation='DIFFERENCE') + butil.delete(hole) + + louver = new_plane() + x = x_min, x_max, x_min, x_max + y = 0, 0, self.depth, self.depth + y_upper = y_min + self.depth * np.tan(self.louver_angle) + z = y_min, y_min, y_upper, y_upper + write_co(louver, np.stack([x, y, z], -1)) + butil.modify_mesh(louver, 'SOLIDIFY', thickness=self.louver_width, offset=0) + butil.modify_mesh( + louver, 'ARRAY', use_relative_offset=False, use_constant_offset=True, + constant_offset_displace=(0, 0, self.louver_size), + count=int(np.ceil((y_max - y_min) / self.louver_size) + .5) + ) + write_attribute(louver, 1, 'louver', 'FACE') + return [cutter, louver] + + def make_panels(self): + panels = super(LouverDoorFactory, self).make_panels() + if len(panels) == 1: + panels[0]['func'] = self.louver + elif len(panels) == 2: + if not self.has_panel: + panels[0]['func'] = self.louver + panels[1]['func'] = self.louver + else: + if self.has_upper_panel: + panels = [panels[0], panels[-1]] + else: + panels = [panels[0]] + for panel in panels: + panel['func'] = self.louver + return panels diff --git a/infinigen/assets/elements/doors/panel.py b/infinigen/assets/elements/doors/panel.py new file mode 100644 index 000000000..eb73f1434 --- /dev/null +++ b/infinigen/assets/elements/doors/panel.py @@ -0,0 +1,92 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.core import surface +from infinigen.core.surface import write_attr_data, read_attr_data +from .casing import DoorCasingFactory +from infinigen.assets.elements.doors.base import BaseDoorFactory +from infinigen.assets.utils.decorate import write_attribute, select_faces, read_area +from infinigen.assets.utils.object import new_cube +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil + + +class PanelDoorFactory(BaseDoorFactory): + def __init__(self, factory_seed, coarse=False): + super(PanelDoorFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.x_subdivisions = 1 if uniform() < .5 else 2 + self.y_subdivisions = np.clip(np.random.binomial(5, .45), 1, None) + + def bevel(self, obj, panel): + x_min, x_max, y_min, y_max = panel['dimension'] + assert x_min <= x_max and y_min <= y_max + cutter = new_cube() + butil.apply_transform(cutter, loc=True) + if panel['attribute_name'] is not None: + write_attribute(cutter, 1, panel['attribute_name'], 'FACE') + cutter.location = (x_max + x_min) / 2, self.bevel_width * .5 - .1, (y_max + y_min) / 2 + cutter.scale = (x_max - x_min) / 2 - 2e-3, .1, (y_max - y_min) / 2 - 2e-3 + butil.apply_transform(cutter, loc=True) + # butil.modify_mesh(cutter, 'BEVEL', width=self.bevel_width) + write_attr_data(cutter, 'cut', np.ones(len(cutter.data.polygons), dtype=int), 'INT', 'FACE') + butil.modify_mesh(obj, 'BOOLEAN', object=cutter, operation='DIFFERENCE') + cutter.location[1] += .2 + self.depth - self.bevel_width + butil.apply_transform(cutter, loc=True) + butil.modify_mesh(obj, 'BOOLEAN', object=cutter, operation='DIFFERENCE') + butil.delete(cutter) + select_faces(obj, (read_area(obj) > .01) & (read_attr_data(obj, 'cut'))) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.inset(thickness=self.shrink_width) + bpy.ops.mesh.inset(thickness=self.bevel_width, depth=self.bevel_width) + obj.data.attributes.remove(obj.data.attributes['cut']) + return [] + + def make_panels(self): + panels = [] + x_cuts = np.random.randint(1, 4, self.x_subdivisions) + x_cuts = np.cumsum(x_cuts / x_cuts.sum()) + y_cuts = np.sort(np.random.randint(2, 5, self.y_subdivisions))[::-1] + y_cuts = np.cumsum(y_cuts / y_cuts.sum()) + for j in range(len(y_cuts)): + for i in range(len(x_cuts)): + x_min = self.panel_margin + (self.width - self.panel_margin) * (x_cuts[i - 1] if i > 0 else 0) + x_max = (self.width - self.panel_margin) * x_cuts[i] + y_min = self.panel_margin + (self.height - self.panel_margin) * (y_cuts[j - 1] if j > 0 else 0) + y_max = (self.height - self.panel_margin) * y_cuts[j] + panels.append( + {'dimension': (x_min, x_max, y_min, y_max), 'func': self.bevel, 'attribute_name': None} + ) + return panels + + +class GlassPanelDoorFactory(PanelDoorFactory): + + def __init__(self, factory_seed, coarse=False): + super(GlassPanelDoorFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.x_subdivisions = 2 + self.y_subdivisions = np.clip(np.random.binomial(5, .5), 2, None) + self.merge_glass = self.y_subdivisions < 4 + self.has_glass = True + + def make_panels(self): + panels = super(GlassPanelDoorFactory, self).make_panels() + if self.merge_glass: + first_dimension = panels[-self.x_subdivisions]['dimension'] + last_dimension = panels[- 1]['dimension'] + merged = { + 'dimension': (first_dimension[0], last_dimension[1], first_dimension[2], last_dimension[3]), + 'func': self.bevel, + 'attribute_name': 'glass' + } + return [merged, *panels[:self.x_subdivisions]] + else: + for panel in panels[-self.x_subdivisions:]: + panel['attribute_name'] = 'glass' + return panels diff --git a/infinigen/assets/elements/nature_shelf_trinkets/generate.py b/infinigen/assets/elements/nature_shelf_trinkets/generate.py new file mode 100644 index 000000000..a7a2bc717 --- /dev/null +++ b/infinigen/assets/elements/nature_shelf_trinkets/generate.py @@ -0,0 +1,98 @@ +# 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: Stamatis Alexandropulos + +import colorsys + +import bpy +import numpy as np +import trimesh +import mathutils +from numpy.random import uniform + + +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from infinigen.assets.corals import CoralFactory +from infinigen.assets.rocks import BlenderRockFactory +from infinigen.assets.rocks.boulder import BoulderFactory +from infinigen.assets.mollusk import MolluskFactory, AugerFactory, ClamFactory, ConchFactory, MusselFactory, ScallopFactory, VoluteFactory +from infinigen.assets.monocot import PineconeFactory +from infinigen.assets.creatures.beetle import BeetleFactory, AntSwarmFactory +from infinigen.assets.creatures.bird import BirdFactory, FlyingBirdFactory +from infinigen.assets.creatures.carnivore import CarnivoreFactory +from infinigen.assets.creatures.herbivore import HerbivoreFactory +from infinigen.assets.creatures.crustacean import CrustaceanFactory, CrabFactory, LobsterFactory, SpinyLobsterFactory +from infinigen.assets.creatures.reptile import FrogFactory +from infinigen.assets.creatures.insects.dragonfly import DragonflyFactory +from infinigen.assets.utils.decorate import remove_vertices +from infinigen.core.util import blender as butil +from infinigen.assets.utils import object as obj +from infinigen.assets.utils.object import join_objects + + + + +class NatureShelfTrinketsFactory(AssetFactory): + factories = [CoralFactory,BlenderRockFactory, BoulderFactory, PineconeFactory, MolluskFactory, + AugerFactory, ClamFactory, ConchFactory, MusselFactory, ScallopFactory, VoluteFactory, CarnivoreFactory, HerbivoreFactory] + probs = np.array([1,1,1,1,3,2,3,2,2,2,2,5,5]) + + def __init__(self, factory_seed, coarse=False): + super(NatureShelfTrinketsFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + base_factory_fn = np.random.choice(self.factories, p=self.probs / self.probs.sum()) + + kwargs = {} + if base_factory_fn in [HerbivoreFactory, CarnivoreFactory]: + kwargs.update({ + 'hair': False + }) + + self.base_factory = base_factory_fn(self.factory_seed, **kwargs) + + + def create_placeholder(self, **params) -> bpy.types.Object: + size = np.random.uniform(0.1, 0.15) + bpy.ops.mesh.primitive_cube_add(size=size, location=(0,0, size/2)) + placeholder = bpy.context.active_object + return placeholder + + + def create_asset(self, i, placeholder=None, **params): + asset = self.base_factory.spawn_asset( + np.random.randint(1e7), + distance=200, + adaptive_resolution = False + ) + + if (list(asset.children)): + asset = join_objects(list(asset.children)) + + # butil.modify_mesh(asset, 'DECIMATE') + butil.apply_transform(asset,loc=True) + butil.apply_modifiers(asset) + if isinstance(self.base_factory, HerbivoreFactory) or isinstance(self.base_factory, CarnivoreFactory): + pass + else: + if not isinstance(asset, trimesh.Trimesh): + mesh = obj.obj2trimesh(asset) + stable_poses, probs = trimesh.poses.compute_stable_poses(mesh) + stable_pose = stable_poses[np.argmax(probs)] + asset.rotation_euler = mathutils.Matrix(stable_pose[:3,:3]).to_euler() + butil.apply_transform(asset,rot =True) + dim = asset.dimensions + bounding_box = placeholder.dimensions + scale = min([bounding_box[i]/dim[i] for i in range(3)]) + asset.scale = [scale for i in range(3)] + # asset.dimensions = placeholder.dimensions + butil.apply_transform(asset,loc=True) + bounds = butil.bounds(asset) + cur_loc = asset.location + new_location = [ + cur_loc[i]-(bounds[0][i] + bounds[1][i])/2 for i in range(3)] + new_location[2] = cur_loc[2] - (bounds[0][2] + bounding_box[2]/2) + asset.location = new_location + butil.apply_transform(asset,loc=True) + return asset \ No newline at end of file diff --git a/infinigen/assets/elements/pillars.py b/infinigen/assets/elements/pillars.py new file mode 100644 index 000000000..8507f1931 --- /dev/null +++ b/infinigen/assets/elements/pillars.py @@ -0,0 +1,123 @@ +# 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: Lingjie Mei +import bmesh +import bpy +import gin +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import marble_regular, marble_voronoi +from infinigen.assets.utils.decorate import ( + read_co, read_edge_center, read_selected, select_edges, + subdivide_edge_ring, subsurf, write_co, +) +from infinigen.assets.utils.object import ( + join_objects, new_base_circle, new_base_cylinder, new_circle, + new_cylinder, +) +from infinigen.core.constraints.example_solver.room import constants +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.core.util.random import log_uniform + + +class PillarFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.height = constants.WALL_HEIGHT - constants.WALL_THICKNESS + self.n = np.random.randint(5, 10) + self.radius = uniform(.08, .12) + self.outer_radius = self.radius * uniform(1.3, 1.5) + self.lower_offset = uniform(.05, .15) + self.upper_offset = uniform(.05, .15) + self.detail_type = np.random.choice(['fluting', 'reeding']) + width = np.pi / 2 / self.n + self.inset_width = width * log_uniform(.1, .2) + self.inset_width_ = (width - self.inset_width * 2) * uniform(-.1, .3) + self.inset_depth = uniform(.1, .15) + self.inset_scale = uniform(.05, .1) + self.outer_n = np.random.choice([1, 2, self.n]) + self.m = np.random.randint(12, 20) + z_profile = uniform(1, 3, self.m) + self.z_profile = np.array([0, *(np.cumsum(z_profile) / np.sum(z_profile))[:-1]]) + alpha = uniform(.7, .85) + r_profile = uniform(0, 1, self.m + 3) + r_profile[[0, 1]] = 1 + r_profile[[-2, -1]] = 0 + r_profile = np.convolve(r_profile, np.array([(1 - alpha) / 2, alpha, (1 - alpha) / 2])) + self.r_profile = np.array([1, *r_profile[2:-2]]) * (self.outer_radius - self.radius) + self.radius + self.n_profile = np.where( + np.arange(self.m) < np.random.randint(2, self.m - 1), self.outer_n, + self.n + ) + self.inset_profile = uniform(0, 1, self.m) < .3 + self.surface = np.random.choice([marble_regular, marble_voronoi]) + + def create_asset(self, **params) -> bpy.types.Object: + obj = new_cylinder(vertices=4 * self.n) + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + geom = [f for f in bm.faces if len(f.verts) > 4] + bmesh.ops.delete(bm, geom=geom, context='FACES_ONLY') + bmesh.update_edit_mesh(obj.data) + + obj.scale = self.radius, self.radius, (1 - self.lower_offset - self.upper_offset) * self.height + obj.location[-1] = self.lower_offset * self.height + butil.apply_transform(obj, True) + inset_scale = 1 + self.inset_scale * (1 if self.detail_type == 'reeding' else -1) + if self.detail_type in ['fluting', 'reeding']: + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='FACE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.inset(thickness=self.inset_width * self.radius, use_individual=True) + bpy.ops.mesh.inset(thickness=self.inset_width_ * self.radius, use_individual=True) + bpy.ops.transform.resize(value=(inset_scale, inset_scale, 1)) + subdivide_edge_ring(obj, 16) + parts = [obj] + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.region_to_loop() + z_rot = np.pi / 2 * np.random.randint(2) + for z, r, n, i in zip(self.z_profile, self.r_profile, self.n_profile, self.inset_profile): + o = new_base_circle(vertices=4 * n) + if i: + co = read_co(o) + stride = np.random.choice([2, 4, 8]) + co *= np.where(np.arange(len(co)) % stride == 0, 1, inset_scale)[:, np.newaxis] + write_co(o, co) + with butil.ViewportMode(o, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.subdivide(number_cuts=self.n // n - 1) + o.location[-1] = z * self.lower_offset * self.height + r_ = r / np.cos(np.pi / 4 / n) + o.scale = r_, r_, 1 + o.rotation_euler[-1] = z_rot + o_ = deep_clone_obj(o) + o_.location[-1] = (1 - z * self.upper_offset) * self.height + butil.apply_transform(o, True) + butil.apply_transform(o_, True) + parts.extend([o, o_]) + obj = join_objects(parts) + selection = read_selected(obj, 'EDGE') + z = read_edge_center(obj)[:, -1] + number_cuts = 0 + smoothness = uniform(1, 1.4) + select_edges(obj, selection & (z < .5)) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.bridge_edge_loops(number_cuts=number_cuts, smoothness=smoothness) + select_edges(obj, selection & (z > .5)) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.bridge_edge_loops(number_cuts=number_cuts, smoothness=smoothness) + subsurf(obj, 1, True) + subsurf(obj, 1) + return obj + + def finalize_assets(self, assets): + self.surface.apply(assets) diff --git a/infinigen/assets/elements/rug.py b/infinigen/assets/elements/rug.py new file mode 100644 index 000000000..0f5072902 --- /dev/null +++ b/infinigen/assets/elements/rug.py @@ -0,0 +1,64 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import rug +from infinigen.assets.materials.art import Art, ArtRug +from infinigen.assets.utils.object import new_bbox, new_circle, new_plane, new_base_circle +from infinigen.assets.utils.uv import wrap_sides +from infinigen.core.nodes import NodeWrangler, Nodes +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform, clip_gaussian +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList + + +class RugFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(RugFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.width = clip_gaussian(3, 1, 2, 6) + self.length = self.width * uniform(1, 1.5) + self.rug_shape = np.random.choice(['rectangle', 'circle', 'rounded', 'ellipse']) + if self.rug_shape == 'circle': + self.length = self.width + self.rounded_buffer = self.width * uniform(.1, .5) + self.thickness = uniform(.01, .02) + material_assignments = AssetList['RugFactory']() + self.surface = material_assignments['surface'].assign_material() + if self.surface == ArtRug: + self.surface = self.surface(self.factory_seed) + + def build_shape(self): + match self.rug_shape: + case 'rectangle': + obj = new_plane() + obj.scale = self.length / 2, self.width / 2, 1 + butil.apply_transform(obj, True) + case 'rounded': + obj = new_plane() + obj.scale = self.length / 2, self.width / 2, 1 + butil.apply_transform(obj, True) + butil.modify_mesh(obj, 'BEVEL', width=self.rounded_buffer, segments=16) + case _: + obj = new_base_circle(vertices=128) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.edge_face_add() + obj.scale = self.length / 2, self.width / 2, 1 + butil.apply_transform(obj, True) + return obj + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + return new_bbox(-self.length / 2, self.length / 2, -self.width / 2, self.width / 2, 0, self.thickness) + + def create_asset(self, **params) -> bpy.types.Object: + obj = self.build_shape() + wrap_sides(obj, self.surface, 'z', 'x', 'y') + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness, offset=1) + return obj diff --git a/infinigen/assets/elements/staircases/__init__.py b/infinigen/assets/elements/staircases/__init__.py new file mode 100644 index 000000000..0558c4205 --- /dev/null +++ b/infinigen/assets/elements/staircases/__init__.py @@ -0,0 +1,18 @@ +# 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: Lingjie Mei +import numpy as np +from .curved import CurvedStaircaseFactory +from .spiral import SpiralStaircaseFactory +from .straight import StraightStaircaseFactory +from .l_shaped import LShapedStaircaseFactory +from .u_shaped import UShapedStaircaseFactory +from .cantilever import CantileverStaircaseFactory + + +def random_staircase_factory(): + door_factories = [StraightStaircaseFactory, LShapedStaircaseFactory, UShapedStaircaseFactory, + SpiralStaircaseFactory, CurvedStaircaseFactory, CantileverStaircaseFactory] + door_probs = np.array([2, 2, 2, .5, 2, 2]) + return np.random.choice(door_factories, p=door_probs / door_probs.sum()) diff --git a/infinigen/assets/elements/staircases/cantilever.py b/infinigen/assets/elements/staircases/cantilever.py new file mode 100644 index 000000000..581a28672 --- /dev/null +++ b/infinigen/assets/elements/staircases/cantilever.py @@ -0,0 +1,32 @@ +# 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: Lingjie Mei +import numpy as np +import shapely +import shapely.affinity +from infinigen.assets.elements.staircases.straight import StraightStaircaseFactory +from infinigen.assets.utils.decorate import read_co +from infinigen.assets.utils.object import join_objects + +from infinigen.core.util import blender as butil + + +class CantileverStaircaseFactory(StraightStaircaseFactory): + support_types = 'wall' + handrail_types = 'weighted_choice', (2, 'horizontal-post'), (2, 'vertical-post') + + def valid_contour(self, offset, contour, doors, lower=True): + valid = super().valid_contour(offset, contour, doors, lower) + if not valid or not lower: + return valid + obj = join_objects([self.make_line_offset(0), self.make_line_offset(1)]) + co = read_co(obj)[:, :-1] + butil.delete(obj) + if self.mirror: + co[:, 0] = -co[:, 0] + points = [shapely.affinity.translate(shapely.affinity.rotate(p, self.rot_z, (0, 0)), *offset) for p in + shapely.points(co)] + others = [shapely.ops.nearest_points(p, contour.boundary)[0] for p in points] + distance = np.array([np.abs(p.x - o.x) + np.abs(p.y - o.y) for p, o in zip(points, others)]) + return (distance < .1).sum() / len(distance) > .5 diff --git a/infinigen/assets/elements/staircases/curved.py b/infinigen/assets/elements/staircases/curved.py new file mode 100644 index 000000000..2190e1af3 --- /dev/null +++ b/infinigen/assets/elements/staircases/curved.py @@ -0,0 +1,67 @@ +# 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: +# - Lingjie Mei +# - Karhan Kayan: fix constants + +import numpy as np + +from infinigen.assets.elements.staircases.straight import StraightStaircaseFactory +from infinigen.assets.utils.decorate import read_co, write_co +from infinigen.core.constraints.example_solver.room import constants +from infinigen.core.util.random import log_uniform +from infinigen.core.util.math import FixedSeed + + +class CurvedStaircaseFactory(StraightStaircaseFactory): + support_types = 'weighted_choice', (2, 'single-rail'), (2, 'double-rail'), (4, 'side'), (4, 'solid'), ( + 4, 'hole') + + handrail_types = 'weighted_choice', (2, 'horizontal-post'), (2, 'vertical-post') + + def __init__(self, factory_seed, coarse=False): + self.full_angle, self.radius, self.theta = 0, 0, 0 + super(CurvedStaircaseFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.has_spiral = True + + def build_size_config(self): + while True: + self.full_angle = np.random.randint(1, 5) * np.pi / 2 + self.n = np.random.randint(13, 21) + self.step_height = constants.WALL_HEIGHT / self.n + self.theta = self.full_angle / self.n + self.step_length = self.step_height * log_uniform(1, 1.5) + self.step_width = log_uniform(.9, 1.5) + self.radius = self.step_length / self.theta + if self.radius / self.step_width > 1.5: + break + + def make_spiral(self, obj): + x, y, z = read_co(obj).T + u = x + self.radius - self.step_width + t = y / self.step_length * self.theta + write_co(obj, np.stack([u * np.cos(t), u * np.sin(t), z], -1)) + + def unmake_spiral(self, obj): + co = read_co(obj) + x, y, z = co.T + u = np.linalg.norm(co[:, :2], axis=-1) + t = np.arctan2(y, x) + margins, ts = [], [] + for o in np.linspace(0, np.pi * 2, 8): + t_ = (t - o) % (np.pi * 2) + o + margins.append(np.max(t_) - np.min(t_)) + ts.append(t_) + t = ts[np.argmin(margins)] + x = u - self.radius + self.step_width + y = t * self.step_length / self.theta + co = np.stack([x, y, z], -1) + write_co(obj, co) + return obj + + @property + def upper(self): + return np.pi / 2 + self.full_angle diff --git a/infinigen/assets/elements/staircases/generate.py b/infinigen/assets/elements/staircases/generate.py new file mode 100644 index 000000000..13e12ffef --- /dev/null +++ b/infinigen/assets/elements/staircases/generate.py @@ -0,0 +1,33 @@ +# 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: Lingjie Mei +import bpy +import numpy as np + +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from .straight import StraightStaircaseFactory +from .l_shaped import LShapedStaircaseFactory +from .u_shaped import UShapedStaircaseFactory +from .cantilever import CantileverStaircaseFactory +from .spiral import SpiralStaircaseFactory +from .curved import CurvedStaircaseFactory + + +class StaircaseFactory(AssetFactory): + factories = [StraightStaircaseFactory, LShapedStaircaseFactory, UShapedStaircaseFactory, + SpiralStaircaseFactory, CurvedStaircaseFactory, CantileverStaircaseFactory] + probs = np.array([4, 3, 3, 1, 2, 2]) + + def __init__(self, factory_seed, coarse=False): + super(StaircaseFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + base_factory_fn = np.random.choice(self.factories, p=self.probs / self.probs.sum()) + self.base_factory = base_factory_fn(self.factory_seed) + + def create_asset(self, **params) -> bpy.types.Object: + return self.base_factory.create_asset(**params) + + def finalize_assets(self, assets): + self.base_factory.finalize_assets(assets) diff --git a/infinigen/assets/elements/staircases/l_shaped.py b/infinigen/assets/elements/staircases/l_shaped.py new file mode 100644 index 000000000..78014290e --- /dev/null +++ b/infinigen/assets/elements/staircases/l_shaped.py @@ -0,0 +1,145 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from .straight import StraightStaircaseFactory +from infinigen.assets.utils.decorate import read_co, write_attribute, write_co +from infinigen.assets.utils.object import new_cube, new_line +from infinigen.core.util.math import FixedSeed +import infinigen.core.util.blender as butil + + +class LShapedStaircaseFactory(StraightStaircaseFactory): + def __init__(self, factory_seed, coarse=False): + super(LShapedStaircaseFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.m = int(self.n * uniform(.4, .6)) + self.is_rail_circular = True + + def make_line(self, alpha): + obj = new_line(self.n + 2) + x = np.concatenate( + [np.full(self.m + 2, alpha * self.step_width), -np.arange(self.n - self.m + 1) * self.step_length] + ) + y = np.concatenate( + [np.arange(self.m + 1) * self.step_length, [self.m * self.step_length + alpha * self.step_width], + np.full(self.n - self.m + 1, self.m * self.step_length + alpha * self.step_width)] + ) + z = np.concatenate([np.arange(self.m + 1), [self.m], np.arange(self.m, self.n + 1)]) * self.step_height + write_co(obj, np.stack([x, y, z], -1)) + return obj + + def make_line_offset(self, alpha): + obj = self.make_line(alpha) + co = read_co(obj) + co[self.m:self.m + 2] = co[self.m + 1:self.m + 3] + x, y, z = co.T + x[self.m + 1] += min(self.step_length / 2, alpha * self.step_width) + x[self.m + 2:] -= self.step_length / 2 + y[:self.m] += self.step_length / 2 + z += self.step_height + z[[self.m, self.m + 1, - 1]] -= self.step_height + write_co(obj, np.stack([x, y, z], -1)) + return obj + + def make_post_locs(self, alpha): + temp = self.make_line_offset(alpha) + cos = read_co(temp) + butil.delete(temp) + chunks = self.split(self.m - 1) + chunks_ = self.split(self.m + 1, self.n + 2) + indices = list(c[0] for c in chunks) + [self.m - 1, self.m, self.m + 1] + list( + c[0] for c in chunks_ + ) + [self.n + 1, self.n + 2] + return cos[indices] + + def make_vertical_post_locs(self, alpha): + temp = self.make_line_offset(alpha) + cos = read_co(temp) + butil.delete(temp) + chunks = self.split(self.m - 1) + chunks_ = self.split(self.m + 1, self.n + 2) + indices = sum(list(c[1:].tolist() for c in chunks), []) + indices_ = sum(list(c[1:].tolist() for c in chunks_), []) + mid_cos = [] + mid = [self.m - 1, self.m] + for m in mid: + for r in np.linspace(0, 1, self.post_k + 1 if m >= self.m else self.post_k + 2)[1:-1]: + mid_cos.append(r * cos[m] + (1 - r) * cos[m + 1]) + return np.concatenate([cos[indices], np.stack(mid_cos), cos[indices_]], 0) + + def make_steps(self): + objs = super(LShapedStaircaseFactory, self).make_steps() + for obj in objs[self.m:]: + obj.rotation_euler[-1] = np.pi / 2 + obj.location = self.m * self.step_length, self.m * self.step_length, 0 + butil.apply_transform(obj, loc=True) + lowest = np.min(read_co(objs[self.m]).T[-1]) + platform = new_cube(location=(1, 1, 1)) + butil.apply_transform(platform, loc=True) + platform.location = 0, self.step_length * self.m, lowest + platform.scale = self.step_width / 2, self.step_width / 2, (self.step_height * self.m - lowest) / 2 + butil.apply_transform(platform, loc=True) + write_attribute(platform, 1, 'steps', 'FACE') + return objs + [platform] + + def make_treads(self): + objs = super(LShapedStaircaseFactory, self).make_treads() + for obj in objs[self.m:]: + obj.rotation_euler[-1] = np.pi / 2 + obj.location = self.m * self.step_length, self.m * self.step_length, 0 + butil.apply_transform(obj, loc=True) + platform = new_cube(location=(1, 1, 1)) + butil.apply_transform(platform, loc=True) + platform.location = 0, self.step_length * self.m, self.step_height * self.m + platform.scale = self.step_width / 2, self.step_width / 2, self.tread_height / 2 + butil.apply_transform(platform, loc=True) + write_attribute(platform, 1, 'treads', 'FACE') + return objs + [platform] + + def make_inner_sides(self): + objs = super(LShapedStaircaseFactory, self).make_inner_sides() + for obj in objs[self.m:]: + obj.rotation_euler[-1] = np.pi / 2 + obj.location = self.m * self.step_length, self.m * self.step_length, 0 + butil.apply_transform(obj, loc=True) + + top_cutter = new_cube(location=(0, 0, 1)) + butil.apply_transform(top_cutter, loc=True) + top_cutter.scale = [100] * 3 + top_cutter.location[-1] = self.m * self.step_height + self.tread_height + for obj in objs[:self.m]: + butil.modify_mesh(obj, 'BOOLEAN', object=top_cutter, operation='DIFFERENCE') + butil.delete(top_cutter) + return objs + + def make_outer_sides(self): + objs = self.make_inner_sides() + for obj in objs[:self.m]: + obj.location[0] += self.step_width + butil.apply_transform(obj, loc=True) + for obj in objs[self.m:]: + obj.location[1] += self.step_width + butil.apply_transform(obj, loc=True) + platform = new_line(2) + x = self.step_width, self.step_width, 0 + y = self.m * self.step_length, self.m * self.step_length + self.step_width, self.m * self.step_length \ + + self.step_width + z = [self.m * self.step_height] * 3 + write_co(platform, np.stack([x, y, z], -1)) + butil.select_none() + with butil.ViewportMode(platform, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={'value': (0, 0, -self.side_height)}) + butil.modify_mesh(platform, 'SOLIDIFY', thickness=self.side_thickness) + write_attribute(platform, 1, 'sides', 'FACE') + return objs + [platform] + + @property + def upper(self): + return np.pi diff --git a/infinigen/assets/elements/staircases/spiral.py b/infinigen/assets/elements/staircases/spiral.py new file mode 100644 index 000000000..6d5a75c9d --- /dev/null +++ b/infinigen/assets/elements/staircases/spiral.py @@ -0,0 +1,62 @@ +# 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: +# - Lingjie Mei +# - Karhan Kayan: fix constants + +import numpy as np +from numpy.random import uniform + +from infinigen.assets.elements.staircases.curved import CurvedStaircaseFactory +from infinigen.assets.utils.decorate import read_co, remove_vertices, write_attribute +from infinigen.core.constraints.example_solver.room import constants +from infinigen.core.util.random import log_uniform +from infinigen.assets.utils.nodegroup import geo_radius +from infinigen.assets.utils.object import new_line, separate_loose +from infinigen.core import surface +from infinigen.core.util.math import FixedSeed +import infinigen.core.util.blender as butil + + +class SpiralStaircaseFactory(CurvedStaircaseFactory): + support_types = 'column' + + def __init__(self, factory_seed, coarse=False): + super(SpiralStaircaseFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.column_radius = self.radius - self.step_width + uniform(.05, .08) + self.has_column = True + self.handrail_alphas = [1 - self.handrail_offset / self.step_width] + + def build_size_config(self): + while True: + self.full_angle = np.random.randint(1, 5) * np.pi / 2 + self.n = np.random.randint(13, 21) + self.step_height = constants.WALL_HEIGHT / self.n + self.theta = self.full_angle / self.n + self.step_length = self.step_height * log_uniform(1, 1.2) + self.radius = self.step_length / self.theta + if .9 < self.radius < 1.5: + self.step_width = self.radius * uniform(.9, .95) + break + + def make_column(self): + obj = new_line(self.n, self.step_height * self.n + self.post_height) + obj.rotation_euler[1] = - np.pi / 2 + butil.apply_transform(obj) + surface.add_geomod(obj, geo_radius, apply=True, input_args=[self.column_radius, 16]) + write_attribute(obj, 1, 'steps', 'FACE') + return obj + + def unmake_spiral(self, obj): + obj = super().unmake_spiral(obj) + x, y, z = read_co(obj).T + margin = .1 + if (x >= 0).sum() >= (x <= 0).sum(): + remove_vertices(obj, lambda x, y, z: x < margin) + else: + remove_vertices(obj, lambda x, y, z: x > -margin) + obj = separate_loose(obj) + return obj diff --git a/infinigen/assets/elements/staircases/straight.py b/infinigen/assets/elements/staircases/straight.py new file mode 100644 index 000000000..11a204524 --- /dev/null +++ b/infinigen/assets/elements/staircases/straight.py @@ -0,0 +1,604 @@ +# 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: +# - Lingjie Mei +# - Karhan Kayan: fix constants + +import bpy +import bmesh +import gin +import numpy as np +import shapely +from numpy.random import uniform +from shapely import LineString, Polygon + +from infinigen.assets.materials.stone_and_concrete import concrete +from infinigen.assets.utils.mesh import canonicalize_ls, convert2ls +from infinigen.assets.utils.shapes import cut_polygon_by_line +from infinigen.assets.materials import metal, glass, plaster, wood, fabrics +from infinigen.assets.utils.decorate import ( + mirror, read_co, remove_faces, remove_vertices, subsurf, + write_attribute, write_co, +) +from infinigen.assets.utils.nodegroup import geo_radius +from infinigen.assets.utils.object import ( + data2mesh, join_objects, mesh2obj, new_circle, new_cube, new_line, + separate_loose, +) +from infinigen.core.constraints.example_solver.room import constants +from infinigen.core.nodes import NodeWrangler, Nodes +from infinigen.core.placement.detail import sharp_remesh_with_attrs +from infinigen.core.placement.factory import AssetFactory +from infinigen.core import surface +from infinigen.core.surface import read_attr_data, write_attr_data +from infinigen.core.tagging import PREFIX +from infinigen.core.util import blender as butil +from infinigen.core.util.math import FixedSeed, normalize +from infinigen.core.util.random import log_uniform, random_general as rg + +from infinigen.core import tags as t + + +class StraightStaircaseFactory(AssetFactory): + support_types = 'weighted_choice', (2, 'single-rail'), (2, 'double-rail'), (3, 'side'), (3, 'solid'), ( + 3, 'hole') + handrail_types = 'weighted_choice', (2, 'glass'), (2, 'horizontal-post'), (2, 'vertical-post') + + def __init__(self, factory_seed, coarse=False): + super(StraightStaircaseFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.support_type = rg(self.support_types) + self.n, self.step_height, self.step_width, self.step_length = 0, 0, 0, 0 + self.build_size_config() + + self.has_step = self.support_type in ['solid', 'hole'] + self.hole_size = log_uniform(.6, 1.) + probs = np.array([3, 2, 2, 2]) + self.step_surface = np.random.choice([wood, plaster, concrete, fabrics], p=probs / probs.sum()) + + self.has_rail = self.support_type in ['single-rail', 'double-rail'] + self.rail_offset = self.step_width * uniform(.15, .3) + self.is_rail_circular = uniform() < .5 + self.rail_width = log_uniform(.08, .2) + self.rail_height = log_uniform(.08, .12) + probs = np.array([3, 2, 2, 1]) + self.rail_surface = np.random.choice([metal, plaster, concrete, fabrics], p=probs / probs.sum()) + + self.has_tread = not self.has_step or uniform() < .75 + self.tread_height = uniform(.01, .02) if self.has_step else uniform(.06, .08) + self.tread_length = self.step_length + uniform(.01, .02) + self.tread_width = self.step_width + uniform(.01, .02) if uniform() < .8 else self.step_width + probs = np.array([3, 3, 1]) + self.tread_surface = np.random.choice([wood, metal, glass], p=probs / probs.sum()) + + self.has_sides = self.support_type in ['side', 'solid', 'hole'] + self.side_type = np.random.choice(['zig-zag', 'straight']) + self.side_height = self.step_height * log_uniform(.2, .8) + self.side_thickness = uniform(.03, .08) + probs = np.array([3, 3, 1, 2]) + self.side_surface = np.random.choice([wood, metal, plaster, fabrics], p=probs / probs.sum()) + + self.has_column = self.support_type == 'chord' + + self.handrail_type = rg(self.handrail_types) + self.is_handrail_circular = uniform() < .7 + self.handrail_width = log_uniform(.02, .06) + self.handrail_height = log_uniform(.02, .06) + self.handrail_offset = self.handrail_width * log_uniform(1, 2) + self.handrail_extension = uniform(.1, .2) + self.handrail_alphas = [self.handrail_offset / self.step_width, + 1 - self.handrail_offset / self.step_width] + probs = np.array([3, 2, 3]) + self.handrail_surface = np.random.choice([wood, metal, fabrics], p=probs / probs.sum()) + + self.post_height = log_uniform(.8, 1.2) + self.post_k = int(np.ceil(self.step_width / self.step_length)) + self.post_width = self.handrail_width * log_uniform(.6, .8) + self.post_minor_width = self.post_width * log_uniform(.3, .5) + self.is_post_circular = uniform() < .5 + probs = np.array([3, 3, 2]) + self.post_surface = np.random.choice([wood, metal, fabrics], p=probs / probs.sum()) + self.has_vertical_post = self.handrail_type == 'vertical-post' + + self.has_bars = self.handrail_type == 'horizontal-post' + self.bar_size = log_uniform(.1, .2) + self.n_bars = int(np.floor(self.post_height / self.bar_size * uniform(.35, .75))) + + self.has_glasses = self.handrail_type == 'glass' + self.glass_height = self.post_height - uniform(0, .05) + self.glass_margin = self.step_height / 2 + uniform(0, .05) + self.glass_surface = glass + + self.has_spiral = False + self.mirror = uniform() < .5 + self.rot_z = np.random.randint(4) * np.pi / 2 + self.end_margin = self.step_length * 8 + + def build_size_config(self): + self.n = np.random.randint(13, 21) + self.step_height = constants.WALL_HEIGHT / self.n + self.step_width = uniform(.8, 1.6) + self.step_length = self.step_height * log_uniform(.8, 1.2) + + def make_line(self, alpha): + obj = new_line(self.n) + x = np.full(self.n + 1, alpha * self.step_width) + y = self.step_length * np.arange(self.n + 1) + z = self.step_height * np.arange(self.n + 1) + np.stack([x, y, z], -1) + write_co(obj, np.stack([x, y, z], -1)) + return obj + + def make_line_offset(self, alpha): + obj = self.make_line(alpha) + x, y, z = read_co(obj).T + y += self.step_length / 2 + z += self.step_height + z[-1] -= self.step_height + write_co(obj, np.stack([x, y, z], -1)) + return obj + + def make_post_locs(self, alpha): + temp = self.make_line_offset(alpha) + cos = read_co(temp) + butil.delete(temp) + chunks = self.split(self.n - 1) + indices = list(c[0] for c in chunks) + [self.n - 1, self.n] + return cos[indices] + + def make_vertical_post_locs(self, alpha): + temp = self.make_line_offset(alpha) + cos = read_co(temp) + butil.delete(temp) + chunks = self.split(self.n - 1) + indices = sum(list(c[1:].tolist() for c in chunks), []) + [self.n] + return cos[indices] + + def split(self, start, end=None): + return np.array_split( + np.arange(start, end), + np.ceil((start if end is None else end - start) / self.post_k) + ) + + @staticmethod + def triangulate(obj): + butil.modify_mesh(obj, 'TRIANGULATE', min_vertices=3) + levels = 1 + butil.modify_mesh(obj, 'SUBSURF', levels=levels, render_levels=levels, subdivision_type='SIMPLE') + return obj + + def vertical_cut(self, p): + cuts = list(LineString([(i, -100), (i, 100)]) for i in range(1, self.n - 1)) + polygons = cut_polygon_by_line(p, *cuts) + parts = [] + for p in polygons: + coords = p.boundary.coords[:][:-1] + part = new_circle(vertices=len(coords)) + with butil.ViewportMode(part, 'EDIT'): + bpy.ops.mesh.edge_face_add() + write_co(part, np.array(list([0, y * self.step_length, z * self.step_height] for y, z in coords))) + parts.append(part) + return parts + + def make_steps(self): + coords = [(0, 0)] + for i in range(self.n): + coords.extend([(i, i + 1), (i + 1, i + 1)]) + coords.extend([(self.n, 0), (0, 0)]) + p = Polygon(LineString(coords)) + if self.support_type == 'hole': + hole = Polygon( + [((1 - self.hole_size) * self.n, 0), (self.n, self.hole_size * self.n), (self.n, 0), + ((1 - self.hole_size) * self.n, 0)] + ) + p = p.difference(hole) + objs = self.vertical_cut(p) + for obj in objs: + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.step_width) + self.triangulate(obj) + write_attribute(obj, 1, 'steps', 'FACE') + return objs + + def make_rails(self): + parts = [] + if self.support_type == 'single-rail': + alphas = [.5] + else: + alphas = [self.rail_offset / self.step_width, 1 - self.rail_offset / self.step_width] + for alpha in alphas: + obj = self.make_line(alpha) + if self.is_rail_circular: + surface.add_geomod(obj, geo_radius, apply=True, input_args=[self.rail_width, 16]) + obj.location[-1] = -self.rail_width + butil.apply_transform(obj, loc=True) + else: + butil.select_none() + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={'value': (0, 0, -self.rail_height * 2)} + ) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.rail_width, offset=0) + self.triangulate(obj) + write_attribute(obj, 1, 'rails', 'FACE') + parts.append(obj) + return parts + + def make_treads(self): + tread = new_cube(location=(1, 1, 1)) + butil.apply_transform(tread, loc=True) + tread.scale = self.tread_width / 2, self.tread_length / 2, self.tread_height / 2 + tread.location = -(self.tread_width - self.step_width) / 2, -( + self.tread_length - self.step_length), self.step_height + butil.apply_transform(tread, loc=True) + self.triangulate(tread) + write_attribute(tread, 1, 'treads', 'FACE') + treads = [tread] + list(butil.deep_clone_obj(tread) for _ in range(self.n - 1)) + for i in range(1, self.n): + treads[i].location = 0, self.step_length * i, self.step_height * i + butil.apply_transform(treads[i], loc=True) + return treads + + def make_inner_sides(self): + offset = -self.side_height / self.step_height + if self.side_type == 'zig-zag': + coords = [(0, 0)] + for i in range(self.n): + coords.extend([(i, i + 1), (i + 1, i + 1)]) + l = LineString(coords) + p = l.buffer(offset, join_style='mitre', single_sided=True, ) + else: + p = Polygon( + LineString([(0, offset), (0, 1), (self.n, self.n + 1), (self.n, self.n + offset), (0, offset)]) + ) + objs = self.vertical_cut(p) + + bottom_cutter = new_cube(location=(0, 0, -1)) + butil.apply_transform(bottom_cutter, loc=True) + bottom_cutter.scale = [100] * 3 + butil.apply_transform(bottom_cutter) + top_cutter = new_cube(location=(0, 0, 1)) + butil.apply_transform(top_cutter, loc=True) + top_cutter.scale = [100] * 3 + top_cutter.location[-1] = self.n * self.step_height + self.tread_height + + for obj in objs: + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.side_thickness, offset=0) + write_attribute(obj, 1, 'sides', 'FACE') + for cutter in [top_cutter, bottom_cutter]: + butil.modify_mesh(obj, 'BOOLEAN', object=cutter, operation='DIFFERENCE') + butil.delete([top_cutter, bottom_cutter]) + return objs + + def make_outer_sides(self): + objs = self.make_inner_sides() + for obj in objs: + obj.location[0] = self.step_width + butil.apply_transform(obj, loc=True) + return objs + + def make_column(self): + return + + def make_handrails(self): + parts = [] + for alpha in self.handrail_alphas: + obj = self.make_line_offset(alpha) + self.make_single_handrail(obj) + parts.append(obj) + return parts + + def make_single_handrail(self, obj): + self.extend_line(obj, self.handrail_extension) + if self.is_handrail_circular: + surface.add_geomod( + obj, geo_radius, apply=True, input_args=[self.handrail_width, 32], + input_kwargs={'to_align_tilt': False} + ) + else: + butil.select_none() + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={'value': (0, 0, -self.handrail_height * 2)} + ) + butil.modify_mesh( + obj, 'SOLIDIFY', thickness=self.handrail_width * 2, offset=0, + solidify_mode='NON_MANIFOLD' + ) + butil.modify_mesh( + obj, 'BEVEL', width=self.handrail_width * uniform(.2, .5), + segments=np.random.randint(4, 7) + ) + obj.location[-1] += self.handrail_height + write_attribute(obj, 1, 'handrails', 'FACE') + obj.location[-1] += self.post_height + butil.apply_transform(obj, loc=True) + self.triangulate(obj) + + @staticmethod + def extend_line(obj, extension): + if len(obj.data.vertices) <= 1: + return + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + bm.verts.ensure_lookup_table() + v0, v1, v2, v3 = bm.verts[0], bm.verts[1], bm.verts[-1], bm.verts[-2] + n_0 = v0.co - v1.co + n_0[2] = 0 + v4 = bm.verts.new(v0.co + n_0 / n_0.length * extension) + bm.edges.new((v4, v0)) + n_1 = v2.co - v3.co + n_1[2] = 0 + v5 = bm.verts.new(v2.co + n_1 / n_1.length * extension) + bm.edges.new((v2, v5)) + bmesh.update_edit_mesh(obj.data) + + def make_posts(self, locs, widths): + parts = [] + existing = np.zeros((0, 3)) + for loc, width in zip(locs, widths): + existing = np.concatenate([existing, loc[:1]], 0) + cos = [0] + for i, l in enumerate(loc): + if i > 0 and np.min( + np.linalg.norm(existing - l[np.newaxis, :], axis=1) + ) > self.handrail_width * 2: + cos.append(i) + existing = np.concatenate([existing, loc[i:i + 1]], 0) + obj = mesh2obj(data2mesh(loc[cos])) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_vertices_move(TRANSFORM_OT_translate={'value': (0, 0, self.post_height)}) + if self.is_post_circular: + surface.add_geomod(obj, geo_radius, apply=True, input_args=[width, 32]) + else: + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={'value': (width * 2, 0, 0)}) + bpy.ops.mesh.select_mode(type='FACE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_region_move(TRANSFORM_OT_translate={'value': (0, width * 2, 0)}) + obj.location = -width, -width, 0 + butil.apply_transform(obj, loc=True) + write_attribute(obj, 1, 'posts', 'FACE') + parts.append(obj) + return parts + + def make_bars(self, locs): + parts = [] + for loc in locs: + for loc, loc_ in zip(loc[:-1], loc[1:]): + for i in range(self.n_bars): + obj = new_line() + write_co(obj, np.stack([loc, loc_])) + subsurf(obj, 4) + surface.add_geomod(obj, geo_radius, apply=True, input_args=[self.post_minor_width]) + obj.location[-1] += self.post_height - (i + 1) * self.bar_size + butil.apply_transform(obj, loc=True) + write_attribute(obj, 1, 'posts', 'FACE') + parts.append(obj) + return parts + + def make_glasses(self, locs): + parts = [] + for loc in locs: + for loc, loc_ in zip(loc[:-1], loc[1:]): + obj = new_line() + write_co(obj, np.stack([loc, loc_])) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={'value': (0, 0, self.glass_height - self.glass_margin)} + ) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.post_minor_width) + obj.location[-1] += self.glass_margin + butil.apply_transform(obj, loc=True) + write_attribute(obj, 1, 'glasses', 'FACE') + parts.append(obj) + return parts + + def make_spiral(self, obj): + return obj + + def unmake_spiral(self, obj): + return obj + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + obj = self.make_line_offset(.5) + if self.has_spiral: + self.make_spiral(obj) + self.extend_line(obj, self.end_margin) + self.decorate_line( + obj, constants.WALL_THICKNESS / 2, + constants.DOOR_SIZE + ) + if self.mirror: + mirror(obj) + obj.rotation_euler[-1] = self.rot_z + butil.apply_transform(obj) + return obj + + def create_cutter(self, **kwargs) -> bpy.types.Object: + obj = self.make_line_offset(.5) + if self.has_spiral: + self.make_spiral(obj) + self.decorate_line(obj, 0, constants.DOOR_SIZE) + if self.mirror: + mirror(obj) + obj.location[-1] = -constants.WALL_THICKNESS / 2 + obj.rotation_euler[-1] = self.rot_z + butil.apply_transform(obj, True) + return obj + + def create_asset(self, **params) -> bpy.types.Object: + parts = [] + if self.has_step: + parts.extend(self.make_steps()) + if self.has_rail: + parts.extend(self.make_rails()) + if self.has_tread: + parts.extend(self.make_treads()) + if self.has_sides: + parts.extend(self.make_inner_sides()) + parts.extend(self.make_outer_sides()) + parts.extend(self.make_handrails()) + post_locs = list(self.make_post_locs(alpha) for alpha in self.handrail_alphas) + if self.has_vertical_post: + vertical_post_locs = list(self.make_vertical_post_locs(alpha) for alpha in self.handrail_alphas) + parts.extend( + self.make_posts( + post_locs + vertical_post_locs, + [self.post_width] * len(post_locs) + [self.post_minor_width] * len( + vertical_post_locs + ) + ) + ) + else: + parts.extend(self.make_posts(post_locs, [self.post_width] * len(post_locs))) + if self.has_bars: + parts.extend(self.make_bars(post_locs)) + if self.has_glasses: + parts.extend(self.make_glasses(post_locs)) + obj = join_objects(parts) + if self.has_spiral: + self.make_spiral(obj) + if self.has_column: + obj = join_objects([obj, self.make_column()]) + if self.mirror: + mirror(obj) + obj.rotation_euler[-1] = self.rot_z + butil.apply_transform(obj) + return obj + + def decorate_line(self, line, low, high): + end = np.zeros(len(line.data.vertices)) + end[[0, -1]] = 1 + write_attr_data(line, 'end', end) + with butil.ViewportMode(line, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={ + 'value': (0, 0, high - low) + } + ) + bpy.ops.mesh.normals_make_consistent(inside=False) + line.location[-1] -= low + butil.modify_mesh(line, 'SOLIDIFY', thickness=self.step_width, offset=0, use_even_offset=True) + self.triangulate(line) + line.location[-1] -= constants.WALL_THICKNESS / 2 + butil.apply_transform(line, True) + write_attribute( + line, lambda nw: nw.compare('LESS_THAN', surface.eval_argument(nw, 'end'), .99), + f'staircase_wall', 'FACE', 'INT' + ) + sharp_remesh_with_attrs(line, .05) + zeros = np.zeros(len(line.data.polygons), dtype=int) + ones = np.ones(len(line.data.polygons), dtype=int) + write_attr_data(line, f'{PREFIX}{t.Subpart.Ceiling.value}', zeros, 'INT', 'FACE') + write_attr_data(line, f'{PREFIX}{t.Subpart.SupportSurface.value}', zeros, 'INT', 'FACE') + write_attr_data(line, f'{PREFIX}{t.Subpart.Wall.value}', ones, 'INT', 'FACE') + write_attr_data(line, f'{PREFIX}{t.Subpart.Visible.value}', ones, 'INT', 'FACE') + with butil.ViewportMode(line, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.normals_make_consistent(inside=False) + + def finalize_assets(self, assets): + if self.has_step: + self.step_surface.apply(assets, selection='steps', metal_color='bw+natural') + if self.has_tread: + self.tread_surface.apply(assets, selection='treads', metal_color='bw+natural') + if self.has_rail: + self.rail_surface.apply(assets, selection='rails') + if self.has_sides: + self.side_surface.apply(assets, selection='sides') + self.handrail_surface.apply(assets, selection='handrails') + self.post_surface.apply(assets, selection='posts') + if self.has_glasses: + self.glass_surface.apply(assets, selection='glasses') + + def make_guardrail(self, mesh): + def geo_extrude(nw: NodeWrangler): + geometry = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + x, y, _ = nw.separate(nw.new_node(Nodes.InputNormal)) + offset = nw.scale(-self.handrail_offset, nw.vector_math('NORMALIZE', nw.combine(x, y, 0))) + geometry = nw.new_node(Nodes.SetPosition, [geometry], input_kwargs={'Offset': offset}) + nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) + + self.unmake_spiral(mesh) + with butil.ViewportMode(mesh, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.normals_make_consistent(inside=False) + surface.add_geomod(mesh, geo_extrude, apply=True) + remove_faces(mesh, read_attr_data(mesh, 'staircase_wall') == 0) + with butil.ViewportMode(mesh, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.region_to_loop() + bpy.ops.mesh.select_all(action='INVERT') + bpy.ops.mesh.delete(type='EDGE') + remove_vertices( + mesh, lambda x, y, z: (z < constants.WALL_THICKNESS / 4) | ( + z > constants.WALL_THICKNESS * 3 / 4) + ) + butil.modify_mesh(mesh, 'WELD', merge_threshold=constants.WALL_THICKNESS / 4) + name = mesh.name + mesh = separate_loose(mesh) + ls = shapely.force_2d(convert2ls(mesh)) + butil.delete(mesh) + parts, locs, minor_locs = [], [], [] + line = canonicalize_ls(ls) + segments = line.segmentize(self.post_k * self.step_length) + locs.append(np.array(shapely.force_3d(segments).coords)) + line = segments.segmentize(self.step_length) + if self.has_vertical_post: + minor_locs.append(np.array(shapely.force_3d(line).coords)) + line = shapely.force_3d(line) + o = new_line(len(line.coords) - 1) + write_co(o, np.array(line.coords)) + self.make_single_handrail(o) + parts.append(o) + parts.extend( + self.make_posts( + locs + minor_locs, + [self.post_width] * len(locs) + [self.post_minor_width] * len(minor_locs) + ) + ) + if self.has_bars: + parts.extend(self.make_bars(locs)) + if self.has_glasses: + parts.extend(self.make_glasses(locs)) + butil.select_none() + obj = join_objects(parts) + self.make_spiral(obj) + self.handrail_surface.apply(obj, selection='handrails') + self.post_surface.apply(obj, selection='posts') + if self.has_glasses: + self.glass_surface.apply(obj, selection='glasses') + obj.name = name + return obj + + @property + def lower(self): + return - np.pi / 2 + + @property + def upper(self): + return np.pi / 2 + + def valid_contour(self, offset, contour, doors, lower=True): + x, y = offset + if len(doors) == 0: + return True + for door in doors: + t = self.lower if lower else self.upper + t = (np.pi - t if self.mirror else t) + self.rot_z + v = np.array([np.cos(t), np.sin(t)]) + if normalize(np.array([door.location[0] - x, door.location[1] - y])) @ v >= -.5: + return True + return False diff --git a/infinigen/assets/elements/staircases/u_shaped.py b/infinigen/assets/elements/staircases/u_shaped.py new file mode 100644 index 000000000..7c0aeebf1 --- /dev/null +++ b/infinigen/assets/elements/staircases/u_shaped.py @@ -0,0 +1,158 @@ +# 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: +# - Lingjie Mei +# - Karhan Kayan: fix constants + +import bpy +import numpy as np + +from infinigen.core.constraints.example_solver.room import constants +from .straight import StraightStaircaseFactory +from infinigen.assets.utils.decorate import read_co, write_attribute, write_co +from infinigen.assets.utils.object import new_cube, new_line +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +import infinigen.core.util.blender as butil + + +class UShapedStaircaseFactory(StraightStaircaseFactory): + def __init__(self, factory_seed, coarse=False): + super(UShapedStaircaseFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.m = self.n // 2 + self.is_rail_circular = True + + def build_size_config(self): + self.n = int(np.random.randint(13, 21) / 2) * 2 + self.step_height = constants.WALL_HEIGHT / self.n + self.step_width = log_uniform(.9, 1.5) + self.step_length = self.step_height * log_uniform(1, 1.2) + + def make_line(self, alpha): + obj = new_line(self.n + 4) + x = np.concatenate( + [np.full(self.m + 2, alpha * self.step_width), [0], np.full(self.m + 2, -alpha * self.step_width)] + ) + y = np.concatenate( + [np.arange(self.m + 1) * self.step_length, + [self.m * self.step_length + alpha * self.step_width] * 3, + np.arange(self.m, -1, -1) * self.step_length] + ) + z = np.concatenate( + [np.arange(self.m + 1), [self.m] * 3, np.arange(self.m, self.n + 1)] + ) * self.step_height + write_co(obj, np.stack([x, y, z], -1)) + return obj + + def make_line_offset(self, alpha): + obj = self.make_line(alpha) + co = read_co(obj) + co[self.m:self.m + 4] = co[self.m + 1:self.m + 5] + x, y, z = co.T + y[:self.m] += self.step_length / 2 + y[self.m + 3] += min(self.step_length / 2, alpha * self.step_width) + y[self.m + 4:] -= self.step_length / 2 + z += self.step_height + z[[self.m, self.m + 1, self.m + 2, self.m + 3, - 1]] -= self.step_height + write_co(obj, np.stack([x, y, z], -1)) + return obj + + def make_post_locs(self, alpha): + temp = self.make_line_offset(alpha) + cos = read_co(temp) + butil.delete(temp) + chunks = self.split(self.m - 1) + chunks_ = self.split(self.m + 3, self.n + 4) + mid = [self.m - 1, self.m, self.m + 1, self.m + 2, self.m + 3] + indices = list(c[0] for c in chunks) + mid + list(c[0] for c in chunks_) + [self.n + 3, self.n + 4] + return cos[indices] + + def make_vertical_post_locs(self, alpha): + temp = self.make_line_offset(alpha) + cos = read_co(temp) + butil.delete(temp) + chunks = self.split(self.m - 1) + chunks_ = np.array_split(np.arange(self.m + 3, self.n + 4), np.ceil((self.n - self.m) / self.post_k)) + indices = sum(list(c[1:].tolist() for c in chunks + chunks_), []) + indices_ = sum(list(c[1:].tolist() for c in chunks_), []) + mid_cos = [] + mid = [self.m - 1, self.m, self.m + 1, self.m + 2] + for m in mid: + for r in np.linspace(0, 1, self.post_k + 1 if m >= self.m else self.post_k + 2)[1:-1]: + mid_cos.append(r * cos[m] + (1 - r) * cos[m + 1]) + return np.concatenate([cos[indices], np.stack(mid_cos), cos[indices_]], 0) + + def make_steps(self): + objs = super(UShapedStaircaseFactory, self).make_steps() + for obj in objs[self.m:]: + obj.rotation_euler[-1] = np.pi + obj.location = 0, 2 * self.m * self.step_length, 0 + butil.apply_transform(obj, loc=True) + lowest = np.min(read_co(objs[self.m]).T[-1]) + platform = new_cube(location=(0, 1, 1)) + butil.apply_transform(platform, loc=True) + platform.location = 0, self.step_length * self.m, lowest + platform.scale = self.step_width, self.step_width / 2, (self.step_height * self.m - lowest) / 2 + butil.apply_transform(platform, loc=True) + write_attribute(platform, 1, 'steps', 'FACE') + return objs + [platform] + + def make_treads(self): + objs = super(UShapedStaircaseFactory, self).make_treads() + for obj in objs[self.m:]: + obj.rotation_euler[-1] = np.pi + obj.location = 0, 2 * self.m * self.step_length, 0 + butil.apply_transform(obj, loc=True) + platform = new_cube(location=(0, 1, 1)) + butil.apply_transform(platform, loc=True) + platform.location = 0, self.step_length * self.m, self.step_height * self.m + platform.scale = self.step_width, self.step_width / 2, self.tread_height / 2 + butil.apply_transform(platform, loc=True) + write_attribute(platform, 1, 'treads', 'FACE') + return objs + [platform] + + def make_inner_sides(self): + objs = super(UShapedStaircaseFactory, self).make_inner_sides() + for obj in objs[self.m:]: + obj.rotation_euler[-1] = np.pi + obj.location = 0, 2 * self.m * self.step_length, 0 + butil.apply_transform(obj, loc=True) + + top_cutter = new_cube(location=(0, 0, 1)) + butil.apply_transform(top_cutter, loc=True) + top_cutter.scale = [100] * 3 + top_cutter.location[-1] = self.m * self.step_height + self.tread_height + for obj in objs[:self.m]: + butil.modify_mesh(obj, 'BOOLEAN', object=top_cutter, operation='DIFFERENCE') + butil.delete(top_cutter) + return objs + + def make_outer_sides(self): + objs = self.make_inner_sides() + for obj in objs[:self.m]: + obj.location[0] += self.step_width + butil.apply_transform(obj, loc=True) + for obj in objs[self.m:]: + obj.location[0] -= self.step_width + butil.apply_transform(obj, loc=True) + platform = new_line(4) + x = self.step_width, self.step_width, 0, -self.step_width, -self.step_width + mid = self.m * self.step_length + self.step_width + y = self.m * self.step_length, mid, mid, mid, self.m * self.step_length + z = [self.m * self.step_height] * 5 + write_co(platform, np.stack([x, y, z], -1)) + butil.select_none() + with butil.ViewportMode(platform, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={'value': (0, 0, -self.side_height)}) + butil.modify_mesh(platform, 'SOLIDIFY', thickness=self.side_thickness) + write_attribute(platform, 1, 'sides', 'FACE') + return objs + [platform] + + @property + def upper(self): + return -np.pi / 2 diff --git a/infinigen/assets/elements/warehouses/__init__.py b/infinigen/assets/elements/warehouses/__init__.py new file mode 100644 index 000000000..ad3fe7df2 --- /dev/null +++ b/infinigen/assets/elements/warehouses/__init__.py @@ -0,0 +1,6 @@ +# 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: Lingjie Mei +from .rack import RackFactory +from .pallet import PalletFactory diff --git a/infinigen/assets/elements/warehouses/pallet.py b/infinigen/assets/elements/warehouses/pallet.py new file mode 100644 index 000000000..754ab60d1 --- /dev/null +++ b/infinigen/assets/elements/warehouses/pallet.py @@ -0,0 +1,87 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import wood +from infinigen.assets.utils.decorate import read_normal +from infinigen.assets.utils.object import join_objects, new_bbox, new_cube +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.surface import write_attr_data +from infinigen.core.tagging import PREFIX +from infinigen.core.util import blender as butil +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core import tagging, tags as t + + +class PalletFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(PalletFactory, self).__init__(factory_seed, coarse) + self.depth = uniform(1.2, 1.4) + self.width = uniform(1.2, 1.4) + self.thickness = uniform(.01, .015) + self.tile_width = uniform(.06, .1) + self.tile_slackness = uniform(1.5, 2) + self.height = uniform(.2, .25) + self.surface = wood + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + bbox = new_bbox(0, self.width, 0, self.depth, 0, self.height) + write_attr_data(bbox, f'{PREFIX}{t.Subpart.SupportSurface.value}', read_normal(bbox)[:, -1] > .5, 'INT', + 'FACE') + return bbox + + def create_asset(self, **params) -> bpy.types.Object: + vertical = self.make_vertical() + vertical.location[-1] = self.thickness + vertical_ = deep_clone_obj(vertical) + vertical_.location[-1] = self.height - self.thickness + horizontal = self.make_horizontal() + horizontal_ = deep_clone_obj(horizontal) + horizontal_.location[-1] = self.height - 2 * self.thickness + support = self.make_support() + support.location[-1] = 2 * self.thickness + obj = join_objects([horizontal, horizontal_, vertical, vertical_, support]) + return obj + + def make_vertical(self): + obj = new_cube() + obj.location = 1, 1, 1 + butil.apply_transform(obj, True) + obj.scale = self.tile_width / 2, self.depth / 2, self.thickness / 2 + butil.apply_transform(obj) + count = int(np.floor((self.width - self.tile_width) / self.tile_width / self.tile_slackness) / 2) * 2 + butil.modify_mesh(obj, 'ARRAY', use_relative_offset=False, use_constant_offset=True, + constant_offset_displace=((self.width - self.tile_width) / count, 0, 0), + count=count + 1) + return obj + + def make_horizontal(self): + obj = new_cube() + obj.location = 1, 1, 1 + butil.apply_transform(obj, True) + obj.scale = self.width / 2, self.tile_width / 2, self.thickness / 2 + butil.apply_transform(obj) + count = int(np.floor((self.depth - self.tile_width) / self.tile_width / self.tile_slackness) / 2) * 2 + butil.modify_mesh(obj, 'ARRAY', use_relative_offset=False, use_constant_offset=True, + constant_offset_displace=(0, (self.depth - self.tile_width) / count, 0), + count=count + 1) + return obj + + def make_support(self): + obj = new_cube() + obj.location = 1, 1, 1 + butil.apply_transform(obj, True) + obj.scale = self.tile_width / 2, self.tile_width / 2, self.height / 2 - 2 * self.thickness + butil.apply_transform(obj) + butil.modify_mesh(obj, 'ARRAY', use_relative_offset=False, use_constant_offset=True, + constant_offset_displace=((self.width - self.tile_width) / 2, 0, 0), count=3) + butil.modify_mesh(obj, 'ARRAY', use_relative_offset=False, use_constant_offset=True, + constant_offset_displace=(0, (self.depth - self.tile_width) / 2, 0), count=3) + return obj + + def finalize_assets(self, assets): + self.surface.apply(assets) diff --git a/infinigen/assets/elements/warehouses/rack.py b/infinigen/assets/elements/warehouses/rack.py new file mode 100644 index 000000000..7a7dd2023 --- /dev/null +++ b/infinigen/assets/elements/warehouses/rack.py @@ -0,0 +1,168 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.elements.warehouses.pallet import PalletFactory +from infinigen.assets.materials import metal +from infinigen.assets.materials.metal import galvanized_metal +from infinigen.assets.utils.decorate import read_co, remove_faces, solidify, write_attribute, write_co +from infinigen.assets.utils.nodegroup import geo_radius +from infinigen.assets.utils.object import ( + join_objects, new_base_cylinder, new_bbox, new_cube, new_line, + new_plane, +) +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.surface import write_attr_data +from infinigen.core.tagging import PREFIX +from infinigen.core.util import blender as butil +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core import tagging, tags as t +from infinigen.core.util.math import FixedSeed + + +class RackFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(RackFactory, self).__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.depth = uniform(1, 1.2) + self.width = uniform(4., 5.) + self.height = uniform(1.6, 1.8) + self.steps = np.random.randint(3, 6) + self.thickness = uniform(.06, .08) + self.hole_radius = self.thickness / 2 * uniform(.5, .6) + self.support_angle = uniform(np.pi / 6, np.pi / 4) + self.is_support_round = uniform() < .5 + self.frame_height = self.thickness * uniform(3, 4) + self.frame_count = np.random.randint(20, 30) + + self.stand_surface = self.support_surface = self.frame_surface = metal + self.pallet_factory = PalletFactory(self.factory_seed) + self.margin_range = .3, .5 + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + bbox = new_bbox( + -self.depth - self.thickness / 2, self.thickness / 2, -self.thickness / 2, self.width + self.thickness / 2, + 0, self.height * self.steps + ) + objs = [bbox] + for i in range(self.steps): + obj = new_plane() + obj.scale = self.depth / 2, self.width / 2 - self.thickness, 1 + obj.location = -self.depth / 2, self.width / 2, self.height * i + butil.apply_transform(obj, True) + write_attr_data(obj, f'{PREFIX}{t.Subpart.SupportSurface.value}', np.ones(1).astype(bool), 'INT', 'FACE') + objs.append(obj) + obj = join_objects(objs) + return obj + + def create_asset(self, **params) -> bpy.types.Object: + stands = self.make_stands() + supports = self.make_supports() + frames = self.make_frames() + obj = join_objects(stands + supports + frames) + co = read_co(obj) + co[:, -1] = np.clip(co[:, -1], 0, self.height * self.steps) + write_co(obj, co) + pallets = [self.pallet_factory(i) for i in range(self.steps * 2)] + for i, p in enumerate(pallets): + p.parent = obj + margin = uniform(*self.margin_range) + p.location = margin if i % 2 else self.width - margin - p.dimensions[0], (self.depth - p.dimensions[ + 1]) / 2, i // 2 * self.height + self.pallet_factory.finalize_assets(pallets) + for p in pallets: + p.parent = obj + # obj = join_objects([obj] + pallets) + obj.rotation_euler[-1] = np.pi / 2 + butil.apply_transform(obj) + return obj + + def make_stands(self): + obj = new_cube() + obj.scale = [self.thickness / 2] * 3 + butil.apply_transform(obj, True) + cylinder = new_base_cylinder() + cylinder.scale = self.hole_radius, self.hole_radius, self.thickness * 2 + cylinder.rotation_euler[1] = np.pi / 2 + butil.apply_transform(cylinder) + butil.modify_mesh(obj, 'BOOLEAN', object=cylinder, operation='DIFFERENCE') + cylinder.rotation_euler[-1] = np.pi / 2 + butil.apply_transform(cylinder) + butil.modify_mesh(obj, 'BOOLEAN', object=cylinder, operation='DIFFERENCE') + butil.delete(cylinder) + remove_faces( + obj, + lambda x, y, z: (np.abs(x) < self.thickness * .49) & (np.abs(y) < self.thickness * .49) & ( + np.abs(z) < self.thickness * .49) + ) + remove_faces(obj, lambda x, y, z: np.abs(x) + np.abs(y) < self.thickness * .1) + obj.location[-1] = self.thickness / 2 + butil.apply_transform(obj, True) + butil.modify_mesh( + obj, 'ARRAY', count=int(np.ceil(self.height / self.thickness * self.steps)), + relative_offset_displace=(0, 0, 1), use_merge_vertices=True + ) + write_attribute(obj, 1, 'stand', 'FACE') + stands = [obj] + for locs in [(0, 1), (1, 1), (1, 0)]: + o = deep_clone_obj(obj) + o.location = locs[0] * self.width, locs[1] * self.depth, 0 + butil.apply_transform(o, True) + stands.append(o) + return stands + + def make_supports(self): + n = int(np.floor(self.height * self.steps / self.depth / np.tan(self.support_angle))) + obj = new_line(n, self.height * self.steps) + obj.rotation_euler[1] = -np.pi / 2 + butil.apply_transform(obj, True) + co = read_co(obj) + co[1::2, 1] = self.depth + write_co(obj, co) + if self.is_support_round: + surface.add_geomod(obj, geo_radius, apply=True, input_args=[self.thickness / 2, 16]) + else: + solidify(obj, 1, self.thickness) + write_attribute(obj, 1, 'support', 'FACE') + o = deep_clone_obj(obj) + o.location[0] = self.width + return [obj, o] + + def make_frames(self): + x_bar = new_cube() + x_bar.scale = self.width / 2, self.thickness / 2, self.frame_height / 2 + x_bar.location = self.width / 2, 0, self.height - self.frame_height / 2 + butil.apply_transform(x_bar, True) + x_bar_ = deep_clone_obj(x_bar) + x_bar_.location[1] = self.depth + butil.apply_transform(x_bar_, True) + y_bar = new_cube() + y_bar.scale = self.thickness / 2, self.depth / 2, self.thickness / 2 + margin = self.width / self.frame_count + y_bar.location = margin, self.depth / 2, self.height - self.thickness / 2 + butil.apply_transform(y_bar, True) + butil.modify_mesh( + y_bar, 'ARRAY', use_relative_offset=False, use_constant_offset=True, + count=self.frame_count - 1, constant_offset_displace=(margin, 0, 0) + ) + frames = [x_bar, x_bar_, y_bar] + for i in range(1, self.steps - 1): + for obj in [x_bar, x_bar_, y_bar]: + o = deep_clone_obj(obj) + o.location[-1] += self.height * i + butil.apply_transform(o, True) + frames.append(o) + + for o in frames: + write_attribute(o, 1, 'frame', 'FACE') + return frames + + def finalize_assets(self, assets): + self.stand_surface.apply(assets, 'stand', metal_color='bw') + self.support_surface.apply(assets, 'support', metal_color='bw') + self.frame_surface.apply(assets, 'frame', metal_color='bw') diff --git a/infinigen/assets/fluid/__init__.py b/infinigen/assets/fluid/__init__.py index b5c49b424..1729ae108 100644 --- a/infinigen/assets/fluid/__init__.py +++ b/infinigen/assets/fluid/__init__.py @@ -7,4 +7,10 @@ CachedCactusFactory, CachedCreatureFactory, CachedTreeFactory +) +from .flip_fluid import ( + make_river, + make_still_water, + make_tilted_river, + make_beach ) \ No newline at end of file diff --git a/infinigen/assets/fluid/cached_factory_wrappers.py b/infinigen/assets/fluid/cached_factory_wrappers.py index 407535cd4..78069b534 100644 --- a/infinigen/assets/fluid/cached_factory_wrappers.py +++ b/infinigen/assets/fluid/cached_factory_wrappers.py @@ -1,3 +1,7 @@ +# 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: Karhan Kayan from infinigen.assets.trees import TreeFactory, BushFactory from infinigen.assets.creatures import CarnivoreFactory diff --git a/infinigen/assets/fluid/fluid_scenecomp_additions.py b/infinigen/assets/fluid/fluid_scenecomp_additions.py index 525bea209..abc713ecf 100644 --- a/infinigen/assets/fluid/fluid_scenecomp_additions.py +++ b/infinigen/assets/fluid/fluid_scenecomp_additions.py @@ -1,3 +1,9 @@ +# 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: Karhan Kayan + + import bpy import mathutils from mathutils import Vector diff --git a/infinigen/assets/fluid/run_asset_cache.py b/infinigen/assets/fluid/run_asset_cache.py index d921dca56..48cacac68 100644 --- a/infinigen/assets/fluid/run_asset_cache.py +++ b/infinigen/assets/fluid/run_asset_cache.py @@ -1,3 +1,8 @@ +# 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: Karhan Kayan + import time import argparse import numpy as np @@ -26,7 +31,7 @@ parser.add_argument("--dom_scale", type=float, default=1) args = init.parse_args_blender(parser) - init.apply_gin_configs(configs=[], overrides=[], configs_folder='infinigen_examples/configs') + init.apply_gin_configs(configs=[], overrides=[], configs_folder='infinigen_examples/configs_nature') surface.registry.initialize_from_gin() factory_name = args.asset diff --git a/infinigen/assets/fruits/general_fruit.py b/infinigen/assets/fruits/general_fruit.py index eac821080..ba68eaf72 100644 --- a/infinigen/assets/fruits/general_fruit.py +++ b/infinigen/assets/fruits/general_fruit.py @@ -29,7 +29,7 @@ from infinigen.assets.fruits.surfaces.coconuthairy_surface import nodegroup_coconuthairy_surface from infinigen.assets.fruits.surfaces.coconutgreen_surface import nodegroup_coconutgreen_surface from infinigen.assets.fruits.surfaces.durian_surface import nodegroup_durian_surface -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup crosssectionlib = { 'circle_cross_section': nodegroup_circle_cross_section, diff --git a/infinigen/assets/grassland/dandelion.py b/infinigen/assets/grassland/dandelion.py index db29e4ec7..51a792af5 100644 --- a/infinigen/assets/grassland/dandelion.py +++ b/infinigen/assets/grassland/dandelion.py @@ -16,7 +16,7 @@ from infinigen.core.placement.factory import AssetFactory import numpy as np from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_pedal_stem_head_geometry', singleton=False, type='GeometryNodeTree') diff --git a/infinigen/assets/grassland/flower.py b/infinigen/assets/grassland/flower.py index 348c5829f..711538326 100644 --- a/infinigen/assets/grassland/flower.py +++ b/infinigen/assets/grassland/flower.py @@ -17,7 +17,7 @@ from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil, color from infinigen.core.util.math import FixedSeed, dict_lerp -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_polar_to_cart_old', singleton=True) def nodegroup_polar_to_cart_old(nw): diff --git a/infinigen/assets/grassland/flowerplant.py b/infinigen/assets/grassland/flowerplant.py index 792079f26..f09757742 100644 --- a/infinigen/assets/grassland/flowerplant.py +++ b/infinigen/assets/grassland/flowerplant.py @@ -18,7 +18,7 @@ from infinigen.assets.grassland import flower as Flower from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_stem_branch_leaf_s_r', singleton=False, type='GeometryNodeTree') diff --git a/infinigen/assets/grassland/grass_tuft.py b/infinigen/assets/grassland/grass_tuft.py index 6f5c9dc24..9b61fb06b 100644 --- a/infinigen/assets/grassland/grass_tuft.py +++ b/infinigen/assets/grassland/grass_tuft.py @@ -17,7 +17,7 @@ from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class GrassTuftFactory(AssetFactory): diff --git a/infinigen/assets/leaves/leaf.py b/infinigen/assets/leaves/leaf.py index 6d67c7375..0caaedd47 100644 --- a/infinigen/assets/leaves/leaf.py +++ b/infinigen/assets/leaves/leaf.py @@ -15,7 +15,7 @@ from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup C = bpy.context D = bpy.data diff --git a/infinigen/assets/leaves/leaf_broadleaf.py b/infinigen/assets/leaves/leaf_broadleaf.py index 5be9cb108..3d69441a0 100644 --- a/infinigen/assets/leaves/leaf_broadleaf.py +++ b/infinigen/assets/leaves/leaf_broadleaf.py @@ -18,7 +18,7 @@ from infinigen.core.util.math import FixedSeed from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_random_mask_vein', singleton=False, type='GeometryNodeTree') diff --git a/infinigen/assets/leaves/leaf_ginko.py b/infinigen/assets/leaves/leaf_ginko.py index 6c440f5d2..7beee01a9 100644 --- a/infinigen/assets/leaves/leaf_ginko.py +++ b/infinigen/assets/leaves/leaf_ginko.py @@ -18,7 +18,7 @@ from infinigen.core.util.math import FixedSeed from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup def deg2rad(deg): return deg / 180.0 * np.pi diff --git a/infinigen/assets/leaves/leaf_maple.py b/infinigen/assets/leaves/leaf_maple.py index febe71b86..d28459f4d 100644 --- a/infinigen/assets/leaves/leaf_maple.py +++ b/infinigen/assets/leaves/leaf_maple.py @@ -18,7 +18,7 @@ from infinigen.core.util.math import FixedSeed from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup def deg2rad(deg): return deg / 180.0 * np.pi diff --git a/infinigen/assets/leaves/leaf_pine.py b/infinigen/assets/leaves/leaf_pine.py index 3ef01abd8..24d25b8c7 100644 --- a/infinigen/assets/leaves/leaf_pine.py +++ b/infinigen/assets/leaves/leaf_pine.py @@ -16,7 +16,7 @@ from infinigen.core.util.math import FixedSeed from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup ######## code for creating pine needles ######## diff --git a/infinigen/assets/leaves/leaf_v2.py b/infinigen/assets/leaves/leaf_v2.py index 78cb7eae0..394a89e2c 100644 --- a/infinigen/assets/leaves/leaf_v2.py +++ b/infinigen/assets/leaves/leaf_v2.py @@ -29,7 +29,7 @@ from infinigen.core.nodes import node_utils from infinigen.core.util.color import color_category from infinigen.core import surface -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('shader_nodegroup_sub_vein', singleton=False, type='ShaderNodeTree') def shader_nodegroup_sub_vein(nw): diff --git a/infinigen/assets/lighting/__init__.py b/infinigen/assets/lighting/__init__.py index a4e8fefe5..daa84a74e 100644 --- a/infinigen/assets/lighting/__init__.py +++ b/infinigen/assets/lighting/__init__.py @@ -1,2 +1,11 @@ +# 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: Hongyu Wen + from . import sky_lighting -from .caustics_lamp import CausticsLampFactory \ No newline at end of file +from .caustics_lamp import CausticsLampFactory +from .ceiling_lights import CeilingLightFactory +from .ceiling_classic_lamp import CeilingClassicLampFactory +from .indoor_lights import PointLampFactory +from .lamp import LampFactory, DeskLampFactory, FloorLampFactory diff --git a/infinigen/assets/lighting/caustics_lamp.py b/infinigen/assets/lighting/caustics_lamp.py index 3533c7fe1..fcb374a98 100644 --- a/infinigen/assets/lighting/caustics_lamp.py +++ b/infinigen/assets/lighting/caustics_lamp.py @@ -11,7 +11,7 @@ from numpy.random import uniform as U, normal as N, randint, uniform import numpy as np -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler from infinigen.core.nodes import node_utils from infinigen.core.placement import placement diff --git a/infinigen/assets/lighting/ceiling_classic_lamp.py b/infinigen/assets/lighting/ceiling_classic_lamp.py new file mode 100644 index 000000000..60307e7be --- /dev/null +++ b/infinigen/assets/lighting/ceiling_classic_lamp.py @@ -0,0 +1,239 @@ +# 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: Stamatis Alexandropoulos + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.core import tagging +from .indoor_lights import PointLampFactory +from infinigen.assets.utils.autobevel import BevelSharp +from infinigen.core.util.color import color_category + +from infinigen.assets.materials.ceiling_light_shaders import shader_lamp_bulb_nonemissive + + +def shader_lamp_material(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + rgb = nw.new_node(Nodes.RGB) + rgb.outputs[0].default_value = color_category('textile') + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': rgb, 'Subsurface Radius': (0.1000, 0.1000, 0.1000), 'Roughness': uniform(0.2,0.9), 'Sheen': 0.2068, 'Clearcoat Roughness': 0.1436, 'Transmission': 0.4045, 'Transmission Roughness': 0.6932, 'Emission': (0.9858, 0.9858, 0.9858, 1.0000), 'Emission Strength': 0.0000, 'Alpha': 0.8614}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Scale': 104.3000, 'Randomness': 0.0000}, + attrs={'feature': 'SMOOTH_F1'}) + + displacement = nw.new_node(Nodes.Displacement, input_kwargs={'Height': voronoi_texture.outputs["Distance"], 'Scale': 0.4000}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': principled_bsdf, 'Displacement': displacement}, + attrs={'is_active_output': True}) + +def shader_inside_medal(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': (0.0018, 0.0015, 0.0000, 1.0000), 'Metallic': 1.0000, 'Roughness': 0.0682}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) + +def shader_cable(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': (0.0000, 0.0000, 0.0000, 1.0000), 'Metallic': 1.0000, 'Roughness': 0.4273}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('geometry_nodes', singleton=True, type='GeometryNodeTree') +def geometry_nodes(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'cable_length', 0.7000), + ('NodeSocketFloat', 'cable_radius', 0.0500), + ('NodeSocketFloat', 'height', 0.0000), + ('NodeSocketFloat', 'bottom_radius', 0.0000), + ('NodeSocketFloat', 'top_radius', 0.0000), + ('NodeSocketFloat', 'Thickness', 0.5000), + ('NodeSocketFloatDistance', 'Amount', 1.0000)]) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["cable_length"]}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': 87, 'Radius': group_input.outputs["cable_radius"]}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={'Curve': curve_line, 'Profile Curve': curve_circle.outputs["Curve"]}) + + transform_geometry = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': curve_to_mesh, 'Scale': (1.0000, 1.0000, -1.0000)}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_geometry, 'Material': surface.shaderfunc_to_material(shader_cable)}) + + curve_circle_3 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': group_input.outputs["top_radius"]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + transform_geometry_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle_3.outputs["Curve"], 'Translation': combine_xyz_4}) + + curve_line_3 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': (-1.0000, 0.0000, 0.0000), 'End': (1.0000, 0.0000, 0.0000)}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': curve_line_3}) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Amount"]}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance, 'Amount': reroute}, + attrs={'domain': 'INSTANCE'}) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': duplicate_elements.outputs["Geometry"]}) + + endpoint_selection_1 = nw.new_node(Nodes.EndpointSelection, input_kwargs={'Start Size': 0}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: 1.0000, 1: reroute}, attrs={'operation': 'DIVIDE'}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: divide}, + attrs={'operation': 'MULTIPLY'}) + + sample_curve = nw.new_node(Nodes.SampleCurve, + input_kwargs={'Curves': transform_geometry_4, 'Factor': multiply_1}, + attrs={'use_all_curves': True}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': realize_instances_1, 'Selection': endpoint_selection_1, 'Position': sample_curve.outputs["Position"]}) + + endpoint_selection_2 = nw.new_node(Nodes.EndpointSelection, input_kwargs={'End Size': 0}) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Thickness"], 2: 0.0000}, + attrs={'operation': 'MULTIPLY_ADD'}) + + curve_circle_4 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': multiply_add}) + + transform_geometry_5 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': curve_circle_4.outputs["Curve"]}) + + sample_curve_1 = nw.new_node(Nodes.SampleCurve, + input_kwargs={'Curves': transform_geometry_5, 'Factor': multiply_1}, + attrs={'use_all_curves': True}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': set_position, 'Selection': endpoint_selection_2, 'Position': sample_curve_1.outputs["Position"]}) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_geometry_4, set_position_1, transform_geometry_5]}) + + curve_circle_5 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': group_input.outputs["Thickness"]}) + + curve_to_mesh_3 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': join_geometry_3, 'Profile Curve': curve_circle_5.outputs["Curve"], 'Fill Caps': True}) + + transform_geometry_6 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': curve_to_mesh_3}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_geometry_6, 'Material': surface.shaderfunc_to_material(shader_inside_medal)}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: -1.5000, 1: -0.1000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_2}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}, attrs={'operation': 'SUBTRACT'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: multiply_3}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_4}) + + curve_line_2 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz_1, 'End': combine_xyz_2}) + + spline_parameter = nw.new_node(Nodes.SplineParameter) + + map_range = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': spline_parameter.outputs["Factor"], 3: group_input.outputs["bottom_radius"], 4: group_input.outputs["top_radius"]}) + + set_curve_radius = nw.new_node(Nodes.SetCurveRadius, input_kwargs={'Curve': curve_line_2, 'Radius': map_range.outputs["Result"]}) + + curve_circle_2 = nw.new_node(Nodes.CurveCircle) + + curve_to_mesh_2 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': set_curve_radius, 'Profile Curve': curve_circle_2.outputs["Curve"]}) + + flip_faces = nw.new_node(Nodes.FlipFaces, input_kwargs={'Mesh': curve_to_mesh_2}) + + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={'Mesh': curve_to_mesh_2, 'Offset Scale': 0.0050, 'Individual': False}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [flip_faces, extrude_mesh.outputs["Mesh"]]}) + + transform_geometry_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry}) + + set_material_2 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_geometry_2, 'Material': surface.shaderfunc_to_material(shader_lamp_material)}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material_1, set_material_2]}) + + ico_sphere = nw.new_node(Nodes.MeshIcoSphere, input_kwargs={'Radius': 0.0500, 'Subdivisions': 4}) + + set_material_3 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': ico_sphere.outputs["Mesh"], 'Material': surface.shaderfunc_to_material(shader_lamp_bulb_nonemissive)}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material, join_geometry_1, set_material_3]}) + + transform_geometry_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry_2, 'Rotation': (0.0000, 3.1416, 0.0000)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_geometry_3}, attrs={'is_active_output': True}) + + + + + +class CeilingClassicLampFactory(AssetFactory): + def __init__(self, factory_seed): + super(CeilingClassicLampFactory, self).__init__(factory_seed) + with FixedSeed(factory_seed): + self.params = { + 'cable_length': uniform(0.6, 0.710), + 'cable_radius': uniform(0.015,0.02), + 'height':uniform(0.4, 0.710), + 'top_radius':uniform(0.05, 0.2), + 'bottom_radius': uniform(0.22,0.35), + 'Thickness': uniform(0.002, 0.006), + 'Amount': randint(1, 8) + } + self.light_factory = PointLampFactory(factory_seed) + + # self.beveler = BevelSharp(mult=uniform(1, 3)) + def create_placeholder(self, **_): + obj = butil.spawn_cube() + butil.modify_mesh( + obj, + 'NODES', + node_group=geometry_nodes(), + ng_inputs=self.params, + apply=True + ) + tagging.tag_system.relabel_obj(obj) + return obj + + def create_asset(self, i, placeholder, face_size, **_): + obj = butil.deep_clone_obj(placeholder, keep_materials=True) + light = self.light_factory.spawn_asset(i) + butil.parent_to(light, obj) + return obj diff --git a/infinigen/assets/lighting/ceiling_lights.py b/infinigen/assets/lighting/ceiling_lights.py new file mode 100644 index 000000000..ccf31fa34 --- /dev/null +++ b/infinigen/assets/lighting/ceiling_lights.py @@ -0,0 +1,217 @@ +# 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: add point light + +import bpy +import random +import mathutils +import numpy as np +from numpy.random import uniform as U, normal as N, randint as RI +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category, hsv2rgba +from infinigen.core import surface +from infinigen.core.util import blender as butil + +from infinigen.core.util.math import FixedSeed, clip_gaussian +from infinigen.core.placement.factory import AssetFactory + +from .indoor_lights import PointLampFactory +from infinigen.assets.utils.autobevel import BevelSharp +from infinigen.assets.material_assignments import AssetList + + +class CeilingLightFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=[1., 1., 1.]): + super(CeilingLightFactory, self).__init__(factory_seed, coarse=coarse) + + self.dimensions = dimensions + self.ceiling_light_default_params = [{ + "Radius": 0.2, + "Thickness": 0.001, + "InnerRadius": 0.2, + "Height": 0.1, + "InnerHeight": 0.1, + "Curvature": 0.1, + }, { + "Radius": 0.18, + "Thickness": 0.05, + "InnerRadius": 0.18, + "Height": 0.1, + "InnerHeight": 0.1, + "Curvature": 0.25, + }, { + "Radius": 0.2, + "Thickness": 0.005, + "InnerRadius": 0.18, + "Height": 0.1, + "InnerHeight": 0.03, + "Curvature": 0.4, + }] + with FixedSeed(factory_seed): + self.light_factory = PointLampFactory(factory_seed) + self.params = self.sample_parameters(dimensions) + self.material_params, self.scratch, self.edge_wear = self.get_material_params() + + self.params.update(self.material_params) + self.beveler = BevelSharp(mult=U(1, 3)) + + def get_material_params(self): + material_assignments = AssetList['CeilingLightFactory']() + black_material = material_assignments['black_material'].assign_material() + white_material = material_assignments['white_material'].assign_material() + + wrapped_params = { + 'BlackMaterial': surface.shaderfunc_to_material(black_material), + 'WhiteMaterial': surface.shaderfunc_to_material(white_material), + } + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = np.random.uniform() < scratch_prob + is_edge_wear = np.random.uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + + def sample_parameters(self, dimensions, use_default=False): + if use_default: + return self.ceiling_light_default_params[RI(0, len(self.ceiling_light_default_params))] + else: + Radius = clip_gaussian(0.12, 0.04, 0.1, 0.25) + Thickness = U(0.005, 0.05) + InnerRadius = Radius * U(0.4, 0.9) + Height = 0.7 * clip_gaussian(0.09, 0.03, 0.07, 0.15) + InnerHeight = Height * U(0.5, 1.1) + Curvature = U(0.1, 0.5) + params = { + "Radius": Radius, + "Thickness": Thickness, + "InnerRadius": InnerRadius, + "Height": Height, + "InnerHeight": InnerHeight, + "Curvature": Curvature, + } + return params + + def create_placeholder(self, i, **params): + obj = butil.spawn_cube() + butil.modify_mesh(obj, 'NODES', node_group=nodegroup_ceiling_light_geometry(), ng_inputs=self.params, apply=True) + return obj + + def create_asset(self, i, placeholder, **params): + obj = butil.copy(placeholder, keep_materials=True) + self.beveler(obj) + + lamp = self.light_factory.spawn_asset(i, loc=(0,0,0), rot=(0,0,0)) + + butil.parent_to(lamp, obj, no_transform=True, no_inverse=True) + lamp.location.z -= 0.03 + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + + +@node_utils.to_nodegroup('nodegroup_ceiling_light_geometry', singleton=True, type='GeometryNodeTree') +def nodegroup_ceiling_light_geometry(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'Radius', 0.2000), + ('NodeSocketFloat', 'Thickness', 0.0050), + ('NodeSocketFloat', 'InnerRadius', 0.1800), + ('NodeSocketFloat', 'Height', 0.1000), + ('NodeSocketFloat', 'InnerHeight', 0.0300), + ('NodeSocketFloat', 'Curvature', 0.4000), + ('NodeSocketMaterial', 'BlackMaterial', None), + ('NodeSocketMaterial', 'WhiteMaterial', None)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': 512, 'Radius': group_input.outputs["Radius"]}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={'Curve': curve_line, 'Profile Curve': curve_circle.outputs["Curve"]}) + + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, + input_kwargs={'Mesh': curve_to_mesh, 'Offset Scale': group_input.outputs["Thickness"], 'Individual': False}) + + flip_faces = nw.new_node(Nodes.FlipFaces, input_kwargs={'Mesh': curve_to_mesh}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [extrude_mesh.outputs["Mesh"], flip_faces]}) + + set_shade_smooth = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': join_geometry, 'Shade Smooth': False}) + + mesh_circle = nw.new_node(Nodes.MeshCircle, input_kwargs={'Radius': group_input.outputs["Radius"]}, attrs={'fill_type': 'NGON'}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_shade_smooth, mesh_circle]}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': join_geometry_1, 'Material': group_input.outputs["BlackMaterial"]}) + + ico_sphere_1 = nw.new_node(Nodes.MeshIcoSphere, input_kwargs={'Radius': group_input.outputs["InnerRadius"], 'Subdivisions': 5}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': ico_sphere_1.outputs["Mesh"], 'Name': 'UVMap', 3: ico_sphere_1.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + position_2 = nw.new_node(Nodes.InputPosition) + + separate_xyz_2 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position_2}) + + less_than = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_2.outputs["Z"], 1: 0.0010}, attrs={'operation': 'LESS_THAN'}) + + separate_geometry_1 = nw.new_node(Nodes.SeparateGeometry, input_kwargs={'Geometry': store_named_attribute, 'Selection': less_than}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["InnerHeight"], 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_1}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': 1.0000, 'Z': group_input.outputs["Curvature"]}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': separate_geometry_1.outputs["Selection"], 'Translation': combine_xyz_2, 'Scale': combine_xyz_3}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_1}) + + curve_line_1 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': (0.0000, 0.0000, -0.0010), 'End': combine_xyz_1}) + + curve_circle_1 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': group_input.outputs["InnerRadius"]}) + + curve_to_mesh_1 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': curve_line_1, 'Profile Curve': curve_circle_1.outputs["Curve"], 'Fill Caps': True}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, curve_to_mesh_1]}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': join_geometry_2, 'Material': group_input.outputs["WhiteMaterial"]}) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material, set_material_1]}) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': join_geometry_3}) + + vector = nw.new_node(Nodes.Vector) + vector.vector = (0.0000, 0.0000, 0.0000) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': join_geometry_3, 'Bounding Box': bounding_box.outputs["Bounding Box"], 'LightPosition': vector}, + attrs={'is_active_output': True}) \ No newline at end of file diff --git a/infinigen/assets/lighting/hdri_lighting.py b/infinigen/assets/lighting/hdri_lighting.py new file mode 100644 index 000000000..89aad92b6 --- /dev/null +++ b/infinigen/assets/lighting/hdri_lighting.py @@ -0,0 +1,33 @@ +# 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: Lingjie Mei +import os + +import bpy +import gin +import numpy as np +from numpy.random import uniform + +from infinigen.core.nodes import NodeWrangler, Nodes +from infinigen.core.util.random import random_general as rg + +HDRI_RESOURCES = f"{os.getcwd()}/resources/hdri" + + +@gin.configurable +def hdri_lighting(nw: NodeWrangler, strength=("uniform", 0.8, 1.2), ): + suffixes = [f for f in os.listdir(HDRI_RESOURCES) if f.endswith('.exr')] + suffix = np.random.choice(suffixes) + image = bpy.data.images.load(filepath=f"{HDRI_RESOURCES}/{suffix}",check_existing=True) + texture_coord = nw.new_node(Nodes.TextureCoord) + coord = nw.new_node(Nodes.Mapping, [texture_coord], input_kwargs={'Rotation': (0, 0, uniform(np.pi * 2))}) + texture = nw.new_node(Nodes.EnvironmentTexture, [coord], attrs={'image': image}) + return nw.new_node(Nodes.Background, input_kwargs={'Color': texture, 'Strength': rg(strength)}) + + +def add_lighting(): + nw = NodeWrangler(bpy.context.scene.world.node_tree) + surface = hdri_lighting(nw) + nw.new_node(Nodes.WorldOutput, input_kwargs={'Surface': surface}) diff --git a/infinigen/assets/lighting/holdout_lighting.py b/infinigen/assets/lighting/holdout_lighting.py new file mode 100644 index 000000000..b4d177edf --- /dev/null +++ b/infinigen/assets/lighting/holdout_lighting.py @@ -0,0 +1,34 @@ +# 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: Lingjie Mei +import os + +import bpy +import gin +import numpy as np +from numpy.random import uniform + +from infinigen.core.nodes import NodeWrangler, Nodes +from infinigen.core.util.random import random_general as rg + +HOLDOUT_RESOURCES = f"{os.getcwd()}/resources/holdout" + + +@gin.configurable +def holdout_lighting(nw: NodeWrangler, strength=("uniform", 0.8, 1.2), ): + suffixes = [f for f in os.listdir(HOLDOUT_RESOURCES) if f.endswith('.png')] + suffix = np.random.choice(suffixes) + image = bpy.data.images.load(filepath=f"{HOLDOUT_RESOURCES}/{suffix}",check_existing=True) + texture_coord = nw.new_node(Nodes.TextureCoord) + coord = nw.new_node(Nodes.Mapping, [texture_coord], input_kwargs={'Rotation': (0, 0, uniform(np.pi * 2))}) + texture = nw.new_node(Nodes.EnvironmentTexture, [coord], attrs={'image': image}) + return nw.new_node(Nodes.Background, input_kwargs={'Color': texture, 'Strength': rg(strength)}) + + +def add_lighting(): + nw = NodeWrangler(bpy.context.scene.world.node_tree) + surface = holdout_lighting(nw) + nw.new_node(Nodes.WorldOutput, input_kwargs={'Surface': surface}) + bpy.context.scene.world.cycles_visibility.camera = False diff --git a/infinigen/assets/lighting/indoor_lights.py b/infinigen/assets/lighting/indoor_lights.py new file mode 100644 index 000000000..a4f02e9bc --- /dev/null +++ b/infinigen/assets/lighting/indoor_lights.py @@ -0,0 +1,56 @@ +# 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 bpy +from mathutils import Vector + +from numpy.random import uniform as U, normal as N, randint, uniform +import numpy as np + +from infinigen.core.util.random import log_uniform +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.placement import placement +from infinigen.core.placement.placement import placeholder_locs +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util import blender as butil +from infinigen.core.util.random import clip_gaussian + + +def shader_blackbody_temp(nw, params): + blackbody = nw.new_node(Nodes.BlackBody, input_kwargs={'Temperature': params['Temperature']}) + emission = nw.new_node(Nodes.Emission, input_kwargs={'Color': blackbody}) + nw.new_node(Nodes.LightOutput, [emission]) + +class PointLampFactory(AssetFactory): + + def __init__(self, factory_seed): + super().__init__(factory_seed) + with FixedSeed(factory_seed): + self.params = { + 'Wattage': U(40, 100), + 'Radius': U(0.02, 0.03), + 'Temperature': clip_gaussian(4700, 700, 3500, 6500) + } + + def create_placeholder(self, **_): + cube = butil.spawn_cube(size=2) + cube.scale = (self.params['Radius'],) * 3 + butil.apply_transform(cube) + return cube + + def create_asset(self, **_) -> bpy.types.Object: + bpy.ops.object.light_add(type='POINT') + lamp = bpy.context.active_object + lamp.data.energy = self.params['Wattage'] + lamp.data.shadow_soft_size = self.params['Radius'] + lamp.data.use_nodes = True + + nw = NodeWrangler(lamp.data.node_tree) + shader_blackbody_temp(nw, params=self.params) + + return lamp \ No newline at end of file diff --git a/infinigen/assets/lighting/lamp.py b/infinigen/assets/lighting/lamp.py new file mode 100644 index 000000000..f78caded8 --- /dev/null +++ b/infinigen/assets/lighting/lamp.py @@ -0,0 +1,548 @@ +# 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: +# - Hongyu Wen: primary author +# - Alexander Raistrick: add point light + +import bpy +import random +import mathutils +import numpy as np +from numpy.random import uniform as U, normal as N, randint as RI +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface +from infinigen.core.util import blender as butil + +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory +from .indoor_lights import PointLampFactory +from infinigen.assets.material_assignments import AssetList + +class LampFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=[1., 1., 1.], lamp_type="FloorLamp"): + super(LampFactory, self).__init__(factory_seed, coarse=coarse) + + self.bulb_fac = PointLampFactory(factory_seed) + self.bulb_fac.params['Temperature'] = max(self.bulb_fac.params['Temperature'] * 0.6, 2500) + self.bulb_fac.params['Wattage'] *= 0.5 + + self.dimensions = dimensions + self.lamp_type = lamp_type + self.lamp_default_params = { + "DeskLamp":{ + "StandRadius": 0.01, + "StandHeight": 0.3, + "BaseRadius": 0.07, + "BaseHeight": 0.02, + "ShadeHeight": 0.18, + "HeadTopRadius": 0.08, + "HeadBotRadius": 0.11, + "ReverseLamp": True, + "RackThickness": 0.002, + "CurvePoint1": (0.0, 0.0, 0.0), + "CurvePoint2": (0.0, 0.0, 0.2), + "CurvePoint3": (0.0, 0.0, 0.3) + }, + "FloorLamp1": { + "StandRadius": 0.01, + "StandHeight": 0.3, + "BaseRadius": 0.1, + "BaseHeight": 0.02, + "ShadeHeight": 0.2, + "HeadTopRadius": 0.1, + "HeadBotRadius": 0.12, + "ReverseLamp": False, + "RackThickness": 0.002, + "CurvePoint1": (0.0, 0.0, 1.0), + "CurvePoint2": (0.05, 0.0, 1.2), + "CurvePoint3": (0.2, 0.0, 1.0) + }, + "FloorLamp2": { + "StandRadius": 0.01, + "StandHeight": 0.3, + "BaseRadius": 0.1, + "BaseHeight": 0.02, + "ShadeHeight": 0.2, + "HeadTopRadius": 0.1, + "HeadBotRadius": 0.11, + "ReverseLamp": True, + "RackThickness": 0.002, + "CurvePoint1": (0.0, 0.0, 1.0), + "CurvePoint2": (0.0, 0.0, 1.1), + "CurvePoint3": (0.0, 0.0, 1.2) + }} + with FixedSeed(factory_seed): + self.params = self.sample_parameters(dimensions) + self.material_params, self.scratch, self.edge_wear = self.get_material_params() + + self.params.update(self.material_params) + + def get_material_params(self): + material_assignments = AssetList['LampFactory']() + black_material = material_assignments['black_material'].assign_material() + white_material = material_assignments['metal'].assign_material() + lampshade_material = material_assignments['lampshade'].assign_material() + + wrapped_params = { + 'BlackMaterial': surface.shaderfunc_to_material(black_material), + 'MetalMaterial': surface.shaderfunc_to_material(white_material), + 'LampshadeMaterial': surface.shaderfunc_to_material(lampshade_material) + } + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = np.random.uniform() < scratch_prob + is_edge_wear = np.random.uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + def sample_parameters(self, dimensions, use_default=False): + if use_default: + if self.lamp_type == "DeskLamp": + return self.lamp_default_params["DeskLamp"] + else: + return random.choice([self.lamp_default_params["FloorLamp1"], self.lamp_default_params["FloorLamp2"]]) + else: + stand_radius = U(0.005, 0.015) + base_radius = U(0.05, 0.15) + base_height = U(0.01, 0.03) + shade_height = U(0.18, 0.3) + head_top_radius = U(0.07, 0.15) + head_bot_radius = head_top_radius + U(0, 0.05) + rack_thickness = U(0.001, 0.003) + reverse_lamp = True + + if self.lamp_type == "DeskLamp": + height = U(0.25, 0.4) + else: + height = U(1, 1.5) + + z1 = U(base_height, height) + z2 = U(z1, height) + z3 = height + + x1, x2, x3 = 0, 0, 0 + # if self.lamp_type == "FloorLamp" and U() < 0.5: + # x2 = U(0.03, 0.1) + # x3 = U(0.2, 0.4) + # z2, z3 = z3, z2 + # reverse_lamp = False + + params = { + "StandRadius": stand_radius, + "BaseRadius": base_radius, + "BaseHeight": base_height, + "ShadeHeight": shade_height, + "HeadTopRadius": head_top_radius, + "HeadBotRadius": head_bot_radius, + "ReverseLamp": reverse_lamp, + "RackThickness": rack_thickness, + "CurvePoint1": (x1, 0.0, z1), + "CurvePoint2": (x2, 0.0, z2), + "CurvePoint3": (x3, 0.0, z3) + } + return params + + def create_asset(self, i, **params): + obj = butil.spawn_cube() + butil.modify_mesh(obj, 'NODES', node_group=nodegroup_lamp_geometry(), ng_inputs=self.params, apply=True) + + if np.random.uniform() < 0.6: + bulb = self.bulb_fac(i) + butil.parent_to(bulb, obj, no_inverse=True, no_transform=True) + bulb.location.z = obj.bound_box[-2][2] - self.params['ShadeHeight'] * 0.5 + + with butil.SelectObjects(obj): + bpy.ops.object.shade_flat() + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + +class DeskLampFactory(LampFactory): + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse=coarse, lamp_type='DeskLamp') + +class FloorLampFactory(LampFactory): + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse, lamp_type=np.random.choice(['FloorLamp1', 'FloorLamp2'])) + + + + +@node_utils.to_nodegroup('nodegroup_bulb', singleton=False, type='GeometryNodeTree') +def nodegroup_bulb(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[ + ('NodeSocketMaterial', 'LampshadeMaterial', None), + ('NodeSocketMaterial', 'MetalMaterial', None)]) + + curve_line_1 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': (0.0000, 0.0000, -0.2000), 'End': (0.0000, 0.0000, 0.0000)}) + + curve_circle_1 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': 0.1500, 'Resolution': 100}) + + curve_to_mesh_1 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': curve_line_1, 'Profile Curve': curve_circle_1.outputs["Curve"], 'Fill Caps': True}) + + spiral = nw.new_node('GeometryNodeCurveSpiral', + input_kwargs={'Rotations': 5.0000, 'Start Radius': 0.1500, 'End Radius': 0.1500, 'Height': 0.2000}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': spiral, 'Translation': (0.0000, 0.0000, -0.2000)}) + + curve_circle_2 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': 0.0150, 'Resolution': 100}) + + curve_to_mesh_2 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': transform, 'Profile Curve': curve_circle_2.outputs["Curve"], 'Fill Caps': True}) + + curve_line_2 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': (0.0000, 0.0000, -0.2000), 'End': (0.0000, 0.0000, -0.3000)}) + + resample_curve_1 = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': curve_line_2, 'Count': 100}) + + spline_parameter_1 = nw.new_node(Nodes.SplineParameter) + + float_curve_1 = nw.new_node(Nodes.FloatCurve, input_kwargs={'Value': spline_parameter_1.outputs["Factor"]}) + node_utils.assign_curve(float_curve_1.mapping.curves[0], [(0.0000, 1.0000), (0.4432, 0.5500), (1.0000, 0.2750)], handles=['AUTO', 'VECTOR', 'AUTO']) + + set_curve_radius_1 = nw.new_node(Nodes.SetCurveRadius, input_kwargs={'Curve': resample_curve_1, 'Radius': float_curve_1}) + + curve_circle_3 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': 0.1500, 'Resolution': 100}) + + curve_to_mesh_3 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': set_curve_radius_1, 'Profile Curve': curve_circle_3.outputs["Curve"], 'Fill Caps': True}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [curve_to_mesh_1, curve_to_mesh_2, curve_to_mesh_3]}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': join_geometry_1, 'Material': group_input.outputs['MetalMaterial']}) + + curve_line = nw.new_node(Nodes.CurveLine) + + resample_curve = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': curve_line, 'Count': 100}) + + spline_parameter = nw.new_node(Nodes.SplineParameter) + + float_curve = nw.new_node(Nodes.FloatCurve, input_kwargs={'Value': spline_parameter.outputs["Factor"]}) + node_utils.assign_curve(float_curve.mapping.curves[0], [(0.0000, 0.1500), (0.0500, 0.1700), (0.1500, 0.2000), (0.5500, 0.3800), (0.8000, 0.3500), (0.9568, 0.2200), (1.0000, 0.0000)]) + + set_curve_radius = nw.new_node(Nodes.SetCurveRadius, input_kwargs={'Curve': resample_curve, 'Radius': float_curve}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': 100}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': set_curve_radius, 'Profile Curve': curve_circle.outputs["Curve"]}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': curve_to_mesh, 'Material': group_input.outputs['LampshadeMaterial']}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material, set_material_1]}) + + transform_geometry = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry, 'Translation': (0.0000, 0.0000, 0.3000)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_geometry}, attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_bulb_rack', singleton=False, type='GeometryNodeTree') +def nodegroup_bulb_rack(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + amount = nw.new_node(Nodes.GroupInput, + label='amount', + expose_input=[('NodeSocketFloatDistance', 'Thickness', 0.0200), + ('NodeSocketInt', 'Amount', 3), + ('NodeSocketFloatDistance', 'InnerRadius', 1.0000), + ('NodeSocketFloatDistance', 'OuterRadius', 1.0000), + ('NodeSocketFloat', 'InnerHeight', 0.0000), + ('NodeSocketFloat', 'OuterHeight', 0.0000)]) + + curve_circle_2 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': amount.outputs["OuterRadius"], 'Resolution': 100}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': amount.outputs["OuterHeight"]}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle_2.outputs["Curve"], 'Translation': combine_xyz}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': (-1.0000, 0.0000, 0.0000), 'End': (1.0000, 0.0000, 0.0000)}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': curve_line}) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': amount.outputs["Amount"]}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance, 'Amount': reroute}, + attrs={'domain': 'INSTANCE'}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': duplicate_elements.outputs["Geometry"]}) + + endpoint_selection = nw.new_node(Nodes.EndpointSelection, input_kwargs={'Start Size': 0}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: 1.0000, 1: reroute}, attrs={'operation': 'DIVIDE'}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: divide}, + attrs={'operation': 'MULTIPLY'}) + + sample_curve = nw.new_node(Nodes.SampleCurve, + input_kwargs={'Curves': transform, 'Factor': multiply}, + attrs={'use_all_curves': True}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': realize_instances, 'Selection': endpoint_selection, 'Position': sample_curve.outputs["Position"]}) + + endpoint_selection_1 = nw.new_node(Nodes.EndpointSelection, input_kwargs={'End Size': 0}) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: amount.outputs["Thickness"], 2: amount.outputs["InnerRadius"]}, + attrs={'operation': 'MULTIPLY_ADD'}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': multiply_add, 'Resolution': 100}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': amount.outputs["InnerHeight"]}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle.outputs["Curve"], 'Translation': combine_xyz_1}) + + sample_curve_1 = nw.new_node(Nodes.SampleCurve, + input_kwargs={'Curves': transform_1, 'Factor': multiply}, + attrs={'use_all_curves': True}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': set_position, 'Selection': endpoint_selection_1, 'Position': sample_curve_1.outputs["Position"]}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, set_position_1, transform_1]}) + + curve_circle_1 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': amount.outputs["Thickness"], 'Resolution': 100}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': join_geometry, 'Profile Curve': curve_circle_1.outputs["Curve"], 'Fill Caps': True}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': curve_to_mesh}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_reversiable_bulb', singleton=False, type='GeometryNodeTree') +def nodegroup_reversiable_bulb(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Scale', 0.3000), + ('NodeSocketBool', 'Reverse', False), + ('NodeSocketMaterial', 'BlackMaterial', None), + ('NodeSocketMaterial', 'LampshadeMaterial', None), + ('NodeSocketMaterial', 'MetalMaterial', None)]) + + bulb = nw.new_node(nodegroup_bulb().name, input_kwargs={'LampshadeMaterial': group_input.outputs["LampshadeMaterial"], + 'MetalMaterial': group_input.outputs["MetalMaterial"]}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Scale"], 'Y': group_input.outputs["Scale"], 'Z': group_input.outputs["Scale"]}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': bulb, 'Scale': combine_xyz_1}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': transform}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Reverse"], 1: 3.1415}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply}) + + rotate_instances = nw.new_node(Nodes.RotateInstances, input_kwargs={'Instances': geometry_to_instance, 'Rotation': combine_xyz_2}) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Reverse"], 1: 2.0000, 2: -1.0000}, + attrs={'operation': 'MULTIPLY_ADD'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: -0.0150, 1: multiply_add}, attrs={'operation': 'MULTIPLY'}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': rotate_instances, 'RackSupport': multiply_1}, + attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_lamp_head', singleton=False, type='GeometryNodeTree') +def nodegroup_lamp_head(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'ShadeHeight', 0.0000), + ('NodeSocketFloat', 'TopRadius', 0.3000), + ('NodeSocketFloat', 'BotRadius', 0.5000), + ('NodeSocketBool', 'ReverseBulb', True), + ('NodeSocketFloatDistance', 'RackThickness', 0.0050), + ('NodeSocketFloat', 'RackHeight', 0.5000), + ('NodeSocketMaterial', 'BlackMaterial', None), + ('NodeSocketMaterial', 'LampshadeMaterial', None), + ('NodeSocketMaterial', 'MetalMaterial', None)]) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["TopRadius"], 1: 0.8000}, + attrs={'operation': 'MULTIPLY'}) + + reversiable_bulb = nw.new_node(nodegroup_reversiable_bulb().name, + input_kwargs={'Scale': multiply, + 'BlackMaterial': group_input.outputs["BlackMaterial"], + 'LampshadeMaterial': group_input.outputs["LampshadeMaterial"], + 'MetalMaterial': group_input.outputs["MetalMaterial"]}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: 0.1500}, attrs={'operation': 'MULTIPLY'}) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["ReverseBulb"], 1: 2.0000, 2: -1.0000}, + attrs={'operation': 'MULTIPLY_ADD'}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["RackHeight"], 1: multiply_add}, + attrs={'operation': 'MULTIPLY'}) + + bulb_rack = nw.new_node(nodegroup_bulb_rack().name, + input_kwargs={'Thickness': group_input.outputs["RackThickness"], 'InnerRadius': multiply_1, 'OuterRadius': group_input.outputs["TopRadius"], 'InnerHeight': reversiable_bulb.outputs["RackSupport"], 'OuterHeight': multiply_2}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': bulb_rack, 'Material': group_input.outputs["BlackMaterial"]}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_2}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["ShadeHeight"], 1: group_input.outputs["RackHeight"]}, + attrs={'operation': 'SUBTRACT'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_add, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: multiply_3}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_4}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz_1, 'End': combine_xyz}) + + spline_parameter = nw.new_node(Nodes.SplineParameter) + + map_range = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': spline_parameter.outputs["Factor"], 3: group_input.outputs["TopRadius"], 4: group_input.outputs["BotRadius"]}) + + set_curve_radius = nw.new_node(Nodes.SetCurveRadius, input_kwargs={'Curve': curve_line, 'Radius': map_range.outputs["Result"]}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': 100}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': set_curve_radius, 'Profile Curve': curve_circle.outputs["Curve"]}) + + flip_faces = nw.new_node(Nodes.FlipFaces, input_kwargs={'Mesh': curve_to_mesh}) + + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={'Mesh': curve_to_mesh, 'Offset Scale': 0.0050, 'Individual': False}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [flip_faces, extrude_mesh.outputs["Mesh"]]}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': join_geometry_1, 'Material': group_input.outputs["LampshadeMaterial"]}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [reversiable_bulb.outputs["Geometry"], set_material, set_material_1]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_lamp_geometry', singleton=False, type='GeometryNodeTree') +def nodegroup_lamp_geometry(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'StandRadius', 0.0200), + ('NodeSocketFloatDistance', 'BaseRadius', 0.1000), + ('NodeSocketFloat', 'BaseHeight', 0.0200), + ('NodeSocketFloat', 'ShadeHeight', 0.0000), + ('NodeSocketFloat', 'HeadTopRadius', 0.3000), + ('NodeSocketFloat', 'HeadBotRadius', 0.5000), + ('NodeSocketBool', 'ReverseLamp', True), + ('NodeSocketFloatDistance', 'RackThickness', 0.0050), + ('NodeSocketVectorTranslation', 'CurvePoint1', (0.0000, 0.0000, 0.0000)), + ('NodeSocketVectorTranslation', 'CurvePoint2', (0.0000, 0.0000, 0.0000)), + ('NodeSocketVectorTranslation', 'CurvePoint3', (0.0000, 0.0000, 0.0000)), + ('NodeSocketMaterial', 'BlackMaterial', None), + ('NodeSocketMaterial', 'LampshadeMaterial', None), + ('NodeSocketMaterial', 'MetalMaterial', None)]) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["BaseHeight"]}) + + curve_line_1 = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_1}) + + curve_circle_1 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': group_input.outputs["BaseRadius"], 'Resolution': 100}) + + curve_to_mesh_1 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': curve_line_1, 'Profile Curve': curve_circle_1.outputs["Curve"], 'Fill Caps': True}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["BaseHeight"]}) + + bezier_segment = nw.new_node(Nodes.CurveBezierSegment, + input_kwargs={'Start': combine_xyz, 'Start Handle': group_input.outputs["CurvePoint1"], 'End Handle': group_input.outputs["CurvePoint2"], 'End': group_input.outputs["CurvePoint3"], 'Resolution': 100}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [bezier_segment, curve_line]}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': group_input.outputs["StandRadius"], 'Resolution': 100}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': join_geometry_2, 'Profile Curve': curve_circle.outputs["Curve"], 'Fill Caps': True}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [curve_to_mesh_1, curve_to_mesh]}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': join_geometry, 'Material': group_input.outputs["BlackMaterial"]}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["ShadeHeight"], 1: 0.4000}, + attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["ShadeHeight"], 1: 0.2000}, + attrs={'operation': 'MULTIPLY'}) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: multiply, 1: group_input.outputs["ReverseLamp"], 2: multiply_1}, + attrs={'operation': 'MULTIPLY_ADD'}) + + lamp_head = nw.new_node(nodegroup_lamp_head().name, + input_kwargs={'ShadeHeight': group_input.outputs["ShadeHeight"], + 'TopRadius': group_input.outputs["HeadTopRadius"], + 'BotRadius': group_input.outputs["HeadBotRadius"], + 'ReverseBulb': group_input.outputs["ReverseLamp"], + 'RackThickness': group_input.outputs["RackThickness"], + 'RackHeight': multiply_add, + 'BlackMaterial': group_input.outputs["BlackMaterial"], + 'LampshadeMaterial': group_input.outputs["LampshadeMaterial"], + 'MetalMaterial': group_input.outputs["MetalMaterial"],}) + + sample_curve = nw.new_node(Nodes.SampleCurve, + input_kwargs={'Curves': bezier_segment, 'Factor': 1.0000}, + attrs={'use_all_curves': True}) + + align_euler_to_vector = nw.new_node(Nodes.AlignEulerToVector, input_kwargs={'Vector': sample_curve.outputs["Tangent"]}, attrs={'axis': 'Z'}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': lamp_head, 'Translation': sample_curve.outputs["Position"], 'Rotation': align_euler_to_vector}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material, transform]}) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': join_geometry_1}) + + curve_line_2 = nw.new_node(Nodes.CurveLine, input_kwargs={'End': (0.0000, 0.0000, 0.1000)}) + + transform_geometry = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_line_2, 'Translation': sample_curve.outputs["Position"], 'Rotation': align_euler_to_vector}) + + sample_curve_1 = nw.new_node(Nodes.SampleCurve, input_kwargs={'Curves': transform_geometry, 'Factor': 1.0000}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': join_geometry_1, 'Bounding Box': bounding_box.outputs["Bounding Box"], 'LightPosition': sample_curve_1.outputs["Position"]}, + attrs={'is_active_output': True}) + diff --git a/infinigen/assets/lighting/sky_lighting.py b/infinigen/assets/lighting/sky_lighting.py index 1249bfb61..4593ef280 100644 --- a/infinigen/assets/lighting/sky_lighting.py +++ b/infinigen/assets/lighting/sky_lighting.py @@ -52,6 +52,7 @@ def nishita_lighting( sky_texture.air_density =rg(air_density) sky_texture.dust_density = rg(dust_density) sky_texture.ozone_density = clip_gaussian(1, 1, 0.1, 10) + strength = rg(strength) return nw.new_node(Nodes.Background, input_kwargs={'Color': sky_texture, 'Strength': strength}) diff --git a/infinigen/assets/lighting/three_point_lighting.py b/infinigen/assets/lighting/three_point_lighting.py new file mode 100644 index 000000000..e1dba2e16 --- /dev/null +++ b/infinigen/assets/lighting/three_point_lighting.py @@ -0,0 +1,28 @@ +# 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: Lingjie Mei + +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.object import center + + +def add_lighting(asset): + dimension = asset.dimensions * asset.scale + radius = np.sqrt(dimension[0] * dimension[1]) / 2 * 1.5 + locations = np.array( + [(uniform(3, 4), -uniform(3, 4), uniform(5, 6)), (uniform(3, 4), uniform(3, 4), uniform(3, 4)), + (-uniform(5, 6), uniform(-2, -3), uniform(3, 4))]) * radius + energies = [1000, 1000 / uniform(5, 10), 1000 * uniform(5, 10)] + for loc, energy in zip(locations, energies): + bpy.ops.object.light_add(type='SPOT') + light = bpy.context.active_object + light.location = loc + asset.location + center(asset) * asset.scale + light.rotation_euler = 0, np.arctan2(np.sqrt(loc[0] ** 2 + loc[1] ** 2), loc[2]), -np.arctan2(-loc[0], + -loc[ + 1])\ + - np.pi / 2 + light.data.energy = energy * radius * radius diff --git a/infinigen/assets/material_assignments.py b/infinigen/assets/material_assignments.py new file mode 100644 index 000000000..a11d1bdff --- /dev/null +++ b/infinigen/assets/material_assignments.py @@ -0,0 +1,592 @@ +# 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: Meenal Parakh + + +import numpy as np +from dataclasses import dataclass +import functools + +from numpy.random import uniform + +from infinigen.assets.materials import (metal, plastic, text, ceramic, woods, dirt, + mirror, + wood, glass_volume, fabrics, plaster, + sofa_fabric, leather, rug, water, glass) +from infinigen.assets.materials.plastics import plastic_rough +from infinigen.assets.materials.plastics.plastic_rough import shader_rough_plastic +from infinigen.assets.materials import (glass_volume, plaster, wood, + sofa_fabric, leather, rug, water, glass, velvet) +from infinigen.assets.materials import (beverage_fridge_shaders, dishwasher_shaders, + ceiling_light_shaders, + vase_shaders, + lamp_shaders, table_marble, + fabrics, + microwave_shaders, oven_shaders) + +from infinigen.assets.materials import black_plastic +from infinigen.assets.materials.art import ArtFabric, ArtRug, Art +from infinigen.assets.materials.wear_tear import procedural_edge_wear, procedural_scratch +from infinigen.assets.color_fits import real_color_distribution + +DEFAULT_EDGE_WEAR_PROB = .5 +DEFAULT_SCRATCH_PROB = .5 + +class TextureAssignments: + + def __init__(self, materials, probabilities): + self.materials = materials + self.probabilities = probabilities + + def assign_material(self): + p = np.array(self.probabilities) + p = p / p.sum() + return np.random.choice(self.materials, p=p) + +class MaterialOptions: + def __init__(self, materials_list): + self.materials, self.probabilities = zip(*materials_list) + self.probabilities = np.array(self.probabilities) + self.probabilities = self.probabilities / self.probabilities.sum() + + def assign_material(self): + return np.random.choice(self.materials, p=self.probabilities) + + +def get_all_metal_shaders(): + metal_shaders_list = [metal.brushed_metal.shader_brushed_metal, + metal.galvanized_metal.shader_galvanized_metal, + metal.grained_and_polished_metal.shader_grained_metal, + metal.hammered_metal.shader_hammered_metal] + color = metal.sample_metal_color() + new_shaders = [functools.partial(shader, base_color=color) for shader in metal_shaders_list] + for idx, ns in enumerate(new_shaders): + # fix taken from: https://github.com/elastic/apm-agent-python/issues/293 + ns.__name__ = metal_shaders_list[idx].__name__ + + return new_shaders + +def plastic_furniture(): + new_shader = functools.partial(shader_rough_plastic, base_color=real_color_distribution('sofa_leather')) + new_shader.__name__ = shader_rough_plastic.__name__ + return new_shader + + +def get_all_fabric_shaders(): + return [fabrics.shader_coarse_fabric_random, fabrics.shader_fine_fabric_random, fabrics.shader_fabric, + fabrics.shader_leather, fabrics.shader_sofa_fabric] + + +def beverage_fridge_materials(): + metal_shaders = get_all_metal_shaders() + return { + "surface": TextureAssignments(metal_shaders, [1.0]* len(metal_shaders)), + "front": TextureAssignments([beverage_fridge_shaders.shader_glass_001], [1.0]), + "handle": TextureAssignments([beverage_fridge_shaders.shader_white_metal_001], [1.0]), + "back": TextureAssignments([beverage_fridge_shaders.shader_black_medal_001], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def dishwasher_materials(): + metal_shaders = get_all_metal_shaders() + return { + "surface": TextureAssignments(metal_shaders, [1.0]* len(metal_shaders)), + "front": TextureAssignments([dishwasher_shaders.shader_glass_002], [1.0]), + "white_metal": TextureAssignments(metal_shaders, [1.0]* len(metal_shaders)), + "top": TextureAssignments([dishwasher_shaders.shader_black_medal_002], [1.0]), + "name_material": TextureAssignments(metal_shaders, [1.0]* len(metal_shaders)), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def microwave_materials(): + metal_shaders = get_all_metal_shaders() + return { + "surface": TextureAssignments(metal_shaders, [1.0]* len(metal_shaders)), + "back": TextureAssignments(metal_shaders, [1.0]* len(metal_shaders)), + "black_glass": TextureAssignments([microwave_shaders.shader_black_glass], [1.0]), + "glass": TextureAssignments([microwave_shaders.shader_glass], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def oven_materials(): + metal_shaders = get_all_metal_shaders() + return { + "surface": TextureAssignments(metal_shaders, [1.0]* len(metal_shaders)), + "back": TextureAssignments([oven_shaders.shader_black_medal], [1.0]), + "white_metal": TextureAssignments(metal_shaders, [1.0]* len(metal_shaders)), + "black_glass": TextureAssignments([oven_shaders.shader_super_black_glass], [1.0]), + "glass": TextureAssignments([oven_shaders.shader_glass], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def tv_materials(): + return { + "surface": TextureAssignments([metal, plastic_rough], [1.0, .2]), + "screen_surface": TextureAssignments([text.Text], [1.0]), + "support": TextureAssignments([metal, plastic_rough], [1.0, .2]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def bathtub_materials(): + return { + "surface": TextureAssignments([ceramic], [1]), + "leg": TextureAssignments([metal], [1.0]), + "hole": TextureAssignments([metal], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def bathroom_sink_materials(): + return { + "surface": TextureAssignments([ceramic, metal], [0.9, 0.1]), + # rest inherited from bathtub_materials + } + +def toilet_materials(): + return { + "surface": TextureAssignments([ceramic, metal], [0.9, 0.1]), + "hardware_surface": TextureAssignments([metal], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def hardware_materials(): + return { + "surface": TextureAssignments([metal], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def blanket_materials(): + return { + "surface": TextureAssignments([ArtFabric, fabrics], + [1.0, 1.0]), + } + +def pants_materials(): + return { + "surface": TextureAssignments([ArtFabric, fabrics], + [1.0, 1.0]), + } + +def towel_materials(): + return { + "surface": TextureAssignments([ArtRug, rug], + [0.2, 0.8]), + } + +def acquarium_materials(): + return { + "glass_surface": TextureAssignments([glass], [1.0]), + "belt_surface": TextureAssignments([metal.galvanized_metal], [1.0]), + "water_surface": TextureAssignments([water], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [0, DEFAULT_EDGE_WEAR_PROB] + } + +def ceiling_light_materials(): + return { + "black_material": TextureAssignments([ceiling_light_shaders.shader_black], [1.0]), + "white_material": TextureAssignments([ceiling_light_shaders.shader_lamp_bulb_nonemissive], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] } + +def lamp_materials(): + return { + 'black_material': TextureAssignments([lamp_shaders.shader_black], [1.0]), + 'metal': TextureAssignments([lamp_shaders.shader_metal], [1.0]), + 'lampshade': TextureAssignments([lamp_shaders.shader_lampshade], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [0, 0] + } + +def table_cocktail_materials(): + # top materials are: choice(['marble', 'tiled_wood', 'plastic', 'glass']), + # choice(['brushed_metal', 'grained_metal', 'galvanized_metal', 'wood', 'glass']), + metal_shaders = get_all_metal_shaders() + return { + 'top': TextureAssignments([table_marble.shader_marble, + woods.tiled_wood.shader_wood_tiled, + shader_rough_plastic, + glass_volume.shader_glass_volume], + [1.0, 1.0, 1.0, 1.0]), + 'leg': TextureAssignments([*metal_shaders, + wood.shader_wood, + glass_volume.shader_glass_volume], + [1.0] * len(metal_shaders)+ [1.0, 1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def table_dining_materials(): + metal_shaders = get_all_metal_shaders() + fabric_shaders = get_all_fabric_shaders() + probs = [1.0 / len(metal_shaders)] * len(metal_shaders) + + return { + 'top': MaterialOptions([ + (table_marble.shader_marble, 2.0), + (wood.shader_wood, 1.0), + (dishwasher_shaders.shader_glass_002, 1.0), + (oven_shaders.shader_super_black_glass, 1.0), + (woods.tiled_wood.shader_wood_tiled, 2.0), + (glass_volume.shader_glass_volume, 1.0), + *(zip(metal_shaders, probs)), + ]), + 'leg': MaterialOptions([ + (wood.shader_wood, 1.0), + (glass_volume.shader_glass_volume, 1.0), + (plastic_furniture(), 1.0), + *(zip(metal_shaders, probs)), + ]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def bar_chair_materials(leg_style=None): + metal_shaders = get_all_metal_shaders() + if leg_style == "wheeled": + probs = [0.01 / len(metal_shaders)] * len(metal_shaders) + else: + probs = [1.0 / len(metal_shaders)] * len(metal_shaders) + return { + 'seat': TextureAssignments([leather.shader_leather], + [1.0]), + 'leg': TextureAssignments([wood.shader_wood, + *metal_shaders], + [1.0] + probs), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, 0.0] + } + +def chair_materials(): + return { + 'limb': TextureAssignments( + [metal, wood, fabrics], + [2.0, 2., 2] + ), + 'surface': TextureAssignments([plastic_rough, wood, fabrics], [.3, 0.5, 0.7]), + 'panel': TextureAssignments([plastic_rough, wood, fabrics], [.3, 0.5, 0.7]), + 'arm': TextureAssignments([plastic, wood, fabrics], [.3, 0.5, 0.7]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def office_chair_materials(leg_style=None): + metal_shaders = get_all_metal_shaders() + if leg_style == "wheeled": + probs = [0.01 / len(metal_shaders)] * len(metal_shaders) + else: + probs = [1.0 / len(metal_shaders)] * len(metal_shaders) + return { + 'top': TextureAssignments([ + leather.shader_leather, + wood.shader_wood, + shader_rough_plastic, + glass_volume.shader_glass_volume], + [1.0, 1.0, 1.0, 1.0]), + 'leg': TextureAssignments([wood.shader_wood, + *metal_shaders], + [1.0] + probs), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def bedframe_materials(): + return { + 'surface': TextureAssignments([wood, plaster], + [2.0, 1.0,]), + 'limb_surface': TextureAssignments([wood, plaster], [2.0, 1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def mattress_materials(): + return { + 'surface': TextureAssignments([sofa_fabric], + [1.0]), + } + +def pillow_materials(): + return { + 'surface': TextureAssignments([ArtFabric, sofa_fabric], + [1.0, 1.0]), + } + +def sofa_materials(): + return { + 'sofa_fabric': MaterialOptions([ + (velvet.shader_velvet, 0.5), + (sofa_fabric.shader_sofa_fabric, 0.3), + (leather.shader_leather, 0.2) + ]), + } + +def book_materials(): + return { + "surface": TextureAssignments([plaster], [1.0]), + "cover_surface": TextureAssignments([text.Text], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [0, 0] + } + +def vase_materials(): + return { + "surface": TextureAssignments([vase_shaders.shader_ceramic, + glass_volume.shader_glass_volume], + [1.0, 1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def pan_materials(): + return { + "surface": TextureAssignments([metal], [1.0]), + "inside": TextureAssignments([metal], [1.0]), + # no guard as it overrides over tableware_materials + } + +def cup_materials(): + return { + 'surface': TextureAssignments([glass, plastic], [1.0, 1.0]), + "wrap_surface": TextureAssignments([text.Text], [1.0]), + } + +def bottle_materials(): + return { + "surface": TextureAssignments([glass, plastic], [1.0, 1.0]), + "wrap_surface": TextureAssignments([text.Text], [1.0]), + "cap_surface": TextureAssignments([metal, plastic], [1.0, 1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, 0.0] + } + +def tableware_materials(fragile=False, transparent=False): + if fragile: + surface_materials = TextureAssignments([ceramic, glass, plastic], [1.0, 1, 1]) + elif transparent: + surface_materials = TextureAssignments([ceramic, glass], [1.0, 1]) + else: + surface_materials = TextureAssignments([ceramic, glass, plastic, metal, wood], [1, 1, 1.0, 1, 1]) + + return { + "surface": surface_materials, + "guard": TextureAssignments([wood, plastic], [1.0, 1.0]), + "inside": TextureAssignments([ceramic, metal], [1.0, 1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + + +def can_materials(): + return { + "surface": TextureAssignments([metal], [1.0]), + "wrap_surface": TextureAssignments([text.Text], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def jar_materials(): + return { + "surface": TextureAssignments([glass], [1.0]), + "cap_surface": TextureAssignments([metal], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def foodbag_materials(): + return { + "surface": TextureAssignments([text.Text], [1.0]), + } + +def lid_materials(): + return { + "surface": TextureAssignments([ceramic, metal], [0.5, 0.5]), + "rim_surface": TextureAssignments([metal], [1.0]), + "handle_surface": TextureAssignments([metal, ceramic], [1.0, 1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, 0.0] + } +def glasslid_materials(): + return { + "surface": TextureAssignments([glass], [1.0]), + "rim_surface": TextureAssignments([metal], [1.0]), + "handle_surface": TextureAssignments([metal, ceramic], [1.0, 1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + 'wear_tear_prob': [DEFAULT_SCRATCH_PROB, 0.0] + } + +def plant_container_materials(): + return { + "surface": TextureAssignments([ceramic, metal], [3., 1.]), + 'dirt_surface': TextureAssignments([dirt], [1.0]), + } + +def balloon_materials(): + return { + "surface": TextureAssignments([metal], + [1.0]), + } + +def range_hood_materials(): + return { + "surface": TextureAssignments([metal], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def wall_art_materials(): + return { + "frame": TextureAssignments([wood, metal], [1.0, 1.0]), + "surface": TextureAssignments([Art], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def mirror_materials(): + return { + "frame": TextureAssignments([wood, metal], [1.0, 1.0]), + "surface": TextureAssignments([mirror], [1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + + +def kitchen_sink_materials(): + shaders = get_all_metal_shaders() + sink_color = metal.sample_metal_color(metal_color='natural') + if uniform() < .5: + tap_color = metal.sample_metal_color(metal_color='plain') + else: + tap_color = metal.sample_metal_color(metal_color='natural') + sink_shaders = [lambda nw, *args: shader(nw, *args, base_color=sink_color) for shader in shaders] + tap_shaders = [lambda nw, *args: shader(nw, *args, base_color=tap_color) for shader in shaders] + return { + "sink": TextureAssignments(sink_shaders, [1.0, 1.0, 1.0, 1.0]), + "tap": TextureAssignments(tap_shaders, [1.0, 1.0, 1.0, 1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + + +def kitchen_tap_materials(): + shaders = get_all_metal_shaders() + if uniform() < .5: + tap_color = metal.sample_metal_color(metal_color='plain') + else: + tap_color = metal.sample_metal_color(metal_color='natural') + tap_shaders = [lambda nw, *args: shader(nw, *args, base_color=tap_color) for shader in shaders] + return { + "tap": TextureAssignments(tap_shaders, [1.0, 1.0, 1.0, 1.0]), + "wear_tear": [procedural_scratch, procedural_edge_wear], + "wear_tear_prob": [DEFAULT_SCRATCH_PROB, DEFAULT_EDGE_WEAR_PROB] + } + +def rug_materials(): + return { + "surface": MaterialOptions([ + (rug, 3.0), + (ArtRug, 2.0), + (fabrics, 5.0), + ]) + } + +AssetList = { + # appliances + 'BeverageFridgeFactory': beverage_fridge_materials, # looks like dishwasher currently + 'DishwasherFactory': dishwasher_materials, + 'MicrowaveFactory': microwave_materials, + 'OvenFactory': oven_materials, # looks like dishwasher currently + 'TVFactory': tv_materials, + 'MonitorFactory': None, # inherits from TVFactory + # bathroom + 'BathtubFactory': bathtub_materials, + 'BathroomSinkFactory': bathroom_sink_materials, # inheriting from bathtub factory, so not used + 'HardwareFactory': hardware_materials, + 'ToiletFactory': toilet_materials, + # clothes + 'BlanketFactory': blanket_materials, # has Art which is a class, not func, + # also "Normal Not Found" is printed when generating + ############## this point onwards, using this dictionary to get corresponding + ############## material functions except for tableware base + 'PantsFactory': pants_materials, # same comment as above + 'ShirtFactory': pants_materials, # same comment as above + 'TowelFactory': towel_materials, + # decor + 'AquariumTankFactory': acquarium_materials, + # lighting + 'CausticsLampFactory': None, # the properties are not materials, so skipping + 'CeilingLightFactory': ceiling_light_materials, + 'PointLampFactory': None, # the properties are not materials, so skipping + 'LampFactory': lamp_materials, # really required bunch of changes to expose the materials + # seating: chairs + 'BarChairFactory': bar_chair_materials, + 'ChairFactory': chair_materials, # an internal reassignment that overrides surface with the limb material + 'OfficeChairFactory': office_chair_materials, + # seating: sofas and beds + 'BedFactory': None, # uses the below factories, so no materials + 'BedFrameFactory': bedframe_materials, + 'MattressFactory': mattress_materials, + 'PillowFactory': pillow_materials, + 'SofaFactory': sofa_materials, + # shelves: todo + 'SimpleDeskFactory': None, + 'SimpleBookcaseFactory': None, + 'CellShelfFactory': None, + 'TVStandFactory': None, + 'TriangleShelfFactory': None, + 'LargeShelfFactory': None, + 'SingleCabinetFactory': None, + 'KitchenCabinetFactory': None, + 'KitchenSpaceFactory': None, + 'KitchenIslandFactory': None, + # table decorations : they have their own materials + 'BookFactory': book_materials, + 'BookColumnFactory': None, # use BookFactory + 'BookStackFactory': None, # use BookFactory + 'VaseFactory': vase_materials, + # sink and tap + 'SinkFactory': kitchen_sink_materials, + 'TapFactory': kitchen_tap_materials, + # tables + 'TableCocktailFactory': table_cocktail_materials, + 'TableDiningFactory': table_dining_materials, + 'TableTopFactory': None, # not sure where the materials are used in it + # Tableware + 'TablewareFactory': tableware_materials, # only function with arguments + # 'TablewareFactory': tableware_materials_default, # directly uses the following functions (not through the AssetList Dictionary) + 'SpoonFactory': None, # uses materials from tableware base + 'KnifeFactory': None, # uses materials from tableware base + 'ChopsticksFactory': None, # uses materials from tableware base + 'ForkFactory': None, # uses materials from tableware base + 'SpatulaFactory': None, # uses materials from tableware base + 'PanFactory': pan_materials, + 'PotFactory': None, # uses the same materials as PanFactory + 'CupFactory': cup_materials, + 'WineglassFactory': None, # uses materials from transparent tableware + 'PlateFactory': None, # uses materials from tableware base + 'BowlFactory': None, # uses materials from tableware base + 'FruitContainerFactory': None, # uses materials from tableware base + 'BottleFactory': bottle_materials, + 'CanFactory': can_materials, + 'JarFactory': jar_materials, + 'FoodBagFactory': foodbag_materials, + 'FoodBoxFactory': foodbag_materials, # same params as above + 'LidFactory': lid_materials, + 'GlassLidFactory': glasslid_materials, + 'PlantContainerFactory': plant_container_materials, + # wall decorations + 'BalloonFactory': balloon_materials, + 'RangeHoodFactory': range_hood_materials, # getting RangeHoodFactory not Found. + 'WallArtFactory': wall_art_materials, + 'MirrorFactory': mirror_materials, + # window + 'WindowFactory': None, + "RugFactory": rug_materials, +} diff --git a/infinigen/assets/materials/__init__.py b/infinigen/assets/materials/__init__.py index 89abe7d35..039b38d05 100644 --- a/infinigen/assets/materials/__init__.py +++ b/infinigen/assets/materials/__init__.py @@ -1,2 +1,55 @@ +# 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: Hongyu Wen + from . import * from infinigen.infinigen_gpl.surfaces import * + +from .metal.brushed_metal import shader_brushed_metal +from .metal.galvanized_metal import shader_galvanized_metal +from .metal.grained_and_polished_metal import shader_grained_metal +from .metal.hammered_metal import shader_hammered_metal +from .metal.metal_basic import shader_metal +from .metal import ( + metal_basic, + galvanized_metal, + grained_and_polished_metal, + hammered_metal, + brushed_metal, +) + +metal_shader_list = [ + shader_brushed_metal, + shader_galvanized_metal, + shader_grained_metal, + shader_hammered_metal, + shader_metal, +] + +from .plastic import shader_rough_plastic +from .plastic import shader_translucent_plastic + +plastic_shader_list = [shader_rough_plastic, shader_translucent_plastic] + +from .woods.wood import shader_wood + +wood_shader_list = [shader_wood] + +from .glass import shader_glass + +glass_shader_list = [shader_glass] + +from .leather_and_fabrics import ( + leather, + general_fabric, + sofa_fabric, + fine_knit_fabric, + coarse_knit_fabric, + lined_fabric, + velvet +) +from .art import Art, DarkArt, ArtRug, ArtFabric +from .stone_and_concrete import concrete +from .woods import tiled_wood, wood, wood_old, square_wood_tile, hexagon_wood_tile, composite_wood_tile, \ + staggered_wood_tile, crossed_wood_tile, wood_tile, non_wood_tile diff --git a/infinigen/assets/materials/art.py b/infinigen/assets/materials/art.py new file mode 100644 index 000000000..a872158bd --- /dev/null +++ b/infinigen/assets/materials/art.py @@ -0,0 +1,100 @@ +# 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: Lingjie Mei +import numpy as np +from numpy.random import uniform + +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from . import text +from ..utils.decorate import read_uv, write_uv +from ...core.nodes import NodeWrangler, Nodes +from infinigen.core.util.random import random_general as rg + + +class Art(text.Text): + def __init__(self, factory_seed): + super().__init__(factory_seed) + with FixedSeed(self.factory_seed): + self.n_barcodes = 0 + self.n_texts = 0 + self.n_patches = np.random.randint(10, 15) + + @staticmethod + def scale_uniform(min_, max_): + return (max_ - min_) * log_uniform(.1, .5) + + +class DarkArt(Art): + + def __init__(self, factory_seed): + super().__init__(factory_seed) + with FixedSeed(self.factory_seed): + self.darken_scale = uniform(5, 10) + self.darken_ratio = uniform(.5, 1) + + def make_shader_func(self, bbox): + art_shader_func = super(DarkArt, self).make_shader_func(bbox) + + def shader_dark_art(nw: NodeWrangler): + art_shader_func(nw) + art_bsdf = nw.find(Nodes.PrincipledBSDF)[0] + art_color = nw.find_from(art_bsdf.inputs[0])[0].from_socket + dark_color = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Scale': self.darken_scale}).outputs[0] + art_color = nw.new_node( + Nodes.MixRGB, [self.darken_ratio, art_color, dark_color], + attrs={'blend_type': 'DARKEN'} + ).outputs[2] + nw.connect_input(art_color, art_bsdf.inputs[0]) + + return shader_dark_art + + +class ArtComposite(DarkArt): + + @property + def base_shader(self): + raise NotImplementedError + + def make_shader_func(self, bbox): + art_shader_func = super(ArtComposite, self).make_shader_func(bbox) + + def shader_art_composite(nw: NodeWrangler, **kwargs): + self.base_shader(nw, **kwargs) + nw_, base_bsdf = nw.find_recursive(Nodes.PrincipledBSDF)[-1] + art_shader_func(nw_) + art_bsdf = nw_.find(Nodes.PrincipledBSDF)[-1] + art_color = nw_.find_from(art_bsdf.inputs[0])[0].from_socket + nw_.nodes.remove(art_bsdf) + nw_.connect_input(art_color, base_bsdf.inputs[0]) + nw_.connect_input(base_bsdf.outputs[0], nw_.find(Nodes.MaterialOutput)[0].inputs['Surface']) + + return shader_art_composite + + def make_sphere(self): + return make_sphere() + + +class ArtRug(ArtComposite): + @property + def base_shader(self): + from . import rug + return rug.shader_rug + + +class ArtFabric(ArtComposite): + @property + def base_shader(self): + from .leather_and_fabrics import fabric_shader_list + return rg(fabric_shader_list) + + +def apply(obj, selection=None, bbox=(0, 1, 0, 1), scale=None, **kwargs): + if scale is not None: + write_uv(obj, read_uv(obj) * scale) + Art(np.random.randint(1e5)).apply(obj, selection, bbox, **kwargs) + + +def make_sphere(): + return text.make_sphere() diff --git a/infinigen/assets/materials/beverage_fridge_shaders.py b/infinigen/assets/materials/beverage_fridge_shaders.py new file mode 100644 index 000000000..2499733e9 --- /dev/null +++ b/infinigen/assets/materials/beverage_fridge_shaders.py @@ -0,0 +1,46 @@ + # 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: Hongyu Wen + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler + +def shader_glass_001(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + glass_bsdf = nw.new_node(Nodes.GlassBSDF, input_kwargs={'IOR': 1.5000}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': glass_bsdf}, attrs={'is_active_output': True}) + + + +def shader_black_medal_001(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + anisotropic_bsdf = nw.new_node('ShaderNodeBsdfAnisotropic', input_kwargs={'Color': (0.0167, 0.0167, 0.0167, 1.0000)}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': anisotropic_bsdf}, attrs={'is_active_output': True}) + + +def shader_white_metal_001(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + mapping = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'Scale': (1.0000, 1.0000, 50.0000)}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'Scale': 20.0000, 'Detail': 20.0000, 'Distortion': 1.0000}) + + colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture_1.outputs["Color"]}) + colorramp.color_ramp.elements[0].position = 0.2500 + colorramp.color_ramp.elements[0].color = [0.5244, 0.5244, 0.5244, 1.0000] + colorramp.color_ramp.elements[1].position = 1.0000 + colorramp.color_ramp.elements[1].color = [0.9698, 0.9698, 0.9698, 1.0000] + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': colorramp.outputs["Color"], 'Subsurface Color': (1.0000, 1.0000, 1.0000, 1.0000), 'Metallic': 1.0000, 'Specular': 1.0000, 'Roughness': 0.1000, 'Anisotropic': 0.9182, 'Sheen': 0.0455, 'Sheen Tint': 0.4948}, + attrs={'subsurface_method': 'BURLEY'}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) diff --git a/infinigen/assets/materials/black_plastic.py b/infinigen/assets/materials/black_plastic.py new file mode 100644 index 000000000..8a8a3a446 --- /dev/null +++ b/infinigen/assets/materials/black_plastic.py @@ -0,0 +1,22 @@ +# 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: Hongyu Wen + +from numpy.random import uniform as U +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.util.color import hsv2rgba + +# used in ceiling lights and tv + +def shader_black(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + color = hsv2rgba( + U(0.45, 0.55), + U(0, 0.1), + U(0, 1) + ) + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Base Color': color}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) diff --git a/infinigen/assets/materials/brick.py b/infinigen/assets/materials/brick.py new file mode 100644 index 000000000..20e45873c --- /dev/null +++ b/infinigen/assets/materials/brick.py @@ -0,0 +1,64 @@ +# 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: Lingjie Mei +from collections.abc import Iterable + +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.object import new_plane +from infinigen.assets.utils.uv import unwrap_faces, unwrap_normal +from infinigen.core.util.color import hsv2rgba +from infinigen.core.nodes.node_info import Nodes +from infinigen.core.nodes.node_wrangler import NodeWrangler +from infinigen.assets.materials import common +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil + + +def shader_brick(nw: NodeWrangler, height=None, **kwargs): + if height is None: + height = log_uniform(.07, .12) + uv_map = nw.new_node(Nodes.UVMap) + + front_color, back_color = [hsv2rgba(uniform(0, .05), uniform(.8, 1), log_uniform(.02, .5)) for _ in + range(2)] + mortar_color = hsv2rgba(uniform(0, .05), uniform(.2, .5), log_uniform(.02, .8)) + dark_color = hsv2rgba(uniform(0, .05), uniform(.8, 1), log_uniform(.005, .02)) + noise = nw.new_node(Nodes.NoiseTexture, [uv_map], + input_kwargs={'Scale': uniform(40, 50), 'Detail': uniform(15, 20)}) + color = nw.new_node(Nodes.BrickTexture, [uv_map, front_color, back_color, mortar_color], input_kwargs={ + 'Scale': 1, + 'Row Height': height, + 'Brick Width': height * log_uniform(1.2, 2.5), + 'Mortar Size': height * log_uniform(.04, .08), + 'Mortar Smooth': noise + }).outputs['Color'] + noise = nw.new_node(Nodes.MusgraveTexture, [uv_map], input_kwargs={'Scale': uniform(2, 5)}) + color = nw.new_node(Nodes.MixRGB, [nw.scalar_multiply(log_uniform(.5, 1.), noise), color, dark_color], + attrs={'blend_type': 'DARKEN'}) + + roughness = nw.build_float_curve(nw.new_node(Nodes.NoiseTexture, input_kwargs={'Scale': 50}), + [(0, .5), (1, 1.)]) + + offset = nw.scalar_add(nw.scalar_multiply(nw.scalar_sub(color, .5), uniform(.01, .04)), nw.scalar_multiply( + nw.new_node(Nodes.MusgraveTexture, [uv_map], input_kwargs={'Scale': 50}), uniform(.0, .01))) + bump = nw.new_node(Nodes.Bump, input_kwargs={'Height': offset}) + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={"Roughness": roughness, 'Base Color': color, 'Normal': bump + }) + nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}) + + +def apply(obj, selection=None, height=None, **kwargs): + for o in obj if isinstance(obj, Iterable) else [obj]: + unwrap_normal(o, selection, axis_='z') + common.apply(obj, shader_brick, selection, height, **kwargs) + + +def make_sphere(): + obj = new_plane() + obj.rotation_euler[0] = np.pi / 2 + butil.apply_transform(obj, True) + return obj diff --git a/infinigen/assets/materials/bumpy_rubber_floor.py b/infinigen/assets/materials/bumpy_rubber_floor.py new file mode 100644 index 000000000..ee5fa8cdb --- /dev/null +++ b/infinigen/assets/materials/bumpy_rubber_floor.py @@ -0,0 +1,176 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category, hsv2rgba +from infinigen.core import surface + +@node_utils.to_nodegroup('nodegroup_node_group', singleton=False, type='ShaderNodeTree') +def nodegroup_node_group(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketColor', 'Base Color', (0.8000, 0.8000, 0.8000, 1.0000)), + ('NodeSocketFloat', 'Scale', 1.0000), + ('NodeSocketFloat', 'Seed', 0.0000), + ('NodeSocketFloatFactor', 'Roughness', 0.4000)]) + + mapping = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Generated"], 'Scale': group_input.outputs["Scale"]}) + + reroute_1 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': mapping}) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Seed"]}) + + noise_texture_9 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': reroute_1, 'W': reroute, 'Scale': 18.0000, 'Detail': 3.0000, 'Roughness': 0.4500}, + attrs={'noise_dimensions': '4D'}) + + map_range_6 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': noise_texture_9.outputs["Fac"], 3: 0.6000, 4: 1.4000}) + + hue_saturation_value = nw.new_node(Nodes.HueSaturationValue, + input_kwargs={'Value': map_range_6.outputs["Result"], 'Color': group_input.outputs["Base Color"]}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': hue_saturation_value, 'Specular': 0.9, 'Roughness': group_input.outputs["Roughness"]}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'W': group_input.outputs["Seed"], 'Scale': 2.0000, 'Detail': 6.0000}, + attrs={'noise_dimensions': '4D'}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': noise_texture.outputs["Fac"], 'Randomness': 0.0000}, + attrs={'feature': 'DISTANCE_TO_EDGE', 'voronoi_dimensions': '4D'}) + + map_range = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': voronoi_texture.outputs["Distance"], 2: 0.0300, 3: 1.0000, 4: 0.0000}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'W': group_input.outputs["Seed"], 'Scale': 2.5000, 'Detail': 6.0000}, + attrs={'noise_dimensions': '4D'}) + + map_range_1 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': noise_texture_1.outputs["Fac"], 1: 0.5500, 2: 0.5700}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: map_range.outputs["Result"], 1: map_range_1.outputs["Result"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + noise_texture_2 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'W': group_input.outputs["Seed"], 'Scale': 10.0000, 'Detail': 15.0000, 'Distortion': 0.1000}, + attrs={'noise_dimensions': '4D'}) + + map_range_2 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': noise_texture_2.outputs["Fac"], 1: 0.6300, 2: 0.6800}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: map_range_2.outputs["Result"], 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: multiply_2}) + + value = nw.new_node(Nodes.Value) + value.outputs[0].default_value = 200.0000 + + noise_texture_3 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'W': group_input.outputs["Seed"], 'Scale': value}, + attrs={'noise_dimensions': '4D'}) + + voronoi_texture_1 = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': mapping, 'W': group_input.outputs["Seed"], 'Scale': value}, + attrs={'voronoi_dimensions': '4D'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.4000, 2: noise_texture_3.outputs["Fac"], 3: voronoi_texture_1.outputs["Distance"]}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: mix.outputs["Result"], 1: 0.1000}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: multiply_3}) + + noise_texture_4 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'W': group_input.outputs["Seed"], 'Scale': 4.0000, 'Detail': 1.0000, 'Roughness': 0.4500}, + attrs={'noise_dimensions': '4D'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: noise_texture_4.outputs["Fac"]}, attrs={'operation': 'SUBTRACT'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: 3.0000}, attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: multiply_4}) + + noise_texture_5 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'W': group_input.outputs["Seed"], 'Scale': 40.0000, 'Detail': 15.0000, 'Distortion': 0.1000}, + attrs={'noise_dimensions': '4D'}) + + map_range_3 = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': noise_texture_5.outputs["Fac"], 1: 0.6500, 2: 0.6400, 3: 1.0000, 4: 0.0000}) + + noise_texture_7 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'W': group_input.outputs["Seed"], 'Scale': 12.0000, 'Detail': 6.0000}, + attrs={'noise_dimensions': '4D'}) + + map_range_4 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': noise_texture_7.outputs["Fac"], 1: 0.5500, 2: 0.5700}) + + multiply_5 = nw.new_node(Nodes.Math, + input_kwargs={0: map_range_3.outputs["Result"], 1: map_range_4.outputs["Result"]}, + attrs={'operation': 'MULTIPLY'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_5}, attrs={'operation': 'SUBTRACT'}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: multiply_6}) + + noise_texture_6 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': reroute_1, 'W': reroute, 'Scale': 30.0000, 'Detail': 3.0000, 'Roughness': 0.4500}, + attrs={'noise_dimensions': '4D'}) + + subtract_2 = nw.new_node(Nodes.Math, input_kwargs={0: noise_texture_6.outputs["Fac"]}, attrs={'operation': 'SUBTRACT'}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_2}, attrs={'operation': 'MULTIPLY'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: multiply_7}) + + noise_texture_8 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': reroute_1, 'W': reroute, 'Scale': 20.0000, 'Detail': 3.0000, 'Roughness': 0.4500}, + attrs={'noise_dimensions': '4D'}) + + map_range_5 = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': noise_texture_8.outputs["Fac"], 1: 0.5500, 2: 0.5100, 3: -0.5000, 4: 0.5000}) + + multiply_8 = nw.new_node(Nodes.Math, input_kwargs={0: map_range_5.outputs["Result"], 1: 0.0500}, attrs={'operation': 'MULTIPLY'}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: multiply_8}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'BSDF': principled_bsdf, 'Displacement': add_5, 'tmp_viewer': principled_bsdf}, + attrs={'is_active_output': True}) + +def shader_bumpy_rubber(nw: NodeWrangler, scale=2.0, base_color=None, seed=None): + # Code generated using version 2.6.5 of the node_transpiler + if base_color is None: + base_color = hsv2rgba(uniform(0, 1), uniform(.2, .5), uniform(.4, .7)) + if seed is None: + seed = uniform(-1000.0, 1000.0) + + roughness = uniform(0.1, 0.3) + + group = nw.new_node(nodegroup_node_group().name, + input_kwargs={'Base Color': base_color, 'Scale': scale, 'Seed': seed, 'Roughness': roughness}) + + displacement = nw.new_node(Nodes.Displacement, + input_kwargs={'Height': group.outputs["Displacement"], 'Midlevel': 0.0000, 'Scale': 0.0010}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': group.outputs["tmp_viewer"], 'Displacement': displacement}, + attrs={'is_active_output': True}) + +def apply(obj, selection=None, **kwargs): + surface.add_material(obj, shader_bumpy_rubber, selection=selection) \ No newline at end of file diff --git a/infinigen/assets/materials/ceiling_light_shaders.py b/infinigen/assets/materials/ceiling_light_shaders.py new file mode 100644 index 000000000..d738d8230 --- /dev/null +++ b/infinigen/assets/materials/ceiling_light_shaders.py @@ -0,0 +1,45 @@ +# 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: Hongyu Wen + +from numpy.random import uniform as U +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.util.color import hsv2rgba + + +def shader_lamp_bulb_nonemissive(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + light_path = nw.new_node(Nodes.LightPath) + + object_info = nw.new_node(Nodes.ObjectInfo_Shader) + + white_noise_texture = nw.new_node(Nodes.WhiteNoiseTexture, + input_kwargs={'Vector': object_info.outputs["Random"]}, + attrs={'noise_dimensions': '4D'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.9000, 6: white_noise_texture.outputs["Color"], 7: (0.5000, 0.4444, 0.3669, 1.0000)}, + attrs={'data_type': 'RGBA'}) + + transparent_bsdf = nw.new_node(Nodes.TransparentBSDF, input_kwargs={'Color': mix.outputs[2]}) + + translucent_bsdf = nw.new_node(Nodes.TranslucentBSDF, input_kwargs={'Color': mix.outputs[2]}) + + mix_shader = nw.new_node(Nodes.MixShader, + input_kwargs={'Fac': light_path.outputs["Is Camera Ray"], 1: transparent_bsdf, 2: translucent_bsdf}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': mix_shader}, attrs={'is_active_output': True}) + +def shader_black(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + color = hsv2rgba( + U(0.45, 0.55), + U(0, 0.1), + U(0, 1) + ) + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Base Color': color}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) \ No newline at end of file diff --git a/infinigen/assets/materials/ceramic.py b/infinigen/assets/materials/ceramic.py new file mode 100644 index 000000000..40e63c96f --- /dev/null +++ b/infinigen/assets/materials/ceramic.py @@ -0,0 +1,44 @@ +# 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: Lingjie Mei +from numpy.random import uniform + +from infinigen.core.util.color import hsv2rgba +from infinigen.assets.materials import common +from infinigen.core.util.random import log_uniform +from infinigen.core.nodes.node_info import Nodes +from infinigen.core.nodes.node_wrangler import NodeWrangler + + +def shader_ceramic(nw: NodeWrangler, clear=False, roughness_min=0, roughness_max=.8, **kwargs): + if uniform(0, 1) < .8 and not clear: + color = hsv2rgba(uniform(0, 1), uniform(.2, .4), log_uniform(.3, .6)) + else: + color = hsv2rgba(0, 0, log_uniform(.3, .6)) + + roughness = nw.build_float_curve(nw.musgrave(log_uniform(20, 40)), [(0, roughness_min), (1, roughness_max)]) + clearcoat_roughness = nw.build_float_curve(nw.musgrave(log_uniform(20, 40)), + [(0, roughness_min), (1, roughness_max)]) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={ + "Roughness": roughness, + 'Clearcoat': 1, + 'Clearcoat Roughness': clearcoat_roughness, + 'Specular': 1, + 'Base Color': color, + 'Subsurface': uniform(.02, .05), + 'Subsurface Radius': (.02, .02, .02) + }) + + displacement = nw.new_node('ShaderNodeDisplacement', input_kwargs={ + 'Height': nw.scalar_multiply(log_uniform(.001, .005), nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Scale': log_uniform(20, 40)})), + 'Midlevel': 0.0000 + }) + + nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf, 'Displacement': displacement}) + + +def apply(obj, selection=None, clear=False, **kwargs): + common.apply(obj, shader_ceramic, selection, clear, **kwargs) diff --git a/infinigen/assets/materials/chunkyrock.py b/infinigen/assets/materials/chunkyrock.py index 9fedacc32..6d0687273 100644 --- a/infinigen/assets/materials/chunkyrock.py +++ b/infinigen/assets/materials/chunkyrock.py @@ -25,24 +25,24 @@ mod_name = "geo_rocks" name = "chunkyrock" -def shader_rocks(nw, rand=True, **input_kwargs): +def shader_rocks(nw, rand=True, random_seed=0, **input_kwargs): nw.force_input_consistency() position = nw.new_node('ShaderNodeNewGeometry') - depth = geo_rocks(nw, geometry=False) - + depth = geo_rocks(nw, random_seed=random_seed, geometry=False) + colorramp_3 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': depth}) colorramp_3.color_ramp.elements[0].position = 0.0285 colorramp_3.color_ramp.elements[0].color = (0.0, 0.0, 0.0, 1.0) colorramp_3.color_ramp.elements[1].position = 0.1347 colorramp_3.color_ramp.elements[1].color = (1.0, 1.0, 1.0, 1.0) - + mapping = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': position, 'Scale': (0.2, 0.2, 0.2)}) - + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Vector': mapping, 'Detail': 15.0}) - + rock_color1 = nw.new_node(Nodes.MixRGB, input_kwargs={'Fac': noise_texture_1.outputs["Fac"], 'Color1': (0.0, 0.0, 0.0, 1.0), 'Color2': (0.01, 0.024, 0.0283, 1.0)}) @@ -62,7 +62,7 @@ def shader_rocks(nw, rand=True, **input_kwargs): mix_1 = nw.new_node(Nodes.MixRGB, input_kwargs={'Fac': colorramp_3.outputs["Color"], 'Color1': rock_color1, 'Color2': rock_color2}) - + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Base Color': mix_1}) @@ -77,16 +77,16 @@ def geo_rocks(nw: NodeWrangler, rand=True, selection=None, random_seed=0, geomet else: position = nw.new_node(Nodes.InputPosition) normal = nw.new_node(Nodes.InputNormal) - + with FixedSeed(random_seed): # Code generated using version 2.4.3 of the node_transpiler - + noise_texture = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Vector': position}) - + mix = nw.new_node(Nodes.MixRGB, input_kwargs={'Fac': 0.8, 'Color1': noise_texture.outputs["Color"], 'Color2': position}) - + if rand: sample_max = 2 sample_min = 1/2 @@ -112,19 +112,19 @@ def geo_rocks(nw: NodeWrangler, rand=True, selection=None, random_seed=0, geomet colorramp.color_ramp.elements[1].position = sample_ratio(colorramp.color_ramp.elements[1].position, 0.5, 2) depth = colorramp - + multiply = nw.new_node(Nodes.VectorMath, input_kwargs={0: colorramp.outputs["Color"], 1: normal}, attrs={'operation': 'MULTIPLY'}) - + value = nw.new_node(Nodes.Value) value.outputs[0].default_value = 0.4 - + offset = nw.new_node(Nodes.VectorMath, input_kwargs={0: multiply.outputs["Vector"], 1: value}, attrs={'operation': 'MULTIPLY'}) - - + + if geometry: groupinput = nw.new_node(Nodes.GroupInput) noise_params = {"scale": ("uniform", 10, 20), "detail": 9, "roughness": 0.6, "zscale": ("log_uniform", 0.08, 0.12)} @@ -152,4 +152,4 @@ def apply(obj, selection=None, geo_kwargs=None, shader_kwargs=None, **kwargs): #bpy.ops.wm.save_as_mainfile(filepath=fn) bpy.context.scene.render.filepath = os.path.join('outputs', mat, '%s_%d.jpg'%(mat, i)) bpy.context.scene.render.image_settings.file_format='JPEG' - bpy.ops.render.render(write_still=True) \ No newline at end of file + bpy.ops.render.render(write_still=True) diff --git a/infinigen/assets/materials/cobble_stone.py b/infinigen/assets/materials/cobble_stone.py index c50e9d30c..a88f37b25 100644 --- a/infinigen/assets/materials/cobble_stone.py +++ b/infinigen/assets/materials/cobble_stone.py @@ -18,10 +18,10 @@ name = "cobble_stone" -def shader_cobblestone(nw: NodeWrangler): +def shader_cobblestone(nw: NodeWrangler, random_seed=0): # Code generated using version 2.4.3 of the node_transpiler, and modified nw.force_input_consistency() - stone_color = geo_cobblestone(nw, geometry=False) + stone_color = geo_cobblestone(nw, random_seed=random_seed, geometry=False) noise_texture = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Vector': nw.new_node('ShaderNodeNewGeometry'), 'Scale': N(10, 1.5) / 25, 'W': U(-5, 5)}, attrs={'noise_dimensions': '4D'}) @@ -154,4 +154,4 @@ def geo_cobblestone(nw: NodeWrangler, selection=None, random_seed=0, geometry=Tr def apply(obj, selection=None, **kwargs): surface.add_geomod(obj,geo_cobblestone, selection=selection) - surface.add_material(obj, shader_cobblestone, selection=selection, reuse=False) \ No newline at end of file + surface.add_material(obj, shader_cobblestone, selection=selection, reuse=False) diff --git a/infinigen/assets/materials/common.py b/infinigen/assets/materials/common.py new file mode 100644 index 000000000..b906c4bcf --- /dev/null +++ b/infinigen/assets/materials/common.py @@ -0,0 +1,68 @@ +# 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: Lingjie Mei +import functools +from collections.abc import Callable, Iterable + +import numpy as np + +from infinigen.assets.utils.decorate import read_material_index, write_material_index +from infinigen.core import surface +from infinigen.core.surface import read_attr_data + +from infinigen.core import tags as t, tagging +from infinigen.core.util.math import FixedSeed + + +def apply(obj, shader_func, selection=None, *args, **kwargs): + if not isinstance(obj, Iterable): + obj = [obj] + if isinstance(shader_func, Callable): + material = surface.shaderfunc_to_material(shader_func, *args, **kwargs) + else: + material = shader_func + for o in obj: + index = len(o.data.materials) + o.data.materials.append(material) + material_index = read_material_index(o) + full_like = np.full_like(material_index, index) + if selection is None: + material_index = full_like + elif isinstance(selection, t.Tag): + sel = tagging.tagged_face_mask(o, selection) + material_index = np.where(sel, index, material_index) + elif isinstance(selection, str): + try: + sel = read_attr_data(o, selection.lstrip('!'), 'FACE') + material_index = np.where(1 - sel if selection.startswith('!') else sel, index, material_index) + except: + material_index = np.zeros(len(material_index), dtype=int) + else: + material_index = np.where(selection, index, material_index) + write_material_index(o, material_index) + + +def get_selection(obj, selection): + if selection is None: + return np.ones(len(obj.data.polygons)) + elif isinstance(selection, t.Tag): + return tagging.tagged_face_mask(obj, selection) + elif isinstance(selection, str): + return read_attr_data(obj, selection.lstrip('!'), 'FACE') + else: + return selection + + +def unique_surface(surface, seed=None): + if seed is None: + seed = np.random.randint(1e7) + + class Surface: + + @classmethod + def apply(cls, *args, **kwargs): + with FixedSeed(seed): + return surface.apply(*args, **kwargs) + + return Surface diff --git a/infinigen/assets/materials/dirt.py b/infinigen/assets/materials/dirt.py index 27f872ded..452731ca7 100644 --- a/infinigen/assets/materials/dirt.py +++ b/infinigen/assets/materials/dirt.py @@ -22,9 +22,9 @@ name = "dirt" -def shader_dirt(nw): +def shader_dirt(nw, random_seed=0): nw.force_input_consistency() - dirt_base_color, dirt_roughness = geo_dirt(nw, selection=None, geometry=False) + dirt_base_color, dirt_roughness = geo_dirt(nw, selection=None, random_seed=random_seed, geometry=False) principled_bsdf = nw.new_node( Nodes.PrincipledBSDF, input_kwargs={ @@ -108,7 +108,7 @@ def geo_dirt(nw, selection=None, random_seed=0, geometry=True): # label = "color_ramp_1_VAR", attrs={'clamp': True} ) - + #nw.new_node( # Nodes.ColorRamp, input_kwargs={"Fac": voronoi_texture.outputs["Distance"]}, # label="color_ramp_1_VAR" @@ -168,30 +168,30 @@ def geo_dirt(nw, selection=None, random_seed=0, geometry=True): input_kwargs={0: vector_math_5.outputs["Vector"], 1: value_3}, attrs={"operation": "MULTIPLY"}, ) - + noise_texture_3 = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Vector': position, "W": nw.new_value(uniform(0, 10), "noise_texture_3_w"), 'Scale': sample_ratio(5, 3/4, 4/3)}, attrs={"noise_dimensions": "4D"}) - + subtract = nw.new_node(Nodes.Math, input_kwargs={0: noise_texture_3.outputs["Fac"]}, attrs={'operation': 'SUBTRACT'}) - + multiply_8 = nw.new_node(Nodes.VectorMath, input_kwargs={0: subtract, 1: normal}, attrs={'operation': 'MULTIPLY'}) - + value_5 = nw.new_node(Nodes.Value) value_5.outputs[0].default_value = 0.05 - + multiply_9 = nw.new_node(Nodes.VectorMath, input_kwargs={0: multiply_8.outputs["Vector"], 1: value_5}, attrs={'operation': 'MULTIPLY'}) - + noise_texture_4 = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Vector': position, 'Scale': sample_ratio(20, 3/4, 4/3), "W": nw.new_value(uniform(0, 10), "noise_texture_4_w")}, attrs={'noise_dimensions': '4D'}) - + colorramp_5 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture_4.outputs["Fac"]}) colorramp_5.color_ramp.elements.new(0) @@ -204,22 +204,22 @@ def geo_dirt(nw, selection=None, random_seed=0, geometry=True): colorramp_5.color_ramp.elements[2].color = (0.5, 0.5, 0.5, 1.0) colorramp_5.color_ramp.elements[3].position = 1.0 colorramp_5.color_ramp.elements[3].color = (1.0, 1.0, 1.0, 1.0) - + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: colorramp_5.outputs["Color"]}, attrs={'operation': 'SUBTRACT'}) - + multiply_10 = nw.new_node(Nodes.VectorMath, input_kwargs={0: subtract_1, 1: normal}, attrs={'operation': 'MULTIPLY'}) - + value_6 = nw.new_node(Nodes.Value) value_6.outputs[0].default_value = 0.1 - + multiply_11 = nw.new_node(Nodes.VectorMath, input_kwargs={0: multiply_10.outputs["Vector"], 1: value_6}, attrs={'operation': 'MULTIPLY'}) - + colorramp = nw.new_node( Nodes.ColorRamp, input_kwargs={"Fac": noise_texture.outputs["Fac"]} ) @@ -232,7 +232,7 @@ def geo_dirt(nw, selection=None, random_seed=0, geometry=True): colorramp.color_ramp.elements[2].color = (0.19, 0.03, 0.02, 1.0) sample_color(colorramp.color_ramp.elements[1].color, offset=0.05) sample_color(colorramp.color_ramp.elements[2].color, offset=0.05) - + dirt_base_color = nw.new_node( Nodes.MixRGB, input_kwargs={ @@ -249,12 +249,12 @@ def geo_dirt(nw, selection=None, random_seed=0, geometry=True): colorramp_3.color_ramp.elements[0].color = (0.0, 0.0, 0.0, 1.0) colorramp_3.color_ramp.elements[1].position = 0.768 colorramp_3.color_ramp.elements[1].color = (1.0, 1.0, 1.0, 1.0) - + dirt_roughness = colorramp_3 offset = nw.add(multiply_11, multiply_9, vector_math_8) - if geometry: + if geometry: noise_params = {"scale": ("uniform", 1, 5), "detail": 7, "roughness": 0.7, "zscale": ("power_uniform", -1, -0.5)} offset = nw.add( geo_MOUNTAIN_general(nw, 3, noise_params, 0, {}, {}), @@ -290,4 +290,3 @@ def apply(obj, selection=None, **kwargs): bpy.context.scene.render.image_settings.file_format='JPEG' bpy.ops.render.render(write_still=True) bpy.ops.wm.save_as_mainfile(filepath=os.path.join('outputs', mat, 'landscape_surface_dev_dirt.blend')) - \ No newline at end of file diff --git a/infinigen/assets/materials/dishwasher_shaders.py b/infinigen/assets/materials/dishwasher_shaders.py new file mode 100644 index 000000000..8f2c5f7f8 --- /dev/null +++ b/infinigen/assets/materials/dishwasher_shaders.py @@ -0,0 +1,85 @@ +# 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: Hongyu Wen + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler + +def default_shader(nw: NodeWrangler): + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF) + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) + +def shader_black_medal_002(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + anisotropic_bsdf = nw.new_node('ShaderNodeBsdfAnisotropic', input_kwargs={'Color': (0.0167, 0.0167, 0.0167, 1.0000)}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': anisotropic_bsdf}, attrs={'is_active_output': True}) + + +def shader_glass_002(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + glass_bsdf = nw.new_node(Nodes.GlassBSDF, input_kwargs={'IOR': 1.5000}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': glass_bsdf}, attrs={'is_active_output': True}) + + +def shader_metal_002(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + mapping = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'Scale': (1.0000, 1.0000, 80.0000)}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'Scale': 10.0000, 'Detail': 20.0000, 'Roughness': 0.0000, 'Distortion': 1.0000}) + + colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture_1.outputs["Color"]}) + colorramp.color_ramp.elements[0].position = 0.0045 + colorramp.color_ramp.elements[0].color = [0.2218, 0.1914, 0.2173, 1.0000] + colorramp.color_ramp.elements[1].position = 0.4432 + colorramp.color_ramp.elements[1].color = [0.1678, 0.1300, 0.0929, 1.0000] + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'Scale': 2.0000, 'Detail': 0.0000}) + + colorramp_1 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture.outputs["Color"]}) + colorramp_1.color_ramp.elements.new(0) + colorramp_1.color_ramp.elements[0].position = 0.0000 + colorramp_1.color_ramp.elements[0].color = [0.5000, 0.5000, 0.5000, 1.0000] + colorramp_1.color_ramp.elements[1].position = 0.5000 + colorramp_1.color_ramp.elements[1].color = [0.3433, 0.3062, 0.1380, 1.0000] + colorramp_1.color_ramp.elements[2].position = 1.0000 + colorramp_1.color_ramp.elements[2].color = [1.0000, 1.0000, 1.0000, 1.0000] + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': colorramp.outputs["Color"], 'Subsurface Color': (0.9456, 0.5597, 0.0681, 1.0000), 'Metallic': 1.0000, 'Roughness': colorramp_1.outputs["Color"]}, + attrs={'subsurface_method': 'BURLEY'}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) + + +def shader_white_metal_002(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + mapping = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'Scale': (1.0000, 1.0000, 50.0000)}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'Scale': 20.0000, 'Detail': 20.0000, 'Distortion': 1.0000}) + + colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture_1.outputs["Color"]}) + colorramp.color_ramp.elements[0].position = 0.2500 + colorramp.color_ramp.elements[0].color = [0.5244, 0.5244, 0.5244, 1.0000] + colorramp.color_ramp.elements[1].position = 1.0000 + colorramp.color_ramp.elements[1].color = [0.9698, 0.9698, 0.9698, 1.0000] + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': colorramp.outputs["Color"], 'Subsurface Color': (1.0000, 1.0000, 1.0000, 1.0000), 'Metallic': 1.0000, 'Specular': 1.0000, 'Roughness': 0.1000, 'Anisotropic': 0.9182, 'Sheen': 0.0455, 'Sheen Tint': 0.4948}, + attrs={'subsurface_method': 'BURLEY'}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) diff --git a/infinigen/assets/materials/fabrics.py b/infinigen/assets/materials/fabrics.py new file mode 100644 index 000000000..feab191bb --- /dev/null +++ b/infinigen/assets/materials/fabrics.py @@ -0,0 +1,10 @@ +# 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: Lingjie Mei +from infinigen.assets.materials import leather_and_fabrics +from .leather_and_fabrics import * + + +def apply(obj, selection=None, **kwargs): + leather_and_fabrics.apply(obj, selection=selection, **kwargs) diff --git a/infinigen/assets/materials/glass.py b/infinigen/assets/materials/glass.py new file mode 100644 index 000000000..be4c28cbb --- /dev/null +++ b/infinigen/assets/materials/glass.py @@ -0,0 +1,48 @@ +# 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: Lingjie Mei +import colorsys + +from numpy.random import uniform + +import bpy + +from infinigen.core.util.color import hsv2rgba +from infinigen.assets.materials import common +from infinigen.core.nodes.node_info import Nodes +from infinigen.core.nodes.node_wrangler import NodeWrangler + +from infinigen.core.util import blender as butil + +def shader_glass(nw: NodeWrangler, color=None, is_window=False, **kwargs): + # Code generated using version 2.6.5 of the node_transpiler + if color is None: + color = get_glass_color(clear=False) + + # TODO windows are currently planes so refract and dont unrefract. ideally we just fix the geometry + # warning: currently this IOR also accidentally just turns off reflections, the window plane is pretty much invisible. + ior = 1.5 if not is_window else 1.0 + + light_path = nw.new_node(Nodes.LightPath) + + transparent_bsdf = nw.new_node(Nodes.TransparentBSDF) + + shader = nw.new_node(Nodes.GlassBSDF, input_kwargs={'Roughness': 0.0200, 'IOR': ior}) + + if is_window: + shader = nw.new_node(Nodes.MixShader, + input_kwargs={'Fac': light_path.outputs["Is Camera Ray"], 1: transparent_bsdf, 2: shader}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': shader}, attrs={'is_active_output': True}) + +def apply(obj, selection=None, clear=False, **kwargs): + color = get_glass_color(clear) + common.apply(obj, shader_glass, selection, color, **kwargs) + +def get_glass_color(clear): + if uniform(0, 1) < .5: + color = 1, 1, 1, 1 + else: + color = hsv2rgba(uniform(0, 1), .01 if clear else uniform(.05, .25), 1) + return color diff --git a/infinigen/assets/materials/glass_volume.py b/infinigen/assets/materials/glass_volume.py new file mode 100644 index 000000000..84fdc059b --- /dev/null +++ b/infinigen/assets/materials/glass_volume.py @@ -0,0 +1,31 @@ +# 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: Yiming Zuo, Lingjie Mei + +from numpy.random import uniform + +from infinigen.core.util.color import hsv2rgba +from infinigen.assets.materials import common +from infinigen.core.nodes.node_info import Nodes +from infinigen.core.nodes.node_wrangler import NodeWrangler + +def shader_glass_volume(nw: NodeWrangler, color=None, density=100.0, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + if color is None: + if uniform(0, 1) < .3: + color = 1, 1, 1, 1 + else: + color = hsv2rgba(uniform(0, 1), uniform(.5, .9), uniform(.6, .9)) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Roughness': 0.0000, 'Transmission': 1.0000}) + + volume_absorption = nw.new_node('ShaderNodeVolumeAbsorption', + input_kwargs={'Color': color, 'Density': density}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': principled_bsdf, 'Volume': volume_absorption}, + attrs={'is_active_output': True}) + +def apply(obj, selection=None, **kwargs): + common.apply(obj, shader_glass_volume, selection, **kwargs) diff --git a/infinigen/assets/materials/hardwood_floor.py b/infinigen/assets/materials/hardwood_floor.py new file mode 100644 index 000000000..34ca16bb5 --- /dev/null +++ b/infinigen/assets/materials/hardwood_floor.py @@ -0,0 +1,46 @@ +# 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: Lingjie Mei +import numpy as np +from numpy.random import uniform + +from . import common +from .utils.surface_utils import perturb_coordinates +from .table_materials import shader_wood +from infinigen.assets.utils.object import new_plane +from ...core.nodes import NodeWrangler, Nodes +from ...core.util.random import log_uniform + + +def shader_hardwood_floor(nw: NodeWrangler, rotation=None): + vec = nw.new_node(Nodes.Mapping, [nw.new_node(Nodes.TextureCoord).outputs["Object"]], + input_kwargs={'Rotation': rotation}) + color, mortar = map( + nw.new_node(Nodes.BrickTexture, [vec, (0, 0, 0, 1), (1, 1, 1, 1), (0, 0, 0, uniform(.01, .02))], + input_kwargs={ + 'Scale': 1, + 'Row Height': log_uniform(.06, .15), + 'Brick Width': log_uniform(.6, 1), + 'Mortar Size': uniform(.002, .002) + }).outputs.get, ['Color', 'Fac']) + location = nw.combine(color, color, color) + shader_wood(nw) + perturb_coordinates(nw, nw.find(Nodes.TextureCoord)[1], location, 0) + principled_bsdf = nw.find(Nodes.PrincipledBSDF)[0] + wood_color = nw.find_from(principled_bsdf.inputs[0])[0].from_socket + color = nw.new_node(Nodes.MixRGB, [mortar, wood_color, color]) + nw.links.remove(nw.find_from(principled_bsdf.inputs[0])[0]) + nw.connect_input(principled_bsdf.inputs[0], color) + + +def apply(obj, selection=None, rotation=None, **kwargs): + if rotation is None: + rotation = (0,0,0) if uniform() < .1 else (0,0,np.pi / 2) + return common.apply(obj, shader_hardwood_floor, selection, rotation, **kwargs) + + +def make_sphere(): + obj = new_plane() + obj.rotation_euler[0] = np.pi / 2 + return obj diff --git a/infinigen/assets/materials/invisible_to_camera.py b/infinigen/assets/materials/invisible_to_camera.py new file mode 100644 index 000000000..3c3e1723e --- /dev/null +++ b/infinigen/assets/materials/invisible_to_camera.py @@ -0,0 +1,37 @@ +# 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 bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +def shader_invisible(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + light_path = nw.new_node(Nodes.LightPath) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Roughness': 0.7697}) + + transparent_bsdf = nw.new_node(Nodes.TransparentBSDF) + + mix_shader = nw.new_node(Nodes.MixShader, + input_kwargs={'Fac': light_path.outputs["Is Camera Ray"], 1: principled_bsdf, 2: transparent_bsdf}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': mix_shader}, attrs={'is_active_output': True}) + +def apply(obj, selection=None, **kwargs): + + if not isinstance(obj, list): + obj = [obj] + + for o in obj: + for i in range(len(o.material_slots)): + bpy.ops.object.material_slot_remove({'object': o}) + surface.add_material(obj, shader_invisible, selection=selection) \ No newline at end of file diff --git a/infinigen/assets/materials/lamp_shaders.py b/infinigen/assets/materials/lamp_shaders.py new file mode 100644 index 000000000..55f437b78 --- /dev/null +++ b/infinigen/assets/materials/lamp_shaders.py @@ -0,0 +1,59 @@ +# 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: Hongyu Wen + +from numpy.random import uniform as U, normal as N, randint as RI +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler + +def shader_metal(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + anisotropic_bsdf = nw.new_node('ShaderNodeBsdfAnisotropic', + input_kwargs={'Color': (0.3224, 0.3224, 0.3224, 1.0000), 'Roughness': 0.1000}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': anisotropic_bsdf}, attrs={'is_active_output': True}) + +def shader_lampshade(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + light_path = nw.new_node(Nodes.LightPath) + + object_info = nw.new_node(Nodes.ObjectInfo_Shader) + + white_noise_texture = nw.new_node(Nodes.WhiteNoiseTexture, + input_kwargs={'Vector': object_info.outputs["Random"]}, + attrs={'noise_dimensions': '4D'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.9000, 6: white_noise_texture.outputs["Color"], 7: (0.5000, 0.4444, 0.3669, 1.0000)}, + attrs={'data_type': 'RGBA'}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={ + 'Base Color': mix.outputs[2], + 'Subsurface': U(0.03, 0.08), + 'Subsurface Radius': (0.1000, 0.1000, 0.1000), + 'Subsurface IOR': 1.6029, + 'Roughness': U(0.5, 0.8), + 'IOR': 4.0000, + 'Transmission': U(0.05, 0.2), + 'Transmission Roughness': 1.0000 + } + ) + + translucent_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Base Color': mix.outputs[2], 'Roughness': 0.7, }) + + + mix_shader = nw.new_node(Nodes.MixShader, + input_kwargs={'Fac': light_path.outputs["Is Camera Ray"], 1: principled_bsdf, 2: translucent_bsdf}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': mix_shader}, attrs={'is_active_output': True}) + + +def shader_black(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Base Color': (0.0039, 0.0039, 0.0039, 1.0000)}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) diff --git a/infinigen/assets/materials/leather_and_fabrics/__init__.py b/infinigen/assets/materials/leather_and_fabrics/__init__.py new file mode 100644 index 000000000..751f12282 --- /dev/null +++ b/infinigen/assets/materials/leather_and_fabrics/__init__.py @@ -0,0 +1,22 @@ +# 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: Lingjie Mei +from .general_fabric import shader_fabric +from .lined_fabric import shader_lined_fur_base +from .coarse_knit_fabric import shader_fabric_random as shader_coarse_fabric_random +from .fine_knit_fabric import shader_fabric_random as shader_fine_fabric_random +from .leather import shader_leather +from .sofa_fabric import shader_sofa_fabric + +from infinigen.core.util.random import random_general as rg +from .. import common +from ...utils.uv import unwrap_faces + +fabric_shader_list = 'weighted_choice', (1, shader_coarse_fabric_random), (1, shader_fine_fabric_random), \ + (2, shader_leather), (1, shader_sofa_fabric), # (1, shader_fabric), + + +def apply(obj, selection=None, **kwargs): + unwrap_faces(obj, selection) + common.apply(obj, rg(fabric_shader_list), selection=selection, **kwargs) diff --git a/infinigen/assets/materials/leather_and_fabrics/coarse_knit_fabric.py b/infinigen/assets/materials/leather_and_fabrics/coarse_knit_fabric.py new file mode 100644 index 000000000..0ac7179b0 --- /dev/null +++ b/infinigen/assets/materials/leather_and_fabrics/coarse_knit_fabric.py @@ -0,0 +1,270 @@ +# 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: Meenal Parakh +# Acknowledgement: This file draws inspiration from following sources: + +# https://www.youtube.com/watch?v=DfoMWLQ-BkM by 5 Minutes Blender +# https://www.youtube.com/watch?v=tS_U3twxKKg by PIXXO 3D +# https://www.youtube.com/watch?v=OCay8AsVD84 by Antonio Palladino +# https://www.youtube.com/watch?v=5dS3N90wPkc by Dr Blender +# https://www.youtube.com/watch?v=12c1J6LhK4Y by blenderian +# https://www.youtube.com/watch?v=kVvOk_7PoUE by Blender Box +# https://www.youtube.com/watch?v=WTK7E443l1E by blenderbitesize +# https://www.youtube.com/watch?v=umrARvXC_MI by Ryan King Art + + +import bpy +import mathutils +from numpy.random import uniform, normal, choice + +from infinigen.assets.utils.uv import unwrap_faces +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +from infinigen.assets.materials import common + + +def get_texture_params(): + return { + "_pattern_mixer": choice([uniform(0.0, 0.75), uniform(0.75, 1.0)]), + "_pattern_density": choice([uniform(0.1, 1.0), uniform(1.0, 10.0)]), + "_color": uniform(0.0, 1.0, 3), + "_brick_knit": choice( + [uniform(0.0, 0.05), uniform(0.05, 0.95), uniform(0.95, 1.0)] + ), + "_knit_resolution": uniform(0.5, 3.0), + "_brick_resolution": uniform(10.0, 30.0), + "_crease_resolution": uniform(10.0, 80.0), + "_smoothness": choice([uniform(0.0, 0.2), uniform(0.2, 1.0)]), + "_color_shader_frac": uniform(0.1, 0.9), + } + + +def shader_fabric_base( + nw: NodeWrangler, + _pattern_mixer=1.0, + _pattern_density=0.15, + _color=[5, 10.0, 10.0], + _brick_knit=0.0, + _knit_resolution=3.0, + _brick_resolution=40, + _crease_resolution=200, + _smoothness=0.7, + _color_shader_frac=0.01, +): + # Code generated using version 2.6.5 of the node_transpiler + + pattern_mixer = nw.new_node(Nodes.Value) + pattern_mixer.outputs[0].default_value = _pattern_mixer + + pattern_density = nw.new_node(Nodes.Value) + pattern_density.outputs[0].default_value = _pattern_density + + color_r = nw.new_node(Nodes.Value) + color_r.outputs[0].default_value = _color[0] + + color_g = nw.new_node(Nodes.Value) + color_g.outputs[0].default_value = _color[1] + + color_b = nw.new_node(Nodes.Value) + color_b.outputs[0].default_value = _color[2] + + brick_knit = nw.new_node(Nodes.Value) + brick_knit.outputs[0].default_value = _brick_knit + + knit_resolution = nw.new_node(Nodes.Value) + knit_resolution.outputs[0].default_value = _knit_resolution + + brick_resolution = nw.new_node(Nodes.Value) + brick_resolution.outputs[0].default_value = _brick_resolution + + crease_resolution = nw.new_node(Nodes.Value) + crease_resolution.outputs[0].default_value = _crease_resolution + + smoothness = nw.new_node(Nodes.Value) + smoothness.outputs[0].default_value = _smoothness + + color_shader_frac = nw.new_node(Nodes.Value) + color_shader_frac.outputs[0].default_value = _color_shader_frac + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + mapping_1 = nw.new_node( + Nodes.Mapping, + input_kwargs={ + "Vector": texture_coordinate.outputs["Object"], + "Scale": (3.2000, 1.0000, 1.0000), + }, + ) + + brick_texture = nw.new_node( + Nodes.BrickTexture, + input_kwargs={"Vector": mapping_1, "Scale": brick_resolution}, + ) + + color_ramp_1 = nw.new_node( + Nodes.ColorRamp, input_kwargs={"Fac": brick_texture.outputs["Color"]} + ) + color_ramp_1.color_ramp.elements[0].position = 0.0000 + color_ramp_1.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + color_ramp_1.color_ramp.elements[1].position = 1.0000 + color_ramp_1.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + mapping = nw.new_node( + Nodes.Mapping, + input_kwargs={ + "Vector": texture_coordinate.outputs["Object"], + "Rotation": (0.0000, 0.0000, 0.7854), + "Scale": (238.8000, 1.0000, 35.6000), + }, + ) + + voronoi_texture = nw.new_node( + Nodes.VoronoiTexture, + input_kwargs={ + "Vector": mapping, + "Scale": pattern_density, + "Randomness": 0.0000, + }, + attrs={"feature": "F2"}, + ) + + color_ramp = nw.new_node( + Nodes.ColorRamp, input_kwargs={"Fac": voronoi_texture.outputs["Distance"]} + ) + color_ramp.color_ramp.elements[0].position = 0.1018 + color_ramp.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + color_ramp.color_ramp.elements[1].position = 1.0000 + color_ramp.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + mix = nw.new_node( + Nodes.Mix, + input_kwargs={ + 0: pattern_mixer, + 6: color_ramp_1.outputs["Color"], + 7: color_ramp.outputs["Color"], + }, + attrs={"clamp_result": True, "data_type": "RGBA"}, + ) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={"Input": mix.outputs[2]}) + + principled_bsdf = nw.new_node( + Nodes.PrincipledBSDF, + input_kwargs={"Base Color": reroute, "Specular": 0.6309, "Roughness": 0.9945}, + ) + + combine_color = nw.new_node( + Nodes.CombineColor, + input_kwargs={"Red": color_r, "Green": color_g, "Blue": color_b}, + ) + + principled_bsdf_1 = nw.new_node( + Nodes.PrincipledBSDF, + input_kwargs={ + "Base Color": combine_color, + "Specular": 0.6309, + "Roughness": 0.9945, + }, + ) + + mix_shader = nw.new_node( + Nodes.MixShader, + input_kwargs={ + "Fac": color_shader_frac, + 1: principled_bsdf, + 2: principled_bsdf_1, + }, + ) + + # bump_1 = nw.new_node(Nodes.Bump, input_kwargs={'Height': color_ramp_1.outputs["Color"]}) + + scale = nw.new_node( + Nodes.VectorMath, + input_kwargs={0: color_ramp_1.outputs["Color"], "Scale": brick_knit}, + attrs={"operation": "SCALE"}, + ) + + mapping_2 = nw.new_node( + Nodes.Mapping, + input_kwargs={ + "Vector": texture_coordinate.outputs["Object"], + "Rotation": (0.0000, 0.0000, 0.6196), + "Scale": (217.5000, 176.2000, 42.0000), + }, + ) + + voronoi_texture_1 = nw.new_node( + Nodes.VoronoiTexture, + input_kwargs={ + "Vector": mapping_2, + "Scale": knit_resolution, + "Randomness": 0.0000, + }, + attrs={"feature": "F2"}, + ) + + mapping_3 = nw.new_node( + Nodes.Mapping, input_kwargs={"Vector": texture_coordinate.outputs["Object"]} + ) + + noise_texture = nw.new_node( + Nodes.NoiseTexture, + input_kwargs={"Vector": mapping_3, "Scale": crease_resolution}, + ) + + add = nw.new_node( + Nodes.Math, + input_kwargs={0: noise_texture.outputs["Fac"], 1: smoothness}, + attrs={"use_clamp": True}, + ) + + multiply = nw.new_node( + Nodes.Math, + input_kwargs={0: voronoi_texture_1.outputs["Distance"], 1: add}, + attrs={"use_clamp": True, "operation": "MULTIPLY"}, + ) + + # bump = nw.new_node(Nodes.Bump, input_kwargs={'Height': multiply}) + + subtract = nw.new_node( + Nodes.Math, + input_kwargs={0: 1.0000, 1: brick_knit}, + attrs={"operation": "SUBTRACT"}, + ) + + scale_1 = nw.new_node( + Nodes.VectorMath, + input_kwargs={0: multiply, "Scale": subtract}, + attrs={"operation": "SCALE"}, + ) + + # scale_1 = nw.new_node(Nodes.VectorMath, input_kwargs={0: bump, 'Scale': subtract}, attrs={'operation': 'SCALE'}) + + add_1 = nw.new_node( + Nodes.VectorMath, + input_kwargs={0: scale.outputs["Vector"], 1: scale_1.outputs["Vector"]}, + ) + + vector_displacement = nw.new_node( + "ShaderNodeVectorDisplacement", input_kwargs={"Vector": add_1.outputs["Vector"]} + ) + + material_output = nw.new_node( + Nodes.MaterialOutput, + input_kwargs={"Surface": mix_shader, "Displacement": vector_displacement}, + attrs={"is_active_output": True}, + ) + + +def shader_fabric_random(nw: NodeWrangler, **kwargs): + fabric_params = get_texture_params() + return shader_fabric_base(nw, **fabric_params) + + +def apply(obj, selection=None, **kwargs): + unwrap_faces(obj, selection) + common.apply(obj, shader_fabric_random, selection, **kwargs) diff --git a/infinigen/assets/materials/leather_and_fabrics/fine_knit_fabric.py b/infinigen/assets/materials/leather_and_fabrics/fine_knit_fabric.py new file mode 100644 index 000000000..d3d8f2dcf --- /dev/null +++ b/infinigen/assets/materials/leather_and_fabrics/fine_knit_fabric.py @@ -0,0 +1,154 @@ +# 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: Meenal Parakh +# Acknowledgement: This file draws inspiration from following sources: + +# https://www.youtube.com/watch?v=DfoMWLQ-BkM by 5 Minutes Blender +# https://www.youtube.com/watch?v=tS_U3twxKKg by PIXXO 3D +# https://www.youtube.com/watch?v=OCay8AsVD84 by Antonio Palladino +# https://www.youtube.com/watch?v=5dS3N90wPkc by Dr Blender +# https://www.youtube.com/watch?v=12c1J6LhK4Y by blenderian +# https://www.youtube.com/watch?v=kVvOk_7PoUE by Blender Box +# https://www.youtube.com/watch?v=WTK7E443l1E by blenderbitesize +# https://www.youtube.com/watch?v=umrARvXC_MI by Ryan King Art + + +import bpy +from numpy.random import uniform + +from infinigen.assets.utils.uv import unwrap_faces +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.assets.materials import common + + +def get_texture_params(): + return { + "_color": uniform(0.2, 1.0, 3), + "_roughness": uniform(0, 1.0), + "_thread_density_x": uniform(100, 300), + "_relative_density_y": uniform(0.75, 1.33), + } + + +def shader_material( + nw: NodeWrangler, + _color=[0.2, 0.2, 0.2], + _roughness=0.4, + _thread_density_x=200, + _relative_density_y=1.0, + _map="UV", +): + red = nw.new_node(Nodes.Value, label="red") + red.outputs[0].default_value = _color[0] + + green = nw.new_node(Nodes.Value, label="green") + green.outputs[0].default_value = _color[1] + + blue = nw.new_node(Nodes.Value, label="blue") + blue.outputs[0].default_value = _color[2] + + roughness = nw.new_node(Nodes.Value, label="roughness") + roughness.outputs[0].default_value = _roughness + + thread_density_x = nw.new_node(Nodes.Value, label="thread_density_x") + thread_density_x.outputs[0].default_value = _thread_density_x + + relative_density_y = nw.new_node(Nodes.Value, label="relative_density_y") + relative_density_y.outputs[0].default_value = _relative_density_y + + combine_color = nw.new_node( + Nodes.CombineColor, input_kwargs={"Red": red, "Green": green, "Blue": blue} + ) + principled_bsdf = nw.new_node( + Nodes.PrincipledBSDF, + input_kwargs={"Base Color": combine_color, "Roughness": roughness}, + ) + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + reroute = nw.new_node( + Nodes.Reroute, input_kwargs={"Input": texture_coordinate.outputs[_map]} + ) + + multiply = nw.new_node( + Nodes.Math, + input_kwargs={0: thread_density_x, 1: relative_density_y}, + attrs={"operation": "MULTIPLY"}, + ) + + wave_texture_1 = nw.new_node( + Nodes.WaveTexture, + input_kwargs={ + "Vector": reroute, + "Scale": multiply, + "Distortion": 5.0000, + "Detail": 6.1000, + }, + attrs={"bands_direction": "Y"}, + ) + + principled_bsdf_1 = nw.new_node( + Nodes.PrincipledBSDF, + input_kwargs={ + "Base Color": wave_texture_1.outputs["Color"], + "Subsurface Color": (0.0000, 0.0000, 0.0000, 1.0000), + "Roughness": roughness, + }, + ) + + mix_shader = nw.new_node( + Nodes.MixShader, + input_kwargs={"Fac": 0.1333, 1: principled_bsdf, 2: principled_bsdf_1}, + ) + + wave_texture = nw.new_node( + Nodes.WaveTexture, + input_kwargs={ + "Vector": reroute, + "Scale": thread_density_x, + "Distortion": 3.8000, + "Detail": 6.1000, + }, + ) + + color_ramp = nw.new_node( + Nodes.ColorRamp, input_kwargs={"Fac": wave_texture.outputs["Color"]} + ) + color_ramp.color_ramp.elements[0].position = 0.8109 + color_ramp.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + color_ramp.color_ramp.elements[1].position = 1.0000 + color_ramp.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + invert_color = nw.new_node( + Nodes.Invert, input_kwargs={"Fac": 0.8400, "Color": color_ramp.outputs["Color"]} + ) + + color_ramp_1 = nw.new_node( + Nodes.ColorRamp, input_kwargs={"Fac": wave_texture_1.outputs["Color"]} + ) + color_ramp_1.color_ramp.elements[0].position = 0.0727 + color_ramp_1.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + color_ramp_1.color_ramp.elements[1].position = 0.8655 + color_ramp_1.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + add = nw.new_node( + Nodes.Math, input_kwargs={0: invert_color, 1: color_ramp_1.outputs["Color"]} + ) + + material_output = nw.new_node( + Nodes.MaterialOutput, + input_kwargs={"Surface": mix_shader, "Displacement": add}, + attrs={"is_active_output": True}, + ) + + +def shader_fabric_random(nw: NodeWrangler, **kwargs): + fabric_params = get_texture_params() + fabric_params["_map"] = "Object" + return shader_material(nw, **fabric_params) + + +def apply(obj, selection=None, **kwargs): + unwrap_faces(obj, selection) + common.apply(obj, shader_fabric_random, selection, **kwargs) diff --git a/infinigen/assets/materials/leather_and_fabrics/general_fabric.py b/infinigen/assets/materials/leather_and_fabrics/general_fabric.py new file mode 100644 index 000000000..33e1e987e --- /dev/null +++ b/infinigen/assets/materials/leather_and_fabrics/general_fabric.py @@ -0,0 +1,170 @@ +# 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: Yiming Zuo +# Acknowledgement: This file draws inspiration https://www.youtube.com/watch?v=umrARvXC_MI by Ryan King Art + + +from infinigen.assets.materials import common + +import bpy +import bpy +import mathutils +import numpy as np +from numpy.random import uniform, normal, randint + +from infinigen.assets.utils.uv import ensure_uv, unwrap_faces +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + + +def func_fabric(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + group_input = { + 'Weave Scale': 0., + 'Color Pattern Scale': 0., + 'Color1': (0.7991, 0.1046, 0.1195, 1.0000), + 'Color2': (1.0000, 0.5271, 0.5711, 1.0000) + } + group_input.update(kwargs) + + wave_texture_1 = nw.new_node(Nodes.WaveTexture, input_kwargs={ + 'Vector': texture_coordinate.outputs["UV"], + 'Scale': group_input["Weave Scale"], + 'Distortion': 7.0000, + 'Detail': 15.0000 + }, attrs={'bands_direction': 'Y'}) + + value_2 = nw.new_node(Nodes.Value) + value_2.outputs[0].default_value = 0.1000 + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': wave_texture_1.outputs["Color"], 1: value_2}) + + wave_texture = nw.new_node(Nodes.WaveTexture, input_kwargs={ + 'Vector': texture_coordinate.outputs["UV"], + 'Scale': group_input["Weave Scale"], + 'Distortion': 7.0000, + 'Detail': 15.0000 + }) + + map_range_1 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': wave_texture.outputs["Color"], 1: value_2}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={6: map_range.outputs["Result"], 7: map_range_1.outputs["Result"]}, + attrs={'data_type': 'RGBA'}) + + greater_than = nw.new_node(Nodes.Math, input_kwargs={0: mix.outputs[2], 1: 0.1000}, + attrs={'operation': 'GREATER_THAN'}) + + transparent_bsdf = nw.new_node(Nodes.TransparentBSDF) + + less_than = nw.new_node(Nodes.Math, input_kwargs={0: group_input["Color Pattern Scale"], 1: 0.0001}, + attrs={'operation': 'LESS_THAN'}) + + brick_texture_2 = nw.new_node(Nodes.BrickTexture, input_kwargs={ + 'Vector': texture_coordinate.outputs["UV"], + 'Color1': group_input["Color1"], + 'Mortar': group_input["Color2"], + 'Scale': group_input["Color Pattern Scale"], + 'Mortar Size': 0.0000, + 'Bias': -1.0000, + 'Row Height': 0.5000 + }, attrs={'offset_frequency': 1, 'squash': 0.0000}) + + vector_rotate = nw.new_node(Nodes.VectorRotate, input_kwargs={ + 'Vector': texture_coordinate.outputs["UV"], + 'Rotation': (0.0000, 0.0000, 1.5708) + }, attrs={'rotation_type': 'EULER_XYZ'}) + + brick_texture = nw.new_node(Nodes.BrickTexture, input_kwargs={ + 'Vector': vector_rotate, + 'Color1': group_input["Color1"], + 'Mortar': group_input["Color2"], + 'Scale': group_input["Color Pattern Scale"], + 'Mortar Size': 0.0000, + 'Bias': -1.0000, + 'Row Height': 0.5000 + }, attrs={'offset_frequency': 1, 'squash': 0.0000}) + + mix_2 = nw.new_node(Nodes.Mix, input_kwargs={ + 0: 1.0000, + 6: brick_texture_2.outputs["Color"], + 7: brick_texture.outputs["Color"] + }, attrs={'data_type': 'RGBA', 'blend_type': 'ADD'}) + + mix_4 = nw.new_node(Nodes.Mix, input_kwargs={0: less_than, 6: mix_2.outputs[2], 7: group_input["Color1"]}, + attrs={'data_type': 'RGBA'}) + + mix_3 = nw.new_node(Nodes.Mix, input_kwargs={ + 0: mix.outputs[2], + 6: (0.0000, 0.0000, 0.0000, 1.0000), + 7: mix_4.outputs[2] + }, attrs={'data_type': 'RGBA'}) + + map_range_2 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': mix.outputs[2], 3: 1.0000, 4: 0.9000}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={ + 'Base Color': mix_3.outputs[2], + 'Roughness': map_range_2.outputs["Result"], + 'Sheen': 1.0000, + 'Sheen Tint': 1.0000 + }) + + mix_shader = nw.new_node(Nodes.MixShader, + input_kwargs={'Fac': greater_than, 1: transparent_bsdf, 2: principled_bsdf}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input["Weave Scale"], 1: 5.0000}, + attrs={'operation': 'MULTIPLY'}) + + musgrave_texture = nw.new_node(Nodes.MusgraveTexture, input_kwargs={'Scale': multiply}) + + mix_1 = nw.new_node(Nodes.Mix, input_kwargs={6: musgrave_texture, 7: mix.outputs[2]}, + attrs={'data_type': 'RGBA'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: mix_1.outputs[2]}, attrs={'operation': 'SUBTRACT'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: 0.0010}, attrs={'operation': 'MULTIPLY'}) + + displacement = nw.new_node( + 'ShaderNodeDisplacement', input_kwargs={'Height': multiply_1, 'Midlevel': 0.0000} + ) + + return {'Shader': mix_shader, 'Displacement': displacement} + + +def shader_fabric(nw: NodeWrangler, weave_scale=500.0, color_scale=None, color_1=None, color_2=None, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + if color_scale is None: + color_scale = np.random.choice([0.0, uniform(5., 20.)]) + if color_1 is None: + color_1 = color_category('fabric') + if color_2 is None: + color_2 = color_category('white') + + group = func_fabric(nw, **{ + 'Weave Scale': weave_scale, + 'Color Pattern Scale': color_scale, + 'Color1': color_1, + 'Color2': color_2 + }) + + displacement = nw.new_node('ShaderNodeDisplacement', + input_kwargs={'Height': group["Displacement"], 'Midlevel': 0.0000}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': group["Shader"], 'Displacement': displacement + }, attrs={'is_active_output': True}) + + +def apply(obj, selection=None, **kwargs): + if not isinstance(obj, list): + obj = [obj] + for o in obj: + unwrap_faces(o, selection) + common.apply(obj, shader_fabric, selection, **kwargs) diff --git a/infinigen/assets/materials/leather_and_fabrics/leather.py b/infinigen/assets/materials/leather_and_fabrics/leather.py new file mode 100644 index 000000000..808f4a950 --- /dev/null +++ b/infinigen/assets/materials/leather_and_fabrics/leather.py @@ -0,0 +1,110 @@ +# 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: Yiming Zuo +# Acknowledgement: This file draws inspiration https://www.youtube.com/watch?v=In9V4-ih16o by Ryan King Art + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +import functools + +from infinigen.assets.materials import common +from infinigen.assets.utils.uv import unwrap_faces +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category, hsv2rgba +from infinigen.core import surface +from infinigen.assets.color_fits import real_color_distribution + +@node_utils.to_nodegroup('nodegroup_leather', singleton=False, type='ShaderNodeTree') +def nodegroup_leather(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Seed', 0.0000), + ('NodeSocketFloat', 'Scale', 0.0000), + ('NodeSocketColor', 'Base Color', (0.0000, 0.0000, 0.0000, 1.0000))]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Scale"], 1: 10.0000}, attrs={'operation': 'MULTIPLY'}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'W': group_input.outputs["Seed"], 'Scale': multiply, 'Detail': 15.0000, 'Distortion': 0.2000}, + attrs={'noise_dimensions': '4D'}) + + color_ramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture.outputs["Fac"]}) + color_ramp.color_ramp.elements[0].position = 0.2841 + color_ramp.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + color_ramp.color_ramp.elements[1].position = 0.9455 + color_ramp.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + mix = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.0200, 6: texture_coordinate.outputs["Object"], 7: noise_texture.outputs["Color"]}, + attrs={'blend_type': 'LINEAR_LIGHT', 'data_type': 'RGBA'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Scale"], 1: 800.0000}, attrs={'operation': 'MULTIPLY'}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': mix.outputs[2], 'W': group_input.outputs["Seed"], 'Scale': multiply_1}, + attrs={'voronoi_dimensions': '4D', 'feature': 'DISTANCE_TO_EDGE'}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: voronoi_texture.outputs["Distance"], 1: group_input.outputs["Scale"]}, + attrs={'use_clamp': True, 'operation': 'MULTIPLY'}) + + hue_saturation_value = nw.new_node('ShaderNodeHueSaturation', input_kwargs={'Value': 0.6000, 'Color': group_input.outputs["Base Color"]}) + + mix_1 = nw.new_node(Nodes.Mix, + input_kwargs={0: multiply_2, 6: group_input.outputs["Base Color"], 7: hue_saturation_value}, + attrs={'data_type': 'RGBA'}) + + hue_saturation_value_1 = nw.new_node('ShaderNodeHueSaturation', input_kwargs={'Value': 0.4000, 'Color': group_input.outputs["Base Color"]}) + + mix_2 = nw.new_node(Nodes.Mix, + input_kwargs={0: color_ramp.outputs["Color"], 6: mix_1.outputs[2], 7: hue_saturation_value_1}, + attrs={'data_type': 'RGBA'}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': mix_2.outputs[2], 3: uniform(.3, .5), 4: uniform(.5, .7)}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': mix_2.outputs[2], 'Roughness': map_range.outputs["Result"]}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: mix_1.outputs[2], 1: -0.2000}, attrs={'operation': 'MULTIPLY'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: color_ramp.outputs["Color"], 1: 0.0500}, attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_3, 1: multiply_4}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: 0.0200}, attrs={'operation': 'MULTIPLY'}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'BSDF': principled_bsdf, 'Displacement': multiply_5}, + attrs={'is_active_output': True}) + +def shader_leather(nw: NodeWrangler, scale=1.0, base_color=None, seed=None, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + if seed is None: + seed = uniform(-1000.0, 1000.0) + + # if base_color is None: + # base_color = color_category('leather') + base_color = real_color_distribution('sofa_leather') + + group = nw.new_node(nodegroup_leather().name, + input_kwargs={'Seed': seed, 'Scale': scale, 'Base Color': base_color}) + + displacement = nw.new_node('ShaderNodeDisplacement', input_kwargs={'Height': group.outputs["Displacement"], 'Midlevel': 0.0000}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': group.outputs["BSDF"], 'Displacement': displacement}, + attrs={'is_active_output': True}) + + +def apply(obj, selection=None, **kwargs): + unwrap_faces(obj, selection) + common.apply(obj, shader_leather, selection=selection, **kwargs) + diff --git a/infinigen/assets/materials/leather_and_fabrics/lined_fabric.py b/infinigen/assets/materials/leather_and_fabrics/lined_fabric.py new file mode 100644 index 000000000..751d52479 --- /dev/null +++ b/infinigen/assets/materials/leather_and_fabrics/lined_fabric.py @@ -0,0 +1,167 @@ +# 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: Meenal Parakh + + +import bpy +import mathutils +from numpy.random import uniform + +from infinigen.assets.utils.uv import unwrap_faces +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.assets.materials import common + + +def get_texture_params(): + return { + "_hue": uniform(0, 1.0), + "_saturation": uniform(0.5, 1.0), + "_line_density": uniform(5, 20.0), + } + + +def shader_lined_fur_base( + nw: NodeWrangler, _hue=0.3, _saturation=0.7, _line_density=10 +): + # Code generated using version 2.6.5 of the node_transpiler + + hue = nw.new_node(Nodes.Value) + hue.outputs[0].default_value = _hue + + saturation = nw.new_node(Nodes.Value) + saturation.outputs[0].default_value = _saturation + + line_density = nw.new_node(Nodes.Value) + line_density.outputs[0].default_value = _line_density + + combine_color = nw.new_node( + Nodes.CombineColor, + input_kwargs={"Red": hue, "Green": saturation, "Blue": 0.6000}, + attrs={"mode": "HSV"}, + ) + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + mapping = nw.new_node( + Nodes.Mapping, input_kwargs={"Vector": texture_coordinate.outputs["Object"]} + ) + + wave_texture = nw.new_node( + Nodes.WaveTexture, + input_kwargs={ + "Vector": mapping, + "Scale": line_density, + "Distortion": 1.0000, + "Detail": 1.0000, + }, + ) + + color_ramp_1 = nw.new_node( + Nodes.ColorRamp, input_kwargs={"Fac": wave_texture.outputs["Color"]} + ) + color_ramp_1.color_ramp.elements[0].position = 0.0073 + color_ramp_1.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + color_ramp_1.color_ramp.elements[1].position = 0.2255 + color_ramp_1.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + mapping_1 = nw.new_node( + Nodes.Mapping, + input_kwargs={ + "Vector": texture_coordinate.outputs["Object"], + "Scale": (1.0000, 1.0000, 87.4000), + }, + ) + + noise_texture = nw.new_node( + Nodes.NoiseTexture, + input_kwargs={ + "Vector": mapping_1, + "Scale": 2.7000, + "Detail": 7.3000, + "Distortion": 7.0000, + }, + ) + + color_ramp = nw.new_node( + Nodes.ColorRamp, input_kwargs={"Fac": noise_texture.outputs["Fac"]} + ) + color_ramp.color_ramp.elements[0].position = 0.3018 + color_ramp.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + color_ramp.color_ramp.elements[1].position = 0.4691 + color_ramp.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + multiply = nw.new_node( + Nodes.Math, + input_kwargs={0: color_ramp_1.outputs["Color"], 1: color_ramp.outputs["Color"]}, + attrs={"operation": "MULTIPLY", "use_clamp": True}, + ) + + scale = nw.new_node( + Nodes.VectorMath, + input_kwargs={0: combine_color, "Scale": multiply}, + attrs={"operation": "SCALE"}, + ) + + principled_bsdf = nw.new_node( + Nodes.PrincipledBSDF, + input_kwargs={"Base Color": scale.outputs["Vector"], "Roughness": 1.0000}, + ) + + subtract = nw.new_node( + Nodes.Math, + input_kwargs={0: 1.0000, 1: multiply}, + attrs={"operation": "SUBTRACT"}, + ) + + multiply_1 = nw.new_node( + Nodes.Math, + input_kwargs={0: subtract, 1: -0.3000}, + attrs={"operation": "MULTIPLY"}, + ) + + mapping_2 = nw.new_node( + Nodes.Mapping, input_kwargs={"Vector": texture_coordinate.outputs["Object"]} + ) + + noise_texture_1 = nw.new_node( + Nodes.NoiseTexture, + input_kwargs={ + "Vector": mapping_2, + "Scale": 42.3000, + "Detail": 7.3000, + "Distortion": 16.6000, + }, + ) + + color_ramp_2 = nw.new_node( + Nodes.ColorRamp, input_kwargs={"Fac": noise_texture_1.outputs["Fac"]} + ) + color_ramp_2.color_ramp.elements[0].position = 0.3018 + color_ramp_2.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + color_ramp_2.color_ramp.elements[1].position = 0.4691 + color_ramp_2.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + multiply_2 = nw.new_node( + Nodes.Math, + input_kwargs={0: color_ramp_2.outputs["Color"], 1: multiply}, + attrs={"operation": "MULTIPLY", "use_clamp": True}, + ) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: multiply_2}) + + material_output = nw.new_node( + Nodes.MaterialOutput, + input_kwargs={"Surface": principled_bsdf, "Displacement": add}, + attrs={"is_active_output": True}, + ) + + +def shader_fabric_random(nw: NodeWrangler, **kwargs): + fabric_params = get_texture_params() + return shader_lined_fur_base(nw, **fabric_params) + + +def apply(obj, selection=None, **kwargs): + unwrap_faces(obj, selection) + common.apply(obj, shader_fabric_random, selection, **kwargs) diff --git a/infinigen/assets/materials/leather_and_fabrics/sofa_fabric.py b/infinigen/assets/materials/leather_and_fabrics/sofa_fabric.py new file mode 100644 index 000000000..03292592a --- /dev/null +++ b/infinigen/assets/materials/leather_and_fabrics/sofa_fabric.py @@ -0,0 +1,40 @@ +# 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: Lingjie Mei +from numpy.random import uniform + +from infinigen.assets.materials import common +from infinigen.assets.utils.uv import unwrap_faces +from infinigen.core.nodes import NodeWrangler, Nodes +from infinigen.core.util.color import color_category + + +def shader_sofa_fabric(nw: NodeWrangler, scale=1, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + attribute = nw.new_node(Nodes.Attribute, attrs={'attribute_name': 'UVMap'}) + attribute = nw.new_node(Nodes.Mapping,[attribute],input_kwargs={'Scale':[scale]*3}) + + rgb = nw.new_node(Nodes.RGB) + rgb.outputs[0].default_value = color_category('fabric') + + brightness_contrast = nw.new_node('ShaderNodeBrightContrast', input_kwargs={'Color': rgb, 'Bright': uniform(-0.1500, -0.05)}) + + brick_texture = nw.new_node(Nodes.BrickTexture, + input_kwargs={'Vector': attribute.outputs["Vector"], 'Color1': rgb, 'Color2': brightness_contrast, 'Scale': 276.9800, 'Mortar Size': 0.0100, 'Mortar Smooth': 1.0000, 'Bias': 0.5000, 'Row Height': 0.1000}, + attrs={'offset': 0.5479, 'squash_frequency': 1}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': brick_texture.outputs["Color"], 'Roughness': 0.8624, 'Sheen': 1.0000}) + + displacement = nw.new_node(Nodes.Displacement, input_kwargs={'Height': brick_texture.outputs["Fac"]}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': principled_bsdf, 'Displacement': displacement}, + attrs={'is_active_output': True}) + +def apply(obj, selection=None, **kwargs): + unwrap_faces(obj, selection) + common.apply(obj, shader_sofa_fabric, selection, **kwargs) + diff --git a/infinigen/assets/materials/leather_and_fabrics/velvet.py b/infinigen/assets/materials/leather_and_fabrics/velvet.py new file mode 100644 index 000000000..3f7f8e2a4 --- /dev/null +++ b/infinigen/assets/materials/leather_and_fabrics/velvet.py @@ -0,0 +1,86 @@ +# 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: Stamatis Alexandropoulos +# Acknowledgement: This file draws inspiration from https://www.youtube.com/watch?v=55MMAnTYhWI by Dikko + +import bpy +import mathutils +from infinigen.assets.materials import common +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + + + +def shader_velvet(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.5 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': texture_coordinate.outputs["Object"]}) + + mapping = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': reroute}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, input_kwargs={'Vector': mapping, 'Scale': 1.0000}) + + mix_6 = nw.new_node(Nodes.Mix, input_kwargs={0: 0.1125, 6: voronoi_texture.outputs["Color"]}, attrs={'data_type': 'RGBA'}) + + musgrave_texture = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': mapping, 'Scale': 9.6000, 'Detail': 11.4000, 'Dimension': 0.1000, 'Lacunarity': 1.9000}, + attrs={'musgrave_type': 'MULTIFRACTAL'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={0: uniform(0,0.8), 6: musgrave_texture, 7: (0.6044, 0.6044, 0.6044, 1.0000)}, + attrs={'data_type': 'RGBA', 'blend_type': 'MULTIPLY'}) + + mix_1 = nw.new_node(Nodes.Mix, input_kwargs={6: mix_6.outputs[2], 7: mix.outputs[2]}, attrs={'data_type': 'RGBA'}) + + color_ramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': mix_1.outputs[2]}) + color_ramp.color_ramp.elements[0].position = 0.0000 + color_ramp.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + color_ramp.color_ramp.elements[1].position = 0.8455 + color_ramp.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + rgb = nw.new_node(Nodes.RGB) + rgb.outputs[0].default_value = color_category('textile') + # (0.3547, 0.3018, 0.3087, 1.0000) + + brightness_contrast = nw.new_node('ShaderNodeBrightContrast', input_kwargs={'Color': rgb, 'Bright': 0.0500}) + + mix_2 = nw.new_node(Nodes.Mix, + input_kwargs={0: color_ramp.outputs["Color"], 6: brightness_contrast, 7: rgb}, + attrs={'data_type': 'RGBA'}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': mix_2.outputs[2], 'Specular': 0.0000, 'Roughness': uniform(0.4,0.9), 'Anisotropic': 0.7614, 'Anisotropic Rotation': 1.0000, 'Sheen': 16.2273, 'Sheen Tint': 1.0000}) + + mapping_1 = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': reroute, 'Rotation': (0.0000, 0.0000, 1.0157), 'Scale': (2.2000, 2.2000, 2.2000)}) + + wave_texture_1 = nw.new_node(Nodes.WaveTexture, + input_kwargs={'Vector': mapping_1, 'Scale': 500.0000, 'Distortion': 4.0000, 'Detail': 6.7000, 'Detail Scale': 1.5000, 'Detail Roughness': 0.4308}, + attrs={'bands_direction': 'DIAGONAL'}) + + mix_3 = nw.new_node(Nodes.Mix, + input_kwargs={0: 1.0000, 6: mapping_1, 7: wave_texture_1.outputs["Color"]}, + attrs={'data_type': 'RGBA', 'blend_type': 'MULTIPLY'}) + + mix_4 = nw.new_node(Nodes.Mix, + input_kwargs={0: 1.0000, 6: color_ramp.outputs["Color"], 7: mix_3.outputs[2]}, + attrs={'data_type': 'RGBA', 'blend_type': 'MULTIPLY'}) + + displacement = nw.new_node(Nodes.Displacement, input_kwargs={'Height': mix_4.outputs[2], 'Midlevel': 0.0000, 'Scale': 0.0150}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': principled_bsdf, 'Displacement': displacement}, + attrs={'is_active_output': True}) + + + +def apply(obj, selection=None, **kwargs): + common.apply(obj, shader_velvet, selection, **kwargs) + # surface.add_material(obj, shader_velvet, selection=selection) +# apply(bpy.context.active_object) \ No newline at end of file diff --git a/infinigen/assets/materials/marble.py b/infinigen/assets/materials/marble.py new file mode 100644 index 000000000..51c23644a --- /dev/null +++ b/infinigen/assets/materials/marble.py @@ -0,0 +1,10 @@ +# 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: Lingjie Mei +from infinigen.assets.materials import common +from infinigen.assets.materials.table_materials import shader_marble + + +def apply(obj, selection=None, **kwargs): + common.apply(obj, shader_marble, selection, **kwargs) diff --git a/infinigen/assets/materials/marble_regular.py b/infinigen/assets/materials/marble_regular.py new file mode 100644 index 000000000..1da008c3c --- /dev/null +++ b/infinigen/assets/materials/marble_regular.py @@ -0,0 +1,58 @@ +# 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: Zeyu Ma +# Acknowledgement: This file draws inspiration from https://physbam.stanford.edu/cs448x/old/Procedural_Noise(2f)Perlin_Noise.html + +import bpy +import mathutils +import numpy as np +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core import surface + + + +def shader_material_001(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + geometry = nw.new_node(Nodes.NewGeometry) + + mapping = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': geometry.outputs["Position"], 'Scale': (20.0000, 20.0000, 20.0000)}) + + roughness = nw.new_node(Nodes.Value, label='roughness ~ U(0.7,0.9)') + roughness.outputs[0].default_value = uniform(0.7, 0.9) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'Scale': 0.1000, 'Detail': 9.0000, 'Roughness': roughness, 'Distortion': 0.2000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: noise_texture.outputs["Fac"], 1: 20.0000}, attrs={'operation': 'MULTIPLY'}) + + random_plane_angle = uniform(0, 2 * np.pi) + + dot_product = nw.new_node(Nodes.VectorMath, + input_kwargs={0: mapping, 1: (np.cos(random_plane_angle), np.sin(random_plane_angle), 0.0000)}, + attrs={'operation': 'DOT_PRODUCT'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: dot_product.outputs["Value"]}) + + sine = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'SINE'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: sine, 1: 1.0000}) + + darkness = nw.new_node(Nodes.Value, label='darkness ~ U(0,1)') + darkness.outputs[0].default_value = uniform(0.0, 1.0) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': darkness, 3: 0.2000, 4: 0.3000}) + + power = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: map_range.outputs["Result"]}, attrs={'operation': 'POWER'}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Base Color': power}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) + + + +def apply(obj, selection=None, **kwargs): + surface.add_material(obj, shader_material_001, selection=selection) \ No newline at end of file diff --git a/infinigen/assets/materials/marble_voronoi.py b/infinigen/assets/materials/marble_voronoi.py new file mode 100644 index 000000000..b1119c8b3 --- /dev/null +++ b/infinigen/assets/materials/marble_voronoi.py @@ -0,0 +1,53 @@ +# 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: Zeyu Ma +# Acknowledgement: This file draws inspiration from https://www.youtube.com/watch?v=wTzk9T06gdw by Ryan King Art + +import bpy +import mathutils +import numpy as np +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core import surface + + + +def shader_material(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + geometry = nw.new_node(Nodes.NewGeometry) + + mapping = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': geometry.outputs["Position"]}) + + roughness = nw.new_node(Nodes.Value, label='roughness ~ U(0.5,0.7)') + roughness.outputs[0].default_value = uniform(0.5, 0.7) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'Scale': 2.0000, 'Detail': 9.0000, 'Roughness': roughness}) + + random_plane_angle = uniform(0, 2 * np.pi) + + dot_product = nw.new_node(Nodes.VectorMath, + input_kwargs={0: mapping, 1: (np.cos(random_plane_angle), np.sin(random_plane_angle), 0.0000)}, + attrs={'operation': 'DOT_PRODUCT'}) + + add = nw.new_node(Nodes.VectorMath, input_kwargs={0: noise_texture.outputs["Color"], 1: dot_product.outputs["Value"]}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, input_kwargs={'Vector': add.outputs["Vector"]}) + + colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': voronoi_texture.outputs["Distance"]}) + colorramp.color_ramp.elements[0].position = uniform(0.4, 0.5) + colorramp.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + colorramp.color_ramp.elements[1].position = 0.9600 + colorramp.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': colorramp.outputs["Color"], 'Metallic': 0.5000, 'Roughness': 0.0000}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) + + + +def apply(obj, selection=None, **kwargs): + surface.add_material(obj, shader_material, selection=selection) \ No newline at end of file diff --git a/infinigen/assets/materials/metal/__init__.py b/infinigen/assets/materials/metal/__init__.py new file mode 100644 index 000000000..c427a1357 --- /dev/null +++ b/infinigen/assets/materials/metal/__init__.py @@ -0,0 +1,56 @@ +# 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: Lingjie Mei +from collections.abc import Iterable + +import numpy as np +from numpy.random import uniform + +from infinigen.core.util.color import hsv2rgba, rgb2hsv +from infinigen.core.util.random import random_general as rg, log_uniform +from . import ( + brushed_metal, galvanized_metal, grained_and_polished_metal, hammered_metal, + metal_basic, +) +from .. import common +from ..bark_random import hex_to_rgb + + +def apply(obj, selection=None, metal_color=None, **kwargs): + color = sample_metal_color(metal_color) + shader = get_shader() + common.apply(obj, shader, selection, base_color=color, **kwargs) + + +def get_shader(): + return np.random.choice( + [brushed_metal.shader_brushed_metal, galvanized_metal.shader_galvanized_metal, + grained_and_polished_metal.shader_grained_metal, + hammered_metal.shader_hammered_metal] + ) + + +plain_colors = 'weighted_choice', (.5, 0xfdd017), (1, 0xc0c0c0), (1, 0x8c7853), (.5, 0xb87333), (.5, 0xb5a642), ( + 1, 0xbdbaae), (1, 0xa9acb6), (1, 0xb6afa9) +natural_colors = 'weighted_choice', (1, 0xc0c0c0), (1, 0x8c7853), (1, 0xbdbaae), (1, 0xa9acb6), (1, 0xb6afa9) + + +def sample_metal_color(metal_color=None, **kwargs): + match metal_color: + case np.ndarray(): + return metal_color + case 'plain': + h, s, v = rgb2hsv(hex_to_rgb(rg(plain_colors))[:-1]) + return hsv2rgba(h + uniform(-.1, .1), s + uniform(-.1, .1), v * log_uniform(.5, .2)) + case 'natural': + h, s, v = rgb2hsv(hex_to_rgb(rg(natural_colors))[:-1]) + return hsv2rgba(h + uniform(-.1, .1), s + uniform(-.1, .1), v * log_uniform(.5, .2)) + case 'bw': + return hsv2rgba(uniform(0, 1), uniform(.0, .2), log_uniform(.01, .2)) + case 'bw+natural': + return sample_metal_color('bw') if uniform() < .5 else sample_metal_color('natural') + case _: + if uniform() < .2: + return sample_metal_color('natural') + return hsv2rgba(uniform(0, 1), uniform(.3, .6), log_uniform(.02, .5)) diff --git a/infinigen/assets/materials/metal/brushed_metal.py b/infinigen/assets/materials/metal/brushed_metal.py new file mode 100644 index 000000000..18d879394 --- /dev/null +++ b/infinigen/assets/materials/metal/brushed_metal.py @@ -0,0 +1,87 @@ +# 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: Yiming Zuo +# Acknowledgement: This file draws inspiration https://www.youtube.com/watch?v=QcAMYRgR03k by blenderian + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + + +@node_utils.to_nodegroup('nodegroup_brushed_metal', singleton=False, type='ShaderNodeTree') +def nodegroup_brushed_metal(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + mapping_1 = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'Scale': (0.2000, 0.2000, 5.0000)}) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketColor', 'Base Color', (0.8000, 0.8000, 0.8000, 1.0000)), + ('NodeSocketFloat', 'Scale', 0.0000), + ('NodeSocketFloat', 'Seed', 0.0000)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Scale"], 1: 100.0000}, attrs={'operation': 'MULTIPLY'}) + + noise_texture_2 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping_1, 'W': group_input.outputs["Seed"], 'Scale': multiply, 'Detail': 15.0000, 'Roughness': 0.4000, 'Distortion': 0.1000}, + attrs={'noise_dimensions': '4D'}) + + mapping = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'Scale': (1.0000, 1.0000, 20.0000)}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'W': group_input.outputs["Seed"], 'Scale': 0.1000, 'Detail': 15.0000, 'Roughness': 0.0000}, + attrs={'noise_dimensions': '4D'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.2000, 6: mapping, 7: noise_texture_1.outputs["Color"]}, + attrs={'data_type': 'RGBA'}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mix.outputs[2], 'W': group_input.outputs["Seed"], 'Scale': multiply, 'Detail': 15.0000, 'Roughness': 0.6000, 'Distortion': 0.1000}, + attrs={'noise_dimensions': '4D'}) + + mix_1 = nw.new_node(Nodes.Mix, + input_kwargs={0: 1.0000, 6: noise_texture_2.outputs["Fac"], 7: noise_texture.outputs["Fac"]}, + attrs={'blend_type': 'DARKEN', 'data_type': 'RGBA'}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': mix_1, 1: 0.4000, 2: 0.6000, 3: 0.8000, 4: 1.2000}) + + hue_saturation_value = nw.new_node('ShaderNodeHueSaturation', + input_kwargs={'Value': map_range.outputs["Result"], 'Color': group_input.outputs["Base Color"]}) + + map_range_1 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': mix_1, 1: 0.4000, 2: 0.6000, 3: 0.2000, 4: 0.3000}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': hue_saturation_value, 'Metallic': 1.0000, 'Specular': 0.0000, 'Roughness': map_range_1.outputs["Result"]}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'BSDF': principled_bsdf, 'tmp_viewer': principled_bsdf}, + attrs={'is_active_output': True}) + +def shader_brushed_metal(nw: NodeWrangler, scale=1.0, base_color=None, seed=None, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + if seed is None: + seed = uniform(-1000.0, 1000.0) + if base_color is None: + from infinigen.assets.materials.metal import sample_metal_color + base_color = sample_metal_color(**kwargs) + + group = nw.new_node(nodegroup_brushed_metal().name, + input_kwargs={'Base Color': base_color, 'Scale': scale, 'Seed': seed}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': group.outputs['BSDF']}, + attrs={'is_active_output': True}) + +def apply(obj, selection=None, **kwargs): + surface.add_material(obj, shader_brushed_metal, selection=selection, input_kwargs=kwargs) diff --git a/infinigen/assets/materials/metal/galvanized_metal.py b/infinigen/assets/materials/metal/galvanized_metal.py new file mode 100644 index 000000000..ddf6a4040 --- /dev/null +++ b/infinigen/assets/materials/metal/galvanized_metal.py @@ -0,0 +1,68 @@ +# 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: Yiming Zuo +# Acknowledgement: This file draws inspiration https://www.youtube.com/watch?v=ECl2pQ1jQm8 by Ryan King Art + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint + +from infinigen.assets.materials import common +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + + +@node_utils.to_nodegroup('nodegroup_galvanized_metal', singleton=False, type='ShaderNodeTree') +def nodegroup_galvanized_metal(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketColor', 'Base Color', (0.8000, 0.8000, 0.8000, 1.0000)), + ('NodeSocketFloat', 'Scale', 0.0000), + ('NodeSocketFloat', 'Seed', 0.0000)]) + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Scale"], 1: 5.0000}, attrs={'operation': 'MULTIPLY'}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'W': group_input.outputs["Seed"], 'Scale': multiply, 'Detail': 15.0000, 'Roughness': 0.4000, 'Distortion': 0.2000}, + attrs={'noise_dimensions': '4D'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.0500, 6: texture_coordinate.outputs["Object"], 7: noise_texture.outputs["Color"]}, + attrs={'clamp_factor': False, 'data_type': 'RGBA'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Scale"], 1: 500.0000}, attrs={'operation': 'MULTIPLY'}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': mix.outputs[2], 'W': group_input.outputs["Seed"], 'Scale': multiply_1}, + attrs={'distance': 'MINKOWSKI', 'voronoi_dimensions': '4D'}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': voronoi_texture.outputs["Color"], 3: 0.1000, 4: 0.5000}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': group_input.outputs["Base Color"], 'Metallic': 1.0000, 'Specular': 0.0000, 'Roughness': map_range.outputs["Result"]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'BSDF': principled_bsdf}, attrs={'is_active_output': True}) + + +def shader_galvanized_metal(nw: NodeWrangler, scale=1.0, base_color=None, seed=None, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + if seed is None: + seed = uniform(-1000.0, 1000.0) + if base_color is None: + from infinigen.assets.materials.metal import sample_metal_color + base_color = sample_metal_color(**kwargs) + + group = nw.new_node(nodegroup_galvanized_metal().name, + input_kwargs={'Base Color': base_color, 'Scale': scale, 'Seed': seed}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': group}, attrs={'is_active_output': True}) + +def apply(obj, selection=None, **kwargs): + common.apply(obj, shader_galvanized_metal, selection=selection,**kwargs) diff --git a/infinigen/assets/materials/metal/grained_and_polished_metal.py b/infinigen/assets/materials/metal/grained_and_polished_metal.py new file mode 100644 index 000000000..5e63f8219 --- /dev/null +++ b/infinigen/assets/materials/metal/grained_and_polished_metal.py @@ -0,0 +1,79 @@ +# 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: Yiming Zuo + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + + +@node_utils.to_nodegroup('nodegroup_grained_metal', singleton=False, type='ShaderNodeTree') +def nodegroup_grained_metal(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketColor', 'Base Color', (0.8000, 0.8000, 0.8000, 1.0000)), + ('NodeSocketFloat', 'Scale', 5.0000), + ('NodeSocketFloat', 'Seed', 0.0000), + ('NodeSocketFloat', 'Roughness', 0.0000)]) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': group_input.outputs["Roughness"], 3: 0.0500, 4: 0.2500}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': group_input.outputs["Base Color"], 'Metallic': 1.0000, 'Specular': 0.0000, 'Roughness': map_range.outputs["Result"]}) + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Scale"], 1: 2000.0000}, + attrs={'operation': 'MULTIPLY'}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'W': group_input.outputs["Seed"], 'Scale': multiply, 'Detail': 15.0000, 'Distortion': 2.0000}, + attrs={'noise_dimensions': '4D'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: map_range.outputs["Result"], 1: 0.4000}, attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: noise_texture.outputs["Fac"], 1: multiply_1}, + attrs={'operation': 'MULTIPLY'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: 0.010}, attrs={'operation': 'MULTIPLY'}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'BSDF': principled_bsdf, 'Displacement': multiply_3}, + attrs={'is_active_output': True}) + + +def shader_grained_metal(nw: NodeWrangler, scale=1.0, base_color=None, roughness=None, seed=None, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + if roughness is None: + roughness = uniform(0.0, 1.0) + if seed is None: + seed = uniform(-1000.0, 1000.0) + if base_color is None: + from infinigen.assets.materials.metal import sample_metal_color + base_color = sample_metal_color(**kwargs) + + + group = nw.new_node(nodegroup_grained_metal().name, + input_kwargs={'Base Color': base_color, + 'Scale': scale, + 'Seed': seed, + 'Roughness': roughness, + }) + + displacement = nw.new_node('ShaderNodeDisplacement', input_kwargs={'Height': group.outputs["Displacement"], 'Midlevel': 0.0000}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': group.outputs["BSDF"], 'Displacement': displacement}, + attrs={'is_active_output': True}) + +def apply(obj, selection=None, **kwargs): + surface.add_material(obj, shader_grained_metal, selection=selection, input_kwargs=kwargs) diff --git a/infinigen/assets/materials/metal/hammered_metal.py b/infinigen/assets/materials/metal/hammered_metal.py new file mode 100644 index 000000000..d87a27f2b --- /dev/null +++ b/infinigen/assets/materials/metal/hammered_metal.py @@ -0,0 +1,81 @@ +# 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: Yiming Zuo +# Acknowledgement: This file draws inspiration https://www.youtube.com/watch?v=82smQvoh0GE by Mix CG Arts + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface +from infinigen.core.util.random import log_uniform + + +@node_utils.to_nodegroup('nodegroup_hammered_metal', singleton=False, type='ShaderNodeTree') +def nodegroup_hammered_metal(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketColor', 'Base Color', (0.8000, 0.8000, 0.8000, 1.0000)), + ('NodeSocketFloat', 'Scale', 0.0000), + ('NodeSocketFloat', 'Seed', 0.0000)]) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': group_input.outputs["Base Color"], 'Metallic': 1.0000, 'Specular': 0.0000, 'Roughness': 0.1000}) + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Scale"], 1: 20.0000}, attrs={'operation': 'MULTIPLY'}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'W': group_input.outputs["Seed"], 'Scale': multiply, 'Detail': 15.0000, 'Roughness': 0.4000, 'Distortion': 0.2000}, + attrs={'noise_dimensions': '4D'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.0100, 6: texture_coordinate.outputs["Object"], 7: noise_texture.outputs["Color"]}, + attrs={'clamp_factor': False, 'data_type': 'RGBA'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Scale"], 1: 300.0000}, attrs={'operation': 'MULTIPLY'}) + + voronoi_texture_1 = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': mix.outputs[2], 'W': group_input.outputs["Seed"], 'Scale': multiply_1, 'Smoothness': 0.2000}, + attrs={'voronoi_dimensions': '4D', 'feature': 'SMOOTH_F1'}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: voronoi_texture_1.outputs["Distance"], 1: group_input.outputs["Scale"]}, + attrs={'operation': 'MULTIPLY'}) + + power = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: 2.5000}, attrs={'operation': 'POWER'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: power, 1: log_uniform(.001,0.003)}, attrs={'operation': 'MULTIPLY'}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'BSDF': principled_bsdf, 'Displacement': multiply_3, 'tmp_viewer': voronoi_texture_1.outputs["Color"]}, + attrs={'is_active_output': True}) + +def shader_hammered_metal(nw: NodeWrangler, scale=None, base_color=None, seed=None, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + if seed is None: + seed = uniform(-1000.0, 1000.0) + if base_color is None: + from infinigen.assets.materials.metal import sample_metal_color + base_color = sample_metal_color(**kwargs) + if scale is None: + scale = log_uniform(.8, 1.2) + + group = nw.new_node(nodegroup_hammered_metal().name, + input_kwargs={'Base Color': base_color, 'Scale': scale, 'Seed': seed}) + + displacement = nw.new_node('ShaderNodeDisplacement', input_kwargs={'Height': group.outputs["Displacement"], 'Midlevel': 0.0000}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': group.outputs["BSDF"], 'Displacement': displacement}, + attrs={'is_active_output': True}) + + +def apply(obj, selection=None, **kwargs): + surface.add_material(obj, shader_hammered_metal, selection=selection, input_kwargs=kwargs) diff --git a/infinigen/assets/materials/metal/metal_basic.py b/infinigen/assets/materials/metal/metal_basic.py new file mode 100644 index 000000000..81d5b96b6 --- /dev/null +++ b/infinigen/assets/materials/metal/metal_basic.py @@ -0,0 +1,33 @@ +# 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: Lingjie Mei +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import common +from infinigen.core.nodes.node_info import Nodes +from infinigen.core.nodes.node_wrangler import NodeWrangler + + +def shader_metal(nw: NodeWrangler, color=None, **kwargs): + position = nw.new_node(Nodes.TextureCoord).outputs['Object'] + roughness = nw.build_float_curve( + nw.new_node(Nodes.NoiseTexture, [position], input_kwargs={'Scale': uniform(10, 25)}), + [(0, uniform(0, .2)), (1, uniform(.4, .7))] + ) + principled_bsdf = nw.new_node( + Nodes.PrincipledBSDF, input_kwargs={ + "Metallic": 1., + 'Specular': uniform(.5, 1.), + 'Base Color': color, + 'Roughness': roughness + } + ) + nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}) + + +def apply(obj, selection=None, **kwargs): + from infinigen.assets.materials.metal import sample_metal_color + color = sample_metal_color(**kwargs) + common.apply(obj, shader_metal, selection, color, **kwargs) diff --git a/infinigen/assets/materials/microwave_shaders.py b/infinigen/assets/materials/microwave_shaders.py new file mode 100644 index 000000000..f08b4ff42 --- /dev/null +++ b/infinigen/assets/materials/microwave_shaders.py @@ -0,0 +1,27 @@ +# 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: Hongyu Wen + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler + +def shader_black_medal(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + anisotropic_bsdf = nw.new_node('ShaderNodeBsdfAnisotropic', input_kwargs={'Color': (0.0167, 0.0167, 0.0167, 1.0000)}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': anisotropic_bsdf}, attrs={'is_active_output': True}) + +def shader_black_glass(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + glossy_bsdf = nw.new_node(Nodes.GlossyBSDF, input_kwargs={'Color': (0.0068, 0.0068, 0.0068, 1.0000), 'Roughness': 0.2000}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': glossy_bsdf}, attrs={'is_active_output': True}) + +def shader_glass(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + glass_bsdf = nw.new_node(Nodes.GlassBSDF, input_kwargs={'IOR': 1.5000}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': glass_bsdf}, attrs={'is_active_output': True}) \ No newline at end of file diff --git a/infinigen/assets/materials/mirror.py b/infinigen/assets/materials/mirror.py new file mode 100644 index 000000000..a725fce4e --- /dev/null +++ b/infinigen/assets/materials/mirror.py @@ -0,0 +1,18 @@ +# 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: Lingjie Mei +from infinigen.assets.materials import common +from infinigen.core.nodes import NodeWrangler, Nodes + + +def shader_mirror(nw: NodeWrangler,**kwargs): + glossy_bsdf = nw.new_node('ShaderNodeBsdfGlossy', + input_kwargs={'Color': (1.0, 1.0, 1.0, 1.0), 'Roughness': 0, + }) + + nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': glossy_bsdf}) + + +def apply(obj, selection=None, **kwargs): + common.apply(obj, shader_mirror, selection, **kwargs) diff --git a/infinigen/assets/materials/oven_shaders.py b/infinigen/assets/materials/oven_shaders.py new file mode 100644 index 000000000..c6e3017e9 --- /dev/null +++ b/infinigen/assets/materials/oven_shaders.py @@ -0,0 +1,28 @@ +# 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: Hongyu Wen + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler + + +def shader_super_black_glass(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + glossy_bsdf = nw.new_node(Nodes.GlossyBSDF, input_kwargs={'Color': (0.0095, 0.0095, 0.0095, 1.0000), 'Roughness': 0.0000}) + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': glossy_bsdf}, attrs={'is_active_output': True}) + + +def shader_black_medal(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + anisotropic_bsdf = nw.new_node('ShaderNodeBsdfAnisotropic', input_kwargs={'Color': (0.0167, 0.0167, 0.0167, 1.0000)}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': anisotropic_bsdf}, attrs={'is_active_output': True}) + +def shader_glass(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + glass_bsdf = nw.new_node(Nodes.GlassBSDF, input_kwargs={'IOR': 1.5000}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': glass_bsdf}, attrs={'is_active_output': True}) + diff --git a/infinigen/assets/materials/plaster.py b/infinigen/assets/materials/plaster.py new file mode 100644 index 000000000..918108828 --- /dev/null +++ b/infinigen/assets/materials/plaster.py @@ -0,0 +1,59 @@ +# 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: Lingjie Mei +from collections.abc import Iterable + +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.object import new_plane +from infinigen.assets.utils.uv import unwrap_normal +from infinigen.core.util.color import hsv2rgba +from infinigen.core.nodes.node_info import Nodes +from infinigen.core.nodes.node_wrangler import NodeWrangler +from infinigen.assets.materials import common +from infinigen.core.util.random import log_uniform +from infinigen.core.nodes.node_utils import build_color_ramp + + +def shader_plaster(nw: NodeWrangler, plaster_colored, **kwargs): + hue = uniform(0, 1) + front_value = log_uniform(.5, 1.) + back_value = front_value * uniform(.6, 1) + if plaster_colored: + front_color = hsv2rgba(hue, uniform(.3, .5), front_value) + back_color = hsv2rgba(hue + uniform(-.1, .1), uniform(.3, .5), back_value) + else: + front_color = hsv2rgba(hue, 0, front_value) + back_color = hsv2rgba(hue + uniform(-.1, .1), 0, back_value) + uv_map = nw.new_node(Nodes.UVMap) + musgrave = nw.new_node(Nodes.MusgraveTexture, [uv_map], + input_kwargs={'Detail': log_uniform(15, 30), 'Dimension': 0}) + noise = nw.new_node(Nodes.NoiseTexture, [uv_map], + input_kwargs={'Detail': log_uniform(15, 30), 'Distortion': log_uniform(4, 8)}) + noise = build_color_ramp(nw, noise, [0, uniform(.3, .5)], [(0, 0, 0, 1), (1, 1, 1, 1)]) + difference = nw.new_node(Nodes.MixRGB, [musgrave, noise], attrs={'blend_type': 'DIFFERENCE'}) + base_color = build_color_ramp(nw, difference, [uniform(.2, .3), 1], [back_color, front_color]) + + displacement = nw.new_node(Nodes.Displacement, input_kwargs={ + 'Scale': log_uniform(.0001, .0003), + 'Height': nw.new_node(Nodes.MusgraveTexture, input_kwargs={'Scale': uniform(1e3, 2e3)}) + }) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={ + 'Base Color': base_color, + 'Roughness': uniform(.7, .8), + }) + + nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf, 'Displacement': displacement}) + + +def apply(obj, selection=None, plaster_colored=None, **kwargs): + if plaster_colored is None: + plaster_colored = uniform() < .4 + for o in obj if isinstance(obj, Iterable) else [obj]: + unwrap_normal(o, selection) + common.apply(obj, shader_plaster, selection, plaster_colored=plaster_colored, **kwargs) + + diff --git a/infinigen/assets/materials/plastic.py b/infinigen/assets/materials/plastic.py new file mode 100644 index 000000000..1dd6912b8 --- /dev/null +++ b/infinigen/assets/materials/plastic.py @@ -0,0 +1,24 @@ +# Copyright (c) Princeton University. +# This source code is licensed under the GPL license found in the LICENSE file in the root directory of this +# source tree. + +# Authors: Mingzhe Wang, Lingjie Mei + +import colorsys + +from infinigen.assets.materials.plastics.plastic_rough import shader_rough_plastic +from infinigen.assets.materials.plastics.plastic_translucent import shader_translucent_plastic +from infinigen.core.util.color import hsv2rgba + +from infinigen.assets.materials import common +from numpy.random import uniform + + + +def apply(obj, selection=None, clear=None, **kwargs): + is_rough = kwargs.get('rough', uniform(0, 1)) + is_translucent = kwargs.get('translucent', uniform(0, 1)) + if clear is None: + clear = uniform() < .2 + shader_func = shader_rough_plastic if is_rough > is_translucent else shader_translucent_plastic + common.apply(obj, shader_func, selection, clear=clear, **kwargs) diff --git a/infinigen/assets/materials/plastics/plastic_rough.py b/infinigen/assets/materials/plastics/plastic_rough.py new file mode 100644 index 000000000..c2db3bd0e --- /dev/null +++ b/infinigen/assets/materials/plastics/plastic_rough.py @@ -0,0 +1,83 @@ +# Copyright (c) Princeton University. +# This source code is licensed under the GPL license found in the LICENSE file in the root directory of this +# source tree. + +# Authors: Mingzhe Wang, Lingjie Mei + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint + +from infinigen.assets.materials import common +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.random import log_uniform +from infinigen.core.util.color import color_category, hsv2rgba +from infinigen.core import surface + +@node_utils.to_nodegroup('nodegroup_plastics', singleton=False, type='ShaderNodeTree') +def nodegroup_plastics(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketColor', 'Base Color', (0.8000, 0.8000, 0.8000, 1.0000)), + ('NodeSocketFloat', 'Scale', 5.0000), + ('NodeSocketFloat', 'Seed', 0.0000), + ('NodeSocketFloat', 'Roughness', 0.0000)]) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': group_input.outputs["Roughness"], 3: 0.0500, 4: 0.2500}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': group_input.outputs["Base Color"], 'Roughness': map_range.outputs["Result"]}) + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Scale"], 1: 2000.0000}, + attrs={'operation': 'MULTIPLY'}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'W': group_input.outputs["Seed"], 'Scale': multiply, 'Detail': 15.0000, 'Distortion': 2.0000}, + attrs={'noise_dimensions': '4D'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: map_range.outputs["Result"], 1: 0.4000}, attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: noise_texture.outputs["Fac"], 1: multiply_1}, + attrs={'operation': 'MULTIPLY'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: 0.0030}, attrs={'operation': 'MULTIPLY'}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'BSDF': principled_bsdf, 'Displacement': multiply_3}, + attrs={'is_active_output': True}) + +def shader_rough_plastic(nw: NodeWrangler, scale=1.0, base_color=None, roughness=None, seed=None, clear=False, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + if roughness is None: + roughness = uniform(0.0, 1.0) + if seed is None: + seed = uniform(-1000.0, 1000.0) + if base_color is None: + if clear: + base_color = hsv2rgba(0, 0, log_uniform(.02, .8)) + else: + base_color = hsv2rgba(uniform(0, 1), uniform(.5, .8), log_uniform(.01, .5)) + + group = nw.new_node(nodegroup_plastics().name, + input_kwargs={'Base Color': base_color, + 'Scale': scale, + 'Seed': seed, + 'Roughness': roughness, + }) + + displacement = nw.new_node('ShaderNodeDisplacement', input_kwargs={'Height': group.outputs["Displacement"], 'Midlevel': 0.0000}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': group.outputs["BSDF"], 'Displacement': displacement}, + attrs={'is_active_output': True}) + + +def apply(obj, selection=None, **kwargs): + common.apply(obj, shader_rough_plastic, selection, **kwargs) \ No newline at end of file diff --git a/infinigen/assets/materials/plastics/plastic_translucent.py b/infinigen/assets/materials/plastics/plastic_translucent.py new file mode 100644 index 000000000..d41a04264 --- /dev/null +++ b/infinigen/assets/materials/plastics/plastic_translucent.py @@ -0,0 +1,44 @@ +# Copyright (c) Princeton University. +# This source code is licensed under the GPL license found in the LICENSE file in the root directory of this +# source tree. + +# Authors: Mingzhe Wang, Lingjie Mei + +import colorsys + +from infinigen.core.util.color import hsv2rgba +from infinigen.assets.materials import common +from infinigen.core.util.random import log_uniform +from infinigen.assets.materials.utils.surface_utils import sample_range +from numpy.random import uniform +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler + +def shader_translucent_plastic(nw: NodeWrangler, clear=False, **input_kwargs): + # Code generated using version 2.4.3 of the node_transpiler + + layer_weight = nw.new_node('ShaderNodeLayerWeight', input_kwargs={'Blend': sample_range(0.2, 0.4)}) + + rgb = nw.new_node(Nodes.RGB) + + if clear: + base_color = hsv2rgba(0, 0, log_uniform(.4, .8)) + else: + base_color = hsv2rgba(uniform(0, 1), uniform(.5, .8), log_uniform(.4, .8)) + rgb.outputs[0].default_value = base_color + + value = nw.new_node(Nodes.Value) + value.outputs[0].default_value = sample_range(1.2, 1.6) + + glass_bsdf = nw.new_node('ShaderNodeBsdfGlass', input_kwargs={'Color': rgb, 'Roughness': 0.2, 'IOR': value}) + + glossy_bsdf = nw.new_node('ShaderNodeBsdfGlossy', input_kwargs={'Roughness': 0.2}) + + mix_shader = nw.new_node(Nodes.MixShader, + input_kwargs={'Fac': layer_weight.outputs["Fresnel"], 1: glass_bsdf, 2: glossy_bsdf + }) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': mix_shader}) + + +def apply(obj, selection=None, **kwargs): + common.apply(obj, shader_translucent_plastic, selection, **kwargs) \ No newline at end of file diff --git a/infinigen/assets/materials/rug.py b/infinigen/assets/materials/rug.py new file mode 100644 index 000000000..4f03d19f2 --- /dev/null +++ b/infinigen/assets/materials/rug.py @@ -0,0 +1,50 @@ +# 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: Lingjie Mei +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import common +from infinigen.core.nodes import NodeWrangler, Nodes +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform + + +def shader_rug(nw: NodeWrangler, strength=1., **kwargs): + coord = nw.new_node(Nodes.Mapping, [nw.new_node(Nodes.TextureCoord).outputs['Object']]) + vec = nw.new_node(Nodes.MixRGB, [uniform(.8, .9), nw.new_node(Nodes.NoiseTexture, [coord]), coord]) + height = 0, 0, 0, 1 + base_scale = log_uniform(250, 500) + for scale, thresh in zip([1, .75, .5], [1, .5, .33]): + voronoi = nw.new_node(Nodes.VoronoiTexture, [vec], input_kwargs={'Scale': scale * base_scale}).outputs[ + 0] + height = nw.new_node(Nodes.MixRGB, [nw.math('GREATER_THAN', voronoi, thresh), voronoi, height]) + + displacement = nw.new_node(Nodes.Displacement, input_kwargs={ + 'Scale': strength, + 'Height': height + }) + + base_hue = uniform(0, 1) + base_value = uniform(.2, .5) + if uniform() < .2: + base_saturation = log_uniform(.02, .05) + front_color = hsv2rgba(base_hue, base_saturation, base_value) + back_color = hsv2rgba(base_hue + uniform(-.01, .01), base_saturation * uniform(.9, 1.1), + base_value * uniform(.9, 1.1)) + else: + base_saturation = log_uniform(.2, .4) + front_color = hsv2rgba(base_hue, base_saturation, base_value) + back_color = hsv2rgba(base_hue + uniform(-.01, .01), base_saturation * uniform(.9, 1.1), + base_value * uniform(.9, 1.1)) + color = nw.new_node(Nodes.MixRGB, [ + nw.build_float_curve(nw.musgrave(uniform(20, 50)), [(0, 1), (uniform(.3, .4), 0), (1, 0)]), front_color, + back_color]) + roughness = nw.build_float_curve(nw.musgrave(uniform(20, 50)), [(.5, .9), (1, .8)]) + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': color, 'Roughness': roughness}) + nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf, 'Displacement': displacement}) + +def apply(obj, selection=None, **kwargs): + common.apply(obj, shader_rug, selection, **kwargs) diff --git a/infinigen/assets/materials/sandstone.py b/infinigen/assets/materials/sandstone.py index 448d7afa6..c9b63bdea 100644 --- a/infinigen/assets/materials/sandstone.py +++ b/infinigen/assets/materials/sandstone.py @@ -4,7 +4,7 @@ # Authors: Ankit Goyal, Mingzhe Wang, Zeyu Ma - + # Code generated using version v2.0.0 of the node_transpiler import gin from infinigen.core.nodes import node_utils @@ -410,7 +410,7 @@ def geometry_sandstone(nw, selection=None, is_rock=False, **kwargs): ) normal = nw.new_node("GeometryNodeInputNormal", []) - + group_3 = nw.new_node( nodegroup_roughness().name, input_kwargs={"Noise 1 Scale": 200.0, "Noise 1 Magnitude": 0.5, 'Normal': normal}, @@ -556,7 +556,7 @@ def geometry_sandstone(nw, selection=None, is_rock=False, **kwargs): nw.new_value(0.2, "stripe_warp_mag"), ) ) - + offset2 = nw.add( multiply_3, nw.multiply( @@ -571,7 +571,7 @@ def geometry_sandstone(nw, selection=None, is_rock=False, **kwargs): normal, ) ) - + noise_params = {"scale": ("uniform", 10, 20), "detail": 9, "roughness": 0.6, "zscale": ("log_uniform", 0.05, 0.1)} offset = nw.add( diff --git a/infinigen/assets/materials/shelf_shaders.py b/infinigen/assets/materials/shelf_shaders.py new file mode 100644 index 000000000..ed92d64ad --- /dev/null +++ b/infinigen/assets/materials/shelf_shaders.py @@ -0,0 +1,303 @@ +# 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: Beining Han +# Acknowledgement: This file draws inspiration from https://www.youtube.com/watch?v=jDEijCwz6to by Lachlan Sarv + +import numpy as np +from numpy.random import uniform, normal, randint + +from infinigen.assets.materials import ( + metal_shader_list, + shader_glass, + shader_rough_plastic, + wood, +) +from infinigen.assets.materials.leather_and_fabrics import fabric_shader_list +from infinigen.core import surface + + + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.util.color import color_category, hsv2rgba +from infinigen.core import surface + +import json +from infinigen.core.util.math import FixedSeed, int_hash +from infinigen.core.util.random import random_general as rg + + +def shader_shelves_white(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + rgb = kwargs.get('rgb', [0.9, 0.9, 0.9]) + base_color = (*rgb, 1.) + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': base_color, + 'Roughness': kwargs.get('roughness', 0.9)}) + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, + attrs={'is_active_output': True}) + + +def shader_shelves_white_sampler(): + params = dict() + v = uniform(0.7, 1.0) + base_color = [v * (1. + normal(0, 0.005)), + v * (1. + normal(0, 0.005)), + v * (1. + normal(0, 0.005))] + params['rgb'] = base_color + params['roughness'] = uniform(0.7, 1.0) + return params + + +def shader_shelves_black_metallic(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + color = (*kwargs.get('rgb', [0., 0., 0.]), 1.) + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={ + 'Base Color': color, + 'Metallic': kwargs.get('metallic', 0.65)}) + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, + attrs={'is_active_output': True}) + + +def shader_shelves_black_metallic_sampler(): + params = dict() + base_color = [uniform(0, 0.01), uniform(0, 0.01), uniform(0, 0.01)] + params['rgb'] = base_color + params['metallic'] = uniform(0.45, 0.75) + return params + + +def shader_shelves_white_metallic(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + rgb = kwargs.get('rgb', [0.9, 0.9, 0.9]) + base_color = (*rgb, 1.) + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': base_color, + 'Metallic': kwargs.get('metallic', 0.65)}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, + attrs={'is_active_output': True}) + + +def shader_shelves_white_metallic_sampler(): + params = dict() + v = uniform(0.7, 1.0) + base_color = [v * (1. + normal(0, 0.005)), + v * (1. + normal(0, 0.005)), + v * (1. + normal(0, 0.005))] + params['rgb'] = base_color + params['metallic'] = uniform(0.45, 0.75) + return params + + +def shader_shelves_black_wood(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.5 of the node_transpiler + + texture_coordinate_1 = nw.new_node(Nodes.TextureCoord) + wave_scale = kwargs.get('wave_scale', 2.0) + if kwargs.get('z_axis_texture', False): + wave_scale = (wave_scale, wave_scale, 0.1) + else: + wave_scale = (wave_scale, 0.1, 0.1) + + mapping_1 = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate_1.outputs["Object"], + 'Scale': (0.1, 0.1, 2.0) if kwargs.get('z_axis_texture', False) else (0.1, 2.0, 2.0)}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping_1, 'Scale': 100.0000, 'Detail': 10.0000, + 'Distortion': 2.0000}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': noise_texture_1.outputs["Fac"], 'Scale': 40.0000}) + + colorramp_1 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': voronoi_texture.outputs["Color"]}) + colorramp_1.color_ramp.elements[0].position = 0.0864 + colorramp_1.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + colorramp_1.color_ramp.elements[1].position = 0.1091 + colorramp_1.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + mapping = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], + 'Scale': wave_scale}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'Scale': 3.0000, 'Detail': 15.0000, + 'Distortion': 2.0000}) + + musgrave_texture = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': noise_texture.outputs["Fac"], 'Scale': 20.0000, + 'Detail': 3.0000}) + + mix_1 = nw.new_node(Nodes.Mix, + input_kwargs={6: musgrave_texture, 7: noise_texture.outputs["Color"]}, + attrs={'data_type': 'RGBA'}) + + colorramp_2 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': mix_1.outputs[2]}) + colorramp_2.color_ramp.elements[0].position = 0.0818 + colorramp_2.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + colorramp_2.color_ramp.elements[1].position = 0.8500 + colorramp_2.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + mix_2 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.6000, 6: colorramp_1.outputs["Color"], 7: colorramp_2.outputs["Color"]}, + attrs={'data_type': 'RGBA'}) + + dark_scale = kwargs.get('dark_scale', 0.005) + gray_scale = kwargs.get('gray_scale', 0.02) + color_scale = [*kwargs.get('rgb', [0.02, 0.002, 0.002]), 1.0] + colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': mix_2}) + colorramp.color_ramp.elements.new(0) + colorramp.color_ramp.elements[0].position = 0.15 + colorramp.color_ramp.elements[0].color = [dark_scale, dark_scale, dark_scale, 1.0000] + colorramp.color_ramp.elements[1].position = 0.5 + colorramp.color_ramp.elements[1].color = [gray_scale, gray_scale, gray_scale, 1.0000] + colorramp.color_ramp.elements[2].position = 1.0000 + colorramp.color_ramp.elements[2].color = color_scale + + mix_3 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.0040, 6: colorramp_1.outputs["Color"], 7: colorramp_2.outputs["Color"]}, + attrs={'data_type': 'RGBA'}) + + bump = nw.new_node(Nodes.Bump, input_kwargs={'Strength': 0.5000, 'Height': mix_3.outputs[2]}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': colorramp.outputs["Color"], + 'Roughness': kwargs.get('roughness', 0.9), 'Normal': bump}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, + attrs={'is_active_output': True}) + + +def shader_shelves_black_wood_sampler(): + params = dict() + params['wave_scale'] = uniform(1., 3.) + params['dark_scale'] = uniform(0.0, 0.01) + params['gray_scale'] = uniform(0.01, 0.03) + params['rgb'] = [uniform(0.015, 0.035), uniform(0., 0.01), uniform(0.0, 0.01)] + params['roughness'] = uniform(0.75, 1.0) + return params + + +def shader_shelves_wood(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.5 of the node_transpiler + + texture_coordinate_1 = nw.new_node(Nodes.TextureCoord) + wave_scale = kwargs.get('wave_scale', 2.0) + if kwargs.get('z_axis_texture', False): + wave_scale = (wave_scale, wave_scale, 0.1) + else: + wave_scale = (wave_scale, 0.1, 0.1) + + mapping_1 = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate_1.outputs["Object"], + 'Scale': (0.1, 0.1, 2.0) if kwargs.get('z_axis_texture', False) else (0.1, 2.0, 2.0)}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping_1, 'Scale': 100.0000, 'Detail': 10.0000, + 'Distortion': 2.0000}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': noise_texture_1.outputs["Fac"], 'Scale': 40.0000}) + + colorramp_1 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': voronoi_texture.outputs["Color"]}) + colorramp_1.color_ramp.elements[0].position = 0.0864 + colorramp_1.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + colorramp_1.color_ramp.elements[1].position = 0.1091 + colorramp_1.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + mapping = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], + 'Scale': wave_scale}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'Scale': 3.0000, 'Detail': 15.0000, + 'Distortion': 2.0000}) + + musgrave_texture = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': noise_texture.outputs["Fac"], 'Scale': 20.0000, + 'Detail': 3.0000}) + + mix_1 = nw.new_node(Nodes.Mix, + input_kwargs={6: musgrave_texture, 7: noise_texture.outputs["Color"]}, + attrs={'data_type': 'RGBA'}) + + colorramp_2 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': mix_1.outputs[2]}) + colorramp_2.color_ramp.elements[0].position = 0.0818 + colorramp_2.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + colorramp_2.color_ramp.elements[1].position = 0.8500 + colorramp_2.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + mix_2 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.6000, 6: colorramp_1.outputs["Color"], 7: colorramp_2.outputs["Color"]}, + attrs={'data_type': 'RGBA'}) + + bright_hsv = kwargs.get('bright_hsv', [0.068, 0.665, 0.805]) + mid_hsv = kwargs.get('mid_hsv', [0.042, 0.853, 0.447]) + dark_hsv = kwargs.get('dark_hsv', [0.043, 0.882, 0.183]) + + colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': mix_2}) + colorramp.color_ramp.elements.new(0) + colorramp.color_ramp.elements[0].position = 0.02 + colorramp.color_ramp.elements[0].color = hsv2rgba(dark_hsv) + colorramp.color_ramp.elements[1].position = 0.11 + colorramp.color_ramp.elements[1].color = hsv2rgba(mid_hsv) + colorramp.color_ramp.elements[2].position = 0.8 + colorramp.color_ramp.elements[2].color = hsv2rgba(bright_hsv) + + mix_3 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.0040, 6: colorramp_1.outputs["Color"], 7: colorramp_2.outputs["Color"]}, + attrs={'data_type': 'RGBA'}) + + bump = nw.new_node(Nodes.Bump, input_kwargs={'Strength': 0.5000, 'Height': mix_3.outputs[2]}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': colorramp.outputs["Color"], + 'Roughness': kwargs.get('roughness', 0.9), 'Normal': bump}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, + attrs={'is_active_output': True}) + + +def shader_shelves_wood_sampler(): + params = dict() + params['bright_hsv'] = [uniform(0.03, 0.09), uniform(0.5, 0.7), uniform(0.7, 1.0)] + params['mid_hsv'] = [uniform(0.02, 0.06), uniform(0.6, 1.0), uniform(0.3, 0.6)] + params['dark_hsv'] = [uniform(0.03, 0.05), uniform(0.6, 1.0), uniform(0.1, 0.3)] + params['wave_scale'] = uniform(1., 3.) + params['roughness'] = uniform(0.75, 1.0) + return params + + +def get_shelf_material(name, **kwargs): + match name: + case 'white': + shader_func = np.random.choice([shader_shelves_white, shader_rough_plastic], p=[.6, .4]) + case 'black_wood': + shader_func = np.random.choice([shader_shelves_black_wood, wood.shader_wood], p=[.6, .4]) + case 'wood': + shader_func = np.random.choice([shader_shelves_wood, wood.shader_wood], p=[.6, .4]) + + case 'glass': + shader_func = shader_glass + case _: + shader_func = np.random.choice([shader_shelves_white, shader_rough_plastic, + shader_shelves_black_wood, wood.shader_wood, + shader_shelves_wood], p=[.3, .2, .3, .1, .1]) + r = uniform() + if name == 'metal': + shader_func = np.random.choice(metal_shader_list) + else: + shader_func = np.random.choice([shader_shelves_white, shader_rough_plastic, + shader_shelves_black_wood, wood.shader_wood, + shader_shelves_wood], p=[.3, .2, .3, .1, .1]) + # elif r < .3: + # shader_func = rg(fabric_shader_list) + return surface.shaderfunc_to_material(shader_func, **kwargs) diff --git a/infinigen/assets/materials/soil.py b/infinigen/assets/materials/soil.py index 48c7e7077..02029a439 100644 --- a/infinigen/assets/materials/soil.py +++ b/infinigen/assets/materials/soil.py @@ -1,7 +1,7 @@ # 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: Ankit Goyal, Zeyu Ma +# Authors: Ankit Goyal, Zeyu Ma, Lingjie Mei import gin @@ -58,7 +58,7 @@ def nodegroup_pebble(nw): position = nw.new_node('ShaderNodeNewGeometry') else: position = nw.new_node(Nodes.InputPosition) - + # Code generated using version 2.3.1 of the node_transpiler noise1_w = nw.new_node(Nodes.Value, label="noise1_w ~ U(0, 10)") @@ -122,9 +122,9 @@ def nodegroup_pebble_shader(nw): nodegroup_pebble(nw) -def shader_soil(nw): +def shader_soil(nw, random_seed=0): nw.force_input_consistency() - big_stone = geometry_soil(nw, geometry=False) + big_stone = geometry_soil(nw, random_seed=random_seed, geometry=False) # Code generated using version 2.3.1 of the node_transpiler darkness = 1.5 soil_col_1 = random_color_neighbour((0.28 / darkness, 0.11 / darkness, 0.042 / darkness, 1.0), 0.05, 0.1, 0.1) @@ -220,7 +220,7 @@ def geometry_soil(nw, selection=None, random_seed=0, geometry=True): else: position = nw.new_node(Nodes.InputPosition) normal = nw.new_node(Nodes.InputNormal) - + with FixedSeed(random_seed): # Code generated using version 2.3.1 of the node_transpiler diff --git a/infinigen/assets/materials/stone.py b/infinigen/assets/materials/stone.py index f05b5a392..185fb12c8 100644 --- a/infinigen/assets/materials/stone.py +++ b/infinigen/assets/materials/stone.py @@ -23,9 +23,9 @@ mod_name = "geo_stone" name = "stone" -def shader_stone(nw): +def shader_stone(nw, random_seed=0): nw.force_input_consistency() - stone_base_color, stone_roughness = geo_stone(nw, geometry=False) + stone_base_color, stone_roughness = geo_stone(nw, random_seed=random_seed, geometry=False) principled_bsdf = nw.new_node( Nodes.PrincipledBSDF, @@ -193,26 +193,26 @@ def geo_stone(nw, selection=None, random_seed=0, geometry=True): noise_texture_3 = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Vector': position, "W": nw.new_value(uniform(0, 10), "noise_texture_3_w"), 'Scale': nw.new_value(sample_ratio(5, 3/4, 4/3), "noise_texture_3_scale")}, attrs={"noise_dimensions": "4D"}) - + subtract = nw.new_node(Nodes.Math, input_kwargs={0: noise_texture_3.outputs["Fac"]}, attrs={'operation': 'SUBTRACT'}) - + multiply_8 = nw.new_node(Nodes.VectorMath, input_kwargs={0: subtract, 1: normal}, attrs={'operation': 'MULTIPLY'}) - + value_5 = nw.new_node(Nodes.Value) value_5.outputs[0].default_value = 0.05 - + multiply_9 = nw.new_node(Nodes.VectorMath, input_kwargs={0: multiply_8.outputs["Vector"], 1: value_5}, attrs={'operation': 'MULTIPLY'}) - + noise_texture_4 = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Vector': position, 'Scale': nw.new_value(sample_ratio(20, 3/4, 4/3), "noise_texture_4_scale"), "W": nw.new_value(uniform(0, 10), "noise_texture_4_w")}, attrs={'noise_dimensions': '4D'}) - + colorramp_5 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture_4.outputs["Fac"]}) colorramp_5.color_ramp.elements.new(0) @@ -225,18 +225,18 @@ def geo_stone(nw, selection=None, random_seed=0, geometry=True): colorramp_5.color_ramp.elements[2].color = (0.5, 0.5, 0.5, 1.0) colorramp_5.color_ramp.elements[3].position = 1.0 colorramp_5.color_ramp.elements[3].color = (1.0, 1.0, 1.0, 1.0) - + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: colorramp_5.outputs["Color"]}, attrs={'operation': 'SUBTRACT'}) - + multiply_10 = nw.new_node(Nodes.VectorMath, input_kwargs={0: subtract_1, 1: normal}, attrs={'operation': 'MULTIPLY'}) - + value_6 = nw.new_node(Nodes.Value) value_6.outputs[0].default_value = 0.1 - + multiply_11 = nw.new_node(Nodes.VectorMath, input_kwargs={0: multiply_10.outputs["Vector"], 1: value_6}, attrs={'operation': 'MULTIPLY'}) @@ -258,7 +258,7 @@ def geo_stone(nw, selection=None, random_seed=0, geometry=True): colorramp.color_ramp.elements[2].color = (color3, color3, color3, 1.0) sample_color(colorramp.color_ramp.elements[1].color, offset=0.01) sample_color(colorramp.color_ramp.elements[2].color, offset=0.01) - + stone_base_color = nw.new_node( Nodes.MixRGB, input_kwargs={ @@ -280,7 +280,7 @@ def geo_stone(nw, selection=None, random_seed=0, geometry=True): stone_roughness = colorramp_3 - if geometry: + if geometry: groupinput = nw.new_node(Nodes.GroupInput) noise_params = {"scale": ("uniform", 10, 20), "detail": 9, "roughness": 0.6, "zscale": ("log_uniform", 0.007, 0.013)} offset = nw.add(offset, geo_MOUNTAIN_general(nw, 3, noise_params, 0, {}, {})) diff --git a/infinigen/assets/materials/stone_and_concrete/concrete.py b/infinigen/assets/materials/stone_and_concrete/concrete.py new file mode 100644 index 000000000..a41f00ec6 --- /dev/null +++ b/infinigen/assets/materials/stone_and_concrete/concrete.py @@ -0,0 +1,242 @@ +# 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: Yiming Zuo +# Acknowledgement: This file draws inspiration https://www.youtube.com/watch?v=XDqRa0ExDqs by Ryan King Art + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint + +from infinigen.assets.materials import common +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +@node_utils.to_nodegroup('nodegroup_crack', singleton=False, type='ShaderNodeTree') +def nodegroup_crack(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Seed', 0.0000), + ('NodeSocketFloat', 'Amount', 1.0000), + ('NodeSocketFloat', 'Scale', 0.0000), + ('NodeSocketFloatFactor', 'Snake Crack', 0.3000)]) + + texture_coordinate_1 = nw.new_node(Nodes.TextureCoord) + + musgrave_texture_2 = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': texture_coordinate_1.outputs["Object"], 'W': group_input.outputs["Seed"], 'Scale': group_input.outputs["Scale"], 'Detail': 15.0000, 'Dimension': 0.2000}, + attrs={'musgrave_dimensions': '4D'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Scale"]}, attrs={'operation': 'MULTIPLY'}) + + noise_texture_2 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': texture_coordinate_1.outputs["Object"], 'W': group_input.outputs["Seed"], 'Scale': multiply, 'Detail': 15.0000}, + attrs={'noise_dimensions': '4D'}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': noise_texture_2.outputs["Fac"], 'Scale': 1.2000}, + attrs={'feature': 'DISTANCE_TO_EDGE'}) + + map_range_4 = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': voronoi_texture.outputs["Distance"], 2: 0.0200, 3: 2.0000, 4: 0.0000}) + + mix_7 = nw.new_node(Nodes.Mix, + input_kwargs={0: group_input.outputs["Snake Crack"], 6: musgrave_texture_2, 7: map_range_4.outputs["Result"]}, + attrs={'blend_type': 'ADD', 'data_type': 'RGBA'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Scale"], 1: 0.6000}, attrs={'operation': 'MULTIPLY'}) + + musgrave_texture_3 = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': texture_coordinate_1.outputs["Object"], 'W': group_input.outputs["Seed"], 'Scale': multiply_1, 'Detail': 15.0000, 'Dimension': 1.0000}, + attrs={'musgrave_dimensions': '4D'}) + + map_range_2 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': group_input.outputs["Amount"], 3: 1.0000, 4: -0.5000}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: map_range_2.outputs["Result"], 1: 0.1000}) + + map_range_1 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': musgrave_texture_3, 1: map_range_2.outputs["Result"], 2: add}) + + mix_4 = nw.new_node(Nodes.Mix, + input_kwargs={0: 1.0000, 6: mix_7.outputs[2], 7: map_range_1.outputs["Result"]}, + attrs={'blend_type': 'DARKEN', 'data_type': 'RGBA'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Scale"], 1: 0.3000}, attrs={'operation': 'MULTIPLY'}) + + musgrave_texture_4 = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': texture_coordinate_1.outputs["Object"], 'W': group_input.outputs["Seed"], 'Scale': multiply_2, 'Detail': 15.0000, 'Dimension': 1.0000}, + attrs={'musgrave_dimensions': '4D'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: map_range_2.outputs["Result"], 1: 0.1000}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': musgrave_texture_4, 1: map_range_2.outputs["Result"], 2: add_1}) + + mix_5 = nw.new_node(Nodes.Mix, + input_kwargs={0: 1.0000, 6: mix_4.outputs[2], 7: map_range.outputs["Result"]}, + attrs={'blend_type': 'DARKEN', 'data_type': 'RGBA'}) + + color_ramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': mix_5.outputs[2]}) + color_ramp.color_ramp.elements[0].position = 0.0000 + color_ramp.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + color_ramp.color_ramp.elements[1].position = 1.0000 + color_ramp.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Color': color_ramp.outputs["Color"]}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_concrete', singleton=False, type='ShaderNodeTree') +def nodegroup_concrete(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input_1 = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketColor', 'Base Color', (0.8000, 0.8000, 0.8000, 1.0000)), + ('NodeSocketFloat', 'Scale', 0.0000), + ('NodeSocketFloat', 'Seed', 0.0000), + ('NodeSocketFloat', 'Roughness', 0.0000), + ('NodeSocketFloat', 'Crack Amount', 0.0000), + ('NodeSocketFloat', 'Crack Scale', 0.0000), + ('NodeSocketFloatFactor', 'Snake Crack', 0.3000)]) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input_1.outputs["Scale"], 1: 10.0000}, + attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: multiply, 1: group_input_1.outputs["Crack Scale"]}, + attrs={'operation': 'MULTIPLY'}) + + group = nw.new_node(nodegroup_crack().name, + input_kwargs={'Seed': group_input_1.outputs["Seed"], 'Amount': group_input_1.outputs["Crack Amount"], 'Scale': multiply_1, 'Snake Crack': group_input_1.outputs["Snake Crack"]}) + + map_range_3 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': group}) + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input_1.outputs["Scale"], 1: 2.0000}, attrs={'operation': 'MULTIPLY'}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'W': group_input_1.outputs["Seed"], 'Scale': multiply_2, 'Detail': 15.0000}, + attrs={'noise_dimensions': '4D'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input_1.outputs["Scale"], 1: 5.0000}, attrs={'operation': 'MULTIPLY'}) + + musgrave_texture_1 = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'W': group_input_1.outputs["Seed"], 'Scale': multiply_3, 'Detail': 15.0000, 'Dimension': 1.0000, 'Lacunarity': 3.0000}, + attrs={'musgrave_dimensions': '4D'}) + + mix_2 = nw.new_node(Nodes.Mix, input_kwargs={6: musgrave_texture_1}, attrs={'data_type': 'RGBA'}) + + hue_saturation_value_1 = nw.new_node('ShaderNodeHueSaturation', + input_kwargs={'Value': 0.6000, 'Color': group_input_1.outputs["Base Color"]}) + + multiply_4 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input_1.outputs["Scale"], 1: 20.0000}, + attrs={'operation': 'MULTIPLY'}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'W': group_input_1.outputs["Seed"], 'Scale': multiply_4, 'Detail': 15.0000, 'Distortion': 0.2000}, + attrs={'noise_dimensions': '4D'}) + + multiply_5 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input_1.outputs["Scale"], 1: 20.0000}, + attrs={'operation': 'MULTIPLY'}) + + musgrave_texture = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'W': group_input_1.outputs["Seed"], 'Scale': multiply_5, 'Detail': 15.0000, 'Dimension': 0.2000}, + attrs={'musgrave_dimensions': '4D'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={6: noise_texture.outputs["Fac"], 7: musgrave_texture}, + attrs={'data_type': 'RGBA'}) + + hue_saturation_value = nw.new_node('ShaderNodeHueSaturation', + input_kwargs={'Value': 1.4000, 'Color': group_input_1.outputs["Base Color"]}) + + mix_1 = nw.new_node(Nodes.Mix, + input_kwargs={0: mix.outputs[2], 6: group_input_1.outputs["Base Color"], 7: hue_saturation_value}, + attrs={'data_type': 'RGBA'}) + + mix_3 = nw.new_node(Nodes.Mix, + input_kwargs={0: mix_2.outputs[2], 6: hue_saturation_value_1, 7: mix_1.outputs[2]}, + attrs={'data_type': 'RGBA'}) + + hue_saturation_value_2 = nw.new_node('ShaderNodeHueSaturation', + input_kwargs={'Value': noise_texture_1.outputs["Fac"], 'Fac': 0.2000, 'Color': mix_3.outputs[2]}) + + hue_saturation_value_3 = nw.new_node('ShaderNodeHueSaturation', + input_kwargs={'Value': 0.2000, 'Color': group_input_1.outputs["Base Color"]}) + + mix_6 = nw.new_node(Nodes.Mix, + input_kwargs={0: map_range_3.outputs["Result"], 6: hue_saturation_value_2, 7: hue_saturation_value_3}, + attrs={'data_type': 'RGBA'}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': mix_6.outputs[2], 'Roughness': group_input_1.outputs["Roughness"]}) + + multiply_6 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input_1.outputs["Crack Amount"], 1: 0.6000}, + attrs={'operation': 'MULTIPLY'}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: 5.0000}, attrs={'operation': 'MULTIPLY'}) + + group_1 = nw.new_node(nodegroup_crack().name, + input_kwargs={'Seed': group_input_1.outputs["Seed"], 'Amount': multiply_6, 'Scale': multiply_7}) + + multiply_8 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input_1.outputs["Roughness"], 1: 1.0000}, + attrs={'operation': 'MULTIPLY'}) + + multiply_9 = nw.new_node(Nodes.Math, input_kwargs={0: group_1, 1: multiply_8}, attrs={'operation': 'MULTIPLY'}) + + multiply_10 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_8, 1: group}, attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_9, 1: multiply_10}) + + value = nw.new_node(Nodes.Value) + value.outputs[0].default_value = 0.3000 + + multiply_11 = nw.new_node(Nodes.Math, + input_kwargs={0: value, 1: group_input_1.outputs["Roughness"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_12 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_11, 1: mix_1.outputs[2]}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: multiply_12}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'BSDF': principled_bsdf, 'Displacement': add_1}, + attrs={'is_active_output': True}) + +def shader_concrete(nw: NodeWrangler, scale=1.0, base_color=None, seed=None, + roughness=None, crack_amount=None, crack_scale=None, snake_crack=None, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + if seed is None: + seed = uniform(-1000.0, 1000.0) + if roughness is None: + roughness = uniform(0.5, 1.0) + if crack_amount is None: + crack_amount = uniform(0.2, 0.8) + if crack_scale is None: + crack_scale = uniform(1.0, 3.0) + if snake_crack is None: + snake_crack = uniform(0.0, 1.0) + if base_color is None: + base_color = color_category('concrete') + + group = nw.new_node(nodegroup_concrete().name, + input_kwargs={'Base Color': base_color, 'Scale': scale, 'Seed': seed, + 'Roughness': roughness, 'Crack Amount': crack_amount, + 'Crack Scale': crack_scale, 'Snake Crack': snake_crack}) + + displacement_1 = nw.new_node('ShaderNodeDisplacement', + input_kwargs={'Height': group.outputs["Displacement"], 'Midlevel': 0.0000, 'Scale': 0.0500}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': group.outputs["BSDF"], 'Displacement': displacement_1}, + attrs={'is_active_output': True}) + +def apply(obj, selection=None, **kwargs): + common.apply(obj, shader_concrete, selection=selection, **kwargs) diff --git a/infinigen/assets/materials/table_marble.py b/infinigen/assets/materials/table_marble.py new file mode 100644 index 000000000..740f9ef75 --- /dev/null +++ b/infinigen/assets/materials/table_marble.py @@ -0,0 +1,158 @@ +# 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: Yiming Zuo +# Acknowledgement: This file draws inspiration https://www.youtube.com/watch?v=wTzk9T06gdw by Ryan King Arts + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category, hsv2rgba +from infinigen.core import surface + +def shader_marble(nw: NodeWrangler,**kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + scale = nw.new_node(Nodes.VectorMath, input_kwargs={0: texture_coordinate.outputs["Object"]}, attrs={'operation': 'SCALE'}) + + vector_rotate = nw.new_node(Nodes.VectorRotate, + input_kwargs={'Vector': scale.outputs["Vector"]}, + attrs={'rotation_type': 'EULER_XYZ'}) + + seed = nw.new_node(Nodes.Value, label='seed') + seed.outputs[0].default_value = 0.0000 + + scale_1 = nw.new_node(Nodes.Value, label='scale') + scale_1.outputs[0].default_value = 3.0000 + + add = nw.new_node(Nodes.Math, input_kwargs={0: scale_1, 1: 1.0000}) + + noise_texture_2 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': vector_rotate, 'W': seed, 'Scale': add, 'Detail': 15.0000}, + attrs={'noise_dimensions': '4D'}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': noise_texture_2.outputs["Fac"], 1: 0.4800, 2: 0.6000}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': vector_rotate, 'W': seed, 'Scale': scale_1, 'Detail': 15.0000}, + attrs={'noise_dimensions': '4D'}) + + noise_texture_3 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': noise_texture.outputs["Color"], 'Scale': 8.0000, 'Detail': 15.0000}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': noise_texture_3.outputs["Color"], 'W': 1.6400, 'Scale': 3.0000}, + attrs={'feature': 'DISTANCE_TO_EDGE', 'voronoi_dimensions': '4D'}) + + colorramp_1 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': voronoi_texture.outputs["Distance"]}) + colorramp_1.color_ramp.elements[0].position = 0.0000 + colorramp_1.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + colorramp_1.color_ramp.elements[1].position = 0.0300 + colorramp_1.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: map_range.outputs["Result"], 1: colorramp_1.outputs["Color"]}, + attrs={'operation': 'MULTIPLY'}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': noise_texture.outputs["Color"], 'W': seed, 'Scale': 8.0000, 'Detail': 15.0000}, + attrs={'noise_dimensions': '4D'}) + + mix_1 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.8000, 6: noise_texture.outputs["Fac"], 7: noise_texture_1.outputs["Fac"]}, + attrs={'data_type': 'RGBA'}) + + colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': mix_1.outputs[2]}) + colorramp.color_ramp.elements[0].position = 0.3000 + colorramp.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + colorramp.color_ramp.elements[1].position = 0.9000 + colorramp.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + mix = nw.new_node(Nodes.Mix, + input_kwargs={0: multiply, 6: colorramp.outputs["Color"], 7: (0.0376, 0.0179, 0.0033, 1.0000)}, + attrs={'data_type': 'RGBA'}) + + bump = nw.new_node('ShaderNodeBump', input_kwargs={'Strength': 0.0200, 'Height': multiply}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': mix_1.outputs[2], 'Specular': 0.6000, 'Roughness': 0.1000, 'Normal': bump}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) + +def shader_wood(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + scale = nw.new_node(Nodes.VectorMath, input_kwargs={0: texture_coordinate.outputs["Object"]}, attrs={'operation': 'SCALE'}) + + vector_rotate = nw.new_node(Nodes.VectorRotate, + input_kwargs={'Vector': scale.outputs["Vector"]}, + attrs={'rotation_type': 'EULER_XYZ'}) + + mapping_2 = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': vector_rotate, 'Scale': (5.0000, 100.0000, 100.0000)}) + + seed = nw.new_node(Nodes.Value, label='seed') + seed.outputs[0].default_value = 0.0000 + + musgrave_texture_2 = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': mapping_2, 'W': seed, 'Scale': 10.0000, 'Detail': 15.0000, 'Dimension': 7.0000}, + attrs={'musgrave_dimensions': '4D'}) + + map_range_2 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': musgrave_texture_2, 3: 1.0000, 4: -1.0000}) + + mapping_1 = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': vector_rotate}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping_1, 'W': seed, 'Scale': 0.5000, 'Detail': 1.0000, 'Distortion': 1.1000}, + attrs={'noise_dimensions': '4D'}) + + musgrave_texture_1 = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'W': seed, 'Scale': noise_texture_1.outputs["Fac"], 'Detail': 15.0000, 'Dimension': 0.2000, 'Lacunarity': 2.4000}, + attrs={'musgrave_dimensions': '4D'}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': musgrave_texture_1, 3: -1.4000, 4: 1.5000}) + + map_range_1 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': map_range.outputs["Result"], 3: 1.0000, 4: 0.5000}) + + mapping = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': vector_rotate, 'Scale': (0.1500, 1.0000, 0.1500)}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'W': seed, 'Detail': 5.0000, 'Distortion': 1.0000}, + attrs={'noise_dimensions': '4D'}) + + musgrave_texture = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': noise_texture.outputs["Fac"], 'W': seed, 'Scale': 4.0000, 'Detail': 10.0000, 'Dimension': 0.0000}, + attrs={'musgrave_dimensions': '4D'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={6: noise_texture.outputs["Fac"], 7: musgrave_texture}, + attrs={'data_type': 'RGBA'}) + + mix_1 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.9000, 6: map_range_1.outputs["Result"], 7: mix.outputs[2]}, + attrs={'data_type': 'RGBA', 'blend_type': 'MULTIPLY'}) + + mix_2 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.9500, 6: map_range_2.outputs["Result"], 7: mix_1.outputs[2]}, + attrs={'data_type': 'RGBA', 'blend_type': 'MULTIPLY'}) + + rgb = nw.new_node(Nodes.RGB) + rgb.outputs[0].default_value = (0.0242, 0.0056, 0.0027, 1.0000) + + rgb_1 = nw.new_node(Nodes.RGB) + rgb_1.outputs[0].default_value = (0.5089, 0.2122, 0.0685, 1.0000) + + mix_3 = nw.new_node(Nodes.Mix, input_kwargs={0: mix_2.outputs[2], 6: rgb, 7: rgb_1}, attrs={'data_type': 'RGBA'}) + + bump = nw.new_node('ShaderNodeBump', input_kwargs={'Strength': 0.2000, 'Height': mix_2.outputs[2]}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Base Color': mix_3.outputs[2], 'Normal': bump}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) diff --git a/infinigen/assets/materials/table_materials.py b/infinigen/assets/materials/table_materials.py new file mode 100644 index 000000000..96c8b5756 --- /dev/null +++ b/infinigen/assets/materials/table_materials.py @@ -0,0 +1,163 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +import numpy as np +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category, hsv2rgba, rgb2hsv +from infinigen.core import surface +from infinigen.core.util.random import log_uniform + + +def shader_marble(nw: NodeWrangler,**kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + scale = nw.new_node(Nodes.VectorMath, input_kwargs={0: texture_coordinate.outputs["Object"]}, attrs={'operation': 'SCALE'}) + + vector_rotate = nw.new_node(Nodes.VectorRotate, + input_kwargs={'Vector': scale.outputs["Vector"]}, + attrs={'rotation_type': 'EULER_XYZ'}) + + seed = nw.new_node(Nodes.Value, label='seed') + seed.outputs[0].default_value = 0.0000 + + scale_1 = nw.new_node(Nodes.Value, label='scale') + scale_1.outputs[0].default_value = 3.0000 + + add = nw.new_node(Nodes.Math, input_kwargs={0: scale_1, 1: 1.0000}) + + noise_texture_2 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': vector_rotate, 'W': seed, 'Scale': add, 'Detail': 15.0000}, + attrs={'noise_dimensions': '4D'}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': noise_texture_2.outputs["Fac"], 1: 0.4800, 2: 0.6000}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': vector_rotate, 'W': seed, 'Scale': scale_1, 'Detail': 15.0000}, + attrs={'noise_dimensions': '4D'}) + + noise_texture_3 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': noise_texture.outputs["Fac"], 'Scale': 8.0000, 'Detail': 15.0000}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': noise_texture_3.outputs["Fac"], 'W': 1.6400, 'Scale': 3.0000}, + attrs={'feature': 'DISTANCE_TO_EDGE', 'voronoi_dimensions': '4D'}) + + colorramp_1 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': voronoi_texture.outputs["Distance"]}) + colorramp_1.color_ramp.elements[0].position = 0.0000 + colorramp_1.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + colorramp_1.color_ramp.elements[1].position = 0.0300 + colorramp_1.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: map_range.outputs["Result"], 1: colorramp_1.outputs["Color"]}, + attrs={'operation': 'MULTIPLY'}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': noise_texture.outputs["Fac"], 'W': seed, 'Scale': 8.0000, 'Detail': 15.0000}, + attrs={'noise_dimensions': '4D'}) + + mix_1 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.8000, 6: noise_texture.outputs["Fac"], 7: noise_texture_1.outputs["Fac"]}, + attrs={'data_type': 'RGBA'}) + + colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': mix_1.outputs[2]}) + colorramp.color_ramp.elements[0].position = 0.3000 + colorramp.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + colorramp.color_ramp.elements[1].position = 0.9000 + colorramp.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + mix = nw.new_node(Nodes.Mix, + input_kwargs={0: multiply, 6: colorramp.outputs["Color"], 7: (0.0376, 0.0179, 0.0033, 1.0000)}, + attrs={'data_type': 'RGBA'}) + + bump = nw.new_node('ShaderNodeBump', input_kwargs={'Strength': 0.0200, 'Height': multiply}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': mix_1.outputs[2], 'Specular': 0.6000, 'Roughness': 0.1000, 'Normal': bump}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) + +def perturb(hsv): + return np.array([hsv[0]+uniform(-.02,.02), hsv[1]+uniform(-.2,.2), hsv[2]*log_uniform(.5,2.)]) + +def shader_wood(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + scale = nw.new_node(Nodes.VectorMath, input_kwargs={0: texture_coordinate.outputs["Object"]}, attrs={'operation': 'SCALE'}) + + vector_rotate = nw.new_node(Nodes.VectorRotate, + input_kwargs={'Vector': scale.outputs["Vector"]}, + attrs={'rotation_type': 'EULER_XYZ'}) + + mapping_2 = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': vector_rotate, 'Scale': (5.0000, 100.0000, 100.0000)}) + + seed = nw.new_node(Nodes.Value, label='seed') + seed.outputs[0].default_value = 0.0000 + + musgrave_texture_2 = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': mapping_2, 'W': seed, 'Scale': 10.0000, 'Detail': 15.0000, 'Dimension': 7.0000}, + attrs={'musgrave_dimensions': '4D'}) + + map_range_2 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': musgrave_texture_2, 3: 1.0000, 4: -1.0000}) + + mapping_1 = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': vector_rotate}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping_1, 'W': seed, 'Scale': 0.5000, 'Detail': 1.0000, 'Distortion': 1.1000}, + attrs={'noise_dimensions': '4D'}) + + musgrave_texture_1 = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'W': seed, 'Scale': noise_texture_1.outputs["Fac"], 'Detail': 15.0000, 'Dimension': 0.2000, 'Lacunarity': 2.4000}, + attrs={'musgrave_dimensions': '4D'}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': musgrave_texture_1, 3: -1.4000, 4: 1.5000}) + + map_range_1 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': map_range.outputs["Result"], 3: 1.0000, 4: 0.5000}) + + mapping = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': vector_rotate, 'Scale': (0.1500, 1.0000, 0.1500)}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'W': seed, 'Detail': 5.0000, 'Distortion': 1.0000}, + attrs={'noise_dimensions': '4D'}) + + musgrave_texture = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': noise_texture.outputs["Fac"], 'W': seed, 'Scale': 4.0000, 'Detail': 10.0000, 'Dimension': 0.0000}, + attrs={'musgrave_dimensions': '4D'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={6: noise_texture.outputs["Fac"], 7: musgrave_texture}, + attrs={'data_type': 'RGBA'}) + + mix_1 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.9000, 6: map_range_1.outputs["Result"], 7: mix.outputs[2]}, + attrs={'data_type': 'RGBA', 'blend_type': 'MULTIPLY'}) + + mix_2 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.9500, 6: map_range_2.outputs["Result"], 7: mix_1.outputs[2]}, + attrs={'data_type': 'RGBA', 'blend_type': 'MULTIPLY'}) + + rgb = nw.new_node(Nodes.RGB) + rgb.outputs[0].default_value = hsv2rgba(perturb(rgb2hsv(0.0242, 0.0056, 0.0027))) + + rgb_1 = nw.new_node(Nodes.RGB) + rgb_1.outputs[0].default_value = hsv2rgba(perturb(rgb2hsv(0.5089, 0.2122, 0.0685))) + + mix_3 = nw.new_node(Nodes.Mix, input_kwargs={0: mix_2.outputs[2], 6: rgb, 7: rgb_1}, attrs={'data_type': 'RGBA'}) + + bump = nw.new_node('ShaderNodeBump', input_kwargs={'Strength': 0.2000, 'Height': mix_2.outputs[2]}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Base Color': mix_3.outputs[2], 'Normal': bump}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) diff --git a/infinigen/assets/materials/text.py b/infinigen/assets/materials/text.py new file mode 100644 index 000000000..fe7ee649a --- /dev/null +++ b/infinigen/assets/materials/text.py @@ -0,0 +1,309 @@ +# 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: +# - Lingjie Mei: text & art generators +# - Stamatis Alexandropoulos: image postprocessing effects +# Acknowledgement: This file draws inspiration from https://www.youtube.com/watch?v=hpamCaVrbTk by Joey Carlino + +import colorsys +import inspect +import io +import string +import logging +import colorsys + +import matplotlib.font_manager +import matplotlib.pyplot as plt +from matplotlib.patches import Arrow, BoxStyle, Circle, Ellipse, FancyBboxPatch, Rectangle, RegularPolygon, Wedge + +import bpy +import numpy as np +from PIL import Image +from numpy.random import uniform, rand + +from infinigen.assets.utils.decorate import decimate +from infinigen.assets.utils.misc import generate_text +from infinigen.assets.utils.object import new_plane +from infinigen.assets.utils.uv import compute_uv_direction +from infinigen.assets.materials import common + +from infinigen.core.nodes.node_info import Nodes +from infinigen.core.nodes.node_wrangler import NodeWrangler +from infinigen.core.util.math import FixedSeed, clip_gaussian +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil + +from infinigen.core.util.random import random_general as rg + +logger = logging.getLogger(__name__) + +class Text: + font_names_all = matplotlib.font_manager.get_font_names() + default_font_name = 'DejaVu Sans' + patch_fns = 'weighted_choice', (2, Circle), (4, Rectangle), (1, Wedge), (1, RegularPolygon), (1, Ellipse), ( + 2, Arrow), (2, FancyBboxPatch) + hatches = {'/', '\\', '|', '-', '+', 'x', 'o', 'O', '.', '*'} + font_weights = ['normal', 'bold', 'heavy'] + font_styles = ['normal', 'italic', 'oblique'] + + def __init__(self, factory_seed, has_barcode=True, emission=0): + self.factory_seed = factory_seed + with FixedSeed(self.factory_seed): + self.size = 4 + self.dpi = 100 + self.colormap = self.build_sequential_colormap() if uniform() < .5 else \ + self.build_diverging_colormap() + self.white_chance = .03 + self.black_chance = .05 + + self.n_patches = np.random.randint(5, 8) + self.force_horizontal = uniform() < .75 + + self.font_names = np.random.choice(self.font_names_all, 3) + self.n_texts = np.random.randint(2, 4) + + self.n_barcodes = 1 if has_barcode and uniform() < .5 else 0 + self.barcode_scale = uniform(.3, .6) + self.barcode_length = np.random.randint(25, 40) + self.barcode_aspect = log_uniform(1.5, 3) + + self.emission = emission + + @staticmethod + def build_diverging_colormap(): + count = 20 + hue = (uniform() + np.linspace(0, .5, count)) % 1 + mid = uniform(.6, .8) + lightness = np.concatenate( + [np.linspace(uniform(.1, .3), mid, count // 2), np.linspace(mid, uniform(.1, .3), count // 2)] + ) + saturation = np.concatenate([np.linspace(1, .5, count // 2), np.linspace(.5, 1, count // 2)]) + + # TODO hack + saturation *= uniform(0, 1) + lightness *= uniform(0.5, 1) + + return np.array([colorsys.hls_to_rgb(h, l, s) for h, l, s in zip(hue, lightness, saturation)]) + + @staticmethod + def build_sequential_colormap(): + count = 20 + hue = (uniform() + np.linspace(0, .5, count)) % 1 + lightness = np.linspace(uniform(.0), uniform(.6, .8), count) + saturation = np.concatenate([np.linspace(1, .5, count // 2), np.linspace(.5, 1, count // 2)]) + + # TODO hack + saturation *= uniform(0, 1) + lightness *= uniform(0.5, 1) + + return np.array([colorsys.hls_to_rgb(h, l, s) for h, l, s in zip(hue, lightness, saturation)]) + + @property + def random_color(self): + r = uniform() + if r < self.white_chance: + return np.array([1, 1, 1]) + elif r < self.white_chance + self.black_chance: + return np.array([0, 0, 0]) + else: + return self.colormap[np.random.randint(len(self.colormap))] + + @property + def random_colors(self): + while True: + c, d = self.random_color, self.random_color + if np.abs(c - d).sum() > .2: + return c, d + + def build_image(self, bbox): + fig = plt.figure(figsize=(self.size, self.size), dpi=self.dpi) + ax = fig.add_axes((0, 0, 1, 1)) + ax.set_facecolor(self.random_color) + locs = self.get_locs(bbox, self.n_patches + self.n_texts + self.n_barcodes) + self.add_divider(bbox) + self.add_patches(locs[:self.n_patches], bbox) + self.add_texts(locs[self.n_patches:self.n_patches + self.n_texts]) + self.add_barcodes(locs[self.n_patches + self.n_texts:]) + buffer = io.BytesIO() + fig.savefig(buffer, format='png') + buffer.seek(0) + size = self.size * self.dpi + image = bpy.data.images.new('text_texture', width=size, height=size, alpha=True) + data = np.asarray(Image.open(buffer), dtype=np.float32)[::-1, :] / 255. + image.pixels.foreach_set(data.ravel()) + image.pack() + plt.close('all') + plt.clf() + return image + + @staticmethod + def loc_uniform(min_, max_, size=None): + ratio = .1 + return uniform(min_ + ratio * (max_ - min_), min_ + (1 - ratio) * (max_ - min_), size) + + @staticmethod + def scale_uniform(min_, max_): + return (max_ - min_) * log_uniform(.2, .8) + + def get_locs(self, bbox, n): + m = 8 * n + x, y = self.loc_uniform(bbox[0], bbox[1], m), self.loc_uniform(bbox[2], bbox[3], m) + return decimate(np.stack([x, y], -1), n) + + def add_divider(self, rs): + if uniform() < .6: return + a = 0 if uniform() < .7 else uniform(5, 10) + x, y = self.loc_uniform(rs[0], rs[1]), self.loc_uniform(rs[2], rs[3]) + if rs[0] == 0 or self.force_horizontal: + args_list = [[(0, y), 2, 2, a], [(0, y), 2, -2, -a], [(1, y), -2, -2, a], [(1, y), -2, 2, -a]] + else: + args_list = [[(x, 0), -2, 2, a], [(x, 0), 2, 2, -a], [(x, 1), 2, -2, a], [(x, 1), -2, -2, -a]] + args = args_list[np.random.randint(len(args_list))] + plt.gca().add_patch(Rectangle(*args[:-1], angle=args[-1], color=self.random_color)) + + def add_patches(self, locs, bbox): + for x, y in locs: + w, h = self.scale_uniform(bbox[0], bbox[1]), self.scale_uniform(bbox[2], bbox[3]) + x_, y_ = x - w / 2, y - h / 2 + r = min(w, h) / 2 + fn = rg(self.patch_fns) + kwargs = { + 'alpha': uniform(.5, .8) if uniform() < .2 else 1, + 'fill': uniform() < .2, + 'angle': 0 if uniform() < .8 else uniform(-30, 30), + 'orientation': uniform(0, np.pi * 2) + } + kwargs = {k: kwargs[k] for k, v in inspect.signature(fn).parameters.items() if k in kwargs} + face_color, edge_color = self.random_colors + kwargs.update( + { + 'facecolor': face_color, 'edgecolor': edge_color, + 'hatch': np.random.choice(list(self.hatches)) if uniform() < .3 else 'none', + 'linewidth': uniform(2, 5) + } + ) + match fn.__name__: + case Circle.__name__: + patch = Circle((x, y), r, **kwargs) + case Rectangle.__name__: + patch = Rectangle((x_, y_), w, h, **kwargs) + case Wedge.__name__: + start = uniform(0, 360) + patch = Wedge((x, y), r, start, start + uniform(0, 360), width=uniform(.2, .8) * r, **kwargs) + case RegularPolygon.__name__: + patch = RegularPolygon((x, y), np.random.randint(3, 9), radius=r, **kwargs) + case Ellipse.__name__: + patch = Ellipse((x, y), w, h, **kwargs) + case Arrow.__name__: + w_, h_ = (w if uniform() < .5 else -w), (h if uniform() < .5 else -h) + patch = Arrow(x - w_ / 2, y - h_ / 2, w, h, width=log_uniform(.6, 1.5), **kwargs) + case FancyBboxPatch.__name__: + pad = uniform(.2, .4) * min(w, h) + box_style = np.random.choice(list(BoxStyle.get_styles().values()))(pad=pad) + patch = FancyBboxPatch( + (x_, y_), w - pad, h - pad, box_style, mutation_scale=log_uniform(.6, 1.5), + mutation_aspect=log_uniform(.6, 1.5), **kwargs + ) + case _: + raise NotImplementedError + try: + plt.gca().add_patch(patch) + except MemoryError: + logger.warning(f'Failed to add patch {fn.__name__} at {x, y} with {w, h} due to MemoryError') + + def add_texts(self, locs): + for x, y in locs: + x = .5 + (x - .5) * .6 + text = generate_text() + family = np.random.permutation(self.font_names).tolist() + ['DejaVu Sans'] + color, background_color = self.random_colors + plt.figtext( + x, y, text, family=family, + size=log_uniform(.75, 1) * self.dpi * clip_gaussian(0.3, 0.2, 0.2, 0.65), + ha='center', va='center', c=color, + rotation=uniform(-10, 10), wrap=True, + fontweight=np.random.choice(self.font_weights), + fontstyle=np.random.choice(self.font_styles), + backgroundcolor=background_color + ) + + def add_barcodes(self, locs): + fig = plt.gcf() + for x, y in locs: + code = np.random.randint(0, 2, self.barcode_length) + h = self.barcode_scale / self.size + w = h * self.barcode_aspect + ax = fig.add_axes((x - w / 2, y - h / 2, w, h)) + ax.set_axis_off() + ax.imshow(code.reshape(1, -1), cmap='binary', aspect='auto', interpolation='nearest') + + def make_shader_func(self, bbox): + assert bbox[1] - bbox[0] > .001 and bbox[3] - bbox[2] >.001 + image = self.build_image(bbox) + + def shader_text(nw: NodeWrangler, **kwargs): + uv_map = nw.new_node(Nodes.UVMap) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': uv_map}) + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, input_kwargs={'Vector': reroute, 'Scale': 60.0000}) + + voronoi_texture_1 = nw.new_node(Nodes.VoronoiTexture, input_kwargs={'Vector': reroute, 'Scale': 60.0000}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={6: voronoi_texture.outputs["Position"], 7: voronoi_texture_1.outputs["Position"]}, + attrs={'data_type': 'RGBA'}) + + musgrave_texture = nw.new_node(Nodes.MusgraveTexture, input_kwargs={'Vector': reroute, 'Detail': 5.6000, 'Dimension': 1.4000}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': reroute, 'Scale': 35.4000, 'Detail': 3.3000, 'Roughness': 1.0000}) + + mix_3 = nw.new_node(Nodes.Mix, + input_kwargs={0: uniform(0.2,1.0), 6: musgrave_texture, 7: noise_texture_1.outputs["Color"]}, + attrs={'data_type': 'RGBA'}) + + mix_1 = nw.new_node(Nodes.Mix, input_kwargs={0: 0.0417, 6: mix.outputs[2], 7: mix_3.outputs[2]}, attrs={'data_type': 'RGBA'}) + + if rand() < 0.5: + mix_2 = nw.new_node(Nodes.Mix, input_kwargs={0: uniform(0, 0.4), 6: mix_1.outputs[2], 7: uv_map}, attrs={'data_type': 'RGBA'}) + else: + mix_2 = nw.new_node(Nodes.Mix, input_kwargs={0: 1.0, 6: mix_1.outputs[2], 7: uv_map}, attrs={'data_type': 'RGBA'}) + # mix_2 = nw.new_node(Nodes.Mix, input_kwargs={0: 0.7375, 6: uv, 7: mix_1.outputs[2]}, attrs={'data_type': 'RGBA'}) + color = nw.new_node(Nodes.ShaderImageTexture, [mix_2], attrs={'image': image}).outputs[0] + roughness = nw.new_node(Nodes.NoiseTexture) + if self.emission > 0: + emission = color + color = (0.05, 0.05, 0.05, 1) + roughness = 0.05 + else: + emission = None + principled_bsdf = nw.new_node( + Nodes.PrincipledBSDF, input_kwargs={ + 'Base Color': color, + 'Roughness': roughness, + 'Metallic': uniform(0, .5), + 'Specular': uniform(0, .2), + 'Emission': emission, + 'Emission Strength': self.emission + } + ) + nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}) + + return shader_text + + def apply(self, obj, selection=None, bbox=(0, 1, 0, 1), **kwargs): + common.apply(obj, self.make_shader_func(bbox), selection, **kwargs) + + +def apply(obj, selection=None, bbox=(0, 1, 0, 1), has_barcode=True, emission=0, **kwargs): + Text(np.random.randint(1e5), has_barcode, emission).apply(obj, selection, bbox, **kwargs) + + +def make_sphere(): + obj = new_plane() + obj.rotation_euler[0] = np.pi / 2 + butil.apply_transform(obj) + compute_uv_direction(obj, 'x', 'z') + return obj diff --git a/infinigen/assets/materials/text_no_barcode.py b/infinigen/assets/materials/text_no_barcode.py new file mode 100644 index 000000000..51021c598 --- /dev/null +++ b/infinigen/assets/materials/text_no_barcode.py @@ -0,0 +1,12 @@ +# 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: Lingjie Mei +import numpy as np + +from .text import Text +from .text import make_sphere + + +def apply(obj, selection=None, bbox=(0, 1, 0, 1), emission=0, **kwargs): + Text(np.random.randint(1e5), False, emission).apply(obj, selection, bbox, **kwargs) diff --git a/infinigen/assets/materials/tile.py b/infinigen/assets/materials/tile.py new file mode 100644 index 000000000..a926f67d9 --- /dev/null +++ b/infinigen/assets/materials/tile.py @@ -0,0 +1,346 @@ +# 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: Lingjie Mei +from inspect import signature + +import numpy as np +from numpy.random import uniform + +from . import ceramic, common +from .utils.surface_utils import perturb_coordinates +from ..utils.object import new_cube + +from ...core.nodes import NodeWrangler, Nodes +from ...core.util.math import FixedSeed +from ...core.util.random import log_uniform + +from functools import partial + + +def mix_shader(nw, base_shader, offset, rotations, mortar, alternating, selections): + n = len(selections) + 1 + seeds = np.random.randint(0, 1e7, n) if alternating else [np.random.randint(1e7)] * n + shaders, disps = [], [] + darken_factor = uniform(.4, 1.) + for i, seed in enumerate(seeds): + with FixedSeed(seed): + kwargs = {} + names = signature(base_shader).parameters + if 'random_seed' in names: + kwargs['random_seed'] = np.random.randint(1e7) + if 'w' in names: + kwargs['w'] = offset + if 'hscale' in names: + if i % 2 == 0: + kwargs['hscale'] = log_uniform(20, 30) + kwargs['vscale'] = .01 + else: + kwargs['hscale'] = .01 + kwargs['vscale'] = log_uniform(20, 30) + base_shader(nw, **kwargs) + bsdfs = nw.find('Bsdf') + n = nw.nodes[-1] + if len(bsdfs) > 0: + bsdf = bsdfs[-1] + links = nw.find_from(bsdf.inputs[0]) + if len(links) > 0: + color = links[0].from_socket + else: + color = bsdf.inputs[0].default_value + color = nw.new_node(Nodes.MixRGB, + input_kwargs={0: darken_factor, 6: color, 7: nw.scalar_sub(1, mortar)}, + attrs={'blend_type': 'MULTIPLY', 'data_type': 'RGBA'}).outputs[2] + nw.connect_input(color, bsdf.inputs[0]) + match type(n).__name__: + case Nodes.GroupOutput: + shaders.append(nw.find_from(n.inputs[0])[0].from_socket) + disp_links = nw.find_from(n.inputs[1]) + disps.append(disp_links[0].from_socket if len(disp_links) > 0 else None) + nw.nodes.remove(n) + case Nodes.PrincipledBSDF | Nodes.GlassBSDF | Nodes.GlossyBSDF | Nodes.TranslucentBSDF | \ + Nodes.TransparentBSDF | Nodes.TranslucentBSDF: + shaders.append(n.outputs[0]) + disps.append(None) + case _: + n = nw.find(Nodes.MaterialOutput)[-1] + shaders.append(nw.find_from(n.inputs['Surface'])[0].from_socket) + disp_links = nw.find_from(n.inputs['Displacement']) + disps.append(disp_links[0].from_socket if len(disp_links) > 0 else None) + shader = shaders[0] + disp = disps[0] + rotation = rotations[0] + for sel, sh, dis, rot in zip(selections, shaders[1:], disps[1:], rotations[1:]): + shader = nw.new_node(Nodes.MixShader, [sel, shader, sh]) + disp = nw.new_node(Nodes.Mix, input_kwargs={'Factor': sel, 'A': disp, 'B': dis}, + attrs={'data_type': 'VECTOR'}) + rotation = nw.new_node(Nodes.Mix, input_kwargs={'Factor': sel, 'A': rotation, 'B': rot}, + attrs={'data_type': 'FLOAT'}) + for node in nw.find(Nodes.TextureCoord)[1:] + nw.find(Nodes.NewGeometry): + perturb_coordinates(nw, node, offset, rotation) + disp = nw.add(disp, nw.new_node(Nodes.Displacement, input_kwargs={ + 'Height': nw.scalar_multiply(mortar, -uniform(.01, .02)), + 'Midlevel': 0. + })) + nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': shader, 'Displacement': disp}) + + +def shader_square_tile(nw: NodeWrangler, base_shader, vertical=False, alternating=None, scale=1, **kwargs): + if alternating is None: + alternating = uniform() < .75 + size = log_uniform(.2, .4) + vec = nw.new_node(Nodes.TextureCoord).outputs['Object'] + normal = nw.new_node(Nodes.ShaderNodeNormalMap).outputs['Normal'] + if vertical: + vec = nw.combine(nw.separate(nw.vector_math('CROSS_PRODUCT', vec, normal))[-1], nw.separate(vec)[-1], 0) + rotation = np.pi / 4 if uniform() < .3 else 0 + vec = nw.new_node( + Nodes.Mapping, + [vec, uniform(0, 1, 3), (0, 0, np.pi / 2 * np.random.randint(4) + rotation), [scale] * 3] + ) + vec = nw.combine(*nw.separate(vec)[:2], 0) + offset, mortar = map(nw.new_node(Nodes.BrickTexture, [vec], input_kwargs={ + 'Scale': 1 / size, + 'Row Height': 1, + 'Brick Width': 1, + 'Mortar Size': uniform(.005, .01), + 'Color2': (0, 0, 0, 1) + }, attrs={'offset': .0, 'offset_frequency': 1}).outputs.get, ['Color', 'Fac']) + selections = [nw.new_node(Nodes.CheckerTexture, [vec, (0, 0, 0, 1), (1, 1, 1, 1), 1 / size]).outputs[1]] + rotations = np.pi / 2 * np.arange(2) + mix_shader(nw, base_shader, offset, rotations, mortar, alternating, selections) + + +def shader_rectangle_tile(nw: NodeWrangler, base_shader, vertical=False, alternating=None, scale=1, **kwargs): + if alternating is None: + alternating = uniform() < .75 + size = log_uniform(.2, .4) + vec = nw.new_node(Nodes.TextureCoord).outputs['Object'] + normal = nw.new_node(Nodes.ShaderNodeNormalMap).outputs['Normal'] + if vertical: + vec = nw.combine(nw.separate(nw.vector_math('CROSS_PRODUCT', vec, normal))[-1], nw.separate(vec)[-1], 0) + vec = nw.new_node( + Nodes.Mapping, + [vec, uniform(0, 1, 3), (0, 0, np.pi / 2 * np.random.randint(4)), [scale, scale * log_uniform(1.3, 2), scale]] + ) + vec = nw.combine(*nw.separate(vec)[:2], 0) + offset, mortar = map(nw.new_node(Nodes.BrickTexture, [vec], input_kwargs={ + 'Scale': 1 / size, + 'Row Height': 1, + 'Brick Width': 1, + 'Mortar Size': uniform(.005, .01), + 'Color2': (0, 0, 0, 1) + }, attrs={'offset': .0, 'offset_frequency': 1}).outputs.get, ['Color', 'Fac']) + selections = [nw.new_node(Nodes.CheckerTexture, [vec, (0, 0, 0, 1), (1, 1, 1, 1), 1 / size]).outputs[1]] + rotations = np.pi / 2 * np.arange(2) + mix_shader(nw, base_shader, offset, rotations, mortar, alternating, selections) + + +def shader_hexagon_tile(nw: NodeWrangler, base_shader, vertical=False, alternating=None, scale=1, **kwargs): + if alternating is None: + alternating = uniform() < .6 + size = log_uniform(.15, .3) + vec = nw.new_node(Nodes.TextureCoord).outputs['Object'] + normal = nw.new_node(Nodes.ShaderNodeNormalMap).outputs['Normal'] + if vertical: + vec = nw.combine(nw.separate(nw.vector_math('CROSS_PRODUCT', vec, normal))[-1], nw.separate(vec)[-1], 0) + vec = nw.new_node(Nodes.Mapping, + [vec, uniform(0, 1, 3), (0, 0, np.pi / 2 * np.random.randint(4)), [scale] * 3]) + qs = [] + for n in np.array([[1 / np.sqrt(3), -1 / 3, 0], [0, 2 / 3, 0], [-1 / np.sqrt(3), -1 / 3, 0]]) / size: + qs.append(nw.vector_math('DOT_PRODUCT', vec, n)) + qs_ = [nw.math('ROUND', q) for q in qs] + qs_diff = [nw.math('ABSOLUTE', nw.scalar_sub(q, q_)) for q, q_ in zip(qs, qs_)] + coords = [] + for i in range(3): + coords.append(nw.new_node(Nodes.Mix, [ + nw.scalar_multiply(nw.math('GREATER_THAN', qs_diff[i], qs_diff[(i + 1) % 3]), + nw.math('GREATER_THAN', qs_diff[i], qs_diff[(i + 2) % 3])), None, qs_[i], + nw.scalar_sub(0, nw.scalar_add(qs_[(i + 1) % 3], qs_[(i + 2) % 3]))])) + offset = nw.combine(coords[0], coords[1], coords[2]) + i = np.random.randint(3) + fraction = nw.math('FRACT', + nw.scalar_divide(nw.scalar_add(nw.scalar_sub(coords[i], coords[(i + 1) % 3]), .5), 3)) + diffs = [nw.math('ABSOLUTE', nw.scalar_sub(q, c)) for q, c in zip(qs, coords)] + max_dist = nw.math('MAXIMUM', + nw.math('MAXIMUM', nw.scalar_add(diffs[0], diffs[1]), nw.scalar_add(diffs[1], diffs[2])), + nw.scalar_add(diffs[2], diffs[0])) + mortar = nw.math('GREATER_THAN', max_dist, 1 - uniform(.005, .01) / size / 2) + rotations = np.pi * 2 / 3 * np.arange(3) + mix_shader(nw, base_shader, offset, rotations, mortar, alternating, + [nw.math('LESS_THAN', fraction, 2 / 3), nw.math('LESS_THAN', fraction, 1 / 3), ]) + + +def shader_staggered_tile(nw: NodeWrangler, base_shader, vertical=False, alternating=None, scale=1, + vertical_scale=None, **kwargs): + horizontal_scale = scale * log_uniform(2., 3.5) + if vertical_scale is None: + vertical_scale = horizontal_scale * log_uniform(0.05, 0.2) + + vec = nw.new_node(Nodes.TextureCoord).outputs["Object"] + normal = nw.new_node(Nodes.ShaderNodeNormalMap).outputs['Normal'] + if vertical: + vec = nw.combine(nw.separate(nw.vector_math('CROSS_PRODUCT', vec, normal))[-1], nw.separate(vec)[-1], 0) + vec = nw.new_node(Nodes.Mapping, [vec, uniform(0, 1, 3)]) + vec = nw.add(vec, nw.combine(0, nw.scalar_divide(.5, horizontal_scale), 0)) + + offset, mortar = map(nw.new_node(Nodes.BrickTexture, input_kwargs={ + 'Vector': vec, + 'Color2': (0, 0, 0, 1.0000), + 'Scale': 1.0000, + 'Mortar Size': uniform(.005, .01), + 'Mortar Smooth': 1.0000, + 'Bias': -0.5000, + 'Brick Width': nw.scalar_divide(1, vertical_scale), + 'Row Height': nw.scalar_divide(1, horizontal_scale) + }, attrs={'squash_frequency': 1}).outputs.get, ['Color', 'Fac']) + mix_shader(nw, base_shader, offset, [0], mortar, alternating, []) + + +def shader_crossed_tile(nw: NodeWrangler, base_shader, vertical=False, alternating=None, scale=1, n=None, + **kwargs): + n = np.random.randint(4, 8) + vec = nw.new_node(Nodes.TextureCoord).outputs["Object"] + normal = nw.new_node(Nodes.ShaderNodeNormalMap).outputs['Normal'] + if vertical: + vec = nw.combine(nw.separate(nw.vector_math('CROSS_PRODUCT', vec, normal))[-1], nw.separate(vec)[-1], 0) + vec = nw.new_node(Nodes.Mapping, + [vec, uniform(0, 1, 3), (0, 0, np.pi / 2 * np.random.randint(4)), [scale] * 3]) + x, y, z = nw.separate(vec) + x_ = nw.scalar_sub(x, nw.scalar_divide(nw.math('FLOOR', nw.scalar_multiply(y, n)), n)) + vec = nw.combine(x_, y, 0) + offset, mortar = map(nw.new_node(Nodes.BrickTexture, input_kwargs={ + 'Vector': vec, + 'Color2': (0, 0, 0, 1.0000), + 'Scale': 1.0000, + 'Mortar Size': uniform(.005, .01), + 'Brick Width': 1, + 'Row Height': 1 / n + }, attrs={'squash_frequency': 1, 'offset': 0}).outputs.get, ['Color', 'Fac']) + vec_ = nw.combine( + nw.scalar_sub(y, nw.scalar_divide(nw.scalar_add(nw.math('FLOOR', nw.scalar_multiply(x, n)), 1), n)), + nw.scalar_sub(0, x), 0) + + offset_, mortar_ = map(nw.new_node(Nodes.BrickTexture, input_kwargs={ + 'Vector': vec_, + 'Color2': (0, 0, 0, 1.0000), + 'Scale': 1.0000, + 'Mortar Size': uniform(.005, .01), + 'Brick Width': 1, + 'Row Height': 1 / n, + }, attrs={'squash_frequency': 1, 'offset': 0}).outputs.get, ['Color', 'Fac']) + selection = nw.math('LESS_THAN', + nw.scalar_sub(nw.scalar_divide(x_, 2), nw.math('FLOOR', nw.scalar_divide(x_, 2))), .5) + offset = nw.new_node(Nodes.Mix, input_kwargs={'Factor': selection, 'A': offset, 'B': offset_}, + attrs={'data_type': 'FLOAT'}) + mortar = nw.new_node(Nodes.Mix, input_kwargs={'Factor': selection, 'A': mortar, 'B': mortar_}, + attrs={'data_type': 'FLOAT'}) + + mix_shader(nw, base_shader, offset, [0, np.pi / 2], mortar, alternating, [selection]) + + +def shader_composite_tile(nw: NodeWrangler, base_shader, vertical=False, alternating=None, scale=1, **kwargs): + if alternating is None: + alternating = uniform() < .75 + size = log_uniform(.2, .4) + vec = nw.new_node(Nodes.TextureCoord).outputs['Object'] + normal = nw.new_node(Nodes.ShaderNodeNormalMap).outputs['Normal'] + if vertical: + vec = nw.combine(nw.separate(nw.vector_math('CROSS_PRODUCT', vec, normal))[-1], nw.separate(vec)[-1], 0) + vec = nw.new_node( + Nodes.Mapping, + [vec, uniform(0, 1, 3), (0, 0, np.pi / 2 * np.random.randint(8)), [scale] * 3] + ) + vec = nw.combine(*nw.separate(vec)[:2], 0) + + selections = [nw.new_node(Nodes.CheckerTexture, [vec, (0, 0, 0, 1), (1, 1, 1, 1), 1 / size]).outputs[1]] + rotations = np.pi / 2 * np.arange(2) + + mortar_size = uniform(.002, .005) + stride = np.random.randint(4, 7) + offset_h, mortar_h = map( + nw.new_node( + Nodes.BrickTexture, input_kwargs={ + 'Vector': vec, + 'Color2': (0, 0, 0, 1.0000), + 'Scale': 1.0000, + 'Mortar Size': mortar_size, + 'Mortar Smooth': 1.0000, + 'Brick Width': size / stride, + 'Row Height': 1000 + }, attrs={'squash_frequency': 1} + ).outputs.get, ['Color', 'Fac'] + ) + offset_v, mortar_v = map( + nw.new_node( + Nodes.BrickTexture, input_kwargs={ + 'Vector': vec, + 'Color2': (0, 0, 0, 1.0000), + 'Scale': 1.0000, + 'Mortar Size': mortar_size, + 'Mortar Smooth': 1.0000, + 'Brick Width': 1000, + 'Row Height': size / stride, + }, attrs={'squash_frequency': 1} + ).outputs.get, ['Color', 'Fac'] + ) + mortar = nw.new_node( + Nodes.Mix, input_kwargs={'Factor': selections[0], 'A': mortar_h, 'B': mortar_v}, + attrs={'data_type': 'FLOAT'} + ) + offset = nw.new_node( + Nodes.Mix, input_kwargs={'Factor': selections[0], 'A': offset_h, 'B': offset_v}, + attrs={'data_type': 'VECTOR'} + ) + mix_shader(nw, base_shader, offset, rotations, mortar, alternating, selections) + + +def get_shader_funcs(): + from . import bone, ceramic, cobble_stone, dirt, stone + from .woods.wood import shader_wood + from .table_materials import shader_marble + return [(bone.shader_bone, 1), (cobble_stone.shader_cobblestone, 1), (ceramic.shader_ceramic, 4), + (dirt.shader_dirt, 1), (stone.shader_stone, 1), (shader_marble, 2), (shader_wood, 5), ] + + +def apply(obj, selection=None, vertical=False, shader_func=None, scale=None, alternating=None, shape=None, + **kwargs): + funcs, weights = zip(*get_shader_funcs()) + weights = np.array(weights) / sum(weights) + if shader_func is None: + shader_func = np.random.choice(funcs, p=weights) + name = shader_func.__name__ + + if scale is None: + scale = log_uniform(1., 2.) + + if shader_func == ceramic.shader_ceramic: + low = uniform(.1, .3) + high = uniform(.6, .8) + shader_func = partial(ceramic.shader_ceramic, roughness_min=low, roughness_max=high) + match shape: + case 'square': + method = shader_square_tile + case 'rectangle': + method = shader_rectangle_tile + case 'hexagon': + method = shader_hexagon_tile + case 'staggered': + method = shader_staggered_tile + case 'crossed': + method = shader_crossed_tile + case 'composite': + method = shader_composite_tile + case _: + method = np.random.choice( + [shader_hexagon_tile, shader_square_tile, shader_rectangle_tile, shader_staggered_tile, + shader_crossed_tile] + ) + + return common.apply( + obj, method, selection, shader_func, vertical, alternating, name=f'{name}_{method.__name__}_tile', + scale=scale, **kwargs) + + +def make_sphere(): + return new_cube() \ No newline at end of file diff --git a/infinigen/assets/materials/utils/surface_utils.py b/infinigen/assets/materials/utils/surface_utils.py index 996323b5c..d740bcc8c 100644 --- a/infinigen/assets/materials/utils/surface_utils.py +++ b/infinigen/assets/materials/utils/surface_utils.py @@ -1,14 +1,16 @@ # 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: Mingzhe Wang +# Authors: Mingzhe Wang, Lingjie Mei import random import math +import numpy as np + from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler -from infinigen.core.nodes import node_utils +from infinigen.core.nodes import Nodes, node_utils from infinigen.core.util.color import color_category from infinigen.core import surface @@ -19,24 +21,24 @@ def nodegroup_norm_value(nw: NodeWrangler): group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'Attribute', 0.0000), ('NodeSocketGeometry', 'Geometry', None)]) - + attribute_statistic_1 = nw.new_node(Nodes.AttributeStatistic, input_kwargs={'Geometry': group_input.outputs["Geometry"], 2: group_input.outputs["Attribute"]}) - + subtract = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Attribute"], 1: attribute_statistic_1.outputs["Min"]}, attrs={'operation': 'SUBTRACT'}) - + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: attribute_statistic_1.outputs["Max"], 1: attribute_statistic_1.outputs["Min"]}, attrs={'operation': 'SUBTRACT'}) - + divide = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: subtract_1}, attrs={'operation': 'DIVIDE'}) - + subtract_2 = nw.new_node(Nodes.Math, input_kwargs={0: divide}, attrs={'operation': 'SUBTRACT'}) - + multiply = nw.new_node(Nodes.Math, input_kwargs={0: subtract_2, 1: 2.0000}, attrs={'operation': 'MULTIPLY'}) - + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Value': multiply}, attrs={'is_active_output': True}) @node_utils.to_nodegroup('nodegroup_norm_vec', singleton=False, type='GeometryNodeTree') @@ -47,24 +49,24 @@ def nodegroup_norm_vec(nw: NodeWrangler): expose_input=[('NodeSocketGeometry', 'Geometry', None), ('NodeSocketString', 'Name', ''), ('NodeSocketVector', 'Vector', (0.0000, 0.0000, 0.0000))]) - + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Vector"]}) - + normvalue = nw.new_node(nodegroup_norm_value().name, input_kwargs={'Attribute': separate_xyz_1.outputs["X"], 'Geometry': group_input.outputs["Geometry"]}) - + normvalue_1 = nw.new_node(nodegroup_norm_value().name, input_kwargs={'Attribute': separate_xyz_1.outputs["Y"], 'Geometry': group_input.outputs["Geometry"]}) - + normvalue_2 = nw.new_node(nodegroup_norm_value().name, input_kwargs={'Attribute': separate_xyz_1.outputs["Z"], 'Geometry': group_input.outputs["Geometry"]}) - + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': normvalue, 'Y': normvalue_1, 'Z': normvalue_2}) - + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={'Geometry': group_input.outputs["Geometry"], 'Name': group_input.outputs["Name"], 2: combine_xyz}, attrs={'data_type': 'FLOAT_VECTOR'}) - + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': store_named_attribute}, attrs={'is_active_output': True}) @@ -107,7 +109,7 @@ def sample_color(color, offset=0, keep_sum=False): color[i] = mean-offset*(f*pcg+(1-f)*(1-pcg)) f = 0 return - + for i in range(3): if offset == 0: color[i] = random.random() @@ -180,3 +182,19 @@ def geo_voronoi_noise(nw, rand=False, **input_kwargs): group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': capture_attribute.outputs["Geometry"], 'Attribute': capture_attribute.outputs["Attribute"]}) + + +def perturb_coordinates(nw, node, location, rotation): + for name in ['Generated', 'Object', "Position", "UV"]: + if name in node.outputs: + node_socket = node.outputs[name] + to_links = nw.find_to(node_socket) + if len(to_links) == 0: + continue + shifted = nw.new_node(Nodes.Mapping, [node_socket], input_kwargs={'Location': location, + 'Rotation': nw.combine(0, 0, rotation)}).outputs[0] + to_sockets = [tl.to_socket for tl in to_links] + for to_link in to_links: + nw.links.remove(to_link) + for to_socket in to_sockets: + nw.connect_input(shifted, to_socket) diff --git a/infinigen/assets/materials/vase_shaders.py b/infinigen/assets/materials/vase_shaders.py new file mode 100644 index 000000000..674f9a553 --- /dev/null +++ b/infinigen/assets/materials/vase_shaders.py @@ -0,0 +1,37 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category, hsv2rgba +from infinigen.core import surface + + +def shader_ceramic(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + hsv = (uniform(0.0, 1.0), uniform(0.0, 0.75), uniform(0.0, 0.3)) + + rgb = nw.new_node(Nodes.RGB) + rgb.outputs[0].default_value = hsv2rgba(hsv) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': rgb, 'Subsurface': 0.3, 'Subsurface Radius': (0.002, 0.002, 0.002), 'Subsurface Color': rgb, 'Subsurface IOR': 1.4700, 'Subsurface Anisotropy': 0.2000, 'Specular': 0.2000, 'Roughness': 0.0500, 'Clearcoat': 0.5000, 'Clearcoat Roughness': 0.0500, 'IOR': 1.4700}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, attrs={'is_active_output': True}) + +def shader_glass(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + hsv = (uniform(0.0, 1.0), uniform(0.0, 0.2), 1.0) + + glass_bsdf = nw.new_node(Nodes.GlassBSDF, input_kwargs={'Color': hsv2rgba(hsv), 'Roughness': uniform(0.05, 0.2)}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': glass_bsdf}, attrs={'is_active_output': True}) + diff --git a/infinigen/assets/materials/water.py b/infinigen/assets/materials/water.py index 7d90f3470..0fd2de9b9 100644 --- a/infinigen/assets/materials/water.py +++ b/infinigen/assets/materials/water.py @@ -177,6 +177,7 @@ def shader( emissive_foam=False, volume_density=("uniform", 0.07, 0.09), anisotropy=("clip_gaussian", 0.75, 0.2, 0.5, 1), + mix_surface=False, random_seed=0, ): nw.force_input_consistency() @@ -195,12 +196,14 @@ def shader( principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={ "Base Color": color_of_transparent_bsdf_principled_bsdf, "Roughness": 0.0, "IOR": 1.33, "Transmission": 1.0 }) - surface_shader = nw.new_node(Nodes.MixShader, input_kwargs={ - 'Fac': nw.scalar_multiply(1.0, light_path.outputs["Is Camera Ray"]), - 1: transparent_bsdf, - 2: principled_bsdf - }) - + if mix_surface: + surface_shader = nw.new_node(Nodes.MixShader, input_kwargs={ + 'Fac': nw.scalar_multiply(1.0, light_path.outputs["Is Camera Ray"]), + 1: transparent_bsdf, + 2: principled_bsdf + }) + else: + surface_shader = principled_bsdf if asset_paths != []: if emissive_foam: foam_bsdf = nw.new_node(Nodes.Emission, input_kwargs={'Strength': 1}) diff --git a/infinigen/assets/materials/wear_tear/procedural_edge_wear.py b/infinigen/assets/materials/wear_tear/procedural_edge_wear.py new file mode 100644 index 000000000..1b4c44854 --- /dev/null +++ b/infinigen/assets/materials/wear_tear/procedural_edge_wear.py @@ -0,0 +1,276 @@ +# 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: Meenal Parakh +# Acknowledgement: This file draws inspiration from following sources: + +# https://www.youtube.com/watch?v=Aa8gf1pwb4E by Riley Brown +# https://www.youtube.com/watch?v=EQ149bMtKRA by Christopher Fraser +# https://www.youtube.com/watch?v=lDbsHpqKgoI by The DiNusty Empire +# https://www.youtube.com/watch?v=bLRwf2rZiAs by DECODED +# https://www.youtube.com/watch?v=NnlaIizA_AQ by Aryan +# https://www.youtube.com/watch?v=_wEXl3LncAc by diivja + + +from numpy.random import uniform, choice +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +import logging +from collections.abc import Iterable + +logger = logging.getLogger(__name__) + +def get_edge_wear_params(): + return { + '_worn_off_opacity': uniform(0.01), + '_worn_off_radius': uniform(0.005, 0.01), + '_scratch_radius': uniform(0.01, 0.03), + '_worn_off_mask_randomness': uniform(2.5, 3.0), + '_edge_base_color_hue': uniform(0.0, 1.0), + '_edge_base_color_whiteness': uniform(0.1, 0.6), + '_scratch_mask_randomness': choice([uniform(0.1, 5.0), uniform(1., 10.0)]), + '_scratch_density': choice([uniform(1.5, 10.0)]), + '_scratch_opacity': uniform(0.5, 1.0), + } + + +def shader_edge_tear_free_node_group(nw: NodeWrangler, + original_bsdf, + original_displacement, + _worn_off_opacity=0.5, + _worn_off_radius=0.015, + _scratch_radius=0.01, + _worn_off_mask_randomness=2.0, + _edge_base_color_hue=1.0, + _edge_base_color_whiteness=0.1, + _scratch_mask_randomness=0.5, + _scratch_density=5.0, + _scratch_opacity=0.2, + ): + + scratch_opacity = nw.new_node(Nodes.Value) + scratch_opacity.outputs[0].default_value = _scratch_opacity + + scratch_mask_randomness = nw.new_node(Nodes.Value) + scratch_mask_randomness.outputs[0].default_value = _scratch_mask_randomness + + scratch_radius = nw.new_node(Nodes.Value) + scratch_radius.outputs[0].default_value = _scratch_radius + + worn_off_opacity = nw.new_node(Nodes.Value) + worn_off_opacity.outputs[0].default_value = _worn_off_opacity + + paint_worn_off_radius = nw.new_node(Nodes.Value) + paint_worn_off_radius.outputs[0].default_value = _worn_off_radius + + worn_off_mask_randomness = nw.new_node(Nodes.Value) + worn_off_mask_randomness.outputs[0].default_value = _worn_off_mask_randomness + + edge_base_color_whiteness = nw.new_node(Nodes.Value) + edge_base_color_whiteness.outputs[0].default_value = _edge_base_color_whiteness + + edge_base_color_hue = nw.new_node(Nodes.Value) + edge_base_color_hue.outputs[0].default_value = _edge_base_color_hue + + scratch_density = nw.new_node(Nodes.Value) + scratch_density.outputs[0].default_value = _scratch_density + + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + mapping = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': texture_coordinate.outputs["Object"]}) + + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'Scale': scratch_mask_randomness, 'Detail': 1.0000}) + + color_ramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture.outputs["Fac"]}) + color_ramp.color_ramp.elements[0].position = 0.4436 + color_ramp.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + color_ramp.color_ramp.elements[1].position = 0.5345 + color_ramp.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + + bevel = nw.new_node('ShaderNodeBevel', input_kwargs={'Radius': scratch_radius}, attrs={'samples': 20}) + + geometry = nw.new_node(Nodes.NewGeometry) + + subtract = nw.new_node(Nodes.VectorMath, + input_kwargs={0: bevel, 1: geometry.outputs["Normal"]}, + attrs={'operation': 'SUBTRACT'}) + + absolute = nw.new_node(Nodes.Math, input_kwargs={0: subtract.outputs["Vector"]}, attrs={'operation': 'ABSOLUTE'}) + + color_ramp_1 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': absolute}) + color_ramp_1.color_ramp.elements[0].position = 0.0691 + color_ramp_1.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + color_ramp_1.color_ramp.elements[1].position = 0.1564 + color_ramp_1.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: color_ramp.outputs["Color"], 1: color_ramp_1.outputs["Color"]}, + attrs={'use_clamp': True, 'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: scratch_opacity, 1: multiply}, + attrs={'use_clamp': True, 'operation': 'MULTIPLY'}) + + + bevel_1 = nw.new_node('ShaderNodeBevel', input_kwargs={'Radius': paint_worn_off_radius}, attrs={'samples': 20}) + + subtract_1 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: bevel_1, 1: geometry.outputs["Normal"]}, + attrs={'operation': 'SUBTRACT'}) + + absolute_1 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1.outputs["Vector"]}, attrs={'operation': 'ABSOLUTE'}) + + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'Scale': worn_off_mask_randomness, 'Detail': 1.0000}) + + color_ramp_2 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture_1.outputs["Fac"]}) + color_ramp_2.color_ramp.elements[0].position = 0.0764 + color_ramp_2.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + color_ramp_2.color_ramp.elements[1].position = 0.5709 + color_ramp_2.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: absolute_1, 1: color_ramp_2.outputs["Color"]}, + attrs={'use_clamp': True, 'operation': 'MULTIPLY'}) + + multiply_3 = nw.new_node(Nodes.Math, + input_kwargs={0: worn_off_opacity, 1: multiply_2}, + attrs={'use_clamp': True, 'operation': 'MULTIPLY'}) + + color_ramp_3 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': multiply_3}) + color_ramp_3.color_ramp.elements[0].position = 0.0000 + color_ramp_3.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + color_ramp_3.color_ramp.elements[1].position = 0.7782 + color_ramp_3.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + combine_color = nw.new_node(Nodes.CombineColor, + input_kwargs={'Red': edge_base_color_hue, 'Green': 0.7733, 'Blue': 0.0100}, + attrs={'mode': 'HSV'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={0: edge_base_color_whiteness, 6: combine_color, 7: (0.02, 0.02, 0.02, 1.0000)}, + attrs={'clamp_result': True, 'data_type': 'RGBA', 'clamp_factor': False}) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': mix.outputs[2]}) + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': reroute, 'Metallic': 0.3745, 'Specular': 0.0000, 'Roughness': 0.1436}) + + mix_shader = nw.new_node(Nodes.MixShader, + input_kwargs={'Fac': color_ramp_3.outputs["Color"], 1: original_bsdf, 2: principled_bsdf}) + + mapping_1 = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'Scale': (10.0000, 1.0000, 1.0000)}) + + + voronoi_texture = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': mapping_1, 'Scale': scratch_density}, + attrs={'feature': 'DISTANCE_TO_EDGE'}) + + mapping_2 = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'Scale': (1.0000, 10.0000, 1.0000)}) + + scale = nw.new_node(Nodes.VectorMath, input_kwargs={0: scratch_density, 'Scale': 2.0000}, attrs={'operation': 'SCALE'}) + + voronoi_texture_1 = nw.new_node(Nodes.VoronoiTexture, + input_kwargs={'Vector': mapping_2, 'Scale': scale.outputs["Vector"]}, + attrs={'feature': 'DISTANCE_TO_EDGE'}) + + multiply_4 = nw.new_node(Nodes.Math, + input_kwargs={0: voronoi_texture.outputs["Distance"], 1: voronoi_texture_1.outputs["Distance"]}, + attrs={'use_clamp': True, 'operation': 'MULTIPLY'}) + + color_ramp_6 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': multiply_4}) + color_ramp_6.color_ramp.elements[0].position = 0.0000 + color_ramp_6.color_ramp.elements[0].color = [1.0000, 1.0000, 1.0000, 1.0000] + color_ramp_6.color_ramp.elements[1].position = 0.0073 + color_ramp_6.color_ramp.elements[1].color = [0.0000, 0.0000, 0.0000, 1.0000] + + multiply_5 = nw.new_node(Nodes.Math, + input_kwargs={0: color_ramp_1.outputs["Color"], 1: color_ramp_6.outputs["Color"]}, + attrs={'use_clamp': True, 'operation': 'MULTIPLY'}) + + multiply_6 = nw.new_node(Nodes.Math, + input_kwargs={0: multiply, 1: multiply_5}, + attrs={'use_clamp': True, 'operation': 'MULTIPLY'}) + + principled_bsdf_1 = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': reroute, 'Metallic': 0.3855, 'Specular': 0.0000, 'Roughness': 0.0000}) + + mix_shader_1 = nw.new_node(Nodes.MixShader, input_kwargs={'Fac': multiply_1, 1: mix_shader, 2: principled_bsdf_1}) + + # add operation + scale_multiply6 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: multiply_6, 'Scale': 2.0000}, + attrs={'operation': 'SCALE'}) + + if original_displacement is None: + total_displacement = scale_multiply6 + else: + total_displacement = nw.new_node(Nodes.Math, + input_kwargs={0: original_displacement, 1: scale_multiply6}, + attrs={'operation': 'ADD', 'use_clamp': True}) + + return mix_shader_1, total_displacement + + +MARKER_LABEL = "wear_tear" + +def apply_over(obj, selection=None, **shader_kwargs): + + # get all materials + # https://blenderartists.org/t/finding-out-if-an-object-has-a-material/512570/6 + materials = obj.data.materials.items() + if len(materials) == 0: + logging.warning(f"No material exist for {obj.name}! Scratches can only be applied over some existing material.") + return + + if len(shader_kwargs) == 0: + logging.debug("Obtaining Randomized Scratch Parameters") + shader_kwargs = get_edge_wear_params() + + for material_name, material in materials: + + # get material node tree + # https://blender.stackexchange.com/questions/240278/how-to-access-shader-node-via-python-script + material_node_tree = material.node_tree + + if any([node.label == MARKER_LABEL for node in material_node_tree.nodes]): + continue + + nw = NodeWrangler(material_node_tree) + + result = nw.find("ShaderNodeOutputMaterial") + if len(result) == 0: + logger.warning("No Material Output Node found in the object's materials! Returning") + continue + + # get nodes and links connected to specific inputs + # https://blender.stackexchange.com/questions/5462/is-it-possible-to-find-the-nodes-connected-to-a-node-in-python + initial_bsdf = result[0].inputs['Surface'].links[0].from_node + displacement_links = result[0].inputs['Displacement'].links + + if len(displacement_links) == 0: + initial_displacement = None + else: + initial_displacement = result[0].inputs['Displacement'].links[0].from_node + + final_bsdf, final_displacement = shader_edge_tear_free_node_group(nw, initial_bsdf, initial_displacement, **shader_kwargs) + # connecting nodes + # https://blender.stackexchange.com/questions/101820/how-to-add-remove-links-to-existing-or-new-nodes-using-python + material_node_tree.links.new(final_bsdf.outputs[0], result[0].inputs['Surface']) + material_node_tree.links.new(final_displacement.outputs[0], result[0].inputs['Displacement']) + + final_bsdf.label = MARKER_LABEL + + return + +def apply(obj): + if not isinstance(obj, Iterable): + obj = [obj] + for o in obj: + apply_over(o) \ No newline at end of file diff --git a/infinigen/assets/materials/wear_tear/procedural_scratch.py b/infinigen/assets/materials/wear_tear/procedural_scratch.py new file mode 100644 index 000000000..2a3ebc5fc --- /dev/null +++ b/infinigen/assets/materials/wear_tear/procedural_scratch.py @@ -0,0 +1,170 @@ +# 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: Meenal Parakh +# Acknowledgement: This file draws inspiration from following sources: + +# https://www.youtube.com/watch?v=l0whu3494_c by Ryan King Art +# https://www.youtube.com/watch?v=qMCuDjXjsZ0 by Ryan King Art +# https://www.youtube.com/watch?v=L3SvNpjIERs by Sina Sinaie +# https://www.youtube.com/watch?v=0B-lexp10jk by Holmes Motion +# https://www.youtube.com/watch?v=ewq69iNRdmQ by Ryan King Art +# https://www.youtube.com/watch?v=MH8iutCKtYc by ChuckCG + + +from numpy.random import uniform, choice +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +import logging +from collections.abc import Iterable + +from infinigen.core.util.random import log_uniform + +logger = logging.getLogger(__name__) + + +def get_scratch_params(): + return { + 'angle1': uniform(10., 80.0), + 'angle2': uniform(-80.0, -10.0), + 'scratch_scale': log_uniform(5,80), + 'scratch_mask_ratio': log_uniform(0.01, 0.9), + 'scratch_mask_noise': log_uniform(5, 40), + 'scratch_depth': log_uniform(.1,1.), + } + +def scratch_shader(nw: NodeWrangler, + original_bsdf, + angle1=45.0, + angle2=-20.0, + scratch_scale=20.0, + scratch_mask_ratio=0.8, + scratch_mask_noise=10.0, + scratch_depth=0.1): + # Code generated using version 2.6.5 of the node_transpiler + + texture_coordinate_1 = nw.new_node(Nodes.TextureCoord) + + n_angle1 = nw.new_node(Nodes.Value) + n_angle1.outputs[0].default_value = angle1 + + n_angle2 = nw.new_node(Nodes.Value) + n_angle2.outputs[0].default_value = angle2 + + n_scratch_scale = nw.new_node(Nodes.Value) + n_scratch_scale.outputs[0].default_value = scratch_scale + + n_scratch_mask_ratio = nw.new_node(Nodes.Value) + n_scratch_mask_ratio.outputs[0].default_value = scratch_mask_ratio + + n_scratch_mask_noise = nw.new_node(Nodes.Value) + n_scratch_mask_noise.outputs[0].default_value = scratch_mask_noise + + n_scratch_depth = nw.new_node(Nodes.Value) + n_scratch_depth.outputs[0].default_value = scratch_depth + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': n_angle1}) + + mapping_1 = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate_1.outputs["Object"], 'Rotation': combine_xyz, 'Scale': (25.0000, 1.0000, 1.0000)}, + attrs={'vector_type': 'TEXTURE'}) + + noise_texture_3 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping_1, 'Scale': n_scratch_scale, 'Detail': 15.0000, 'Roughness': 0.0000, 'Distortion': 22.8000}) + + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': n_angle2}) + + mapping_2 = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate_1.outputs["Object"], 'Rotation': combine_xyz_1, 'Scale': (25.0000, 1.0000, 1.0000)}, + attrs={'vector_type': 'TEXTURE'}) + + noise_texture_5 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping_2, 'Scale': n_scratch_scale, 'Detail': 15.0000, 'Roughness': 0.0000, 'Distortion': 22.8000}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: noise_texture_3.outputs["Fac"], 1: noise_texture_5.outputs["Fac"]}) + + mapping_3 = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate_1.outputs["Object"], 'Rotation': (0.1588, -0.5742, 0.1920)}, + attrs={'vector_type': 'TEXTURE'}) + + noise_texture_6 = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Vector': mapping_3, 'Scale': n_scratch_mask_noise, 'Detail': 1.0000}) + + color_ramp_2 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture_6.outputs["Fac"]}) + color_ramp_2.color_ramp.elements[0].position = 0.4109 + color_ramp_2.color_ramp.elements[0].color = [0.0000, 0.0000, 0.0000, 1.0000] + color_ramp_2.color_ramp.elements[1].position = 1.0000 + color_ramp_2.color_ramp.elements[1].color = [1.0000, 1.0000, 1.0000, 1.0000] + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: n_scratch_mask_ratio, 1: color_ramp_2.outputs["Color"]}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: multiply}, attrs={'use_clamp': True}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': add_1, 1: 0.7000, 2: 0.7200, 4: 0.9000}) + # scaled_scratch = nw.new_node(Nodes.Math, input_kwargs={0: n_scratch_depth, 1: map_range.outputs["Result"]}, attrs={'operation': 'MULTIPLY'}) + + # material_output = nw.new_node(Nodes.MaterialOutput, + # input_kwargs={'Surface': original_bsdf, 'Displacement': scaled_scratch}, + # attrs={'is_active_output': True}) + + # return material_output + + bump = nw.new_node(Nodes.Bump, input_kwargs={'Strength': n_scratch_depth, 'Height': map_range.outputs["Result"]}) + return {'Normal': bump} + + +def find_normal_input(bsdf): + for i, o in enumerate(bsdf.inputs): + if o.name == "Normal": + return i + logger.debug(f"Normal not found for {bsdf}") + return None + +MARKER_LABEL = "scratch" + +def apply_over(obj, selection=None, **shader_kwargs): + + # get all materials + # https://blenderartists.org/t/finding-out-if-an-object-has-a-material/512570/6 + materials = obj.data.materials.items() + if len(materials) == 0: + logging.warning(f"No material exist for {obj.name}! Scratches can only be applied over some existing material.") + return + + if len(shader_kwargs) == 0: + logging.debug("Obtaining Randomized Scratch Parameters") + shader_kwargs = get_scratch_params() + + for material_name, material in materials: + + # get material node tree + # https://blender.stackexchange.com/questions/240278/how-to-access-shader-node-via-python-script + material_node_tree = material.node_tree + + if any([n.label == MARKER_LABEL for n in material_node_tree.nodes]): + logging.warning(f"Scratch already applied to {material_name}! Skipping") + continue + + nw = NodeWrangler(material_node_tree) + + result = nw.find_recursive("ShaderNodeBsdf") + if len(result) == 0: + logging.debug("No BSDF found in the object's materials! Returning") + continue + + nw_bsdf, bsdf = result[-1] + # final_bsdf = scratch_shader(nw_bsdf, bsdf, **shader_kwargs) + + if 'Normal' in bsdf.inputs.keys(): + if len(nw_bsdf.find_from(bsdf.inputs['Normal'])) == 0: + bump = scratch_shader(nw_bsdf, None, **shader_kwargs)['Normal'] + + # connecting nodes: https://blender.stackexchange.com/questions/101820/how-to-add-remove-links-to-existing-or-new-nodes-using-python + nw_bsdf.links.new(bump.outputs[0], bsdf.inputs['Normal']) + + nw_bsdf.label = MARKER_LABEL + +def apply(obj): + if not isinstance(obj, Iterable): + obj = [obj] + for o in obj: + apply_over(o) \ No newline at end of file diff --git a/infinigen/assets/materials/wood.py b/infinigen/assets/materials/wood.py deleted file mode 100644 index 235f62942..000000000 --- a/infinigen/assets/materials/wood.py +++ /dev/null @@ -1,89 +0,0 @@ -# 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: Mingzhe Wang - - -import os, sys -import numpy as np -import math as ma -from infinigen.assets.materials.utils.surface_utils import clip, sample_range, sample_ratio, sample_color, geo_voronoi_noise -import bpy -import mathutils -from numpy.random import uniform, normal, randint -from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler -from infinigen.core.nodes import node_utils -from infinigen.core.util.color import color_category -from infinigen.core import surface - -def shader_wood(nw: NodeWrangler, rand=False, **input_kwargs): - # Code generated using version 2.4.3 of the node_transpiler - - texture_coordinate_1 = nw.new_node(Nodes.TextureCoord) - - mapping_2 = nw.new_node(Nodes.Mapping, - input_kwargs={'Vector': texture_coordinate_1.outputs["Generated"], 'Rotation': uniform(0,ma.pi*2, 3)}) - - mapping_1 = nw.new_node(Nodes.Mapping, - input_kwargs={'Vector': mapping_2, 'Scale': (0.5, sample_range(2, 4) if rand else 3, 0.5)}) - - musgrave_texture_2 = nw.new_node(Nodes.MusgraveTexture, - input_kwargs={'Vector': mapping_1, 'Scale': 2.0}, - attrs={'musgrave_dimensions': '4D'}) - if rand: - musgrave_texture_2.inputs['W'].default_value = sample_range(0, 5) - musgrave_texture_2.inputs['Scale'].default_value = sample_ratio(2.0, 3/4, 4/3) - - noise_texture_1 = nw.new_node(Nodes.NoiseTexture, - input_kwargs={'Vector': musgrave_texture_2, 'W': 0.7, 'Scale': 10.0}, - attrs={'noise_dimensions': '4D'}) - if rand: - noise_texture_1.inputs['W'].default_value = sample_range(0, 5) - noise_texture_1.inputs['Scale'].default_value = sample_ratio(5, 0.5, 2) - - colorramp_2 = nw.new_node(Nodes.ColorRamp, - input_kwargs={'Fac': noise_texture_1.outputs["Fac"]}) - colorramp_2.color_ramp.elements.new(0) - colorramp_2.color_ramp.elements[0].position = 0.1727 - colorramp_2.color_ramp.elements[0].color = (0.1567, 0.0162, 0.0017, 1.0) - colorramp_2.color_ramp.elements[1].position = 0.4364 - colorramp_2.color_ramp.elements[1].color = (0.2908, 0.1007, 0.0148, 1.0) - colorramp_2.color_ramp.elements[2].position = 0.5864 - colorramp_2.color_ramp.elements[2].color = (0.0814, 0.0344, 0.0125, 1.0) - if rand: - colorramp_2.color_ramp.elements[0].position += sample_range(-0.05, 0.05) - colorramp_2.color_ramp.elements[1].position += sample_range(-0.1, 0.1) - colorramp_2.color_ramp.elements[2].position += sample_range(-0.05, 0.05) - for e in colorramp_2.color_ramp.elements: - sample_color(e.color, offset=0.03) - - colorramp_4 = nw.new_node(Nodes.ColorRamp, - input_kwargs={'Fac': noise_texture_1.outputs["Fac"]}) - colorramp_4.color_ramp.elements[0].position = 0.0 - colorramp_4.color_ramp.elements[0].color = (0.4855, 0.4855, 0.4855, 1.0) - colorramp_4.color_ramp.elements[1].position = 1.0 - colorramp_4.color_ramp.elements[1].color = (1.0, 1.0, 1.0, 1.0) - - principled_bsdf_1 = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': colorramp_2.outputs["Color"], 'Roughness': colorramp_4.outputs["Color"]}, - attrs={'subsurface_method': 'BURLEY'}) - - material_output = nw.new_node(Nodes.MaterialOutput, - input_kwargs={'Surface': principled_bsdf_1}) - -def apply(obj, geo_kwargs=None, shader_kwargs=None, **kwargs): - surface.add_material(obj, shader_wood, reuse=False, input_kwargs=shader_kwargs) - - -if __name__ == "__main__": - mat = 'wood' - if not os.path.isdir(os.path.join('outputs', mat)): - os.mkdir(os.path.join('outputs', mat)) - for i in range(10): - bpy.ops.wm.open_mainfile(filepath='test.blend') - apply(bpy.data.objects['SolidModel'], geo_kwargs={'rand':True}, shader_kwargs={'rand': True}) - #fn = os.path.join(os.path.abspath(os.curdir), 'giraffe_geo_test.blend') - #bpy.ops.wm.save_as_mainfile(filepath=fn) - bpy.context.scene.render.filepath = os.path.join('outputs', mat, '%s_%d.jpg'%(mat, i)) - bpy.context.scene.render.image_settings.file_format='JPEG' - bpy.ops.render.render(write_still=True) \ No newline at end of file diff --git a/infinigen/assets/materials/woods/composite_wood_tile.py b/infinigen/assets/materials/woods/composite_wood_tile.py new file mode 100644 index 000000000..218bc27e9 --- /dev/null +++ b/infinigen/assets/materials/woods/composite_wood_tile.py @@ -0,0 +1,18 @@ +# 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: Lingjie Mei +from functools import wraps + +from numpy.random import uniform + +from infinigen.core.nodes import Nodes +from infinigen.core.util.random import log_uniform +from .tiled_wood import shader_wood_tiled +from .. import shader_wood, tile +from ..tile import shader_staggered_tile + + +def apply(obj, selection=None, vertical=False, scale=None, alternating=None, shape=None, **kwargs): + shader_func = shader_wood + tile.apply(obj, selection, vertical, shader_func, scale, alternating, 'composite', **kwargs) diff --git a/infinigen/assets/materials/woods/crossed_wood_tile.py b/infinigen/assets/materials/woods/crossed_wood_tile.py new file mode 100644 index 000000000..685915b2a --- /dev/null +++ b/infinigen/assets/materials/woods/crossed_wood_tile.py @@ -0,0 +1,14 @@ +# 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: Lingjie Mei +from .. import tile +from .wood import shader_wood +from ...utils.object import new_plane + + +def apply(obj, selection=None, vertical=False, scale=None, alternating=None, shape=None, **kwargs): + shader_func = shader_wood + tile.apply(obj, selection, vertical, shader_func, scale, False, 'crossed', **kwargs) + +# def make_sphere():e diff --git a/infinigen/assets/materials/woods/hexagon_wood_tile.py b/infinigen/assets/materials/woods/hexagon_wood_tile.py new file mode 100644 index 000000000..e530c0c46 --- /dev/null +++ b/infinigen/assets/materials/woods/hexagon_wood_tile.py @@ -0,0 +1,12 @@ +# 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: Lingjie Mei +from .. import tile +from .wood import shader_wood +from ...utils.object import new_plane + + +def apply(obj, selection=None, vertical=False, scale=None, alternating=None, shape=None, **kwargs): + shader_func = shader_wood + tile.apply(obj, selection, vertical, shader_func, scale, alternating, 'hexagon', **kwargs) diff --git a/infinigen/assets/materials/woods/non_wood_tile.py b/infinigen/assets/materials/woods/non_wood_tile.py new file mode 100644 index 000000000..d63aa058e --- /dev/null +++ b/infinigen/assets/materials/woods/non_wood_tile.py @@ -0,0 +1,22 @@ +# 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: Lingjie Mei +import numpy as np + + +def apply( + obj, selection=None, vertical=False, shader_func=None, scale=None, alternating=None, shape=None, + **kwargs +): + from .. import tile + from .wood import shader_wood + shader_funcs = tile.get_shader_funcs() + shader_funcs = [(f, w) for f, w in shader_funcs if f != shader_wood] + funcs, weights = zip(*shader_funcs) + weights = np.array(weights) / sum(weights) + if shader_func is None: + shader_func = np.random.choice(funcs, p=weights) + if shape is None: + shape = np.random.choice(['square', 'hexagon', 'rectangle']) + tile.apply(obj, selection, vertical, shader_func, scale, alternating, shape, **kwargs) diff --git a/infinigen/assets/materials/woods/square_wood_tile.py b/infinigen/assets/materials/woods/square_wood_tile.py new file mode 100644 index 000000000..d86306616 --- /dev/null +++ b/infinigen/assets/materials/woods/square_wood_tile.py @@ -0,0 +1,14 @@ +# 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: Lingjie Mei +from .. import tile +from .wood import shader_wood +from ...utils.object import new_plane + + +def apply(obj, selection=None, vertical=False, scale=None, alternating=None, shape=None, **kwargs): + shader_func = shader_wood + tile.apply(obj, selection, vertical, shader_func, scale, alternating, 'square', **kwargs) + +# def make_sphere():e diff --git a/infinigen/assets/materials/woods/staggered_wood_tile.py b/infinigen/assets/materials/woods/staggered_wood_tile.py new file mode 100644 index 000000000..e28507145 --- /dev/null +++ b/infinigen/assets/materials/woods/staggered_wood_tile.py @@ -0,0 +1,12 @@ +# 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: Lingjie Mei +from .. import tile +from .wood import shader_wood +from ...utils.object import new_plane + + +def apply(obj, selection=None, vertical=False, scale=None, alternating=None, shape=None, **kwargs): + shader_func = shader_wood + tile.apply(obj, selection, vertical, shader_func, scale, alternating, 'staggered', **kwargs) diff --git a/infinigen/assets/materials/woods/tiled_wood.py b/infinigen/assets/materials/woods/tiled_wood.py new file mode 100644 index 000000000..3cc818923 --- /dev/null +++ b/infinigen/assets/materials/woods/tiled_wood.py @@ -0,0 +1,188 @@ +# 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: Yiming Zuo +# Acknowledgement: This file draws inspiration https://www.youtube.com/watch?v=rd2jhGV6tqo by Ryan King Art + + +import bpy +import bpy +import mathutils +import numpy as np +from numpy.random import uniform, normal, randint, choice + +from infinigen.assets.materials import common +from infinigen.assets.materials.woods.wood import get_color +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import rgb2hsv + +from infinigen.core.util.random import clip_gaussian +from infinigen.assets.materials.bark_random import get_random_bark_params, hex_to_rgb + + + +@node_utils.to_nodegroup('nodegroup_tiling', singleton=False, type='ShaderNodeTree') +def nodegroup_tiling(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Horizontal Scale', 0.5000), + ('NodeSocketFloat', 'Vertical Scale', 0.5), + ('NodeSocketFloat', 'Seed', 0.5000)]) + + divide = nw.new_node(Nodes.Math, + input_kwargs={0: 1.0000, 1: group_input.outputs["Horizontal Scale"]}, + attrs={'operation': 'DIVIDE'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: divide}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply}) + + vec = texture_coordinate.outputs["Object"] + + vec = nw.new_node(Nodes.Mapping, [vec, uniform(0, 1, 3)]) + + add = nw.new_node(Nodes.VectorMath, input_kwargs={0: vec, 1: combine_xyz}) + + divide_1 = nw.new_node(Nodes.Math, + input_kwargs={0: 1.0000, 1: group_input.outputs["Vertical Scale"]}, + attrs={'operation': 'DIVIDE'}) + + brick_texture = nw.new_node(Nodes.BrickTexture, + input_kwargs={'Vector': add.outputs["Vector"], 'Color2': (0,0,0, 1.0000), 'Scale': 1.0000, 'Mortar Size': 0.0050, 'Mortar Smooth': 1.0000, 'Bias': -0.5000, 'Brick Width': divide_1, 'Row Height': divide}, + attrs={'squash_frequency': 1}) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: brick_texture.outputs["Color"], 1: 1000.0000, 2: group_input.outputs["Seed"]}, + attrs={'operation': 'MULTIPLY_ADD'}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Tile Color': brick_texture.outputs["Color"], 'Seed': multiply_add, 'Displacement': brick_texture.outputs["Color"]}, + attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_tiled_wood', singleton=False, type='ShaderNodeTree') +def nodegroup_tiled_wood(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + texture_coordinate = nw.new_node(Nodes.TextureCoord) + + mapping_2 = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'Scale': (5.0000, 100.0000, 100.0000)}) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Tile Horizontal Scale', 0.0000), + ('NodeSocketFloat', 'Tile Vertical Scale', 2.9600), + ('NodeSocketColor', 'Main Color', (0.0000, 0.0000, 0.0000, 1.0000)), + ('NodeSocketFloat', 'Seed', 0.0000)]) + + group = nw.new_node(nodegroup_tiling().name, + input_kwargs={'Horizontal Scale': group_input.outputs["Tile Horizontal Scale"], 'Vertical Scale': group_input.outputs["Tile Vertical Scale"], 'Seed': group_input.outputs["Seed"]}) + + musgrave_texture_2 = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': mapping_2, 'W': group.outputs["Seed"], 'Scale': 10.0000, 'Detail': 15.0000, 'Dimension': 7.0000}, + attrs={'musgrave_dimensions': '4D'}) + + map_range_2 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': musgrave_texture_2, 3: 1.0000, 4: -1.0000}) + + mapping_1 = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': texture_coordinate.outputs["Object"]}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping_1, 'W': group.outputs["Seed"], 'Scale': 0.5000, 'Detail': 1.0000, 'Distortion': 1.1000}, + attrs={'noise_dimensions': '4D'}) + + musgrave_texture_1 = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'W': group.outputs["Seed"], 'Scale': noise_texture_1.outputs["Fac"], 'Detail': 15.0000, 'Dimension': 0.2000, 'Lacunarity': 2.4000}, + attrs={'musgrave_dimensions': '4D'}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': musgrave_texture_1, 3: -1.4000, 4: 1.5000}) + + map_range_1 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': map_range.outputs["Result"], 3: 1.0000, 4: 0.5000}) + + mapping = nw.new_node(Nodes.Mapping, + input_kwargs={'Vector': texture_coordinate.outputs["Object"], 'Scale': (0.1500, 1.0000, 0.1500)}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'W': group.outputs["Seed"], 'Detail': 5.0000, 'Distortion': 1.0000}, + attrs={'noise_dimensions': '4D'}) + + musgrave_texture = nw.new_node(Nodes.MusgraveTexture, + input_kwargs={'Vector': noise_texture.outputs["Fac"], 'W': group.outputs["Seed"], 'Scale': 4.0000, 'Detail': 10.0000, 'Dimension': 0.0000}, + attrs={'musgrave_dimensions': '4D'}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={6: noise_texture.outputs["Fac"], 7: musgrave_texture}, + attrs={'data_type': 'RGBA'}) + + mix_1 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.9000, 6: map_range_1.outputs["Result"], 7: mix.outputs[2]}, + attrs={'blend_type': 'MULTIPLY', 'data_type': 'RGBA'}) + + mix_2 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.9500, 6: map_range_2.outputs["Result"], 7: mix_1.outputs[2]}, + attrs={'blend_type': 'MULTIPLY', 'data_type': 'RGBA'}) + + hue_saturation_value = nw.new_node('ShaderNodeHueSaturation', + input_kwargs={'Saturation': 0.8000, 'Value': 0.2000, 'Fac': 0.0, 'Color': group_input.outputs["Main Color"]}) + + mix_3 = nw.new_node(Nodes.Mix, + input_kwargs={0: mix_2.outputs[2], 6: hue_saturation_value, 7: group_input.outputs["Main Color"]}, + attrs={'data_type': 'RGBA'}) + + mix_4 = nw.new_node(Nodes.Mix, + input_kwargs={0: 1.0000, 6: mix_3.outputs[2], 7: group.outputs["Tile Color"]}, + attrs={'blend_type': 'MULTIPLY', 'data_type': 'RGBA'}) + + color = mix_4.outputs[2] + roughness = nw.build_float_curve(color, [(0, uniform(.3, .5)), (1, uniform(.8, 1.))]) + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Base Color': color, 'Roughness': roughness}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: mix_2.outputs[2], 1: 0.1000}, attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group.outputs["Displacement"], 1: multiply}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: 0.0100}, attrs={'operation': 'MULTIPLY'}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'BSDF': principled_bsdf, 'Displacement': multiply_1}, + attrs={'is_active_output': True}) + + +def shader_wood_tiled(nw: NodeWrangler, hscale=None, vscale=None, base_color=None, seed=None, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + if hscale is None: + hscale = clip_gaussian(6, 4, 3, 9) + if vscale is None: + vscale = uniform(0.05, 0.2) * hscale + if seed is None: + seed = uniform(-1000.0, 1000.0) + if base_color is None: + base_color = get_color() + + group = nw.new_node(nodegroup_tiled_wood().name, + input_kwargs={'Tile Horizontal Scale': hscale, + 'Tile Vertical Scale': vscale, + 'Seed': seed, + 'Main Color': base_color}) + + displacement = nw.new_node('ShaderNodeDisplacement', input_kwargs={'Height': group.outputs["Displacement"], 'Midlevel': 0.0000}) + + material_output = nw.new_node(Nodes.MaterialOutput, + input_kwargs={'Surface': group.outputs["BSDF"], 'Displacement': displacement}, + attrs={'is_active_output': True}) + + +def get_random_light_wood_params(): + color_fac = [0xdeb887, 0xcdaa7d, 0xfff8dc] + color_factory = [hex_to_rgb(c) for c in color_fac] + return color_factory[randint(len(color_fac))] + + +def apply(obj, selection=None, **kwargs): + common.apply(obj, shader_wood_tiled, selection=selection, **kwargs) + +# def make_sphere(): +# return new_plane() diff --git a/infinigen/assets/materials/woods/wood.py b/infinigen/assets/materials/woods/wood.py new file mode 100644 index 000000000..1891ab51d --- /dev/null +++ b/infinigen/assets/materials/woods/wood.py @@ -0,0 +1,146 @@ +# 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: Yiming Zuo +# Acknowledgement: This file draws inspiration https://www.youtube.com/watch?v=jDEijCwz6to by Lachlan Sarv + +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import common +from infinigen.core.nodes import NodeWrangler, Nodes +from infinigen.core.util.color import hsv2rgba, rgb2hsv +from infinigen.core.util.random import log_uniform +from ...utils.object import new_cube + + +def get_color(): + from infinigen.assets.materials.bark_random import get_random_bark_params + _, color_params = get_random_bark_params(np.random.randint(1e7)) + h, s, v = rgb2hsv(color_params['Color'][:-1]) + return hsv2rgba(h + uniform(-.0, .05), s + uniform(-.3, .2), v * log_uniform(.2, 20)) + + +def shader_wood(nw: NodeWrangler, color=None, w=None, vertical=False, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + vec = nw.new_node(Nodes.TextureCoord).outputs['Object'] + if vertical: + vec = nw.new_node( + Nodes.Mapping, [vec], input_kwargs={'Rotation': (np.pi / 2, 0, np.pi / 2 * np.random.randint(2))} + ) + + mapping_2 = nw.new_node( + Nodes.Mapping, input_kwargs={ + 'Vector': vec, + 'Scale': (5.0000, 100.0000, 100.0000) + }) + + if color is None: + color = get_color() + if w is None: + w = uniform(0, 1) + musgrave_texture_2 = nw.new_node(Nodes.MusgraveTexture, input_kwargs={ + 'Vector': mapping_2, + 'W': w, + 'Scale': 10.0000, + 'Detail': 15.0000, + 'Dimension': 7.0000 + }, attrs={'musgrave_dimensions': '4D'}) + + map_range_2 = nw.new_node(Nodes.MapRange, input_kwargs={'Value': musgrave_texture_2, 3: 1.0000, 4: -1.0000}) + + mapping_1 = nw.new_node(Nodes.Mapping, input_kwargs={'Vector': vec}) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, input_kwargs={ + 'Vector': mapping_1, + 'W': w, + 'Scale': 0.5000, + 'Detail': 1.0000, + 'Distortion': 1.1000 + }, attrs={'noise_dimensions': '4D'}) + + musgrave_texture_1 = nw.new_node(Nodes.MusgraveTexture, input_kwargs={ + 'W': w, + 'Scale': noise_texture_1.outputs["Fac"], + 'Detail': 15.0000, + 'Dimension': 0.2000, + 'Lacunarity': 2.4000 + }, attrs={'musgrave_dimensions': '4D'}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': musgrave_texture_1, 3: -1.4000, 4: 1.5000}) + + map_range_1 = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': map_range.outputs["Result"], 3: 1.0000, 4: 0.5000}) + + mapping = nw.new_node(Nodes.Mapping, input_kwargs={ + 'Vector': vec, + 'Scale': (0.1500, 1.0000, 0.1500) + }) + + noise_texture = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': mapping, 'W': w, 'Detail': 5.0000, 'Distortion': 1.0000 + }, attrs={'noise_dimensions': '4D'}) + + musgrave_texture = nw.new_node(Nodes.MusgraveTexture, input_kwargs={ + 'Vector': noise_texture.outputs["Fac"], + 'W': w, + 'Scale': 4.0000, + 'Detail': 10.0000, + 'Dimension': 0.0000 + }, attrs={'musgrave_dimensions': '4D'}) + + mix = nw.new_node(Nodes.Mix, input_kwargs={6: noise_texture.outputs["Fac"], 7: musgrave_texture}, + attrs={'data_type': 'RGBA'}) + + mix_1 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.9000, 6: map_range_1.outputs["Result"], 7: mix.outputs[2]}, + attrs={'blend_type': 'MULTIPLY', 'data_type': 'RGBA'}) + + mix_2 = nw.new_node(Nodes.Mix, + input_kwargs={0: 0.9500, 6: map_range_2.outputs["Result"], 7: mix_1.outputs[2]}, + attrs={'blend_type': 'MULTIPLY', 'data_type': 'RGBA'}) + + hue_saturation_value = nw.new_node('ShaderNodeHueSaturation', + input_kwargs={'Saturation': 0.8000, 'Value': 0.2000, 'Color': color, + }) + + mix_3 = nw.new_node(Nodes.Mix, input_kwargs={0: mix_2.outputs[2], 6: hue_saturation_value, 7: color, + }, attrs={'data_type': 'RGBA'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: mix_2.outputs[2], 1: log_uniform(.0002, .01)}, + attrs={'operation': 'MULTIPLY'}) + + displacement = nw.new_node('ShaderNodeDisplacement', input_kwargs={'Height': multiply, 'Midlevel': 0.0000}) + + color = mix_3.outputs[2] + roughness = uniform(.0, .4) + roughness = nw.build_float_curve( + nw.new_node(Nodes.NoiseTexture, input_kwargs={'Scale': log_uniform(40, 50)}), + [(0, roughness), (1, roughness + uniform(.0, .8))]) + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={ + 'Base Color': color, + 'Roughness': roughness, + 'Clearcoat': np.clip(uniform(0, 1.4), 0, 1) + }) + nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf, 'Displacement': displacement}) + + +def apply(obj, selection=None, **kwargs): + + # TODO HACK - avoiding circular imports for now + from infinigen.assets.materials.shelf_shaders import shader_shelves_white, shader_shelves_black_wood, shader_shelves_wood + + r = uniform() + if r < 1 / 12: + shader = shader_shelves_white + elif r < 2 / 12: + shader = shader_shelves_wood + elif r < 3 / 12: + shader = shader_shelves_black_wood + else: + shader = shader_wood + common.apply(obj, shader, selection, **kwargs) + +def make_sphere(): + return new_cube() diff --git a/infinigen/assets/materials/woods/wood_old.py b/infinigen/assets/materials/woods/wood_old.py new file mode 100644 index 000000000..b1c2d2e49 --- /dev/null +++ b/infinigen/assets/materials/woods/wood_old.py @@ -0,0 +1,76 @@ +# 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: Mingzhe Wang, Lingjie Mei + +import math as ma + +import numpy as np + +from infinigen.assets.materials import common +from infinigen.assets.materials.utils.surface_utils import clip, sample_range, sample_ratio, sample_color +from numpy.random import uniform + +from infinigen.core import surface +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.util.random import log_uniform + + +def shader_wood_old(nw: NodeWrangler, scale=1, offset=None, rotation=None, **kwargs): + # Code generated using version 2.4.3 of the node_transpiler + + texture_coordinate_1 = nw.new_node(Nodes.TextureCoord) + + rotation = uniform(0, ma.pi * 2, 3) if rotation is None else surface.eval_argument(nw, rotation) + mapping_2 = nw.new_node(Nodes.Mapping, input_kwargs={ + 'Vector': texture_coordinate_1.outputs["Object"], + 'Location': surface.eval_argument(nw, offset), + 'Rotation': rotation + }) + + mapping_1 = nw.new_node(Nodes.Mapping, input_kwargs={ + 'Vector': mapping_2, + 'Scale': np.array([log_uniform(2, 4), log_uniform(8, 16), log_uniform(2, 4)]) * scale, + }) + + musgrave_texture_2 = nw.new_node(Nodes.MusgraveTexture, input_kwargs={'Vector': mapping_1, 'Scale': 2.0}, + attrs={'musgrave_dimensions': '4D'}) + musgrave_texture_2.inputs['W'].default_value = sample_range(0, 5) + musgrave_texture_2.inputs['Scale'].default_value = sample_ratio(2.0, 3 / 4, 4 / 3) + + noise_texture_1 = nw.new_node(Nodes.NoiseTexture, + input_kwargs={'Vector': musgrave_texture_2, 'W': 0.7, 'Scale': 10.0}, + attrs={'noise_dimensions': '4D'}) + noise_texture_1.inputs['W'].default_value = sample_range(0, 5) + noise_texture_1.inputs['Scale'].default_value = sample_ratio(5, 0.5, 2) + + colorramp_2 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture_1.outputs["Fac"]}) + colorramp_2.color_ramp.elements.new(0) + colorramp_2.color_ramp.elements[0].position = 0.1727 + colorramp_2.color_ramp.elements[0].color = (0.1567, 0.0162, 0.0017, 1.0) + colorramp_2.color_ramp.elements[1].position = 0.4364 + colorramp_2.color_ramp.elements[1].color = (0.2908, 0.1007, 0.0148, 1.0) + colorramp_2.color_ramp.elements[2].position = 0.5864 + colorramp_2.color_ramp.elements[2].color = (0.0814, 0.0344, 0.0125, 1.0) + colorramp_2.color_ramp.elements[0].position += sample_range(-0.05, 0.05) + colorramp_2.color_ramp.elements[1].position += sample_range(-0.1, 0.1) + colorramp_2.color_ramp.elements[2].position += sample_range(-0.05, 0.05) + for e in colorramp_2.color_ramp.elements: + sample_color(e.color, offset=0.04) + + colorramp_4 = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': noise_texture_1.outputs["Fac"]}) + colorramp_4.color_ramp.elements[0].position = 0.0 + colorramp_4.color_ramp.elements[0].color = (0.4855, 0.4855, 0.4855, 1.0) + colorramp_4.color_ramp.elements[1].position = 1.0 + colorramp_4.color_ramp.elements[1].color = (1.0, 1.0, 1.0, 1.0) + + principled_bsdf_1 = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={ + 'Base Color': colorramp_2.outputs["Color"], + 'Roughness': colorramp_4.outputs["Color"] + }, attrs={'subsurface_method': 'BURLEY'}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf_1}) + + +def apply(obj, selection=None, scale=1, **kwargs): + common.apply(obj, shader_wood_old, selection, scale=scale, **kwargs) diff --git a/infinigen/assets/materials/woods/wood_tile.py b/infinigen/assets/materials/woods/wood_tile.py new file mode 100644 index 000000000..07ec1d9d0 --- /dev/null +++ b/infinigen/assets/materials/woods/wood_tile.py @@ -0,0 +1,15 @@ +# 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: Lingjie Mei +import numpy as np + + +def get_wood_tiles(): + from . import square_wood_tile, staggered_wood_tile, crossed_wood_tile, composite_wood_tile, hexagon_wood_tile + return [square_wood_tile, staggered_wood_tile, crossed_wood_tile, composite_wood_tile, hexagon_wood_tile] + + +def apply(obj, selection=None, vertical=False, scale=None, alternating=None, **kwargs): + func = np.random.choice(get_wood_tiles()) + func.apply(obj, selection, vertical, scale, alternating, **kwargs) diff --git a/infinigen/assets/mollusk/generate.py b/infinigen/assets/mollusk/generate.py index 6cb37767b..103068df0 100644 --- a/infinigen/assets/mollusk/generate.py +++ b/infinigen/assets/mollusk/generate.py @@ -14,13 +14,15 @@ from .base import BaseMolluskFactory from .shell import ShellBaseFactory, ScallopBaseFactory, ClamBaseFactory, MusselBaseFactory from .snail import SnailBaseFactory, ConchBaseFactory, AugerBaseFactory, VoluteBaseFactory, NautilusBaseFactory -from infinigen.assets.utils.misc import build_color_ramp, log_uniform -from ..utils.decorate import assign_material, subsurface2face_size +from infinigen.core.nodes.node_utils import build_color_ramp +from infinigen.core.util.random import log_uniform +from infinigen.assets.utils.decorate import subsurface2face_size +from infinigen.assets.utils.misc import assign_material from infinigen.core.nodes.node_wrangler import NodeWrangler, Nodes from infinigen.core import surface from infinigen.core.placement.factory import AssetFactory from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class MolluskFactory(AssetFactory): diff --git a/infinigen/assets/mollusk/shell.py b/infinigen/assets/mollusk/shell.py index b038ea297..3babd5753 100644 --- a/infinigen/assets/mollusk/shell.py +++ b/infinigen/assets/mollusk/shell.py @@ -11,15 +11,15 @@ import infinigen.core.util.blender as butil from infinigen.assets.creatures.util.animation.driver_repeated import repeated_driver from infinigen.assets.mollusk.base import BaseMolluskFactory -from infinigen.assets.utils.object import mesh2obj, data2mesh, new_circle +from infinigen.assets.utils.object import join_objects, mesh2obj, data2mesh, new_circle from infinigen.assets.utils.draw import shape_by_angles -from infinigen.assets.utils.misc import log_uniform -from infinigen.assets.utils.decorate import displace_vertices, join_objects +from infinigen.core.util.random import log_uniform +from infinigen.assets.utils.decorate import displace_vertices from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class ShellBaseFactory(BaseMolluskFactory): diff --git a/infinigen/assets/mollusk/snail.py b/infinigen/assets/mollusk/snail.py index 5a54bcfb8..8c34c48d0 100644 --- a/infinigen/assets/mollusk/snail.py +++ b/infinigen/assets/mollusk/snail.py @@ -11,12 +11,12 @@ import infinigen.core.util.blender as butil from infinigen.assets.mollusk.base import BaseMolluskFactory from infinigen.assets.utils.object import center, mesh2obj, data2mesh, new_empty -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class SnailBaseFactory(BaseMolluskFactory): freq = 256 diff --git a/infinigen/assets/monocot/agave.py b/infinigen/assets/monocot/agave.py index 5b8abbd54..bf8fd4deb 100644 --- a/infinigen/assets/monocot/agave.py +++ b/infinigen/assets/monocot/agave.py @@ -12,13 +12,14 @@ import infinigen.core.util.blender as butil from infinigen.assets.monocot.growth import MonocotGrowthFactory -from infinigen.assets.utils.decorate import add_distance_to_boundary, join_objects, displace_vertices +from infinigen.assets.utils.decorate import distance2boundary, displace_vertices +from infinigen.assets.utils.object import join_objects from infinigen.assets.utils.draw import cut_plane, leaf -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.random import log_uniform from infinigen.core.surface import shaderfunc_to_material from infinigen.core.util.blender import deep_clone_obj from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class AgaveMonocotFactory(MonocotGrowthFactory): use_distance = True @@ -45,7 +46,7 @@ def build_leaf(self, face_size): x_anchors = 0, .2 * np.cos(self.bud_angle), uniform(1., 1.4), 1.5 y_anchors = 0, .2 * np.sin(self.bud_angle), uniform(.1, .15), 0 obj = leaf(x_anchors, y_anchors, face_size=face_size) - distance = add_distance_to_boundary(obj) + distance = distance2boundary(obj) lower = deep_clone_obj(obj) z_offset = -log_uniform(.08, .16) diff --git a/infinigen/assets/monocot/banana.py b/infinigen/assets/monocot/banana.py index 356ed705b..74fa33461 100644 --- a/infinigen/assets/monocot/banana.py +++ b/infinigen/assets/monocot/banana.py @@ -3,21 +3,21 @@ # Authors: Lingjie Mei - +import bpy import bmesh import numpy as np from numpy.random import uniform -from infinigen.assets.utils.decorate import displace_vertices, join_objects, read_co +from infinigen.assets.utils.decorate import displace_vertices, read_co from infinigen.assets.utils.draw import bezier_curve, leaf from infinigen.assets.utils.nodegroup import geo_radius -from infinigen.assets.utils.object import origin2lowest +from infinigen.assets.utils.object import join_objects, origin2lowest from infinigen.core import surface from infinigen.assets.monocot.growth import MonocotGrowthFactory -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.random import log_uniform from infinigen.core.util import blender as butil from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class BananaMonocotFactory(MonocotGrowthFactory): diff --git a/infinigen/assets/monocot/generate.py b/infinigen/assets/monocot/generate.py index 4ce3b293d..98b16eb63 100644 --- a/infinigen/assets/monocot/generate.py +++ b/infinigen/assets/monocot/generate.py @@ -16,9 +16,9 @@ from .tussock import TussockMonocotFactory from infinigen.core.placement.factory import AssetFactory from infinigen.core.util.math import FixedSeed -from ..utils.decorate import join_objects -from ..utils.mesh import polygon_angles -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.assets.utils.object import join_objects +from infinigen.assets.utils.mesh import polygon_angles +from infinigen.core.tagging import tag_object, tag_nodegroup class MonocotFactory(AssetFactory): max_cluster = 10 diff --git a/infinigen/assets/monocot/grasses.py b/infinigen/assets/monocot/grasses.py index b8f2d65ec..0fea24138 100644 --- a/infinigen/assets/monocot/grasses.py +++ b/infinigen/assets/monocot/grasses.py @@ -11,11 +11,14 @@ from numpy.random import uniform from infinigen.assets.monocot.growth import MonocotGrowthFactory -from infinigen.assets.utils.decorate import assign_material, join_objects, remove_vertices, write_attribute, \ +from infinigen.assets.utils.decorate import remove_vertices, write_attribute, \ write_material_index +from infinigen.assets.utils.misc import assign_material +from infinigen.assets.utils.object import join_objects from infinigen.assets.utils.draw import bezier_curve, leaf, spin from infinigen.assets.utils.mesh import polygon_angles -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler @@ -26,7 +29,7 @@ from infinigen.core.surface import read_attr_data, shaderfunc_to_material from infinigen.core.util import blender as butil from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class GrassesMonocotFactory(MonocotGrowthFactory): @@ -136,7 +139,6 @@ def __init__(self, factory_seed, coarse=False): self.leaf_range = .1, .7 def build_leaf(self, face_size): - super().build_leaf(face_size) x_anchors = np.array([0, uniform(.1, .2), uniform(.5, .7), 1.]) y_anchors = np.array([0, uniform(.03, .06), uniform(.03, .06), 0]) obj = leaf(x_anchors, y_anchors, face_size=face_size) @@ -238,7 +240,7 @@ def create_asset(self, **params): @staticmethod def shader_ear(nw: NodeWrangler): - color = *colorsys.hsv_to_rgb(uniform(.06, .1), uniform(.2, .5), log_uniform(.2, .5)), 1 + color = hsv2rgba(uniform(.06, .1), uniform(.2, .5), log_uniform(.2, .5)) specular = uniform(.0, .2) clearcoat = 0 if uniform(0, 1) < .8 else uniform(.2, .5) noise_texture = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Scale': 50}) diff --git a/infinigen/assets/monocot/growth.py b/infinigen/assets/monocot/growth.py index bc843034b..b35402ed7 100644 --- a/infinigen/assets/monocot/growth.py +++ b/infinigen/assets/monocot/growth.py @@ -1,5 +1,6 @@ # 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. +# 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: Lingjie Mei @@ -12,13 +13,15 @@ import numpy as np from numpy.random import uniform -from infinigen.assets.utils.decorate import assign_material, displace_vertices, geo_extension, join_objects -from infinigen.assets.utils.misc import build_color_ramp, log_uniform +from infinigen.assets.utils.decorate import displace_vertices, geo_extension +from infinigen.assets.utils.misc import assign_material +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform from infinigen.assets.utils.nodegroup import geo_radius from infinigen.core.placement.detail import adapt_mesh_resolution from infinigen.core.surface import shaderfunc_to_material from infinigen.core.util import blender as butil -from infinigen.assets.utils.object import data2mesh, mesh2obj, new_cube, origin2leftmost +from infinigen.assets.utils.object import data2mesh, join_objects, mesh2obj, new_cube, origin2leftmost from infinigen.core.nodes.node_info import Nodes from infinigen.core.placement.factory import AssetFactory, make_asset_collection from infinigen.core.nodes.node_wrangler import NodeWrangler @@ -26,7 +29,9 @@ from infinigen.core import surface from infinigen.core.util.blender import deep_clone_obj from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup +from infinigen.core.nodes.node_utils import build_color_ramp + class MonocotGrowthFactory(AssetFactory): use_distance = False @@ -51,9 +56,9 @@ def __init__(self, factory_seed, coarse=False): self.align_factor = 0 self.align_direction = 1, 0, 0 self.base_hue = self.build_base_hue() - self.bright_color = *colorsys.hsv_to_rgb(self.base_hue, uniform(.6, .8), log_uniform(.05, .1)), 1 - self.dark_color = *colorsys.hsv_to_rgb((self.base_hue + uniform(-.03, .03)) % 1, uniform(.8, 1.), - log_uniform(.05, .2)), 1 + self.bright_color = hsv2rgba(self.base_hue, uniform(.6, .8), log_uniform(.05, .1)) + self.dark_color = hsv2rgba((self.base_hue + uniform(-.03, .03)) % 1, uniform(.8, 1.), + log_uniform(.05, .2)) self.material = shaderfunc_to_material(self.shader_monocot, self.dark_color, self.bright_color, self.use_distance) @@ -130,7 +135,7 @@ def geo_flower(nw: NodeWrangler, leaves): 'Scale': scale }) geometry = nw.new_node(Nodes.RealizeInstances, [instances]) - geometry = nw.new_node(Nodes.StoreNamedAttribute, + geometry = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={'Geometry': geometry, 'Name':'z_rotation', 'Value': z_rotation}) geometry = nw.new_node(Nodes.JoinGeometry, [[stem, geometry]]) nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) diff --git a/infinigen/assets/monocot/kelp.py b/infinigen/assets/monocot/kelp.py index 99f14773e..5851f9cc2 100644 --- a/infinigen/assets/monocot/kelp.py +++ b/infinigen/assets/monocot/kelp.py @@ -12,9 +12,9 @@ from infinigen.assets.creatures.util.animation.driver_repeated import repeated_driver from infinigen.assets.monocot.growth import MonocotGrowthFactory from infinigen.assets.utils.draw import bezier_curve, leaf -from infinigen.assets.utils.decorate import assign_material, join_objects -from infinigen.assets.utils.misc import log_uniform -from infinigen.assets.utils.object import origin2leftmost +from infinigen.assets.utils.misc import assign_material +from infinigen.core.util.random import log_uniform +from infinigen.assets.utils.object import join_objects, origin2leftmost from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core.placement.detail import remesh_with_attrs from infinigen.core.util.math import FixedSeed diff --git a/infinigen/assets/monocot/pinecone.py b/infinigen/assets/monocot/pinecone.py index bb60c3db4..c360da329 100644 --- a/infinigen/assets/monocot/pinecone.py +++ b/infinigen/assets/monocot/pinecone.py @@ -14,13 +14,15 @@ from infinigen.assets.monocot.growth import MonocotGrowthFactory from infinigen.assets.utils.object import new_circle from infinigen.assets.utils.draw import shape_by_angles, shape_by_xs -from infinigen.assets.utils.misc import build_color_ramp, log_uniform +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core.placement.detail import remesh_with_attrs from infinigen.core.surface import shaderfunc_to_material from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup +from infinigen.core.nodes.node_utils import build_color_ramp class PineconeFactory(MonocotGrowthFactory): def __init__(self, factory_seed, coarse=False): @@ -33,8 +35,8 @@ def __init__(self, factory_seed, coarse=False): self.stem_offset = uniform(.2, .4) self.perturb = 0 self.scale_curve = [(0, .5), (.5, uniform(.6, 1.)), (1, uniform(.1, .2))] - self.bright_color = *colorsys.hsv_to_rgb(uniform(.02, .06), uniform(.8, 1.), .01), 1 - self.dark_color = *colorsys.hsv_to_rgb(uniform(.02, .06), uniform(.8, 1.), .005), 1 + self.bright_color = hsv2rgba(uniform(.02, .06), uniform(.8, 1.), .01) + self.dark_color = hsv2rgba(uniform(.02, .06), uniform(.8, 1.), .005) self.material = shaderfunc_to_material(self.shader_monocot, self.dark_color, self.bright_color, self.use_distance) diff --git a/infinigen/assets/monocot/tussock.py b/infinigen/assets/monocot/tussock.py index ffc0493de..225427d8a 100644 --- a/infinigen/assets/monocot/tussock.py +++ b/infinigen/assets/monocot/tussock.py @@ -8,9 +8,9 @@ from infinigen.assets.utils.draw import leaf from infinigen.assets.monocot.growth import MonocotGrowthFactory -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.random import log_uniform from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object +from infinigen.core.tagging import tag_object class TussockMonocotFactory(MonocotGrowthFactory): diff --git a/infinigen/assets/monocot/veratrum.py b/infinigen/assets/monocot/veratrum.py index e22f01a6a..a7906be1e 100644 --- a/infinigen/assets/monocot/veratrum.py +++ b/infinigen/assets/monocot/veratrum.py @@ -1,5 +1,6 @@ # 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. +# 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: Lingjie Mei @@ -11,10 +12,12 @@ from numpy.random import uniform from infinigen.assets.monocot.growth import MonocotGrowthFactory -from infinigen.assets.utils.decorate import add_distance_to_boundary, assign_material, join_objects, write_attribute, \ - write_material_index +from infinigen.assets.utils.decorate import distance2boundary, write_attribute, write_material_index +from infinigen.assets.utils.misc import assign_material +from infinigen.assets.utils.object import join_objects from infinigen.assets.utils.draw import leaf, spin -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core.placement.factory import AssetFactory @@ -22,7 +25,8 @@ from infinigen.core.surface import shaderfunc_to_material from infinigen.core.util.math import FixedSeed from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup + class VeratrumMonocotFactory(MonocotGrowthFactory): @@ -49,7 +53,7 @@ def build_base_hue(): @staticmethod def shader_ear(nw: NodeWrangler): - color = *colorsys.hsv_to_rgb(uniform(.1, .35), uniform(.1, .5), log_uniform(.2, .5)), 1 + color = hsv2rgba(uniform(.1, .35), uniform(.1, .5), log_uniform(.2, .5)) specular = uniform(.0, .2) clearcoat = 0 if uniform(0, 1) < .8 else uniform(.2, .5) noise_texture = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Scale': 50}) @@ -68,9 +72,9 @@ def build_leaf(self, face_size): x_anchors = 0, .2 * np.cos(self.bud_angle), uniform(.6, .7), .8 y_anchors = 0, .2 * np.sin(self.bud_angle), uniform(.06, .1), 0 obj = leaf(x_anchors, y_anchors, face_size=face_size) - distance = add_distance_to_boundary(obj) + distance = distance2boundary(obj) - vg = obj.vertex_groups['distance'] + vg = obj.vertex_groups.new(name='distance') weights = np.cos(self.freq * distance) ** 4 for i, w in enumerate(weights): vg.add([i], w, 'REPLACE') @@ -86,7 +90,7 @@ def create_asset(self, **params): self.decorate_monocot(obj) assign_material(obj, [self.material, self.branch_material]) - write_material_index(obj, surface.read_attr_data(obj, 'ear', 'FACE').astype(int)[:, 0]) + write_material_index(obj, surface.read_attr_data(obj, 'ear', 'FACE').astype(int)) tag_object(obj, 'veratrum') return obj @@ -128,10 +132,10 @@ def __init__(self, factory_seed, coarse=False): self.leaf_range = 0, .98 def build_leaf(self, face_size): - x_achors = 0, .04, .06, .04, 0 + x_anchors = 0, .04, .06, .04, 0 y_anchors = 0, .01, 0, -.01, 0 z_anchors = 0, - .01, -.01, -.006, 0 - anchors = [x_achors, y_anchors, z_anchors] + anchors = [x_anchors, y_anchors, z_anchors] obj = spin(anchors, [0, 2, 4], dupli=True, loop=True, resolution=np.random.randint(3, 5), axis=(1, 0, 0)) butil.modify_mesh(obj, 'WELD', merge_threshold=face_size / 2) diff --git a/infinigen/assets/mushroom/cap.py b/infinigen/assets/mushroom/cap.py index 8a36d7b8c..49374adcf 100644 --- a/infinigen/assets/mushroom/cap.py +++ b/infinigen/assets/mushroom/cap.py @@ -10,12 +10,14 @@ import numpy as np from numpy.random import uniform -from infinigen.assets.utils.decorate import assign_material, displace_vertices, geo_extension, join_objects, \ +from infinigen.assets.utils.decorate import displace_vertices, geo_extension, \ subsurface2face_size +from infinigen.assets.utils.misc import assign_material from infinigen.assets.utils.draw import spin from infinigen.assets.utils.mesh import polygon_angles -from infinigen.assets.utils.misc import build_color_ramp, log_uniform -from infinigen.assets.utils.object import data2mesh, mesh2obj +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform +from infinigen.assets.utils.object import data2mesh, join_objects, mesh2obj from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core.placement.detail import remesh_with_attrs @@ -23,7 +25,8 @@ from infinigen.core import surface from infinigen.core.util import blender as butil from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup +from infinigen.core.nodes.node_utils import build_color_ramp class MushroomCapFactory(AssetFactory): @@ -177,8 +180,8 @@ def geo_xyz(nw: NodeWrangler): m = nw.new_node(Nodes.AttributeStatistic, [geometry, None, component]).outputs['Max'] geometry = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ - 'Geometry': geometry, - 'Name': name, + 'Geometry': geometry, + 'Name': name, 'Value': nw.scalar_divide(component, m) }) nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) @@ -190,7 +193,7 @@ def geo_morel(nw: NodeWrangler): 'Scale': uniform(15, 20), 'Randomness': uniform(.5, 1) }, attrs={'feature': 'DISTANCE_TO_EDGE'}), .05) - geometry = nw.new_node(Nodes.StoreNamedAttribute, + geometry = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={'Geometry':geometry, 'Name':'morel', 'Value': selection}) nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) @@ -225,8 +228,9 @@ def create_asset(self, face_size, **params) -> bpy.types.Object: assign_material(obj, self.material_cap) if self.is_morel: - obj.data.attributes.active = obj.data.attributes['morel'] - bpy.ops.geometry.attribute_convert(mode='VERTEX_GROUP') + with butil.SelectObjects(obj): + surface.set_active(obj,'morel') + bpy.ops.geometry.attribute_convert(mode='VERTEX_GROUP') butil.modify_mesh(obj, 'DISPLACE', vertex_group='morel', strength=.04, mid_level=.7) if self.gill_config is not None: @@ -252,12 +256,12 @@ def create_asset(self, face_size, **params) -> bpy.types.Object: @staticmethod def shader_voronoi(nw: NodeWrangler, base_hue): - bright_color = *colorsys.hsv_to_rgb(base_hue, uniform(.4, .8), log_uniform(.05, .2)), 1 + bright_color = hsv2rgba(base_hue, uniform(.4, .8), log_uniform(.05, .2)) dark_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.05, .05)) % 1, uniform(.4, .8), log_uniform(.01, .05)), 1 subsurface_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.05, .05)) % 1, uniform(.4, .8), log_uniform(.05, .2)), 1 - light_color = *colorsys.hsv_to_rgb(base_hue, uniform(0, .1), uniform(.2, .8)), 1 + light_color = hsv2rgba(base_hue, uniform(0, .1), uniform(.2, .8)) anchors = [.0, .3, .6, 1.] if uniform(0, 1) < .5 else [.0, .4, .7, 1.] color = build_color_ramp(nw, nw.musgrave(500), anchors, [dark_color, dark_color, bright_color, bright_color]) @@ -296,12 +300,12 @@ def shader_voronoi(nw: NodeWrangler, base_hue): @staticmethod def shader_speckle(nw: NodeWrangler, base_hue): - bright_color = *colorsys.hsv_to_rgb(base_hue, uniform(.4, .8), log_uniform(.05, .2)), 1 + bright_color = hsv2rgba(base_hue, uniform(.4, .8), log_uniform(.05, .2)) dark_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.05, .05)) % 1, uniform(.4, .8), log_uniform(.01, .05)), 1 subsurface_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.05, .05)) % 1, uniform(.4, .8), log_uniform(.05, .2)), 1 - light_color = *colorsys.hsv_to_rgb(base_hue, uniform(0, .1), uniform(.2, .8)), 1 + light_color = hsv2rgba(base_hue, uniform(0, .1), uniform(.2, .8)) anchors = [.0, .3, .6, 1.] if uniform(0, 1) < .5 else [.0, .4, .7, 1.] color = build_color_ramp(nw, nw.musgrave(500), anchors, [dark_color, dark_color, bright_color, bright_color]) @@ -325,12 +329,12 @@ def shader_speckle(nw: NodeWrangler, base_hue): @staticmethod def shader_noise(nw: NodeWrangler, base_hue): - bright_color = *colorsys.hsv_to_rgb(base_hue, uniform(.4, .8), log_uniform(.05, .2)), 1 + bright_color = hsv2rgba(base_hue, uniform(.4, .8), log_uniform(.05, .2)) dark_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.05, .05)) % 1, uniform(.4, .8), log_uniform(.01, .05)), 1 subsurface_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.05, .05)) % 1, uniform(.4, .8), log_uniform(.05, .2)), 1 - light_color = *colorsys.hsv_to_rgb(base_hue, uniform(0, .1), uniform(.2, .8)), 1 + light_color = hsv2rgba(base_hue, uniform(0, .1), uniform(.2, .8)) anchors = [.0, .3, .6, 1.] if uniform(0, 1) < .5 else [.0, .4, .7, 1.] color = build_color_ramp(nw, nw.musgrave(500), anchors, [dark_color, dark_color, bright_color, bright_color]) @@ -356,10 +360,10 @@ def shader_noise(nw: NodeWrangler, base_hue): @staticmethod def shader_cap(nw: NodeWrangler, base_hue): - bright_color = *colorsys.hsv_to_rgb(base_hue, uniform(.6, .8), log_uniform(.05, .2)), 1 + bright_color = hsv2rgba(base_hue, uniform(.6, .8), log_uniform(.05, .2)) dark_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.05, .05)) % 1, uniform(.4, .8), log_uniform(.01, .05)), 1 - light_color = *colorsys.hsv_to_rgb(base_hue, uniform(0, .1), uniform(.6, .8)), 1 + light_color = hsv2rgba(base_hue, uniform(0, .1), uniform(.6, .8)) subsurface_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.05, .05)) % 1, uniform(.6, .8), log_uniform(.05, .2)), 1 diff --git a/infinigen/assets/mushroom/generate.py b/infinigen/assets/mushroom/generate.py index 369b39904..1f4c49459 100644 --- a/infinigen/assets/mushroom/generate.py +++ b/infinigen/assets/mushroom/generate.py @@ -13,12 +13,12 @@ from infinigen.core.util.blender import deep_clone_obj from infinigen.core.util.math import FixedSeed from .growth import MushroomGrowthFactory -from infinigen.assets.utils.decorate import join_objects +from infinigen.assets.utils.object import join_objects from infinigen.assets.utils.mesh import polygon_angles from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil -from ..utils.misc import log_uniform -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.util.random import log_uniform +from infinigen.core.tagging import tag_object, tag_nodegroup class MushroomFactory(AssetFactory): max_cluster = 10 diff --git a/infinigen/assets/mushroom/growth.py b/infinigen/assets/mushroom/growth.py index ba60c1194..4164986ec 100644 --- a/infinigen/assets/mushroom/growth.py +++ b/infinigen/assets/mushroom/growth.py @@ -13,14 +13,14 @@ from infinigen.core.util.math import FixedSeed from .cap import MushroomCapFactory from .stem import MushroomStemFactory -from infinigen.assets.utils.object import origin2lowest +from infinigen.assets.utils.object import join_objects, origin2lowest from infinigen.core.placement.factory import AssetFactory -from ..utils.decorate import join_objects -from ..utils.misc import build_color_ramp, log_uniform - +from infinigen.assets.utils.object import join_objects +from infinigen.core.util.random import log_uniform +from infinigen.core.nodes.node_utils import build_color_ramp class MushroomGrowthFactory(AssetFactory): - + def __init__(self, factory_seed, coarse=False): super().__init__(factory_seed, coarse) with FixedSeed(factory_seed): diff --git a/infinigen/assets/mushroom/stem.py b/infinigen/assets/mushroom/stem.py index 28846a49a..d18cfd36f 100644 --- a/infinigen/assets/mushroom/stem.py +++ b/infinigen/assets/mushroom/stem.py @@ -8,10 +8,12 @@ import numpy as np from numpy.random import uniform -from infinigen.assets.utils.decorate import assign_material, geo_extension, join_objects, subsurface2face_size +from infinigen.assets.utils.decorate import geo_extension, subsurface2face_size +from infinigen.assets.utils.misc import assign_material +from infinigen.assets.utils.object import join_objects from infinigen.assets.utils.draw import spin -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core.placement.detail import remesh_with_attrs @@ -19,7 +21,7 @@ from infinigen.core import surface from infinigen.core.util import blender as butil from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class MushroomStemFactory(AssetFactory): diff --git a/infinigen/assets/organizer/__init__.py b/infinigen/assets/organizer/__init__.py new file mode 100644 index 000000000..4eeef6039 --- /dev/null +++ b/infinigen/assets/organizer/__init__.py @@ -0,0 +1,9 @@ +# 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: Beining Han + + +from .basket import BasketBaseFactory +from .hook import HookBaseFactory, SpatulaOnHookBaseFactory +from .plate_rack import PlateRackBaseFactory, PlateOnRackBaseFactory diff --git a/infinigen/assets/organizer/basket.py b/infinigen/assets/organizer/basket.py new file mode 100644 index 000000000..5e711c113 --- /dev/null +++ b/infinigen/assets/organizer/basket.py @@ -0,0 +1,306 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core import tagging + +import bpy +from infinigen.assets.shelves.utils import nodegroup_tagged_cube +from infinigen.assets.materials.plastics.plastic_rough import shader_rough_plastic + + +@node_utils.to_nodegroup('nodegroup_holes', singleton=False, type='GeometryNodeTree') +def nodegroup_holes(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Value1', 0.5000), + ('NodeSocketFloat', 'Value2', 0.5000), + ('NodeSocketFloat', 'Value3', 0.5000), + ('NodeSocketFloat', 'Value4', 0.5000), + ('NodeSocketFloat', 'Value5', 0.5000), + ('NodeSocketFloat', 'Value6', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Value3"], 1: 0.0000}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Value1"], 1: add}, attrs={'operation': 'SUBTRACT'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Value6"], 1: 0.0000}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: add}, attrs={'operation': 'SUBTRACT'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Value4"], 1: 0.0000}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: group_input.outputs["Value2"]}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: add_3}, attrs={'operation': 'DIVIDE'}) + + divide_1 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: add_3}, attrs={'operation': 'DIVIDE'}) + + grid = nw.new_node(Nodes.MeshGrid, + input_kwargs={'Size X': subtract, 'Size Y': subtract_1, 'Vertices X': divide, 'Vertices Y': divide_1}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': grid.outputs["Mesh"], 'Name': 'uv_map', 3: grid.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute, 'Rotation': (0.0000, 1.5708, 0.0000)}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Value5"], 1: 0.0000}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: 0.1}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_5, 'Y': add_2, 'Z': add_2}) + + cube_2 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_3}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_2.outputs["Mesh"], 'Name': 'uv_map', 3: cube_2.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + instance_on_points = nw.new_node(Nodes.InstanceOnPoints, input_kwargs={'Points': transform_1, 'Instance': store_named_attribute_1}) + + subtract_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: add}, attrs={'operation': 'SUBTRACT'}) + + divide_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_2, 1: add_3}, attrs={'operation': 'DIVIDE'}) + + grid_1 = nw.new_node(Nodes.MeshGrid, + input_kwargs={'Size X': subtract_2, 'Size Y': subtract, 'Vertices X': divide_2, 'Vertices Y': divide}) + + store_named_attribute_2 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': grid_1.outputs["Mesh"], 'Name': 'uv_map', 3: grid_1.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_2, 'Rotation': (1.5708, 0.0000, 0.0000)}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: 0.1}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_2, 'Y': add_6, 'Z': add_2}) + + cube_3 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_4}) + + store_named_attribute_3 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_3.outputs["Mesh"], 'Name': 'uv_map', 3: cube_3.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + instance_on_points_1 = nw.new_node(Nodes.InstanceOnPoints, input_kwargs={'Points': transform_2, 'Instance': store_named_attribute_3}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Instances1': instance_on_points, 'Instances2': instance_on_points_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_handle_hole', singleton=False, type='GeometryNodeTree') +def nodegroup_handle_hole(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'X', 0.0000), + ('NodeSocketFloat', 'Z', 0.0000), + ('NodeSocketFloat', 'Value', 0.5000), + ('NodeSocketFloat', 'Value2', 0.5000), + ('NodeSocketInt', 'Level', 0)]) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["X"], 'Y': 1.0000, 'Z': group_input.outputs["Z"]}) + + cube_2 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_3}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_2.outputs["Mesh"], 'Name': 'uv_map', 3: cube_2.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + subdivide_mesh_2 = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': store_named_attribute}) + + subdivision_surface_2 = nw.new_node(Nodes.SubdivisionSurface, + input_kwargs={'Mesh': subdivide_mesh_2, 'Level': group_input.outputs["Level"]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Value"]}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: multiply, 1: group_input.outputs["Value2"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': subtract}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': subdivision_surface_2, 'Translation': combine_xyz_4}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_1}, attrs={'is_active_output': True}) + + +def geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + depth = nw.new_node(Nodes.Value, label='depth') + depth.outputs[0].default_value = kwargs['depth'] + + width = nw.new_node(Nodes.Value, label='width') + width.outputs[0].default_value = kwargs['width'] + + height = nw.new_node(Nodes.Value, label='height') + height.outputs[0].default_value = kwargs['height'] + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': depth, 'Y': width, 'Z': height}) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', + 3: cube.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': store_named_attribute, 'Level': 2}) + + sub_level = nw.new_node(Nodes.Integer, label='sub_level') + sub_level.integer = kwargs['frame_sub_level'] + + subdivision_surface = nw.new_node(Nodes.SubdivisionSurface, + input_kwargs={'Mesh': subdivide_mesh, 'Level': sub_level}) + + differences = [] + + if kwargs['has_handle']: + hole_depth = nw.new_node(Nodes.Value, label='hole_depth') + hole_depth.outputs[0].default_value = kwargs['handle_depth'] + + hole_height = nw.new_node(Nodes.Value, label='hole_height') + hole_height.outputs[0].default_value = kwargs['handle_height'] + + hole_dist = nw.new_node(Nodes.Value, label='hole_dist') + hole_dist.outputs[0].default_value = kwargs['handle_dist_to_top'] + + handle_level = nw.new_node(Nodes.Integer, label='handle_level') + handle_level.integer = kwargs['handle_sub_level'] + handle_hole = nw.new_node(nodegroup_handle_hole().name, + input_kwargs={'X': hole_depth, 'Z': hole_height, 'Value': height, 'Value2': hole_dist, + 'Level': handle_level}) + differences.append(handle_hole) + + thickness = nw.new_node(Nodes.Value, label='thickness') + thickness.outputs[0].default_value = kwargs['thickness'] + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: depth, 1: thickness}, attrs={'operation': 'SUBTRACT'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: width, 1: thickness}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract, 'Y': subtract_1, 'Z': height}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_1}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_1.outputs["Mesh"], 'Name': 'uv_map', + 3: cube_1.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + subdivide_mesh_1 = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': store_named_attribute_1, 'Level': 2}) + + subdivision_surface_1 = nw.new_node(Nodes.SubdivisionSurface, + input_kwargs={'Mesh': subdivide_mesh_1, 'Level': sub_level}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: thickness, 2: 0.2500}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': subdivision_surface_1, 'Translation': combine_xyz_2}) + + if kwargs['has_holes']: + gap_size = nw.new_node(Nodes.Value, label='gap_size') + gap_size.outputs[0].default_value = kwargs['hole_gap_size'] + + hole_edge_gap = nw.new_node(Nodes.Value, label='hole_edge_gap') + hole_edge_gap.outputs[0].default_value = kwargs['hole_edge_gap'] + + hole_size = nw.new_node(Nodes.Value, label='hole_size') + hole_size.outputs[0].default_value = kwargs['hole_size'] + holes = nw.new_node(nodegroup_holes().name, + input_kwargs={'Value1': height, 'Value2': gap_size, 'Value3': hole_edge_gap, + 'Value4': hole_size, 'Value5': depth, 'Value6': width}) + differences.extend([holes.outputs["Instances1"], holes.outputs["Instances2"]]) + + difference = nw.new_node(Nodes.MeshBoolean, + input_kwargs={'Mesh 1': subdivision_surface, 'Mesh 2': [transform] + differences}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': difference.outputs["Mesh"]}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: height}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_1}) + + transform_geometry = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': realize_instances, 'Translation': combine_xyz_3}) + + set_material = nw.new_node(Nodes.SetMaterial, input_kwargs={'Geometry': transform_geometry, + 'Material': surface.shaderfunc_to_material(shader_rough_plastic)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_material}, + attrs={'is_active_output': True}) + + +class BasketBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(BasketBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = params + + def sample_params(self): + return self.params.copy() + + def get_asset_params(self, i=0): + params = self.sample_params() + if params.get('depth', None) is None: + params['depth'] = uniform(0.15, 0.4) + if params.get('width', None) is None: + params['width'] = uniform(0.2, 0.6) + if params.get('height', None) is None: + params['height'] = uniform(0.06, 0.24) + if params.get('frame_sub_level', None) is None: + params['frame_sub_level'] = np.random.choice([0, 3], p=[0.5, 0.5]) + if params.get('thickness', None) is None: + params['thickness'] = uniform(0.001, 0.005) + + if params.get('has_handle', None) is None: + params['has_handle'] = np.random.choice([True, False], p=[0.8, 0.2]) + if params.get('handle_sub_level', None) is None: + params['handle_sub_level'] = np.random.choice([0, 1, 2], p=[0.2, 0.4, 0.4]) + if params.get('handle_depth', None) is None: + params['handle_depth'] = params['depth'] * uniform(0.2, 0.4) + if params.get('handle_height', None) is None: + params['handle_height'] = params['height'] * uniform(0.1, 0.25) + if params.get('handle_dist_to_top', None) is None: + params['handle_dist_to_top'] = (params['handle_height'] * 0.5 + + params['height'] * uniform(0.08, 0.15)) + + if params.get('has_holes', None) is None: + if params['height'] < 0.12: + params['has_holes'] = False + else: + params['has_holes'] = np.random.choice([True, False], p=[0.5, 0.5]) + if params.get('hole_size', None) is None: + params['hole_size'] = uniform(0.005, 0.01) + if params.get('hole_gap_size', None) is None: + params['hole_gap_size'] = params['hole_size'] * uniform(0.8, 1.1) + if params.get('hole_edge_gap', None) is None: + params['hole_edge_gap'] = uniform(0.04, 0.06) + + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, geometry_nodes, attributes=[], apply=True, input_kwargs=obj_params) + tagging.tag_system.relabel_obj(obj) + + return obj diff --git a/infinigen/assets/organizer/hook.py b/infinigen/assets/organizer/hook.py new file mode 100644 index 000000000..2403dd132 --- /dev/null +++ b/infinigen/assets/organizer/hook.py @@ -0,0 +1,387 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core import tagging + +import bpy +from infinigen.assets.materials import shader_rough_plastic, shader_brushed_metal +from infinigen.assets.materials.plastics.plastic_rough import shader_rough_plastic + + +def hook_geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.5 of the node_transpiler + + hook_num = nw.new_node(Nodes.Integer, label='hook_num') + hook_num.integer = kwargs["num_hook"] + + add = nw.new_node(Nodes.Math, input_kwargs={0: hook_num, 1: -1.0000}) + + hook_gap = nw.new_node(Nodes.Value, label='hook_gap') + hook_gap.outputs[0].default_value = kwargs["hook_gap"] + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: hook_gap, 1: add}, attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply}, attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_2}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_1}) + + mesh_line = nw.new_node(Nodes.MeshLine, + input_kwargs={'Count': add, 'Start Location': combine_xyz_2, 'Offset': combine_xyz_1}, + attrs={'mode': 'END_POINTS'}) + + bezier_segment = nw.new_node(Nodes.CurveBezierSegment, + input_kwargs={'Start': (0.0000, 0.0000, 0.0000), + 'Start Handle': (0.0000, 0.0000, kwargs["init_handle"]), + 'End Handle': kwargs["curve_handle"], + 'End': kwargs["curve_end_point"]}) + + curve_line = nw.new_node(Nodes.CurveLine) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [bezier_segment, curve_line]}) + + spline_parameter = nw.new_node(Nodes.SplineParameter) + + float_curve = nw.new_node(Nodes.FloatCurve, input_kwargs={'Factor': spline_parameter.outputs["Factor"]}) + node_utils.assign_curve(float_curve.mapping.curves[0], [(0.0000, 0.8), (0.5, 0.8), (1.0000, 0.8)]) + + raduis = nw.new_node(Nodes.Value, label='raduis') + raduis.outputs[0].default_value = kwargs['hook_radius'] + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: float_curve, 1: raduis}, attrs={'operation': 'MULTIPLY'}) + + set_curve_radius = nw.new_node(Nodes.SetCurveRadius, input_kwargs={'Curve': join_geometry_3, 'Radius': multiply_3}) + + curve_circle = nw.new_node(Nodes.CurveCircle, + input_kwargs={'Resolution': kwargs['hook_resolution'], + 'Point 1': (1.0000, 0.0000, 0.0000), + 'Point 3': (-1.0000, 0.0000, 0.0000)}, + attrs={'mode': 'POINTS'}) + + hook_reshape = nw.new_node(Nodes.Vector, label='hook_reshape') + hook_reshape.vector = (1.0000, 1.0000, 1.0000) + + transform_geometry_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle.outputs["Curve"], 'Scale': hook_reshape}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': set_curve_radius, 'Profile Curve': transform_geometry_2, + 'Fill Caps': True}) + + hook_size = nw.new_node(Nodes.Value, label='hook_size') + hook_size.outputs[0].default_value = kwargs['hook_size'] + + transform_geometry = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': curve_to_mesh, 'Scale': hook_size}) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': transform_geometry}) + + merge_by_distance_1 = nw.new_node(Nodes.MergeByDistance, input_kwargs={'Geometry': realize_instances_1}) + + instance_on_points = nw.new_node(Nodes.InstanceOnPoints, + input_kwargs={'Points': mesh_line, 'Instance': merge_by_distance_1}) + + scale_instances = nw.new_node(Nodes.ScaleInstances, input_kwargs={'Instances': instance_on_points}) + + set_material = nw.new_node(Nodes.SetMaterial, input_kwargs={'Geometry': scale_instances, + 'Material': surface.shaderfunc_to_material(shader_brushed_metal)}) + + board_side_gap = nw.new_node(Nodes.Value, label='board_side_gap') + board_side_gap.outputs[0].default_value = kwargs['board_side_gap'] + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: board_side_gap}) + + board_thickness = nw.new_node(Nodes.Value, label='board_thickness') + board_thickness.outputs[0].default_value = kwargs['board_thickness'] + + board_height = nw.new_node(Nodes.Value, label='board_height') + board_height.outputs[0].default_value = kwargs['board_height'] + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_1, 'Y': board_thickness, 'Z': board_height}) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: board_thickness, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: board_height}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: hook_size, 1: multiply_5}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_4, 'Z': subtract}) + + transform_geometry_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Translation': combine_xyz_3}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, input_kwargs={'Geometry': transform_geometry_1, + 'Material': surface.shaderfunc_to_material(shader_rough_plastic)}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material, set_material_1]}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_2}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': realize_instances}) + + transform_geometry_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': triangulate, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_geometry_3}, + attrs={'is_active_output': True}) + + +def spatula_geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.5 of the node_transpiler + + handle_length = nw.new_node(Nodes.Value, label='handle_length') + handle_length.outputs[0].default_value = kwargs['handle_length'] + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': handle_length}) + + mesh_line = nw.new_node(Nodes.MeshLine, input_kwargs={'Count': 64, 'Offset': combine_xyz}, attrs={'mode': 'END_POINTS'}) + + mesh_to_curve = nw.new_node(Nodes.MeshToCurve, input_kwargs={'Mesh': mesh_line}) + + handle_radius = nw.new_node(Nodes.Value, label='handle_radius') + handle_radius.outputs[0].default_value = kwargs['handle_radius'] + + spline_parameter = nw.new_node(Nodes.SplineParameter) + + float_curve = nw.new_node(Nodes.FloatCurve, input_kwargs={'Value': spline_parameter.outputs["Factor"]}) + node_utils.assign_curve(float_curve.mapping.curves[0], kwargs['handle_control_points']) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: handle_radius, 1: float_curve}, attrs={'operation': 'MULTIPLY'}) + + set_curve_radius = nw.new_node(Nodes.SetCurveRadius, input_kwargs={'Curve': mesh_to_curve, 'Radius': multiply}) + + curve_circle = nw.new_node(Nodes.CurveCircle) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': set_curve_radius, 'Profile Curve': curve_circle.outputs["Curve"], 'Fill Caps': True}) + + transform_geometry = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': curve_to_mesh, + 'Scale': (kwargs['handle_ratio'], 1.0, 1.0)}) + + hole_radius = nw.new_node(Nodes.Value, label='hole_radius') + hole_radius.outputs[0].default_value = kwargs['hole_radius'] + + cylinder = nw.new_node('GeometryNodeMeshCylinder', input_kwargs={'Radius': hole_radius, 'Depth': 0.1000}) + + hole_place_ratio = nw.new_node(Nodes.Value, label='hole_placement') + hole_place_ratio.outputs[0].default_value = kwargs['hole_placement'] + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: handle_length, 1: hole_place_ratio}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_1}) + + transform_geometry_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz_1, + 'Rotation': (0.0000, 1.5708, 0.0000), 'Scale': (kwargs['hole_ratio'], 1.0000, 1.0000)}) + + difference = nw.new_node(Nodes.MeshBoolean, input_kwargs={'Mesh 1': transform_geometry, 'Mesh 2': transform_geometry_1}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': (kwargs['plate_thickness'], kwargs['plate_width'], kwargs['plate_length']), + 'Vertices X': 4, 'Vertices Y': 4, 'Vertices Z': 4}) + + transform_geometry_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cube.outputs["Mesh"], + 'Translation': (0.0000, 0.0000, -kwargs['plate_length'] / 2.)}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [difference.outputs["Mesh"], transform_geometry_3]}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': realize_instances}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_2}) + + transform_geometry_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': triangulate, 'Translation': combine_xyz_2}) + + set_material = nw.new_node(Nodes.SetMaterial, input_kwargs={'Geometry': transform_geometry_2, + 'Material': surface.shaderfunc_to_material(shader_rough_plastic)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_material}, + attrs={'is_active_output': True}) + + +class HookBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(HookBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = params + + def sample_params(self): + return self.params.copy() + + def get_hang_points(self, params): + # compute the lowest point in the bezier curve + x = params['init_handle'] + y = params['curve_handle'][2] - params['init_handle'] + z = params['curve_end_point'][2] - params['curve_handle'][2] + + t1 = (x - y + np.sqrt(y ** 2 - x * z)) / (x + z - 2 * y) + t2 = (x - y - np.sqrt(y ** 2 - x * z)) / (x + z - 2 * y) + + t = 0 + if t1 >= 0 and t1 <= 1: + t = max(t1, t) + if t2 >= 0 and t2 <= 1: + t = max(t2, t) + if t == 0: + t = 0.5 + + # get x, z coordinate + alpha1 = 3 * ((1 - t) ** 2) * t + alpha2 = 3 * (1 - t) * (t ** 2) + alpha3 = t ** 3 + + z = alpha1 * params['init_handle'] + alpha2 * params['curve_handle'][-1] + alpha3 * params['curve_end_point'][-1] + x = alpha2 * params['curve_handle'][-2] + alpha3 * params['curve_end_point'][-2] + + ys = [] + total_length = params['board_side_gap'] + (params['num_hook'] - 1) * params['hook_gap'] + for i in range(params['num_hook']): + y = - total_length / 2. + params['board_side_gap'] / 2. + i * params['hook_gap'] + ys.append(y) + + hang_points = [] + for y in ys: + hang_points.append((x * params['hook_size'], y, z * params['hook_size'])) + + return hang_points + + def get_asset_params(self, i=0): + params = self.sample_params() + if params.get('num_hook', None) is None: + params['num_hook'] = randint(3, 6) + if params.get('hook_size', None) is None: + params['hook_size'] = uniform(0.05, 0.1) + if params.get('hook_radius', None) is None: + params['hook_radius'] = uniform(0.002, 0.004) / params['hook_size'] + else: + params['hook_radius'] = params['hook_radius'] / params['hook_size'] + + if params.get('hook_resolution', None) is None: + params['hook_resolution'] = np.random.choice([4, 32], p=[0.5, 0.5]) + + if params.get("hook_gap", None) is None: + params["hook_gap"] = uniform(0.04, 0.08) + if params.get('board_height', None) is None: + params['board_height'] = params['hook_size'] + uniform(-0.02, 0.01) + if params.get('board_thickness', None) is None: + params['board_thickness'] = uniform(0.005, 0.015) + if params.get('board_side_gap', None) is None: + params['board_side_gap'] = uniform(0.03, 0.05) + + params['init_handle'] = uniform(-0.15, -0.25) + params["curve_handle"] = (0, uniform(0.15, 0.35), uniform(-0.15, -0.35)) + params["curve_end_point"] = (0, uniform(0.35, 0.55), uniform(-0.05, 0.15)) + + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, hook_geometry_nodes, attributes=[], apply=True, input_kwargs=obj_params) + tagging.tag_system.relabel_obj(obj) + + hang_points = self.get_hang_points(obj_params) + + return obj, hang_points + + +class SpatulaBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(SpatulaBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = params + + def sample_params(self): + return self.params.copy() + + def get_asset_params(self, i=0): + params = self.sample_params() + + if params.get('hole_radius', None) is None: + params['hole_radius'] = uniform(0.003, 0.008) + if params.get('hole_placement', None) is None: + params['hole_placement'] = uniform(0.75, 0.9) + if params.get('hole_ratio', None) is None: + params['hole_ratio'] = uniform(0.8, 2.0) + + if params.get('handle_length', None) is None: + params['handle_length'] = uniform(0.15, 0.25) + + if params.get("handle_ratio", None) is None: + params["handle_ratio"] = uniform(0.1, 0.4) + if params.get("handle_control_points", None) is None: + params["handle_control_points"] = [(0, 0.5), (0.5, uniform(0.45, 0.65)), (1.0, uniform(0.4, 0.6))] + if params.get("handle_radius", None) is None: + params["handle_radius"] = (params['hole_radius'] / params["handle_control_points"][0][1]) / uniform(0.6, 0.8) + + if params.get('plate_thickness', None) is None: + params['plate_thickness'] = uniform(0.005, 0.01) + if params.get('plate_width', None) is None: + params['plate_width'] = uniform(0.04, 0.06) + if params.get('plate_length', None) is None: + params['plate_length'] = uniform(0.05, 0.08) + + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, spatula_geometry_nodes, attributes=[], apply=True, input_kwargs=obj_params) + tagging.tag_system.relabel_obj(obj) + + return obj + + +class SpatulaOnHookBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(SpatulaOnHookBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = params + + self.hook_fac = HookBaseFactory(factory_seed, params=params) + self.spatula_fac = SpatulaBaseFactory(factory_seed, params=params) + + def get_asset_params(self, i): + if self.params.get('hook_radius', None) is None: + r = uniform(0.002, 0.0035) + self.hook_fac.params['hook_radius'] = r + self.spatula_fac.params['hole_radius'] = r / uniform(0.3, 0.6) + + def create_asset(self, i, **params): + + self.get_asset_params(i) + hook, hang_points = self.hook_fac.create_asset(i) + spatula = self.spatula_fac.create_asset(i) + + spatula.location = hang_points[0] + butil.apply_transform(spatula, loc=True) + + return hook + + + + + + + diff --git a/infinigen/assets/organizer/plate_rack.py b/infinigen/assets/organizer/plate_rack.py new file mode 100644 index 000000000..3894741fa --- /dev/null +++ b/infinigen/assets/organizer/plate_rack.py @@ -0,0 +1,335 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core import tagging + +import bpy +from infinigen.assets.shelves.utils import nodegroup_tagged_cube +from infinigen.assets.materials import shader_wood +from infinigen.assets.materials.plastics.plastic_rough import shader_rough_plastic + + +@node_utils.to_nodegroup('nodegroup_plate_rack_connect', singleton=False, type='GeometryNodeTree') +def nodegroup_plate_rack_connect(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'Radius', 1.0000), + ('NodeSocketFloat', 'Value1', 0.5000), + ('NodeSocketFloat', 'Value', 0.5000)]) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Value1"], 1: 2.0000, 2: -0.0020}, + attrs={'operation': 'MULTIPLY_ADD'}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Radius': group_input.outputs["Radius"], 'Depth': multiply_add}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Name': 'uv_map', + 3: cylinder.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + multiply_add_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Value"], 2: -uniform(0.02, 0.045)}, + attrs={'operation': 'MULTIPLY_ADD'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_add_1}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute, 'Translation': combine_xyz, + 'Rotation': (1.5708, 0.0000, 0.0000)}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform, 'Scale': (-1.0000, 1.0000, 1.0000)}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_2, transform]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_2}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_rack_cyn', singleton=False, type='GeometryNodeTree') +def nodegroup_rack_cyn(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'Radius', 1.0000), + ('NodeSocketFloat', 'Value', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Value"], 1: 0.0000}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Radius': group_input.outputs["Radius"], 'Depth': add}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Name': 'uv_map', + 3: cylinder.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + multiply_add = nw.new_node(Nodes.Math, input_kwargs={0: add, 2: 0.0010}, attrs={'operation': 'MULTIPLY_ADD'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_add}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute, 'Translation': combine_xyz_4}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_2}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_rack_base', singleton=False, type='GeometryNodeTree') +def nodegroup_rack_base(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Instance', None), + ('NodeSocketFloat', 'Value1', 0.5000), + ('NodeSocketFloat', 'Value2', 0.5000), + ('NodeSocketFloat', 'Value3', 0.5000), + ('NodeSocketInt', 'Count', 10)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Value1"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Value2"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_1}) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', + 3: cube.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Value3"], 1: 0.0000}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_2}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute, 'Translation': combine_xyz_1}) + + multiply_add = nw.new_node(Nodes.Math, input_kwargs={0: add, 2: -0.0150}, attrs={'operation': 'MULTIPLY_ADD'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_add, 'Y': add_2}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: multiply_add, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': add_2}) + + mesh_line = nw.new_node(Nodes.MeshLine, + input_kwargs={'Count': group_input.outputs["Count"], 'Start Location': combine_xyz_2, + 'Offset': combine_xyz_3}, + attrs={'mode': 'END_POINTS'}) + + instance_on_points = nw.new_node(Nodes.InstanceOnPoints, + input_kwargs={'Points': mesh_line, 'Instance': group_input.outputs["Instance"]}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': instance_on_points}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Base': transform, 'Racks': realize_instances}, + attrs={'is_active_output': True}) + + +def rack_geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.5 of the node_transpiler + + rack_radius = nw.new_node(Nodes.Value, label='rack_radius') + rack_radius.outputs[0].default_value = kwargs['rack_radius'] + + rack_height = nw.new_node(Nodes.Value, label='rack_height') + rack_height.outputs[0].default_value = kwargs['rack_height'] + + rack_cyn = nw.new_node(nodegroup_rack_cyn().name, input_kwargs={'Radius': rack_radius, 'Value': rack_height}) + + base_length = nw.new_node(Nodes.Value, label='base_length') + base_length.outputs[0].default_value = kwargs['base_length'] + + base_width = nw.new_node(Nodes.Value, label='base_width') + base_width.outputs[0].default_value = kwargs['base_width'] + + base_gap = nw.new_node(Nodes.Value, label='base_gap') + base_gap.outputs[0].default_value = kwargs['base_gap'] + + integer = nw.new_node(Nodes.Integer) + integer.integer = kwargs['num_rack'] + + rack_base = nw.new_node(nodegroup_rack_base().name, + input_kwargs={'Instance': rack_cyn, 'Value1': base_length, 'Value2': base_width, + 'Value3': base_gap, 'Count': integer}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [rack_base.outputs["Base"], rack_base.outputs["Racks"]]}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry, 'Scale': (1.0000, -1.0000, 1.0000)}) + + plate_rack_connect = nw.new_node(nodegroup_plate_rack_connect().name, + input_kwargs={'Radius': rack_radius, 'Value1': base_gap, 'Value': base_length}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_1, join_geometry, plate_rack_connect]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: base_width}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry_1, 'Translation': combine_xyz}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': transform}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': realize_instances}) + + set_material = nw.new_node(Nodes.SetMaterial, input_kwargs={'Geometry': triangulate, + 'Material': surface.shaderfunc_to_material(shader_wood)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_material}, + attrs={'is_active_output': True}) + + +def plate_geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.5 of the node_transpiler + + radius = nw.new_node(Nodes.Value, label='radius') + radius.outputs[0].default_value = kwargs['radius'] + + thickness = nw.new_node(Nodes.Value, label='thickness') + thickness.outputs[0].default_value = kwargs['thickness'] + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Vertices': 64, 'Radius': radius, 'Depth': thickness}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': radius}) + + transform_geometry = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz, + 'Rotation': (0.0000, 1.5708, 0.0000)}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': transform_geometry}) + + set_material = nw.new_node(Nodes.SetMaterial, input_kwargs={'Geometry': triangulate, + 'Material': surface.shaderfunc_to_material(shader_rough_plastic)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_material}, + attrs={'is_active_output': True}) + + +class PlateRackBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(PlateRackBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = params + + def sample_params(self): + return self.params.copy() + + def get_place_points(self, params): + # compute the lowest point in the bezier curve + xs = [] + for i in range(params['num_rack']-1): + l = params['base_length'] + d = (l - 0.03) / (params['num_rack']-1) + x = - l / 2. + 0.015 + (i + 0.5) * d + xs.append(x) + + y = 0 + z = params['base_width'] + + place_points = [] + for x in xs: + place_points.append((x, y, z)) + + return place_points + + def get_asset_params(self, i=0): + params = self.sample_params() + if params.get('num_rack', None) is None: + params['num_rack'] = randint(3, 7) + if params.get('rack_radius', None) is None: + params['rack_radius'] = uniform(0.0025,0.006) + if params.get('rack_height', None) is None: + params['rack_height'] = uniform(0.08, 0.15) + if params.get('base_length', None) is None: + params['base_length'] = (params['num_rack'] - 1) * uniform(0.03, 0.06) + 0.03 + if params.get('base_gap', None) is None: + params['base_gap'] = uniform(0.05, 0.08) + if params.get('base_width', None) is None: + params['base_width'] = uniform(0.015, 0.03) + + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, rack_geometry_nodes, attributes=[], apply=True, input_kwargs=obj_params) + tagging.tag_system.relabel_obj(obj) + + place_points = self.get_place_points(obj_params) + + return obj, place_points + + +class PlateBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(PlateBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = params + + def sample_params(self): + return self.params.copy() + + def get_asset_params(self, i=0): + params = self.sample_params() + if params.get('radius', None) is None: + params['radius'] = uniform(0.15, 0.25) + if params.get('thickness', None) is None: + params['thickness'] = uniform(0.01, 0.025) + + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, plate_geometry_nodes, attributes=[], apply=True, input_kwargs=obj_params) + tagging.tag_system.relabel_obj(obj) + + return obj + + +class PlateOnRackBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(PlateOnRackBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = params + + self.rack_fac = PlateRackBaseFactory(factory_seed, params=params) + self.plate_fac = PlateBaseFactory(factory_seed, params=params) + + def get_asset_params(self, i): + if self.params.get('base_gap', None) is None: + d = uniform(0.05, 0.08) + self.rack_fac.params['base_gap'] = d + self.plate_fac.params['radius'] = d + uniform(0.025, 0.06) + + def create_asset(self, i, **params): + + self.get_asset_params(i) + rack, place_points = self.rack_fac.create_asset(i) + plate = self.plate_fac.create_asset(i) + + plate.location = place_points[0] + butil.apply_transform(plate, loc=True) + + return plate diff --git a/infinigen/assets/rocks/blender_rock.py b/infinigen/assets/rocks/blender_rock.py index b7a89c633..76fb9d1ce 100644 --- a/infinigen/assets/rocks/blender_rock.py +++ b/infinigen/assets/rocks/blender_rock.py @@ -13,7 +13,7 @@ from infinigen.core.util.math import FixedSeed from infinigen.core.util import blender as butil from infinigen.core.placement.factory import AssetFactory -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup bpy.ops.preferences.addon_enable(module='add_mesh_extra_objects') @@ -50,8 +50,12 @@ def create_asset(self, **params): pass except RuntimeError: pass + obj = bpy.context.active_object bpy.ops.object.shade_flat() + + butil.apply_modifiers(obj) + tag_object(obj, 'blender_rock') return obj \ No newline at end of file diff --git a/infinigen/assets/rocks/boulder.py b/infinigen/assets/rocks/boulder.py index 3160b6c70..d05d585a4 100644 --- a/infinigen/assets/rocks/boulder.py +++ b/infinigen/assets/rocks/boulder.py @@ -14,16 +14,17 @@ from numpy.random import uniform import gin +from infinigen.assets.scatters import ivy from infinigen.core.util import blender as butil from infinigen.core.util.math import FixedSeed from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler from infinigen.core import surface from infinigen.assets.utils.object import trimesh2obj from infinigen.assets.utils.decorate import geo_extension, write_attribute -from infinigen.assets.utils.misc import log_uniform +from infinigen.core.util.random import log_uniform from infinigen.core.placement.factory import AssetFactory from infinigen.core.placement.detail import remesh_with_attrs -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup from infinigen.core.util.blender import deep_clone_obj from infinigen.core.placement.split_in_view import split_inview @@ -143,5 +144,7 @@ def create_asset(self, i, placeholder, face_size=0.01, distance=0, **params): with butil.DisableModifiers(skin_obj): detail.adapt_mesh_resolution(skin_obj, face_size, method=self.adapt_mesh_method, apply=True) + butil.apply_modifiers(skin_obj) tag_object(skin_obj, 'boulder') + return skin_obj \ No newline at end of file diff --git a/infinigen/assets/rocks/glowing_rocks.py b/infinigen/assets/rocks/glowing_rocks.py index d8011b7ce..a52dbfe21 100644 --- a/infinigen/assets/rocks/glowing_rocks.py +++ b/infinigen/assets/rocks/glowing_rocks.py @@ -14,7 +14,7 @@ from infinigen.assets.rocks.blender_rock import BlenderRockFactory from infinigen.core import surface from infinigen.core.util.color import color_category -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup def shader_glowrock(nw: NodeWrangler, transparent_for_bounce=True): object_info = nw.new_node(Nodes.ObjectInfo_Shader) @@ -53,11 +53,7 @@ def create_placeholder(self, i, loc, rot): def create_asset(self, *args, **kwargs) -> bpy.types.Object: src_obj = np.random.choice(list(self.rock_collection.objects)) - - new_obj = src_obj.copy() - new_obj.data = src_obj.data - new_obj.animation_data_clear() - bpy.context.collection.objects.link(new_obj) + new_obj = butil.deep_clone_obj(src_obj) new_obj.rotation_euler = np.random.uniform(-np.pi, np.pi, 3) new_obj.scale = np.random.uniform(0.7, 1.5, 3) * 0.5 @@ -71,5 +67,8 @@ def create_asset(self, *args, **kwargs) -> bpy.types.Object: point_light = bpy.context.selected_objects[0] point_light.data.energy = round(np.random.uniform(*self.watt_power_range)) point_light.parent = new_obj + + butil.apply_transform(new_obj) tag_object(new_obj, 'glowing_rocks') + return new_obj diff --git a/infinigen/assets/rocks/pile.py b/infinigen/assets/rocks/pile.py index 5eaca464d..e6d37fd0b 100644 --- a/infinigen/assets/rocks/pile.py +++ b/infinigen/assets/rocks/pile.py @@ -14,10 +14,12 @@ from infinigen.core.placement.detail import remesh_with_attrs from infinigen.core.placement.factory import AssetFactory import infinigen.core.util.blender as butil -from infinigen.assets.utils.decorate import join_objects, multi_res, toggle_hide +from infinigen.assets.utils.decorate import multi_res +from infinigen.assets.utils.misc import toggle_hide +from infinigen.assets.utils.object import join_objects from infinigen.assets.utils.draw import surface_from_func from infinigen.core.util.blender import deep_clone_obj -from infinigen.assets.utils.tag import tag_object +from infinigen.core.tagging import tag_object from infinigen.core.util.random import log_uniform diff --git a/infinigen/assets/scatters/clothes.py b/infinigen/assets/scatters/clothes.py new file mode 100644 index 000000000..123197206 --- /dev/null +++ b/infinigen/assets/scatters/clothes.py @@ -0,0 +1,78 @@ +# 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: Lingjie Mei + +from collections.abc import Iterable + +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.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 +from infinigen.core.util import blender as butil +from infinigen.core.util.blender import deep_clone_obj + + +def cloth_sim(clothes, obj=None, end_frame=50, **kwargs): + with butil.ViewportMode(clothes, mode='OBJECT'), butil.SelectObjects(clothes), butil.Suppress(): + bpy.ops.ptcache.free_bake_all() + if obj is None: + obj = [] + for o in obj if isinstance(obj, Iterable) else [obj]: + butil.modify_mesh(o, 'COLLISION', apply=False) + o.collision.damping_factor = .9 + o.collision.cloth_friction = 10. + o.collision.friction_factor = 1. + o.collision.stickiness = .9 + frame = bpy.context.scene.frame_current + butil.select_none() + with butil.Suppress(): + mod = bake_cloth(clothes, kwargs, frame_start=1, frame_end=end_frame) + bpy.context.scene.frame_set(end_frame) + butil.apply_modifiers(clothes, mod) + for o in obj if isinstance(obj, Iterable) else [obj]: + with butil.SelectObjects(o): + bpy.ops.object.modifier_remove(modifier=o.modifiers[-1].name) + bpy.context.scene.frame_set(frame) + with butil.Suppress(): + bpy.ops.ptcache.free_bake_all() + + +class ClothesCover: + def __init__(self, bbox=(.3, .7, .3, .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( + [blanket.BlanketFactory, shirt.ShirtFactory, pants.PantsFactory], + p=probs / probs.sum() + ) + self.factory = factory_fn(np.random.randint(1e5)) + if width is not None: + self.factory.width = width + if size is not None: + self.factory.size = size + self.col = make_asset_collection(self.factory, name='clothes', centered=True, n=3, verbose=False) + self.bbox = bbox + self.z_offset = .2 + + def apply(self, obj, selection=None, **kwargs): + for obj in obj if isinstance(obj, list) else [obj]: + x, y, z = read_co(obj).T + clothes = deep_clone_obj(np.random.choice(self.col.objects), keep_materials=True) + clothes.parent = obj + clothes.location = uniform(self.bbox[0], self.bbox[1]) * (np.max(x) - np.min(x)) + np.min( + x + ), uniform(self.bbox[2], self.bbox[3]) * (np.max(y) - np.min(y)) + np.min(y), np.max( + z + ) + self.z_offset - np.min(read_co(clothes)[:, -1]) + clothes.rotation_euler[-1] = uniform(0, np.pi * 2) + cloth_sim(clothes, obj, mass=.05, tension_stiffness=2, distance_min=5e-3) + subsurf(clothes, 2) + + +def apply(obj, selection=None, **kwargs): + ClothesCover().apply(obj, selection, **kwargs) \ No newline at end of file diff --git a/infinigen/assets/scatters/ground_twigs.py b/infinigen/assets/scatters/ground_twigs.py index 2dee88b0b..825ea305a 100644 --- a/infinigen/assets/scatters/ground_twigs.py +++ b/infinigen/assets/scatters/ground_twigs.py @@ -21,15 +21,19 @@ from infinigen.assets.trees.generate import make_twig_collection, random_species from .chopped_trees import approx_settle_transform +from ..utils.misc import toggle_show, toggle_hide + def apply(obj, selection=None, n_leaf=0, n_twig=10, **kwargs): (_, twig_params, leaf_params), _ = random_species(season='winter') twigs = make_twig_collection(np.random.randint(1e5), twig_params, leaf_params, n_leaf=n_leaf, n_twig=n_twig, leaf_types=None, trunk_surface=surface.registry('bark')) - + + toggle_show(twigs) for o in twigs.objects: approx_settle_transform(o, samples=40) + toggle_hide(twigs) scatter_obj = scatter_instances( base_obj=obj, collection=twigs, diff --git a/infinigen/assets/scatters/ivy.py b/infinigen/assets/scatters/ivy.py index 04a47ea55..9a7def7d0 100644 --- a/infinigen/assets/scatters/ivy.py +++ b/infinigen/assets/scatters/ivy.py @@ -12,7 +12,9 @@ from infinigen.assets.leaves.leaf_maple import LeafFactoryMaple from infinigen.assets.trees.generate import random_season -from infinigen.assets.utils.decorate import assign_material, fix_tree + +from infinigen.assets.utils.mesh import fix_tree +from infinigen.assets.utils.misc import assign_material from infinigen.assets.utils.nodegroup import geo_base_selection, geo_radius from infinigen.assets.utils.shortest_path import geo_shortest_path from infinigen.core.nodes.node_info import Nodes @@ -22,7 +24,7 @@ from infinigen.core.surface import shaderfunc_to_material from infinigen.assets.materials.simple_brownish import shader_simple_brown from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup def geo_leaf(nw: NodeWrangler, leaves): diff --git a/infinigen/assets/scatters/lichen.py b/infinigen/assets/scatters/lichen.py index 1bd79605a..aff9f28ef 100644 --- a/infinigen/assets/scatters/lichen.py +++ b/infinigen/assets/scatters/lichen.py @@ -11,7 +11,7 @@ import numpy as np from numpy.random import uniform, normal as N -from infinigen.assets.utils.decorate import assign_material +from infinigen.assets.utils.misc import assign_material from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler from infinigen.core.placement.factory import AssetFactory, make_asset_collection from infinigen.core.placement.instance_scatter import scatter_instances @@ -21,7 +21,7 @@ from infinigen.assets.utils.object import data2mesh from infinigen.assets.utils.mesh import polygon_angles from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup from infinigen.assets.debris import LichenFactory @@ -34,8 +34,8 @@ def __init__(self): def apply(self, obj, selection=None): scatter_obj = scatter_instances( - base_obj=obj, collection=self.col, - density=5e3, min_spacing=.08, + base_obj=obj, collection=self.col, + density=5e3, min_spacing=.08, scale=1, scale_rand=N(0.5, 0.07), selection=selection ) @@ -46,8 +46,8 @@ def apply(obj, selection=None): fac = LichenFactory(np.random.randint(1e5)) col = make_asset_collection(fac, name='lichen', n=5) scatter_obj = scatter_instances( - base_obj=obj, collection=col, - density=5e3, min_spacing=.08, + base_obj=obj, collection=col, + density=5e3, min_spacing=.08, scale=1, scale_rand=N(0.5, 0.07), selection=selection ) diff --git a/infinigen/assets/scatters/mollusk.py b/infinigen/assets/scatters/mollusk.py index 1e2542036..d806cbf37 100644 --- a/infinigen/assets/scatters/mollusk.py +++ b/infinigen/assets/scatters/mollusk.py @@ -7,9 +7,8 @@ import numpy as np from infinigen.assets.mollusk import MolluskFactory -from infinigen.assets.utils.misc import CountInstance +from infinigen.assets.utils.misc import CountInstance, toggle_hide from infinigen.core.placement.factory import AssetFactory, make_asset_collection -from infinigen.assets.utils.decorate import toggle_hide from infinigen.core.util import blender as butil from infinigen.core.nodes import node_utils from infinigen.core.placement.instance_scatter import scatter_instances @@ -29,7 +28,7 @@ def scaling(nw): scatter_obj = scatter_instances('mollusk', base_obj=obj, collection=mollusk, - density=density, scaling=scaling, + density=density, scaling=scaling, min_spacing=scale, normal=(0,0,1), selection=selection, taper_density=True) diff --git a/infinigen/assets/scatters/moss.py b/infinigen/assets/scatters/moss.py index 686675dcd..2e39692cf 100644 --- a/infinigen/assets/scatters/moss.py +++ b/infinigen/assets/scatters/moss.py @@ -7,7 +7,7 @@ from numpy.random import uniform as U from infinigen.core.placement.instance_scatter import scatter_instances -from infinigen.assets.utils.decorate import assign_material +from infinigen.assets.utils.misc import assign_material from infinigen.core.placement.factory import make_asset_collection from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler from infinigen.core import surface @@ -31,10 +31,10 @@ def instance_index(nw: NodeWrangler, n): nw.new_node(Nodes.FloatToInt, [nw.scalar_multiply(nw.musgrave(10), 2 * n)]), n) scatter_obj = scatter_instances( - base_obj=obj, collection=self.col, - density=2e4, min_spacing=.005, + base_obj=obj, collection=self.col, + density=2e4, min_spacing=.005, scale=1, scale_rand=U(0.3, 0.7), - selection=selection, + selection=selection, instance_index=instance_index) return scatter_obj diff --git a/infinigen/assets/scatters/mushroom.py b/infinigen/assets/scatters/mushroom.py index 80661cddb..8b03640c2 100644 --- a/infinigen/assets/scatters/mushroom.py +++ b/infinigen/assets/scatters/mushroom.py @@ -6,8 +6,8 @@ from collections.abc import Iterable -import bmesh import bpy +import bmesh import numpy as np from mathutils import Matrix from numpy.random import uniform diff --git a/infinigen/assets/scatters/slime_mold.py b/infinigen/assets/scatters/slime_mold.py index 5ae38a7eb..cafbf825a 100644 --- a/infinigen/assets/scatters/slime_mold.py +++ b/infinigen/assets/scatters/slime_mold.py @@ -8,20 +8,22 @@ import numpy as np from numpy.random import uniform -from infinigen.assets.utils.decorate import assign_material, treeify +from infinigen.assets.utils.mesh import treeify +from infinigen.assets.utils.misc import assign_material from infinigen.assets.utils.nodegroup import geo_base_selection, geo_radius from infinigen.assets.utils.shortest_path import geo_shortest_path from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface -from infinigen.assets.utils.misc import build_color_ramp +from infinigen.core.util.color import hsv2rgba from infinigen.core.surface import shaderfunc_to_material from infinigen.core.util import blender as butil +from infinigen.core.nodes.node_utils import build_color_ramp def shader_mold(nw: NodeWrangler, base_hue): - bright_color = *colorsys.hsv_to_rgb((base_hue + uniform(-.04, .04)) % 1, uniform(.8, 1.), .8), 1 - dark_color = *colorsys.hsv_to_rgb(base_hue, uniform(.4, .6), .2), 1 + bright_color = hsv2rgba((base_hue + uniform(-.04, .04)) % 1, uniform(.8, 1.), .8) + dark_color = hsv2rgba(base_hue, uniform(.4, .6), .2) color = build_color_ramp(nw, nw.musgrave(10), [.0, .3, .7, 1.], [dark_color, dark_color, bright_color, bright_color]) diff --git a/infinigen/assets/scatters/snow_layer.py b/infinigen/assets/scatters/snow_layer.py index c9efef17f..7f70018f2 100644 --- a/infinigen/assets/scatters/snow_layer.py +++ b/infinigen/assets/scatters/snow_layer.py @@ -14,7 +14,7 @@ from infinigen.core.util import blender as butil from infinigen.core.nodes import node_utils -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class Snowlayer: def __init__(self): diff --git a/infinigen/assets/scatters/utils/wind.py b/infinigen/assets/scatters/utils/wind.py index a8d3181fc..2dc550abb 100644 --- a/infinigen/assets/scatters/utils/wind.py +++ b/infinigen/assets/scatters/utils/wind.py @@ -1,3 +1,4 @@ + # 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. @@ -59,4 +60,4 @@ def wind_rotation(nw, speed=1.0, direction=None, scale=1.0, strength=30): return rotation def wind(*args, **kwargs): - return lambda nw: wind_rotation(nw, *args, **kwargs) \ No newline at end of file + return lambda nw: wind_rotation(nw, *args, **kwargs) diff --git a/infinigen/assets/seating/__init__.py b/infinigen/assets/seating/__init__.py new file mode 100644 index 000000000..5abb955c1 --- /dev/null +++ b/infinigen/assets/seating/__init__.py @@ -0,0 +1,11 @@ +# 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: Lingjie Mei + +from .sofa import SofaFactory, ArmChairFactory +from .bedframe import BedFrameFactory +from .pillow import PillowFactory +from .mattress import MattressFactory +from .bed import BedFactory +from .chairs import * diff --git a/infinigen/assets/seating/bed.py b/infinigen/assets/seating/bed.py new file mode 100644 index 000000000..7a437dcd1 --- /dev/null +++ b/infinigen/assets/seating/bed.py @@ -0,0 +1,186 @@ +# 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: Lingjie Mei +from functools import cached_property + +import bpy +import numpy as np +import trimesh +from numpy.random import uniform + +from ..scatters import clothes +from . import BedFrameFactory, MattressFactory, PillowFactory +from ..scatters.clothes import ClothesCover +from ..utils.decorate import decimate, read_co, subsurf +from ..utils.object import obj2trimesh +from ...core import surface +from infinigen.core.util import blender as butil +from infinigen.core.util.random import random_general as rg, log_uniform +from ...core.util.blender import deep_clone_obj + + +class BedFactory(BedFrameFactory): + mattress_types = 'weighted_choice', (1, 'coiled'), (3, 'wrapped') + sheet_types = 'weighted_choice', (4, 'quilt'), (4, 'comforter'), (4, 'box_comforter'), (1, 'none') + + def __init__(self, factory_seed, coarse=False): + super(BedFactory, self).__init__(factory_seed, coarse) + self.sheet_type = rg(self.sheet_types) + self.sheet_folded = uniform() < .5 + self.has_cover = uniform() < .5 + self.clothes_scatter = ClothesCover((.3, .7, .3, .7)) if uniform() < 0.2 else surface.NoApply + + @cached_property + def mattress_factory(self): + factory = MattressFactory(self.factory_seed, self.coarse) + factory.type = rg(self.mattress_types) + factory.width = self.width * uniform(.88, .96) + factory.size = self.size * uniform(.88, .96) + return factory + + @cached_property + def quilt_factory(self): + from ..clothes.blanket import BlanketFactory + factory = BlanketFactory(self.factory_seed, self.coarse) + factory.width = self.mattress_factory.width * uniform(1.4, 1.6) + factory.size = self.mattress_factory.size * uniform(.9, 1.1) + return factory + + @cached_property + def comforter_factory(self): + from ..clothes.blanket import ComforterFactory + factory = ComforterFactory(self.factory_seed, self.coarse) + factory.width = self.mattress_factory.width * uniform(1.4, 1.8) + factory.size = self.mattress_factory.size * uniform(.9, 1.2) + return factory + + @cached_property + def box_comforter_factory(self): + from ..clothes.blanket import BoxComforterFactory + factory = BoxComforterFactory(self.factory_seed, self.coarse) + factory.width = self.mattress_factory.width * uniform(1.4, 1.8) + factory.size = self.mattress_factory.size * uniform(.9, 1.2) + return factory + + @cached_property + def cover_factory(self): + from ..clothes.blanket import BlanketFactory + factory = BlanketFactory(self.factory_seed, self.coarse) + factory.width = self.mattress_factory.width * uniform(1.6, 1.8) + factory.size = self.mattress_factory.size * uniform(.3, .4) + return factory + + @cached_property + def towel_factory(self): + from ..clothes import TowelFactory + return TowelFactory(self.factory_seed) + + @cached_property + def cloth_scatter(self): + return ClothesCover((.3, .7, .3, .7)) if uniform() < 0.0 else surface.NoApply + + @cached_property + def pillow_factory(self): + return PillowFactory(self.factory_seed, self.coarse) + + def create_asset(self, i, **params) -> bpy.types.Object: + frame = super().create_asset(i=i, **params) + + mattress = self.make_mattress(i) + sheet = self.make_sheet(i, mattress, frame) + cover = self.make_cover(i, sheet, mattress) + self.cloth_scatter.apply(sheet) + + n_pillows = np.random.randint(2, 4) + if n_pillows > 0: + pillow = self.pillow_factory(i) + pillows = [pillow] + [deep_clone_obj(pillow) for _ in range(n_pillows - 1)] + else: + pillows = [] + self.pillow_factory.finalize_assets(pillows) + points = np.stack( + [uniform(.1, .4, 10) * self.size, + uniform(-.3, .3, 10) * self.width, + np.full(10, 1)], -1 + ) + self.scatter(pillows, points, [sheet, mattress]) + + n_towels = np.random.randint(1, 2) + if n_towels > 0: + towel = self.towel_factory(i) + towels = [towel] + [deep_clone_obj(towel) for _ in range(n_towels - 1)] + else: + towels = [] + self.towel_factory.finalize_assets(towels) + points = np.stack( + [uniform(.5, .8, 10) * self.size, + uniform(-.3, .3, 10) * self.width, + np.full(10, 1)], -1 + ) + self.scatter(towels, points, [sheet, mattress]) + + for _ in [mattress, sheet, cover] + pillows + towels: + _.parent = frame + butil.select_none() + return frame + + def make_mattress(self, i): + mattress = self.mattress_factory(i=i) + mattress.location = self.size / 2, 0, self.mattress_factory.thickness / 2 + mattress.rotation_euler[-1] = np.pi / 2 + butil.apply_transform(mattress, True) + self.mattress_factory.finalize_assets(mattress) + return mattress + + def make_sheet(self, i, mattress, obj): + match self.sheet_type: + case 'quilt': + factory = self.quilt_factory + pressure = 0 + case 'comforter': + factory = self.comforter_factory + pressure = uniform(1., 1.5) + case _: + factory = self.box_comforter_factory + pressure = log_uniform(8, 15) + sheet = factory(i) + if self.sheet_folded: + factory.fold(sheet) + factory.finalize_assets(sheet) + z_sheet = mattress.location[-1] + np.max(read_co(mattress)[:, -1]) + sheet.location = factory.size / 2 + uniform(0, .15), 0, z_sheet + sheet.rotation_euler[-1] = np.pi / 2 + butil.apply_transform(sheet, True) + clothes.cloth_sim( + sheet, [mattress, obj], mass=.05, tension_stiffness=2, distance_min=5e-3, use_pressure=True, + uniform_pressure_force=pressure, use_self_collision=self.sheet_folded + ) + subsurf(sheet, 2) + return sheet + + def make_cover(self, i, sheet, mattress): + cover = self.cover_factory(i) + self.cover_factory.finalize_assets(cover) + z_sheet = sheet.location[-1] + np.max(read_co(sheet)[:, -1]) + cover.location = self.size / 2 + uniform(0, .3), 0, z_sheet + cover.rotation_euler[-1] = np.pi / 2 + butil.apply_transform(cover, True) + clothes.cloth_sim( + cover, [sheet, mattress], 80, mass=.05, tension_stiffness=2, distance_min=5e-3 + ) + subsurf(cover, 2) + return cover + + def scatter(self, pillows, points, bases): + dir = np.array([[0, 0, -1]]) + lengths = np.full(len(points), np.inf) + for b in bases: + lengths = np.minimum( + lengths, trimesh.proximity.longest_ray(obj2trimesh(b), points, np.repeat(dir, len(points), 0)) + ) + points += dir * lengths[:, np.newaxis] + for a, loc in zip(pillows, decimate(points, len(pillows))): + a.location = loc + a.location[-1] += .02 - np.min(read_co(a)[:, -1]) + a.rotation_euler[-1] = uniform(0, np.pi) diff --git a/infinigen/assets/seating/bedframe.py b/infinigen/assets/seating/bedframe.py new file mode 100644 index 000000000..d7029a2b3 --- /dev/null +++ b/infinigen/assets/seating/bedframe.py @@ -0,0 +1,179 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.seating.chairs.chair import ChairFactory +from infinigen.assets.seating.mattress import make_coiled +from infinigen.assets.utils.decorate import ( + subdivide_edge_ring, remove_faces, read_normal, read_co, write_co, + remove_vertices, select_faces, write_attribute, +) +from infinigen.assets.utils.object import new_grid, join_objects +from infinigen.core import surface +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.core.util.random import random_general as rg +from infinigen.assets.material_assignments import AssetList + + +class BedFrameFactory(ChairFactory): + scale = 1. + leg_decor_types = 'weighted_choice', (2, 'coiled'), (2, 'pad'), (1, 'plain'), (2, 'legs') + back_types = 'weighted_choice', (3, 'coiled'), (3, 'pad'), (2, 'whole'), (1, 'horizontal-bar'), (1, 'vertical-bar') + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.width = log_uniform(1.4, 2.4) + self.size = uniform(2, 2.4) + self.thickness = uniform(.05, .12) + self.has_all_legs = uniform() < .2 + self.leg_thickness = uniform(.08, .12) + self.leg_height = uniform(.2, .6) + self.leg_decor_type = rg(self.leg_decor_types) + self.leg_decor_wrapped = uniform() < .5 + self.back_height = uniform(.5, 1.3) + self.seat_back = 1 + self.seat_subdivisions_x = np.random.randint(1, 4) + self.seat_subdivisions_y = int(log_uniform(4, 10)) + self.has_arm = False + self.leg_type = 'vertical' + self.leg_x_offset = 0 + self.leg_y_offset = 0, 0 + self.back_x_offset = 0 + self.back_y_offset = 0 + + materials = AssetList['BedFrameFactory']() + self.surface = materials['surface'].assign_material() + self.limb_surface = materials['limb_surface'].assign_material() + + scratch_prob, edge_wear_prob = materials['wear_tear_prob'] + self.scratch, self.edge_wear = materials['wear_tear'] + self.scratch = None if uniform() > scratch_prob else self.scratch + self.edge_wear = None if uniform() > edge_wear_prob else self.edge_wear + + self.clothes_scatter = surface.NoApply + self.dot_distance = log_uniform(.16, .2) + self.dot_size = uniform(.005, .02) + self.dot_depth = uniform(.04, .08) + self.panel_distance = uniform(.3, .5) + self.panel_margin = uniform(.01, .02) + self.post_init() + + def make_seat(self): + obj = new_grid(x_subdivisions=self.seat_subdivisions_x, y_subdivisions=self.seat_subdivisions_y) + obj.scale = (self.width - self.leg_thickness) / 2, (self.size - self.leg_thickness) / 2, 1 + butil.apply_transform(obj, True) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.delete(type='ONLY_FACE') + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={'value': (0, 0, self.thickness)}) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.leg_thickness - 1e-3, offset=0, solidify_mode='NON_MANIFOLD') + obj.location = 0, -self.size / 2, -self.thickness / 2 + butil.apply_transform(obj, True) + butil.modify_mesh(obj, 'BEVEL', width=self.bevel_width, segments=8) + return obj + + def make_legs(self): + legs = super().make_legs() + if self.has_all_legs: + leg_starts = np.array( + [[-1, -.5, 0], [0, -1, 0], [0, 0, 0], [1, -.5, 0]] + ) * np.array( + [[self.width / 2, self.size, 0]] + ) + leg_ends = leg_starts.copy() + leg_ends[0, 0] -= self.leg_x_offset + leg_ends[3, 0] += self.leg_x_offset + leg_ends[2, 1] += self.leg_y_offset[0] + leg_ends[1, 1] -= self.leg_y_offset[1] + leg_ends[:, -1] = -self.leg_height + legs += self.make_limb(leg_ends, leg_starts) + return legs + + def make_leg_decors(self, legs): + if self.leg_decor_type == 'none': + return super().make_leg_decors(legs) + obj = join_objects([deep_clone_obj(_) for _ in legs]) + x, y, z = read_co(obj).T + z = np.maximum(z, -self.leg_height * uniform(.7, .9)) + write_co(obj, np.stack([x, y, z], -1)) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.convex_hull() + bpy.ops.mesh.normals_make_consistent(inside=False) + remove_faces(obj, np.abs(read_normal(obj)[:, -1]) > .5) + if self.leg_decor_wrapped: + x, y, z = read_co(obj).T + x[x < 0] -= self.leg_thickness / 2 + 1e-3 + x[x > 0] += self.leg_thickness / 2 + 1e-3 + y[y < -self.size / 2] -= self.leg_thickness / 2 + 1e-3 + y[y > -self.size / 2] += self.leg_thickness / 2 + 1e-3 + write_co(obj, np.stack([x, y, z], -1)) + match self.leg_decor_type: + case 'coiled': + self.divide(obj, self.dot_distance) + make_coiled(obj, self.dot_distance, self.dot_depth, self.dot_size) + case 'pad': + self.divide(obj, self.panel_distance) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.inset(thickness=self.panel_margin, depth=self.panel_margin, use_individual=True) + butil.modify_mesh(obj, 'BEVEL', segments=4) + write_attribute(obj, 1, 'panel', 'FACE') + return [obj] + + def divide(self, obj, distance): + for i, size in enumerate(obj.dimensions): + axis = np.zeros(3) + axis[i] = 1 + distance = distance if i != 2 else distance * uniform(.5, 1.) + subdivide_edge_ring(obj, int(np.ceil(size / distance)), axis) + + def make_back_decors(self, backs, finalize=True): + decors = super().make_back_decors(backs) + match self.back_type: + case 'coiled': + obj = self.make_back(backs) + self.divide(obj, self.dot_distance) + make_coiled(obj, self.dot_distance, self.dot_depth, self.dot_size) + obj.scale = (1 - 1e-3,) * 3 + write_attribute(obj, 1, 'panel', 'FACE') + with butil.ViewportMode(decors[0], 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bisect(plane_co=(0, 0, self.back_height), plane_no=(0, 0, 1), clear_inner=True) + return [obj] + decors + case 'pad': + obj = self.make_back(backs) + self.divide(obj, self.panel_distance) + with butil.ViewportMode(obj, 'EDIT'): + select_faces(obj, np.abs(read_normal(obj)[:, 1]) > .5) + bpy.ops.mesh.inset(thickness=self.panel_margin, depth=self.panel_margin, use_individual=True) + butil.modify_mesh(obj, 'BEVEL', segments=4) + write_attribute(obj, 1, 'panel', 'FACE') + obj.scale = (1 - 1e-3,) * 3 + with butil.ViewportMode(decors[0], 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bisect(plane_co=(0, 0, self.back_height), plane_no=(0, 0, 1), clear_inner=True) + return [obj] + decors + case _: + return decors + + def make_back(self, backs): + obj = join_objects([deep_clone_obj(b) for b in backs]) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.convex_hull() + butil.modify_mesh(obj, 'SOLIDIFY', thickness=np.minimum(self.thickness, self.leg_thickness), offset=0) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.normals_make_consistent(inside=False) + return obj diff --git a/infinigen/assets/seating/chairs/__init__.py b/infinigen/assets/seating/chairs/__init__.py new file mode 100644 index 000000000..5d10c9c3b --- /dev/null +++ b/infinigen/assets/seating/chairs/__init__.py @@ -0,0 +1,7 @@ +# 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: Lingjie Mei +from .bar_chair import BarChairFactory +from .office_chair import OfficeChairFactory +from .chair import ChairFactory diff --git a/infinigen/assets/seating/chairs/bar_chair.py b/infinigen/assets/seating/chairs/bar_chair.py new file mode 100644 index 000000000..733cb79ac --- /dev/null +++ b/infinigen/assets/seating/chairs/bar_chair.py @@ -0,0 +1,173 @@ +# 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: Yiming Zuo + + +import bpy +from numpy.random import uniform, choice +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core import surface + +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory +from infinigen.core import tagging, tags as t + +from infinigen.assets.seating.chairs.seats.round_seats import generate_round_seats + +from infinigen.assets.tables.cocktail_table import geometry_create_legs +from infinigen.assets.material_assignments import AssetList + +def geometry_assemble_chair(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + generateseat = nw.new_node(generate_round_seats(thickness=kwargs['Top Thickness'], + radius=kwargs['Top Profile Width'], + seat_material=kwargs['SeatMaterial']).name) + + seat_instance = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': generateseat, + 'Translation': (0.0000, 0.0000, kwargs['Top Height'])}) + + legs = nw.new_node(geometry_create_legs(**kwargs).name) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [seat_instance, legs]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, attrs={'is_active_output': True}) + +class BarChairFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=None): + super(BarChairFactory, self).__init__(factory_seed, coarse=coarse) + + self.dimensions = dimensions + + with FixedSeed(factory_seed): + self.params, leg_style = self.sample_parameters(dimensions) + self.material_params, self.scratch, self.edge_wear = self.get_material_params(leg_style) + + self.params.update(self.material_params) + + def get_material_params(self, leg_style): + material_assignments = AssetList['BarChairFactory'](leg_style=leg_style) + + params = { + "SeatMaterial": material_assignments['seat'].assign_material(), + "LegMaterial": material_assignments['leg'].assign_material(), + } + wrapped_params = { + k: surface.shaderfunc_to_material(v) for k, v in params.items() + } + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = uniform() < scratch_prob + is_edge_wear = uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + @staticmethod + def sample_parameters(dimensions): + # all in meters + if dimensions is None: + x = uniform(0.35, 0.45) + z = uniform(0.7, 1) + dimensions = (x, x, z) + + x, y, z = dimensions + + top_thickness = uniform(0.06, 0.10) + + leg_style = choice(['straight', 'single_stand', 'wheeled']) + + parameters = { + 'Top Profile Width': x, + 'Top Thickness': top_thickness, + 'Height': z, + 'Top Height': z - top_thickness, + 'Leg Style': leg_style, + 'Leg NGon': choice([4, 32]), + 'Leg Placement Top Relative Scale': 0.7, + 'Leg Placement Bottom Relative Scale': uniform(1.1, 1.3), + 'Leg Height': 1.0, + } + + if leg_style == "single_stand": + leg_number = 1 + leg_diameter = uniform(0.7*x, 0.9*x) + + leg_curve_ctrl_pts = [(0.0, uniform(0.1, 0.2)), + (0.5, uniform(0.1, 0.2)), (0.9, uniform(0.2, 0.3)), (1.0, 1.0)] + + parameters.update({ + 'Leg Number': leg_number, + 'Leg Diameter': leg_diameter, + 'Leg Curve Control Points': leg_curve_ctrl_pts, + # 'Leg Material': choice(['metal', 'wood']) + }) + + elif leg_style == "straight": + leg_diameter = uniform(0.04, 0.06) + leg_number = choice([3, 4]) + + leg_curve_ctrl_pts = [(0.0, 1.0), (0.4, uniform(0.85, 0.95)), (1.0, uniform(0.4, 0.6))] + + parameters.update({ + 'Leg Number': leg_number, + 'Leg Diameter': leg_diameter, + 'Leg Curve Control Points': leg_curve_ctrl_pts, + # 'Leg Material': choice(['metal', 'wood']), + 'Strecher Relative Pos': uniform(0.6, 0.9), + 'Strecher Increament': choice([0, 1, 2]) + }) + + elif leg_style == "wheeled": + leg_diameter = uniform(0.03, 0.05) + leg_number = 1 + pole_number = choice([4, 5]) + joint_height = uniform(0.5, 0.8) * (z - top_thickness) + wheel_arc_sweep_angle = uniform(120, 240) + wheel_width = uniform(0.11, 0.15) + wheel_rot = uniform(0, 360) + pole_length = uniform(1.6, 2.0) + + parameters.update({ + 'Leg Number': leg_number, + 'Leg Pole Number': pole_number, + 'Leg Diameter': leg_diameter, + 'Leg Joint Height': joint_height, + 'Leg Wheel Arc Sweep Angle': wheel_arc_sweep_angle, + 'Leg Wheel Width': wheel_width, + 'Leg Wheel Rot': wheel_rot, + 'Leg Pole Length': pole_length, + # 'Leg Material': choice(['metal']) + }) + + else: + raise NotImplementedError + + + + return parameters, leg_style + + def create_asset(self, **params): + + bpy.ops.mesh.primitive_plane_add( + size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + surface.add_geomod(obj, geometry_assemble_chair, apply=True, input_kwargs=self.params) + tagging.tag_system.relabel_obj(obj) + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + diff --git a/infinigen/assets/seating/chairs/chair.py b/infinigen/assets/seating/chairs/chair.py new file mode 100644 index 000000000..ed37dafdf --- /dev/null +++ b/infinigen/assets/seating/chairs/chair.py @@ -0,0 +1,364 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import ( + read_co, read_edge_center, read_edge_direction, remove_edges, + remove_vertices, select_edges, solidify, subsurf, write_attribute, write_co, +) +from infinigen.assets.utils.draw import align_bezier, bezier_curve +from infinigen.assets.utils.nodegroup import geo_radius +from infinigen.assets.utils.object import join_objects, new_bbox +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.surface import NoApply +from infinigen.core.util import blender as butil +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.math import FixedSeed, normalize +from infinigen.core.util.random import log_uniform +from infinigen.assets.material_assignments import AssetList + +from infinigen.core.util.random import random_general as rg + + +class ChairFactory(AssetFactory): + back_types = 'weighted_choice', (1, 'whole'), (1, 'partial'), (1, 'horizontal-bar'), (1, 'vertical-bar') + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.width = uniform(.4, .5) + self.size = uniform(.38, .45) + self.thickness = uniform(.04, .08) + self.bevel_width = self.thickness * (.1 if uniform() < .4 else .5) + self.seat_back = uniform(.7, 1.) if uniform() < .75 else 1. + self.seat_mid = uniform(.7, .8) + self.seat_mid_x = uniform(self.seat_back + self.seat_mid * (1 - self.seat_back), 1) + self.seat_mid_z = uniform(0, .5) + self.seat_front = uniform(1., 1.2) + self.is_seat_round = uniform() < .6 + self.is_seat_subsurf = uniform() < .5 + + self.leg_thickness = uniform(.04, .06) + self.limb_profile = uniform(1.5, 2.5) + self.leg_height = uniform(.45, .5) + self.back_height = uniform(.4, .5) + self.is_leg_round = uniform() < .5 + self.leg_type = np.random.choice(['vertical', 'straight', 'up-curved', 'down-curved']) + + self.leg_x_offset = 0 + self.leg_y_offset = 0, 0 + self.back_x_offset = 0 + self.back_y_offset = 0 + + self.has_leg_x_bar = uniform() < .6 + self.has_leg_y_bar = uniform() < .6 + self.leg_offset_bar = uniform(.2, .4), uniform(.6, .8) + + self.has_arm = uniform() < 0.7 + self.arm_thickness = uniform(.04, .06) + self.arm_height = self.arm_thickness * uniform(.6, 1) + self.arm_y = uniform(.8, 1) * self.size + self.arm_z = uniform(.3, .6) * self.back_height + self.arm_mid = np.array([uniform(-.03, .03), uniform(-.03, .09), uniform(-.09, .03)]) + self.arm_profile = log_uniform(.1, 3, 2) + + self.back_thickness = uniform(.04, .05) + self.back_type = rg(self.back_types) + self.back_profile = [(0, 1)] + self.back_vertical_cuts = np.random.randint(1, 4) + self.back_partial_scale = uniform(1, 1.4) + + materials = AssetList['ChairFactory']() + self.limb_surface = materials['limb'].assign_material() + self.surface = materials['surface'].assign_material() + if uniform() < .3: + self.panel_surface = self.surface + else: + self.panel_surface = materials['panel'].assign_material() + + scratch_prob, edge_wear_prob = materials['wear_tear_prob'] + self.scratch, self.edge_wear = materials['wear_tear'] + is_scratch = uniform() < scratch_prob + is_edge_wear = uniform() < edge_wear_prob + if not is_scratch: + self.scratch = None + if not is_edge_wear: + self.edge_wear = None + + #from infinigen.assets.clothes import blanket + #from infinigen.assets.scatters.clothes import ClothesCover + #self.clothes_scatter = ClothesCover(factory_fn=blanket.BlanketFactory, width=log_uniform(.8, 1.2), + # size=uniform(.8, 1.2)) if uniform() < .3 else NoApply() + self.clothes_scatter = NoApply() + self.post_init() + + def post_init(self): + with FixedSeed(self.factory_seed): + if self.leg_type == 'vertical': + self.leg_x_offset = 0 + self.leg_y_offset = 0, 0 + self.back_x_offset = 0 + self.back_y_offset = 0 + else: + self.leg_x_offset = self.width * uniform(.05, .2) + self.leg_y_offset = self.size * uniform(.05, .2, 2) + self.back_x_offset = self.width * uniform(-.1, .15) + self.back_y_offset = self.size * uniform(.1, .25) + + match self.back_type: + case 'partial': + self.back_profile = (uniform(.4, .8), 1), + case 'horizontal-bar': + n_cuts = np.random.randint(2, 4) + locs = uniform(1, 2, n_cuts).cumsum() + locs = locs / locs[-1] + ratio = uniform(.5, .75) + locs = np.array([(p + ratio * (l - p), l) for p, l in zip([0, *locs[:-1]], locs)]) + lowest = uniform(0, .4) + self.back_profile = locs * (1 - lowest) + lowest + case 'vertical-bar': + self.back_profile = (uniform(.8, .9), 1), + case _: + self.back_profile = [(0, 1)] + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + obj = new_bbox( + -self.width / 2 - max(self.leg_x_offset, self.back_x_offset), + self.width / 2 + max(self.leg_x_offset, self.back_x_offset), + -self.size - self.leg_y_offset[1] - self.leg_thickness * .5, + max(self.leg_y_offset[0], self.back_y_offset), + -self.leg_height, + self.back_height * 1.2 + ) + obj.rotation_euler.z += np.pi / 2 + butil.apply_transform(obj) + return obj + + def create_asset(self, **params) -> bpy.types.Object: + + obj = self.make_seat() + legs = self.make_legs() + backs = self.make_backs() + + parts = [obj] + legs + backs + parts.extend(self.make_leg_decors(legs)) + if self.has_arm: + parts.extend(self.make_arms(obj, backs)) + parts.extend(self.make_back_decors(backs)) + + for obj in legs: + self.solidify(obj, 2) + for obj in backs: + self.solidify(obj, 2, self.back_thickness) + + obj = join_objects(parts) + obj.rotation_euler.z += np.pi / 2 + butil.apply_transform(obj) + + with FixedSeed(self.factory_seed): + # TODO: wasteful to create unique materials for each individual asset + self.surface.apply(obj) + self.panel_surface.apply(obj, selection='panel') + self.limb_surface.apply(obj, selection='limb') + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + + def make_seat(self): + x_anchors = np.array( + [0, -self.seat_back, -self.seat_mid_x, -1, 0, 1, self.seat_mid_x, self.seat_back, + 0] + ) * self.width / 2 + y_anchors = np.array([0, 0, -self.seat_mid, -1, -self.seat_front, -1, -self.seat_mid, 0, 0]) * self.size + z_anchors = np.array([0, 0, self.seat_mid_z, 0, 0, 0, self.seat_mid_z, 0, 0]) * self.thickness + vector_locations = [1, 7] if self.is_seat_round else [1, 3, 5, 7] + obj = bezier_curve((x_anchors, y_anchors, z_anchors), vector_locations, 8) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.fill_grid(use_interp_simple=True) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness, offset=0) + subsurf(obj, 1, not self.is_seat_subsurf) + butil.modify_mesh(obj, 'BEVEL', width=self.bevel_width, segments=8) + return obj + + def make_legs(self): + leg_starts = np.array( + [[-self.seat_back, 0, 0], [-1, -1, 0], [1, -1, 0], [self.seat_back, 0, 0]] + ) * np.array( + [[self.width / 2, self.size, 0]] + ) + leg_ends = leg_starts.copy() + leg_ends[[0, 1], 0] -= self.leg_x_offset + leg_ends[[2, 3], 0] += self.leg_x_offset + leg_ends[[0, 3], 1] += self.leg_y_offset[0] + leg_ends[[1, 2], 1] -= self.leg_y_offset[1] + leg_ends[:, -1] = -self.leg_height + return self.make_limb(leg_ends, leg_starts) + + def make_limb(self, leg_ends, leg_starts): + limbs = [] + for leg_start, leg_end in zip(leg_starts, leg_ends): + match self.leg_type: + case 'up-curved': + axes = [(0, 0, 1), None] + scale = [self.limb_profile, 1] + case 'down-curved': + axes = [None, (0, 0, 1)] + scale = [1, self.limb_profile] + case _: + axes = None + scale = None + limb = align_bezier(np.stack([leg_start, leg_end], -1), axes, scale, resolution=64) + limb.location = np.array( + [1 if leg_start[0] < 0 else -1, 1 if leg_start[1] < -self.size / 2 else -1, + 0] + ) * self.leg_thickness / 2 + butil.apply_transform(limb, True) + limbs.append(limb) + return limbs + + def make_backs(self): + back_starts = np.array([[-self.seat_back, 0, 0], [self.seat_back, 0, 0]]) * self.width / 2 + back_ends = back_starts.copy() + back_ends[:, 0] += np.array([self.back_x_offset, -self.back_x_offset]) + back_ends[:, 1] = self.back_y_offset + back_ends[:, 2] = self.back_height + return self.make_limb(back_starts, back_ends) + + def make_leg_decors(self, legs): + decors = [] + if self.has_leg_x_bar: + z_height = -self.leg_height * uniform(*self.leg_offset_bar) + locs = [] + for leg in legs: + co = read_co(leg) + locs.append(co[np.argmin(np.abs(co[:, -1] - z_height))]) + decors.append(self.solidify(bezier_curve(np.stack([locs[0], locs[3]], -1)), 0)) + decors.append(self.solidify(bezier_curve(np.stack([locs[1], locs[2]], -1)), 0)) + if self.has_leg_y_bar: + z_height = -self.leg_height * uniform(*self.leg_offset_bar) + locs = [] + for leg in legs: + co = read_co(leg) + locs.append(co[np.argmin(np.abs(co[:, -1] - z_height))]) + decors.append(self.solidify(bezier_curve(np.stack([locs[0], locs[1]], -1)), 1)) + decors.append(self.solidify(bezier_curve(np.stack([locs[2], locs[3]], -1)), 1)) + for d in decors: + write_attribute(d, 1, 'limb', 'FACE') + return decors + + def make_back_decors(self, backs, finalize=True): + obj = join_objects([deep_clone_obj(b) for b in backs]) + x, y, z = read_co(obj).T + x += np.where(x > 0, self.back_thickness / 2, -self.back_thickness / 2) + write_co(obj, np.stack([x, y, z], -1)) + smoothness = uniform(0, 1) + profile_shape_factor = uniform(0, .4) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + center = read_edge_center(obj) + for z_min, z_max in self.back_profile: + select_edges( + obj, (z_min * self.back_height <= center[:, -1]) & ( + center[:, -1] <= z_max * self.back_height) + ) + bpy.ops.mesh.bridge_edge_loops( + number_cuts=32, interpolation='LINEAR', smoothness=smoothness, + profile_shape_factor=profile_shape_factor + ) + bpy.ops.mesh.select_loose() + bpy.ops.mesh.delete() + butil.modify_mesh(obj, 'SOLIDIFY', thickness=np.minimum(self.thickness, self.back_thickness), offset=0) + if finalize: + butil.modify_mesh(obj, 'BEVEL', width=self.bevel_width, segments=8) + parts = [obj] + if self.back_type == 'vertical-bar': + other = join_objects([deep_clone_obj(b) for b in backs]) + with butil.ViewportMode(other, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bridge_edge_loops( + number_cuts=self.back_vertical_cuts, interpolation='LINEAR', + smoothness=smoothness, profile_shape_factor=profile_shape_factor + ) + bpy.ops.mesh.select_all(action='INVERT') + bpy.ops.mesh.delete() + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.delete(type='ONLY_FACE') + remove_edges(other, np.abs(read_edge_direction(other)[:, -1]) < .5) + remove_vertices(other, lambda x, y, z: z < -self.thickness / 2) + remove_vertices( + other, lambda x, y, z: z > ( + self.back_profile[0][0] + self.back_profile[0][1]) * self.back_height / 2 + ) + parts.append(self.solidify(other, 2, self.back_thickness)) + elif self.back_type == 'partial': + co = read_co(obj) + co[:, 1] *= self.back_partial_scale + write_co(obj, co) + for p in parts: + write_attribute(p, 1, 'panel', 'FACE') + return parts + + def make_arms(self, base, backs): + co = read_co(base) + end = co[np.argmin(co[:, 0] - (np.abs(co[:, 1] + self.arm_y) < .02))] + end[0] += self.arm_thickness / 4 + end_ = end.copy() + end_[0] = -end[0] + arms = [] + co = read_co(backs[0]) + start = co[np.argmin(co[:, 0] - (np.abs(co[:, -1] - self.arm_z) < .02))] + start[0] -= self.arm_thickness / 4 + start_ = start.copy() + start_[0] = -start[0] + for start, end in zip([start, start_], [end, end_]): + mid = np.array( + [end[0] + self.arm_mid[0] * (-1 if end[0] > 0 else 1), end[1] + self.arm_mid[1], + start[2] + self.arm_mid[2]] + ) + arm = align_bezier( + np.stack([start, mid, end], -1), + np.array([[end[0] - start[0], end[1] - start[1], 0], [0, 1 / np.sqrt(2), 1 / np.sqrt(2)], [0, 0, 1]]), + [1, *self.arm_profile, 1] + ) + if self.is_leg_round: + surface.add_geomod( + arm, geo_radius, apply=True, input_args=[self.arm_thickness / 2, 32], + input_kwargs={'to_align_tilt': False} + ) + else: + with butil.ViewportMode(arm, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={ + 'value': (self.arm_thickness if end[0] < 0 else -self.arm_thickness, 0, 0) + } + ) + butil.modify_mesh(arm, 'SOLIDIFY', thickness=self.arm_height, offset=0) + write_attribute(arm, 1, 'limb', 'FACE') + arms.append(arm) + return arms + + def solidify(self, obj, axis, thickness=None): + if thickness is None: + thickness = self.leg_thickness + if self.is_leg_round: + solidify(obj, axis, thickness) + butil.modify_mesh(obj, 'BEVEL', width=self.bevel_width, segments=8) + else: + surface.add_geomod(obj, geo_radius, apply=True, input_args=[thickness / 2, 32]) + write_attribute(obj, 1, 'limb', 'FACE') + return obj + + diff --git a/infinigen/assets/seating/chairs/office_chair.py b/infinigen/assets/seating/chairs/office_chair.py new file mode 100644 index 000000000..7a4f3ba5a --- /dev/null +++ b/infinigen/assets/seating/chairs/office_chair.py @@ -0,0 +1,206 @@ +# 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: Yiming Zuo + +import bpy + +import numpy as np +from numpy.random import uniform, choice + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface, tagging + +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.core.placement.factory import AssetFactory + +from infinigen.assets.seating.chairs.seats.curvy_seats import generate_curvy_seats + +from infinigen.assets.tables.cocktail_table import geometry_create_legs +from infinigen.assets.material_assignments import AssetList + +def geometry_assemble_chair(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + generateseat = nw.new_node(generate_curvy_seats().name, + input_kwargs={ + 'Width': kwargs['Top Profile Width'], + 'Front Relative Width': kwargs['Top Front Relative Width'], + 'Front Bent': kwargs['Top Front Bent'], + 'Seat Bent': kwargs['Top Seat Bent'], + 'Mid Bent': kwargs['Top Mid Bent'], + 'Mid Relative Width': kwargs['Top Mid Relative Width'], + 'Back Bent': kwargs['Top Back Bent'], + 'Back Relative Width': kwargs['Top Back Relative Width'], + 'Mid Pos': kwargs['Top Mid Pos'], + 'Seat Height': kwargs['Top Thickness'], + }) + + seat_instance = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': generateseat, + 'Translation': (0.0000, 0.0000, kwargs['Top Height'])}) + + seat_instance = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': seat_instance, 'Material': kwargs['TopMaterial']}) + + legs = nw.new_node(geometry_create_legs(**kwargs).name) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [seat_instance, legs]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, attrs={'is_active_output': True}) + +class OfficeChairFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=None): + super(OfficeChairFactory, self).__init__(factory_seed, coarse=coarse) + + self.dimensions = dimensions + + with FixedSeed(factory_seed): + self.params, leg_style = self.sample_parameters(dimensions) + self.material_params, self.scratch, self.edge_wear = self.get_material_params(leg_style) + self.params.update(self.material_params) + + def get_material_params(self, leg_style): + material_assignments = AssetList['OfficeChairFactory'](leg_style) + params = { + "TopMaterial": material_assignments['top'].assign_material(), + "LegMaterial": material_assignments['leg'].assign_material(), + } + wrapped_params = { + k: surface.shaderfunc_to_material(v) for k, v in params.items() + } + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = uniform() < scratch_prob + is_edge_wear = uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + @staticmethod + def sample_parameters(dimensions): + # all in meters + + if dimensions is None: + x = uniform(0.5, 0.6) + z = uniform(1.0, 1.4) + dimensions = ( + x, x, z + ) + + x, y, z = dimensions + + top_thickness = uniform(0.5, 0.7) + + # straight has the bug that seat and legs are disjoint, so disable for now. + + # leg_style = choice(['straight', 'single_stand', 'wheeled']) + leg_style = choice(['single_stand', 'wheeled']) + + parameters = { + 'Top Profile Width': x, + 'Top Thickness': top_thickness, + 'Top Front Relative Width': uniform(0.5, 0.8), + 'Top Front Bent': uniform(-1.5, -0.4), + 'Top Seat Bent': uniform(-1.5, -0.4), + 'Top Mid Bent': uniform(-2.4, -0.5), + 'Top Mid Relative Width': uniform(0.5, 0.9), + 'Top Back Bent': uniform(-1, -0.1), + 'Top Back Relative Width': uniform(0.6, 0.9), + 'Top Mid Pos': uniform(0.4, 0.6), + # 'Top Material': choice(['leather', 'wood', 'plastic', 'glass']), + 'Height': z, + 'Top Height': z - top_thickness, + 'Leg Style': leg_style, + 'Leg NGon': choice([4, 32]), + 'Leg Placement Top Relative Scale': 0.7, + 'Leg Placement Bottom Relative Scale': uniform(1.1, 1.3), + 'Leg Height': 1.0, + } + + if leg_style == "single_stand": + leg_number = 1 + leg_diameter = uniform(0.7*x, 0.9*x) + + leg_curve_ctrl_pts = [(0.0, uniform(0.1, 0.2)), + (0.5, uniform(0.1, 0.2)), (0.9, uniform(0.2, 0.3)), (1.0, 1.0)] + + parameters.update({ + 'Leg Number': leg_number, + 'Leg Diameter': leg_diameter, + 'Leg Curve Control Points': leg_curve_ctrl_pts, + # 'Leg Material': choice(['metal', 'wood']) + }) + + elif leg_style == "straight": + leg_diameter = uniform(0.04, 0.06) + leg_number = 4 + + leg_curve_ctrl_pts = [(0.0, 1.0), (0.4, uniform(0.85, 0.95)), (1.0, uniform(0.4, 0.6))] + + parameters.update({ + 'Leg Number': leg_number, + 'Leg Diameter': leg_diameter, + 'Leg Curve Control Points': leg_curve_ctrl_pts, + # 'Leg Material': choice(['metal', 'wood']), + 'Strecher Relative Pos': uniform(0.2, 0.6), + 'Strecher Increament': choice([0, 1, 2]) + }) + + elif leg_style == "wheeled": + leg_diameter = uniform(0.03, 0.05) + leg_number = 1 + pole_number = choice([4, 5]) + joint_height = uniform(0.5, 0.8) * (z - top_thickness) + wheel_arc_sweep_angle = uniform(120, 240) + wheel_width = uniform(0.11, 0.15) + wheel_rot = uniform(0, 360) + pole_length = uniform(1.6, 2.0) + + parameters.update({ + 'Leg Number': leg_number, + 'Leg Pole Number': pole_number, + 'Leg Diameter': leg_diameter, + 'Leg Joint Height': joint_height, + 'Leg Wheel Arc Sweep Angle': wheel_arc_sweep_angle, + 'Leg Wheel Width': wheel_width, + 'Leg Wheel Rot': wheel_rot, + 'Leg Pole Length': pole_length, + # 'Leg Material': choice(['metal']) + }) + + else: + raise NotImplementedError + + return parameters, leg_style + + def create_asset(self, **params): + + bpy.ops.mesh.primitive_plane_add( + size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + + surface.add_geomod(obj, geometry_assemble_chair, apply=True, input_kwargs=self.params) + tagging.tag_system.relabel_obj(obj) + + obj.rotation_euler.z += np.pi / 2 + butil.apply_transform(obj) + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + diff --git a/infinigen/assets/seating/chairs/seats/curvy_seats.py b/infinigen/assets/seating/chairs/seats/curvy_seats.py new file mode 100644 index 000000000..128d5d1ff --- /dev/null +++ b/infinigen/assets/seating/chairs/seats/curvy_seats.py @@ -0,0 +1,150 @@ +# 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: Yiming Zuo + +import bpy +import bpy +import mathutils +import numpy as np +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +from infinigen.assets.tables.table_utils import nodegroup_bent +from infinigen.assets.table_decorations.utils import nodegroup_lofting, nodegroup_warp_around_curve + +# TODO: set material automatically + +@node_utils.to_nodegroup('generate_curvy_seats', singleton=False, type='GeometryNodeTree') +def generate_curvy_seats(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'U Resolution', 256), + ('NodeSocketInt', 'V Resolution', 128), + ('NodeSocketFloat', 'Width', 0.5000), + ('NodeSocketFloat', 'Thickness', 0.0300), + ('NodeSocketFloat', 'Front Relative Width', 0.5000), + ('NodeSocketFloat', 'Front Bent', -0.3800), + ('NodeSocketFloat', 'Seat Bent', -0.5600), + ('NodeSocketFloat', 'Mid Relative Width', 0.5000), + ('NodeSocketFloat', 'Mid Bent', -0.7000), + ('NodeSocketFloat', 'Back Relative Width', 0.5000), + ('NodeSocketFloat', 'Back Bent', -0.2000), + ('NodeSocketFloat', 'Top Relative Width', 0.5000), + ('NodeSocketFloat', 'Top Bent', -0.2000), + ('NodeSocketFloat', 'Seat Height', 0.6000), + ('NodeSocketFloat', 'Mid Pos', 0.5000), + ('NodeSocketMaterial', 'SeatMaterial', None)]) + + curve_circle_1 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': group_input.outputs["U Resolution"], 'Radius': 0.5000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Width"], 'Y': group_input.outputs["Thickness"], 'Z': 1.0000}) + + transform_geometry_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle_1.outputs["Curve"], 'Translation': (0.0000, 0.0000, 0.5000), 'Scale': combine_xyz}) + + bent = nw.new_node(nodegroup_bent().name, + input_kwargs={'Geometry': transform_geometry_1, 'Amount': group_input.outputs["Seat Bent"]}) + + curve_circle_2 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': group_input.outputs["U Resolution"], 'Radius': 0.5000}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Width"], 1: group_input.outputs["Mid Relative Width"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': group_input.outputs["Thickness"], 'Z': 1.0000}) + + transform_geometry_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle_2.outputs["Curve"], 'Translation': (0.0000, 0.0000, 1.0000), 'Scale': combine_xyz_2}) + + bent_1 = nw.new_node(nodegroup_bent().name, + input_kwargs={'Geometry': transform_geometry_2, 'Amount': group_input.outputs["Mid Bent"]}) + + curve_circle_3 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': group_input.outputs["U Resolution"], 'Radius': 0.5000}) + + transform_geometry_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle_3.outputs["Curve"], 'Scale': (0.0000, 0.0050, 1.0000)}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': group_input.outputs["U Resolution"], 'Radius': 0.5000}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Width"], 1: group_input.outputs["Front Relative Width"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_1, 'Y': 0.0050, 'Z': 1.0000}) + + transform_geometry = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle.outputs["Curve"], 'Translation': (0.0000, 0.0000, 0.0600), 'Scale': combine_xyz_1}) + + bent_2 = nw.new_node(nodegroup_bent().name, + input_kwargs={'Geometry': transform_geometry, 'Amount': group_input.outputs["Front Bent"]}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [bent_1, bent, bent_2, transform_geometry_3]}) + + curve_circle_4 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': group_input.outputs["U Resolution"], 'Radius': 0.5000}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Width"], 1: group_input.outputs["Back Relative Width"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_2, 'Y': group_input.outputs["Thickness"], 'Z': 1.0000}) + + transform_geometry_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle_4.outputs["Curve"], 'Translation': (0.0000, 0.0000, 1.5000), 'Scale': combine_xyz_3}) + + bent_3 = nw.new_node(nodegroup_bent().name, + input_kwargs={'Geometry': transform_geometry_4, 'Amount': group_input.outputs["Back Bent"]}) + + curve_circle_5 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': group_input.outputs["U Resolution"], 'Radius': 0.5000}) + + multiply_3 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Width"], 1: group_input.outputs["Top Relative Width"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': 0.0050, 'Z': 1.0000}) + + transform_geometry_5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle_5.outputs["Curve"], 'Translation': (0.0000, 0.0000, 2.0200), 'Scale': combine_xyz_4}) + + bent_4 = nw.new_node(nodegroup_bent().name, + input_kwargs={'Geometry': transform_geometry_5, 'Amount': group_input.outputs["Top Bent"]}) + + curve_circle_6 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': group_input.outputs["U Resolution"], 'Radius': 0.5000}) + + transform_geometry_6 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle_6.outputs["Curve"], 'Translation': (0.0000, 0.0000, 2.1000), 'Scale': (0.0000, 0.0050, 1.0000)}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_geometry_6, bent_4, bent_3]}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [join_geometry_2, join_geometry]}) + + lofting_001 = nw.new_node(nodegroup_lofting().name, + input_kwargs={'Profile Curves': join_geometry_1, 'U Resolution': group_input.outputs["U Resolution"], 'V Resolution': group_input.outputs["V Resolution"]}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_4, 'Z': 0.0300}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': group_input.outputs["Mid Pos"], 'Z': -0.0500}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_5, 'Z': group_input.outputs["Seat Height"]}) + + bezier_segment = nw.new_node(Nodes.CurveBezierSegment, + input_kwargs={'Resolution': 128, 'Start': combine_xyz_6, 'Start Handle': combine_xyz_7, 'End Handle': (0.0000, 0.1000, 0.1000), 'End': combine_xyz_5}) + + warparoundcurvealt = nw.new_node(nodegroup_warp_around_curve().name, + input_kwargs={'Geometry': lofting_001.outputs["Geometry"], 'Curve': bezier_segment}) + + # material_func =np.random.choice([plastic.shader_rough_plastic, metal.get_shader(), wood_new.shader_wood, leather.shader_leather]) + + warparoundcurvealt = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': warparoundcurvealt, 'Material': group_input.outputs["SeatMaterial"]}) + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': warparoundcurvealt}, attrs={'is_active_output': True}) + diff --git a/infinigen/assets/seating/chairs/seats/round_seats.py b/infinigen/assets/seating/chairs/seats/round_seats.py new file mode 100644 index 000000000..99959d77f --- /dev/null +++ b/infinigen/assets/seating/chairs/seats/round_seats.py @@ -0,0 +1,41 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +from infinigen.assets.tables.table_top import nodegroup_capped_cylinder +from infinigen.assets.materials.leather_and_fabrics.leather import shader_leather + +@node_utils.to_nodegroup('generate_round_seats', singleton=False, type='GeometryNodeTree') +def generate_round_seats(nw: NodeWrangler, thickness=None, radius=None, cap_radius=None, bevel_factor=None, seat_material=None): + # Code generated using version 2.6.4 of the node_transpiler + if thickness is None: + thickness = uniform(0.05, 0.12) + if radius is None: + radius = uniform(0.35, 0.45) + if cap_radius is None: + cap_radius = uniform(2.0, 3.2) + if bevel_factor is None: + bevel_factor = uniform(0.01, 0.04) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: thickness, 1: 1.0}, attrs={'operation': 'MULTIPLY'}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: bevel_factor, 1: thickness}, attrs={'operation': 'DIVIDE'}) + + cappedcylinder = nw.new_node(nodegroup_capped_cylinder().name, + input_kwargs={'Thickness': multiply, 'Radius': radius, 'Cap Flatness': cap_radius, 'Fillet Radius Vertical': divide, 'Cap Relative Scale': 0.0140, 'Cap Relative Z Offset': -0.0020, 'Resolution': 128}) + + seat = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': cappedcylinder, 'Material': seat_material}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': seat}, attrs={'is_active_output': True}) \ No newline at end of file diff --git a/infinigen/assets/seating/mattress.py b/infinigen/assets/seating/mattress.py new file mode 100644 index 000000000..1d9a9612a --- /dev/null +++ b/infinigen/assets/seating/mattress.py @@ -0,0 +1,126 @@ +# 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: Lingjie Mei +import bpy +import bmesh +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import fabrics +from infinigen.assets.scatters import clothes +from infinigen.assets.utils.decorate import ( + subdivide_edge_ring, read_co, +) +from infinigen.assets.utils.object import new_bbox, new_cube +from infinigen.core import surface +from infinigen.core.nodes import NodeWrangler, Nodes +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.surface import write_attr_data +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.core.util.random import random_general as rg +from infinigen.assets.material_assignments import AssetList + + + +def make_coiled(obj, dot_distance, dot_depth, dot_size): + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='FACE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.poke() + bpy.ops.mesh.tris_convert_to_quads() + bpy.ops.mesh.poke() + bpy.ops.mesh.poke() + bpy.ops.mesh.select_all(action='DESELECT') + bm = bmesh.from_edit_mesh(obj.data) + for v in bm.verts: + if len(v.link_edges) == 16: + v.select_set(True) + bm.select_flush(False) + bmesh.update_edit_mesh(obj.data) + radius = dot_distance * uniform(.06, .08) + bpy.ops.mesh.bevel(offset=radius, affect='VERTICES') + bpy.ops.mesh.extrude_region_shrink_fatten(TRANSFORM_OT_shrink_fatten={'value': -dot_depth}) + bpy.ops.mesh.extrude_region_shrink_fatten(TRANSFORM_OT_shrink_fatten={'value': dot_depth}) + bpy.ops.mesh.select_more() + bpy.ops.mesh.select_more() + write_attr_data(obj, 'tip', np.zeros(len(obj.data.polygons)), domain='FACE') + surface.set_active(obj, 'tip') + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.attribute_set(value_float=1) + + def geo_scale(nw: NodeWrangler): + geometry = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + selection = nw.new_node(Nodes.NamedAttribute, ['tip']) + geometry = nw.new_node( + Nodes.ScaleElements, + [geometry, selection, nw.combine(*([dot_size / radius] * 3))] + ) + nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) + + surface.add_geomod(obj, geo_scale, apply=True) + butil.modify_mesh(obj, 'TRIANGULATE', min_vertices=4) + butil.modify_mesh(obj, 'SMOOTH', factor=uniform(.5, 1.), iterations=5) + + +class MattressFactory(AssetFactory): + types = 'weighted_choice', (1, 'coiled'), (1, 'wrapped') + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.width = log_uniform(.9, 2.) + self.size = uniform(2, 2.4) + self.thickness = uniform(.2, .35) + self.dot_distance = log_uniform(.16, .2) + self.dot_size = uniform(.005, .02) + self.dot_depth = uniform(.04, .08) + self.wrap_distance = .05 + self.surface = fabrics + self.type= rg(self.types) + materials = AssetList['MattressFactory']() + self.surface = materials['surface'].assign_material() + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + return new_bbox( + -self.width / 2, self.width / 2, -self.size / 2, self.size / 2, -self.thickness / 2, + self.thickness / 2 + ) + + def create_asset(self, **params) -> bpy.types.Object: + obj = new_cube() + obj.scale = self.width / 2, self.size / 2, self.thickness / 2 + butil.apply_transform(obj) + match self.type: + case 'coiled': + self.make_coiled(obj) + case 'wrapped': + self.make_wrapped(obj) + return obj + + def make_coiled(self, obj): + for i, size in enumerate(obj.dimensions): + axis = np.zeros(3) + axis[i] = 1 + subdivide_edge_ring(obj, int(np.ceil(size / self.dot_distance)), axis) + make_coiled(obj, self.dot_distance, self.dot_depth, self.dot_size) + + def make_wrapped(self, obj): + for i, size in enumerate([self.width, self.size, self.thickness]): + axis = np.zeros(3) + axis[i] = 1 + subdivide_edge_ring(obj, int(np.ceil(size / self.wrap_distance)), axis) + butil.modify_mesh(obj, 'BEVEL', width=self.wrap_distance / 3, segments=2) + vg = obj.vertex_groups.new(name='pin') + vg.add(np.nonzero((read_co(obj)[:, -1] < 1e-1 - self.thickness / 2))[0].tolist(), 1, 'REPLACE') + clothes.cloth_sim( + obj, gravity=0, + use_pressure=True, + uniform_pressure_force=uniform(.1, .2), + vertex_group_mass='pin' + ) + + def finalize_assets(self, assets): + self.surface.apply(assets) diff --git a/infinigen/assets/seating/pillow.py b/infinigen/assets/seating/pillow.py new file mode 100644 index 000000000..f5f871a24 --- /dev/null +++ b/infinigen/assets/seating/pillow.py @@ -0,0 +1,117 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import art, fabrics +from infinigen.assets.scatters import clothes +from infinigen.assets.utils.decorate import read_normal, read_selected, select_faces, subsurf, set_shade_smooth +from infinigen.assets.utils.object import center, join_objects, new_base_circle, new_grid +from infinigen.assets.utils.uv import unwrap_faces +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.core.util.random import random_general as rg +from infinigen.assets.material_assignments import AssetList + + +class PillowFactory(AssetFactory): + shapes = 'weighted_choice', (4, 'square'), (4, 'rectangle'), (1, 'circle'), (1, 'torus') + + def __init__(self, factory_seed, coarse=False): + super(PillowFactory, self).__init__(factory_seed, coarse) + self.shape = rg(self.shapes) + self.width = uniform(.4, .7) + match self.shape: + case 'square': + self.size = self.width + case _: + self.size = self.width * log_uniform(.6, .8) + self.bevel_width = uniform(.02, .05) + self.thickness = log_uniform(.006, .008) + self.extrude_thickness = self.thickness * log_uniform(1, 8) if uniform() < .5 else 0 + self.surface = np.random.choice([art.ArtFabric(self.factory_seed), fabrics]) + self.has_seam = uniform() < .3 and not self.shape == 'torus' + self.seam_radius = uniform(.01, .02) + + materials = AssetList['PillowFactory']() + self.surface = materials['surface'].assign_material() + if self.surface == art.ArtFabric: + self.surface = self.surface(self.factory_seed) + + def create_asset(self, **params) -> bpy.types.Object: + match self.shape: + case 'circle': + obj = new_base_circle(vertices=128) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.fill_grid() + case 'torus': + obj = new_base_circle(vertices=128) + inner = new_base_circle(vertices=128, radius=uniform(.2, .4)) + obj = join_objects([obj, inner]) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bridge_edge_loops(number_cuts=12, interpolation='LINEAR') + obj = bpy.context.active_object + case _: + obj = new_grid(x_subdivisions=32, y_subdivisions=32) + obj.scale = self.width / 2, self.size / 2, 1 + butil.apply_transform(obj, True) + unwrap_faces(obj) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness, offset=0) + normal = read_normal(obj) + + group = obj.vertex_groups.new(name='pin') + if self.has_seam: + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='FACE') + select_faces(obj, lambda x, y, z: (x ** 2 + y ** 2 < self.seam_radius ** 2) & (z > 0)) + bpy.ops.mesh.region_to_loop() + bpy.ops.mesh.select_mode(type='VERT') + selection = read_selected(obj) + group.add(np.nonzero(selection)[0].tolist(), 1, 'REPLACE') + select_faces(obj, np.abs(normal[:, -1]) < .1) + + match self.shape: + case 'torus': + pressure = uniform(8, 12) + case _: + pressure = uniform(1, 2) + clothes.cloth_sim( + obj, tension_stiffness=uniform(0, 5), + gravity=0, + use_pressure=True, + uniform_pressure_force=pressure, + vertex_group_mass='pin' if self.has_seam else "" + ) + if self.extrude_thickness > 0: + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.extrude_region_shrink_fatten( + TRANSFORM_OT_shrink_fatten={'value': self.extrude_thickness} + ) + obj.location = -center(obj) + butil.apply_transform(obj, True) + subsurf(obj, 2) + set_shade_smooth(obj) + return obj + + def make_circle(self): + obj = new_base_circle(vertices=128) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.fill_grid() + select_faces(obj, lambda x, y, z: x ** 2 + y ** 2 < self.seam_radius ** 2) + bpy.ops.mesh.region_to_loop() + return obj + + def make_gird(self): + obj = new_grid(x_subdivisions=64, y_subdivisions=64) + with butil.ViewportMode(obj, 'EDIT'): + select_faces(obj, lambda x, y, z: (np.abs(x) < self.seam_radius) & (np.abs(y) < self.seam_radius)) + bpy.ops.mesh.region_to_loop() + return obj + + def finalize_assets(self, assets): + self.surface.apply(assets) diff --git a/infinigen/assets/seating/sofa.py b/infinigen/assets/seating/sofa.py new file mode 100644 index 000000000..0f33fb326 --- /dev/null +++ b/infinigen/assets/seating/sofa.py @@ -0,0 +1,743 @@ +# 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, Stamatis Alexandropolous, Yiming Zuo + +import bpy +import bpy +import mathutils +import random + +import numpy as np +from numpy.random import uniform, normal, randint, choice + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface + +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util import blender as butil +from infinigen.core.util.math import FixedSeed +from infinigen.core import tagging, tags as t + +from infinigen.core.util.random import log_uniform, clip_gaussian + +from infinigen.assets.material_assignments import AssetList + +@node_utils.to_nodegroup('nodegroup_array_fill_line', singleton=False, type='GeometryNodeTree') +def nodegroup_array_fill_line(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVector', 'Line Start', (0.0000, 0.0000, 0.0000)), + ('NodeSocketVector', 'Line End', (0.0000, 0.0000, 0.0000)), + ('NodeSocketVector', 'Instance Dimensions', (0.0000, 0.0000, 0.0000)), + ('NodeSocketInt', 'Count', 10), + ('NodeSocketGeometry', 'Instance', None)]) + + multiply = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Instance Dimensions"], 1: (0.0000, -0.5000, 0.0000)}, + attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.VectorMath, input_kwargs={0: group_input.outputs["Line End"], 1: multiply.outputs["Vector"]}) + + subtract = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Line Start"], 1: multiply.outputs["Vector"]}, + attrs={'operation': 'SUBTRACT'}) + + mesh_line = nw.new_node(Nodes.MeshLine, + input_kwargs={'Count': group_input.outputs["Count"], 'Start Location': add.outputs["Vector"], 'Offset': subtract.outputs["Vector"]}, + attrs={'mode': 'END_POINTS'}) + + instance_on_points_1 = nw.new_node(Nodes.InstanceOnPoints, + input_kwargs={'Points': mesh_line, 'Instance': group_input.outputs["Instance"]}) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': instance_on_points_1}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': realize_instances_1}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_corner_cube', singleton=False, type='GeometryNodeTree') +def nodegroup_corner_cube(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVectorTranslation', 'Location', (0.0000, 0.0000, 0.0000)), + ('NodeSocketVectorTranslation', 'CenteringLoc', (0.5000, 0.5000, 0.0000)), + ('NodeSocketVectorTranslation', 'Dimensions', (1.0000, 1.0000, 1.0000)), + ('NodeSocketFloat', 'SupportingEdgeFac', 0.0000), + ('NodeSocketInt', 'Vertices X', 4), + ('NodeSocketInt', 'Vertices Y', 4), + ('NodeSocketInt', 'Vertices Z', 4)]) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': group_input.outputs["Dimensions"], 'Vertices X': group_input.outputs["Vertices X"], 'Vertices Y': group_input.outputs["Vertices Y"], 'Vertices Z': group_input.outputs["Vertices Z"]}) + + map_range = nw.new_node(Nodes.MapRange, + input_kwargs={'Vector': group_input.outputs["CenteringLoc"], 9: (0.5000, 0.5000, 0.5000), 10: (-0.5000, -0.5000, -0.5000)}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + multiply_add = nw.new_node(Nodes.VectorMath, + input_kwargs={0: map_range.outputs["Vector"], 1: group_input.outputs["Dimensions"], 2: group_input.outputs["Location"]}, + attrs={'operation': 'MULTIPLY_ADD'}) + + transform_geometry = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Translation': multiply_add.outputs["Vector"]}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': transform_geometry, 'Name': 'UVMap', 3: cube.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': store_named_attribute}, attrs={'is_active_output': True}) + +ARM_TYPE_SQUARE = 0 +ARM_TYPE_ROUND = 1 +ARM_TYPE_ANGULAR = 2 + +@node_utils.to_nodegroup('nodegroup_sofa_geometry', singleton=False, type='GeometryNodeTree') +def nodegroup_sofa_geometry(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Geometry', None), + ('NodeSocketVector', 'Dimensions', (0.0000, 0.9000, 2.5000)), + ('NodeSocketVector', 'Arm Dimensions', (0.0000, 0.0000, 0.0000)), + ('NodeSocketVector', 'Back Dimensions', (0.0000, 0.0000, 0.0000)), + ('NodeSocketVector', 'Seat Dimensions', (0.0000, 0.0000, 0.0000)), + ('NodeSocketVector', 'Foot Dimensions', (0.0000, 0.0000, 0.0000)), + ('NodeSocketFloat', 'Baseboard Height', 0.1300), + ('NodeSocketFloat', 'Backrest Width', 0.1100), + ('NodeSocketFloat', 'Seat Margin', 0.9700), + ('NodeSocketFloat', 'Backrest Angle', -0.2000), + ('NodeSocketFloatFactor', 'arm_width', 0.7000), + ('NodeSocketInt', 'Arm Type', 0), + ('NodeSocketFloatFactor', 'Arm_height', 0.7318), + ('NodeSocketFloatAngle', 'arms_angle', 0.8727), + ('NodeSocketBool', 'Footrest', False), + ('NodeSocketInt', 'Count', 4), + ('NodeSocketFloat', 'Scaling footrest', 1.5000), + ('NodeSocketInt', 'Reflection', 0), + ('NodeSocketBool', 'leg_type', False), + ('NodeSocketFloat', 'leg_dimensions', 0.5000), + ('NodeSocketFloat', 'leg_z', 1.0000), + ('NodeSocketInt', 'leg_faces', 20), + ('NodeSocketBool', 'Subdivide', True)]) + + multiply = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Dimensions"], 1: (0.0000, 0.5000, 0.0000)}, + attrs={'operation': 'MULTIPLY'}) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Arm Dimensions"]}) + + arm_cube = nw.new_node(nodegroup_corner_cube().name, + input_kwargs={'Location': multiply.outputs["Vector"], 'CenteringLoc': (0.0000, 1.0000, 0.0000), 'Dimensions': reroute, 'Vertices Z': 10}, + label='ArmCube') + + reroute_1 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': arm_cube}) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': reroute}) + + map_range = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': separate_xyz.outputs["Z"], 1: -0.1000, 2: separate_xyz_1.outputs["Z"], 3: -0.1000, 4: 0.2000}) + + float_curve = nw.new_node(Nodes.FloatCurve, + input_kwargs={'Factor': group_input.outputs["arm_width"], 'Value': map_range.outputs["Result"]}) + node_utils.assign_curve(float_curve.mapping.curves[0], [(0.0092, 0.7688), (0.1011, 0.5937), (0.1494, 0.4062), (0.3954, 0.0781), (1.0000, 0.2187)]) + + separate_xyz_2 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': multiply.outputs["Vector"]}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["Y"], 1: separate_xyz_2.outputs["Y"]}, + attrs={'operation': 'SUBTRACT'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: float_curve, 1: subtract}, attrs={'operation': 'MULTIPLY'}) + + position_1 = nw.new_node(Nodes.InputPosition) + + separate_xyz_14 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position_1}) + + map_range_1 = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': separate_xyz_14.outputs["X"], 1: -1.0000, 2: 0.6000, 3: 2.1000, 4: -1.1000}) + + float_curve_1 = nw.new_node(Nodes.FloatCurve, + input_kwargs={'Factor': group_input.outputs["Arm_height"], 'Value': map_range_1.outputs["Result"]}) + node_utils.assign_curve(float_curve_1.mapping.curves[0], [(0.1341, 0.2094), (0.7386, 1.0000), (0.9682, 0.0781), (1.0000, 0.0000)]) + + separate_xyz_15 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': (-2.9000, 3.3000, 0.0000)}) + + subtract_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_14.outputs["Z"], 1: separate_xyz_15.outputs["Z"]}, + attrs={'operation': 'SUBTRACT'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: float_curve_1, 1: subtract_1}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_1, 'Z': multiply_2}) + + vector_rotate = nw.new_node(Nodes.VectorRotate, + input_kwargs={'Vector': combine_xyz, 'Axis': (1.0000, 0.0000, 0.0000), 'Angle': group_input.outputs["arms_angle"]}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': reroute_1, 'Offset': vector_rotate}) + + multiply_3 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Dimensions"], 1: (0.0000, 0.5000, 0.0000)}, + attrs={'operation': 'MULTIPLY'}) + + separate_xyz_3 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Arm Dimensions"]}) + + subtract_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_3.outputs["Z"], 1: separate_xyz_3.outputs["Y"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz_3.outputs["X"], 'Y': separate_xyz_3.outputs["Y"], 'Z': subtract_2}) + + reroute_2 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': combine_xyz_1}) + + arm_cube_1 = nw.new_node(nodegroup_corner_cube().name, + input_kwargs={'Location': multiply_3.outputs["Vector"], 'CenteringLoc': (0.0000, 1.0000, 0.0000), 'Dimensions': reroute_2}, + label='ArmCube') + + separate_xyz_4 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': reroute_2}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_4.outputs["X"], 1: 1.0001}, attrs={'operation': 'MULTIPLY'}) + + reroute_3 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': multiply_4}) + + arm_cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Side Segments': 4, 'Radius': separate_xyz_4.outputs["Y"], 'Depth': reroute_3}, + attrs={'fill_type': 'TRIANGLE_FAN'}) + + arm_cylinder = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': arm_cylinder.outputs["Mesh"], 'Name': 'UVMap', 3: arm_cylinder.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: reroute_3, 1: 2.0000}, attrs={'operation': 'DIVIDE'}) + + separate_xyz_5 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': multiply_3.outputs["Vector"]}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': divide, 'Y': separate_xyz_5.outputs["Y"], 'Z': separate_xyz_4.outputs["Z"]}) + + arm_cylinder = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': arm_cylinder, 'Translation': combine_xyz_2, 'Rotation': (0.0000, 1.5708, 0.0000)}) + + roundtop = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [arm_cube_1, arm_cylinder]}) + + square_or_round = nw.new_node( + Nodes.Switch, + input_kwargs={ + 'Switch': nw.compare('EQUAL', group_input.outputs['Arm Type'], ARM_TYPE_SQUARE), + 'False': roundtop, + 'True': arm_cube_1, + } + ) + + angular_or_squareround = nw.new_node(Nodes.Switch, + input_kwargs={ + 'Switch': nw.compare('EQUAL', group_input.outputs['Arm Type'], ARM_TYPE_ANGULAR), + 'False': square_or_round, + 'True': set_position + } + ) + + transform_geometry_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': angular_or_squareround, 'Scale': (1.0000, -1.0000, 1.0000)}) + + flip_faces = nw.new_node(Nodes.FlipFaces, input_kwargs={'Mesh': transform_geometry_1}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [flip_faces, angular_or_squareround]}) + + separate_xyz_6 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Back Dimensions"]}) + + separate_xyz_7 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Arm Dimensions"]}) + + separate_xyz_8 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Dimensions"]}) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_7.outputs["Y"], 1: -2.0000, 2: separate_xyz_8.outputs["Y"]}, + attrs={'operation': 'MULTIPLY_ADD'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz_6.outputs["X"], 'Y': multiply_add, 'Z': separate_xyz_6.outputs["Z"]}) + + back_board = nw.new_node(nodegroup_corner_cube().name, + input_kwargs={'CenteringLoc': (0.0000, 0.5000, -1.0000), 'Dimensions': combine_xyz_3, 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}, + label='BackBoard') + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [join_geometry_2, back_board]}) + + multiply_5 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: combine_xyz_3, 1: (1.0000, 0.0000, 0.0000)}, + attrs={'operation': 'MULTIPLY'}) + + multiply_add_1 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Arm Dimensions"], 1: (0.0000, -2.0000, 0.0000), 2: group_input.outputs["Dimensions"]}, + attrs={'operation': 'MULTIPLY_ADD'}) + + multiply_add_2 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Back Dimensions"], 1: (-1.0000, 0.0000, 0.0000), 2: multiply_add_1.outputs["Vector"]}, + attrs={'operation': 'MULTIPLY_ADD'}) + + separate_xyz_9 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': multiply_add_2.outputs["Vector"]}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz_9.outputs["X"], 'Y': separate_xyz_9.outputs["Y"], 'Z': group_input.outputs["Baseboard Height"]}) + + base_board = nw.new_node(nodegroup_corner_cube().name, + input_kwargs={'Location': multiply_5.outputs["Vector"], 'CenteringLoc': (0.0000, 0.5000, -1.0000), 'Dimensions': combine_xyz_4, 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}, + label='BaseBoard') + + reroute_13 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Count"]}) + + equal = nw.new_node(Nodes.Compare, input_kwargs={2: reroute_13, 3: 4}, attrs={'operation': 'EQUAL', 'data_type': 'INT'}) + + reroute_5 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': separate_xyz_9.outputs["Y"]}) + + separate_xyz_10 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Seat Dimensions"]}) + + divide_1 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_5, 1: separate_xyz_10.outputs["Y"]}, attrs={'operation': 'DIVIDE'}) + + ceil = nw.new_node(Nodes.Math, input_kwargs={0: divide_1}, attrs={'operation': 'CEIL'}) + + combine_xyz_14 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': ceil, 'Z': 1.0000}) + + divide_2 = nw.new_node(Nodes.VectorMath, input_kwargs={0: combine_xyz_4, 1: combine_xyz_14}, attrs={'operation': 'DIVIDE'}) + + reroute_12 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': divide_2.outputs["Vector"]}) + + base_board_1 = nw.new_node(nodegroup_corner_cube().name, + input_kwargs={'Location': multiply_5.outputs["Vector"], 'CenteringLoc': (0.0000, 0.5000, -1.0000), 'Dimensions': reroute_12, 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}, + label='BaseBoard') + + equal_1 = nw.new_node(Nodes.Compare, + input_kwargs={0: 4.0000, 2: reroute_13, 3: 4}, + attrs={'operation': 'EQUAL', 'data_type': 'INT'}) + + switch_8 = nw.new_node(Nodes.Switch, + input_kwargs={0: equal_1, 8: divide_2.outputs["Vector"], 9: combine_xyz_4}, + attrs={'input_type': 'VECTOR'}) + + separate_xyz_16 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': switch_8.outputs[3]}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_16.outputs["Y"], 1: 0.7000}, attrs={'operation': 'MULTIPLY'}) + + grid_1 = nw.new_node(Nodes.MeshGrid, input_kwargs={'Size Y': multiply_6, 'Vertices X': 1, 'Vertices Y': 2}) + + combine_xyz_18 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': 0.1000, 'Y': separate_xyz_16.outputs["Y"], 'Z': separate_xyz_16.outputs["Z"]}) + + subtract_3 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: switch_8.outputs[3], 1: combine_xyz_18}, + attrs={'operation': 'SUBTRACT'}) + + multiply_7 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Back Dimensions"], 1: (1.0000, 0.0000, 0.0000)}, + attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.VectorMath, input_kwargs={0: subtract_3.outputs["Vector"], 1: multiply_7.outputs["Vector"]}) + + transform_geometry_10 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': grid_1.outputs["Mesh"], 'Translation': add.outputs["Vector"], 'Scale': (1.0000, 1.0000, 0.9000)}) + + cone = nw.new_node('GeometryNodeMeshCone', + input_kwargs={'Vertices': group_input.outputs["leg_faces"], 'Side Segments': 4, 'Radius Top': 0.0100, 'Radius Bottom': 0.0250, 'Depth': 0.0700}) + + reroute_9 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["leg_dimensions"]}) + + combine_xyz_17 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': reroute_9, 'Y': reroute_9, 'Z': group_input.outputs["leg_z"]}) + + transform_geometry_9 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cone.outputs["Mesh"], 'Translation': (0.0000, 0.0000, 0.0100), 'Rotation': (0.0000, 3.1416, 0.0000), 'Scale': combine_xyz_17}) + + foot_cube = nw.new_node(nodegroup_corner_cube().name, + input_kwargs={'CenteringLoc': (0.5000, 0.5000, 0.9000), 'Dimensions': group_input.outputs["Foot Dimensions"]}, + label='FootCube') + + transform_geometry_12 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': foot_cube, 'Scale': (0.5000, 0.8000, 0.8000)}) + + switch_6 = nw.new_node(Nodes.Switch, + input_kwargs={1: group_input.outputs["leg_type"], 14: transform_geometry_9, 15: transform_geometry_12}) + + transform_geometry_8 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': switch_6.outputs[6]}) + + instance_on_points_1 = nw.new_node(Nodes.InstanceOnPoints, + input_kwargs={'Points': transform_geometry_10, 'Instance': transform_geometry_8, 'Scale': (1.0000, 1.0000, 1.2000)}) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': instance_on_points_1}) + + join_geometry_10 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [base_board_1, realize_instances_1]}) + + subtract_4 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: combine_xyz_14, 1: (1.0000, 1.0000, 1.0000)}, + attrs={'operation': 'SUBTRACT'}) + + multiply_8 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: subtract_4.outputs["Vector"], 1: (0.0000, 0.5000, 0.0000)}, + attrs={'operation': 'MULTIPLY'}) + + multiply_9 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: divide_2.outputs["Vector"], 1: multiply_8.outputs["Vector"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_16 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': group_input.outputs["Reflection"], 'Z': 1.0000}) + + multiply_10 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: multiply_9.outputs["Vector"], 1: combine_xyz_16}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_12 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Scaling footrest"], 'Y': 1.0000, 'Z': 1.0000}) + + transform_geometry_5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry_10, 'Translation': multiply_10.outputs["Vector"], 'Scale': combine_xyz_12}) + + switch_2 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Footrest"], 15: transform_geometry_5}) + + combine_xyz_19 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Scaling footrest"], 'Y': 1.3000, 'Z': 1.0000}) + + transform_geometry_11 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': realize_instances_1, 'Scale': combine_xyz_19}) + + base_board_2 = nw.new_node(nodegroup_corner_cube().name, + input_kwargs={'Location': multiply_5.outputs["Vector"], 'CenteringLoc': (0.0000, 0.5000, -1.0000), 'Dimensions': combine_xyz_4, 'Vertices X': 3, 'Vertices Y': 3, 'Vertices Z': 3}, + label='BaseBoard') + + combine_xyz_13 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Scaling footrest"], 'Y': 1.0000, 'Z': 1.0000}) + + transform_geometry_6 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': base_board_2, 'Scale': combine_xyz_13}) + + join_geometry_11 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_geometry_11, transform_geometry_6]}) + + switch_4 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Footrest"], 15: join_geometry_11}) + + switch_5 = nw.new_node(Nodes.Switch, input_kwargs={1: equal, 14: switch_2.outputs[6], 15: switch_4.outputs[6]}) + + join_geometry_4 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [join_geometry_3, base_board, switch_5.outputs[6]]}) + + grid = nw.new_node(Nodes.MeshGrid, input_kwargs={'Vertices X': 2, 'Vertices Y': 2}) + + multiply_11 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Dimensions"], 1: (0.5000, 0.0000, 0.0000)}, + attrs={'operation': 'MULTIPLY'}) + + multiply_12 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Dimensions"], 1: (1.0000, 1.0000, 0.0000)}, + attrs={'operation': 'MULTIPLY'}) + + multiply_13 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["Foot Dimensions"], 1: (2.5000, 2.5000, 0.0000)}, + attrs={'operation': 'MULTIPLY'}) + + subtract_5 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: multiply_12.outputs["Vector"], 1: multiply_13.outputs["Vector"]}, + attrs={'operation': 'SUBTRACT'}) + + transform_geometry_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': grid.outputs["Mesh"], 'Translation': multiply_11.outputs["Vector"], 'Scale': subtract_5.outputs["Vector"]}) + + instance_on_points = nw.new_node(Nodes.InstanceOnPoints, + input_kwargs={'Points': transform_geometry_2, 'Instance': transform_geometry_8}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': instance_on_points}) + + join_geometry_5 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [join_geometry_4, realize_instances]}) + + reroute_10 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Count"]}) + + equal_2 = nw.new_node(Nodes.Compare, + input_kwargs={1: 4.0000, 2: reroute_10, 3: 4}, + attrs={'operation': 'EQUAL', 'data_type': 'INT'}) + + reroute_4 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': combine_xyz_4}) + + multiply_14 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: reroute_4, 1: (0.0000, -0.5000, 1.0000)}, + attrs={'operation': 'MULTIPLY'}) + + multiply_15 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: reroute_4, 1: (0.0000, 0.5000, 1.0000)}, + attrs={'operation': 'MULTIPLY'}) + + equal_3 = nw.new_node(Nodes.Compare, + input_kwargs={1: 4.0000, 2: reroute_10, 3: 4}, + attrs={'operation': 'EQUAL', 'data_type': 'INT'}) + + reroute_11 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Reflection"]}) + + switch_7 = nw.new_node(Nodes.Switch, input_kwargs={0: equal_3, 4: reroute_11, 5: 1}, attrs={'input_type': 'INT'}) + + combine_xyz_15 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': switch_7.outputs[1], 'Z': 1.1000}) + + multiply_16 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: multiply_15.outputs["Vector"], 1: combine_xyz_15}, + attrs={'operation': 'MULTIPLY'}) + + divide_3 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_5, 1: ceil}, attrs={'operation': 'DIVIDE'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz_10.outputs["X"], 'Y': divide_3, 'Z': separate_xyz_10.outputs["Z"]}) + + reroute_6 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': combine_xyz_5}) + + multiply_17 = nw.new_node(Nodes.VectorMath, input_kwargs={0: reroute_6, 1: combine_xyz_15}, attrs={'operation': 'MULTIPLY'}) + + multiply_18 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: combine_xyz_5, 1: (1.0000, 1.0300, 1.0000)}, + attrs={'operation': 'MULTIPLY'}) + + seat_cushion = nw.new_node(nodegroup_corner_cube().name, + input_kwargs={'CenteringLoc': (0.0000, 0.5000, 0.0000), 'Dimensions': multiply_18.outputs["Vector"], 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}, + label='SeatCushion') + + upwards_part = nw.new_node(Nodes.Compare, input_kwargs={'A': nw.new_node(Nodes.Index), 'B': 2}, attrs={'data_type': 'INT', 'operation': 'EQUAL'}) + seat_cushion = tagging.tag_nodegroup(nw, seat_cushion, t.Subpart.SupportSurface, selection=upwards_part) + + index = nw.new_node(Nodes.Index) + + equal_4 = nw.new_node(Nodes.Compare, input_kwargs={2: index, 3: 1}, attrs={'operation': 'EQUAL', 'data_type': 'INT'}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': seat_cushion, 'Selection': equal_4, 'Name': 'TAG_support', 6: True}, + attrs={'data_type': 'BOOLEAN', 'domain': 'FACE'}) + + value = nw.new_node(Nodes.Value) + value.outputs[0].default_value = 1.0000 + + store_named_attribute_2 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': store_named_attribute_1, 'Selection': value, 'Name': 'TAG_cushion', 6: True}, + attrs={'data_type': 'BOOLEAN', 'domain': 'FACE'}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Seat Margin"], 'Y': group_input.outputs["Seat Margin"], 'Z': 1.0000}) + + transform_geometry_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute_2, 'Scale': combine_xyz_6}) + + combine_xyz_11 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Scaling footrest"], 'Y': 1.0000, 'Z': 1.1000}) + + transform_geometry_7 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_geometry_3, 'Scale': combine_xyz_11}) + + nodegroup_array_fill_line_002 = nw.new_node(nodegroup_array_fill_line().name, + input_kwargs={'Line Start': multiply_14.outputs["Vector"], 'Line End': multiply_16.outputs["Vector"], 'Instance Dimensions': multiply_17.outputs["Vector"], 'Count': reroute_10, 'Instance': transform_geometry_7}) + + separate_xyz_17 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': multiply_16.outputs["Vector"]}) + + combine_xyz_21 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': separate_xyz_17.outputs["Z"]}) + + reroute_14 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': ceil}) + + combine_xyz_20 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': reroute_14, 'Z': 1.0000}) + + transform_geometry_13 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_geometry_7, 'Scale': combine_xyz_20}) + + nodegroup_array_fill_line_002_1 = nw.new_node(nodegroup_array_fill_line().name, + input_kwargs={'Line End': combine_xyz_21, 'Count': 1, 'Instance': transform_geometry_13}) + + switch_9 = nw.new_node(Nodes.Switch, + input_kwargs={1: equal_2, 14: nodegroup_array_fill_line_002, 15: nodegroup_array_fill_line_002_1}) + + switch_3 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Footrest"], 15: switch_9.outputs[6]}) + + nodegroup_array_fill_line_002_2 = nw.new_node(nodegroup_array_fill_line().name, + input_kwargs={'Line Start': multiply_14.outputs["Vector"], 'Line End': multiply_15.outputs["Vector"], 'Instance Dimensions': reroute_6, 'Count': reroute_14, 'Instance': transform_geometry_3}) + + join_geometry_9 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [switch_3.outputs[6], nodegroup_array_fill_line_002_2]}) + + subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': join_geometry_9, 'Level': 2}) + + separate_xyz_11 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Seat Dimensions"]}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Backrest Width"], 'Z': separate_xyz_11.outputs["Z"]}) + + add_1 = nw.new_node(Nodes.VectorMath, input_kwargs={0: multiply_14.outputs["Vector"], 1: combine_xyz_7}) + + add_2 = nw.new_node(Nodes.VectorMath, input_kwargs={0: multiply_15.outputs["Vector"], 1: combine_xyz_7}) + + separate_xyz_12 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Dimensions"]}) + + subtract_6 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_12.outputs["Z"], 1: separate_xyz_11.outputs["Z"]}, + attrs={'operation': 'SUBTRACT'}) + + subtract_7 = nw.new_node(Nodes.Math, + input_kwargs={0: subtract_6, 1: group_input.outputs["Baseboard Height"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': subtract_7, 'Y': divide_3, 'Z': group_input.outputs["Backrest Width"]}) + + seat_cushion_1 = nw.new_node(nodegroup_corner_cube().name, + input_kwargs={'CenteringLoc': (0.1000, 0.5000, 1.0000), 'Dimensions': combine_xyz_8, 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}, + label='SeatCushion') + + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={'Mesh': seat_cushion_1, 'Offset Scale': 0.0300}) + + scale_elements = nw.new_node(Nodes.ScaleElements, + input_kwargs={'Geometry': extrude_mesh.outputs["Mesh"], 'Selection': extrude_mesh.outputs["Top"], 'Scale': 0.6000}) + + subdivision_surface_1 = nw.new_node(Nodes.SubdivisionSurface, input_kwargs={'Mesh': scale_elements}) + + random_value = nw.new_node(Nodes.RandomValue, attrs={'data_type': 'FLOAT_VECTOR'}) + + store_named_attribute_3 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': subdivision_surface_1, 'Name': 'UVMap', 3: random_value.outputs["Value"]}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + multiply_19 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Backrest Width"], 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + separate_xyz_13 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': group_input.outputs["Back Dimensions"]}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz_13.outputs["X"], 1: 0.1000}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_19, 1: add_3}) + + combine_xyz_9 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_4}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Backrest Angle"], 1: -1.5708}) + + combine_xyz_10 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_5}) + + transform_geometry_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_3, 'Translation': combine_xyz_9, 'Rotation': combine_xyz_10, 'Scale': combine_xyz_6}) + + nodegroup_array_fill_line_003 = nw.new_node(nodegroup_array_fill_line().name, + input_kwargs={'Line Start': add_1.outputs["Vector"], 'Line End': add_2.outputs["Vector"], 'Instance Dimensions': reroute_6, 'Count': ceil, 'Instance': transform_geometry_4}) + + join_geometry_6 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [subdivide_mesh, nodegroup_array_fill_line_003]}) + + join_geometry_7 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [join_geometry_5, realize_instances, join_geometry_6]}) + + subdivide_mesh_1 = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': join_geometry_5, 'Level': 2}) + + join_geometry_8 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [subdivide_mesh_1, realize_instances, join_geometry_6]}) + + subdivision_surface_2 = nw.new_node(Nodes.SubdivisionSurface, input_kwargs={'Mesh': join_geometry_8, 'Level': 1}) + + switch_1 = nw.new_node(Nodes.Switch, input_kwargs={1: True, 14: join_geometry_7, 15: subdivision_surface_2}) + switch = nw.new_node(Nodes.Switch, input_kwargs={ + 1: group_input.outputs['Subdivide'], + 14: join_geometry_7, + 15: subdivision_surface_2 + }) + + bounding_box = nw.new_node(nodegroup_corner_cube().name, + input_kwargs={'CenteringLoc': (0.0000, 0.5000, -1.0000), 'Dimensions': group_input.outputs["Dimensions"], 'Vertices X': 2, 'Vertices Y': 2, 'Vertices Z': 2}, + label='BoundingBox') + + reroute_7 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': bounding_box}) + + reroute_8 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': reroute_7}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': switch_1.outputs[6], 'BoundingBox': reroute_8}, + attrs={'is_active_output': True}) + + + +def sofa_parameter_distribution(dimensions=None): + + if dimensions is None: + dimensions = ( + uniform(0.95, 1.1), + clip_gaussian(1.75, 0.75, 0.9, 3), + uniform(0.69, 0.97) + ) + + return { + 'Dimensions': dimensions, + 'Arm Dimensions': ( + uniform(1, 1), + uniform(0.06, 0.15), + uniform(0.5, 0.75), + ), + 'Back Dimensions': ( + uniform(0.15, 0.25), + 0.0000, + uniform(0.5, 0.75) + ), + 'Seat Dimensions': ( + dimensions[0], + uniform(0.7, 1), + uniform(0.15, 0.3) + ), + 'Foot Dimensions': ( + uniform(0.07, 0.25), + 0.06, + 0.06 + ), + 'Baseboard Height': uniform(0.05, 0.09), + 'Backrest Width': uniform(0.1, 0.2), + 'Seat Margin': uniform(0.9700, 1), + 'Backrest Angle': uniform(-0.15, -0.5), + + 'Arm Type': np.random.choice( + [ARM_TYPE_SQUARE, ARM_TYPE_ROUND, ARM_TYPE_ANGULAR], + p=[0.4, 0.2, 0.4] + ), + 'arm_width': uniform(0.6, 0.9), + 'Arm_height': uniform(0.7,1.0), + 'arms_angle': uniform(0.0, 1.08), + 'Footrest': True if uniform() > 0.5 and dimensions[1] > 2 else False, + 'Count': 1 if uniform()>0.2 else 4, + 'Scaling footrest': uniform(1.3, 1.6), + 'Reflection':1 if uniform()>0.5 else -1, + 'leg_type': True if uniform()>0.5 else False, + 'leg_dimensions': uniform(0.4,0.9), + 'leg_z':uniform(1.1, 2.5), + 'leg_faces':uniform(4,25) + } + + +class SofaFactory(AssetFactory): + def __init__(self, factory_seed): + from infinigen.assets.clothes import blanket + super().__init__(factory_seed) + with FixedSeed(factory_seed): + self.params = sofa_parameter_distribution() + #from infinigen.assets.scatters.clothes import ClothesCover + #self.clothes_scatter = ClothesCover(factory_fn=blanket.BlanketFactory, width=log_uniform(1, 1.5), + # size=uniform(.8, 1.2)) if uniform() < .3 else NoApply() + materials = AssetList['SofaFactory']() + self.sofa_fabric = materials['sofa_fabric'].assign_material() + + def create_placeholder(self, **_): + obj = butil.spawn_vert() + butil.modify_mesh( + obj, + 'NODES', + node_group=nodegroup_sofa_geometry(), + ng_inputs={**self.params, }, + apply=True + ) + tagging.tag_system.relabel_obj(obj) + surface.add_material(obj, self.sofa_fabric) + return obj + + def create_asset(self, i, placeholder, face_size, **_): + + hipoly = butil.copy(placeholder, keep_materials=True) + + butil.modify_mesh(hipoly, 'SUBSURF', levels=1, apply=True) + + with butil.SelectObjects(hipoly): + bpy.ops.object.shade_smooth() + + return hipoly + +class ArmChairFactory(SofaFactory): + + def __init__(self, factory_seed): + super().__init__(factory_seed) + with FixedSeed(factory_seed): + dimensions = ( + uniform(0.8, 1), + uniform(0.9, 1.1), + uniform(0.69, 0.97) + ) + self.params = sofa_parameter_distribution(dimensions=dimensions) \ No newline at end of file diff --git a/infinigen/assets/shelves/__init__.py b/infinigen/assets/shelves/__init__.py new file mode 100644 index 000000000..7fea19c8b --- /dev/null +++ b/infinigen/assets/shelves/__init__.py @@ -0,0 +1,10 @@ +from .simple_desk import SimpleDeskFactory, SidetableDeskFactory +from .simple_bookcase import SimpleBookcaseFactory +from .cell_shelf import CellShelfFactory, TVStandFactory +from .triangle_shelf import TriangleShelfFactory +from .large_shelf import LargeShelfFactory +from .doors import CabinetDoorBaseFactory +from .single_cabinet import SingleCabinetFactory +from .kitchen_cabinet import KitchenCabinetFactory +from .kitchen_space import KitchenSpaceFactory, KitchenIslandFactory +from .countertop import CountertopFactory diff --git a/infinigen/assets/shelves/cabinet.py b/infinigen/assets/shelves/cabinet.py new file mode 100644 index 000000000..517816530 --- /dev/null +++ b/infinigen/assets/shelves/cabinet.py @@ -0,0 +1,1003 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t + +import bpy +from infinigen.assets.shelves.utils import nodegroup_tagged_cube, blender_rotate +from infinigen.assets.shelves.large_shelf import LargeShelfBaseFactory, LargeShelfFactory, LargeShelfIkeaFactory +from infinigen.assets.materials.shelf_shaders import get_shelf_material +from infinigen.core.util.math import FixedSeed + + +@node_utils.to_nodegroup('nodegroup_node_group', singleton=False, type='GeometryNodeTree') +def nodegroup_node_group(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': (0.0120, 0.00060, 0.0400)}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Vertices': 64, 'Radius': 0.0100, 'Depth': 0.00050}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': cylinder.outputs["Mesh"], + 'Translation': (0.0050, 0.0000, 0.0000), + 'Rotation': (1.5708, 0.0000, 0.0000) + }) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': (0.0200, 0.0006, 0.0120)}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cube_1, 'Translation': (0.0080, 0.0000, 0.0000)}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [cube, transform, transform_1]}) + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'attach_height', 0.1000), + ('NodeSocketFloat', 'door_width', 0.5000)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["door_width"]}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: 0.0181}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': subtract, 'Z': group_input.outputs["attach_height"]}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry_1, 'Translation': combine_xyz}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_2}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_knob_handle', singleton=False, type='GeometryNodeTree') +def nodegroup_knob_handle(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloatDistance', 'Radius', 0.0100), + ('NodeSocketFloat', 'thickness_1', 0.5000), ('NodeSocketFloat', 'thickness_2', 0.5000), + ('NodeSocketFloat', 'length', 0.5000), ('NodeSocketFloat', 'knob_mid_height', 0.0000), + ('NodeSocketFloat', 'edge_width', 0.5000), ('NodeSocketFloat', 'door_width', 0.5000)]) + + add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["thickness_2"], 1: group_input.outputs["thickness_1"] + }) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: group_input.outputs["length"]}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Vertices': 64, 'Radius': group_input.outputs["Radius"], 'Depth': add_1 + }) + + subtract = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["door_width"], + 1: group_input.outputs["edge_width"] + }, attrs={'operation': 'SUBTRACT'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: -0.005}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': add_2, + 'Y': multiply_1, + 'Z': group_input.outputs["knob_mid_height"] + }) + + transform_6 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': cylinder.outputs["Mesh"], + 'Translation': combine_xyz_6, + 'Rotation': (1.5708, 0.0000, 0.0000) + }) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_6}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_mid_board', singleton=False, type='GeometryNodeTree') +def nodegroup_mid_board(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'thickness', 0.5000), ('NodeSocketFloat', 'width', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: -0.0001}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_k = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: 0.5000}, attrs={'operation': 'MULTIPLY'}) + + add_k = nw.new_node(Nodes.Math, input_kwargs={0: multiply_k, 1: 0.004}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: -0.0001}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_2}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_3, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_k, 'Z': multiply_1}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_4}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_4, 'Material': kwargs['material'][0]}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_2}) + + cube_1 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_7, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5 + }) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: 1.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_k, 'Z': multiply_2}) + + transform_7 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_1, 'Translation': combine_xyz_8}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_7, 'Material': kwargs['material'][1]}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material, set_material_1]}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_1}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': realize_instances, 'mid_height': multiply}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_mid_board_001', singleton=False, type='GeometryNodeTree') +def nodegroup_mid_board_001(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'thickness', 0.5000), ('NodeSocketFloat', 'width', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: -0.0001}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"], 1: 0.0000}) + + multiply_k = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: 0.5000}, attrs={'operation': 'MULTIPLY'}) + + add_k = nw.new_node(Nodes.Math, input_kwargs={0: multiply_k, 1: 0.004}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 1.0000}, + attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: -0.0001}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_2}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_3, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_k, 'Z': multiply_1}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_4}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_4, 'Material': kwargs['material'][0]}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': set_material}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_1}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': realize_instances, 'mid_height': multiply}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_double_rampled_edge', singleton=False, type='GeometryNodeTree') +def nodegroup_double_rampled_edge(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'thickness_2', 0.5000), ('NodeSocketFloat', 'width', 0.5000), + ('NodeSocketFloat', 'thickness_1', 0.5000), ('NodeSocketFloat', 'ramp_angle', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}) + + combine_xyz_10 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_10}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': 3, 'Radius': 0.0100}) + + endpoint_selection = nw.new_node(Nodes.EndpointSelection, input_kwargs={'End Size': 0}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: 0.0000}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["ramp_angle"], 1: 0.0000}) + + tangent = nw.new_node(Nodes.Math, input_kwargs={0: add_2}, attrs={'operation': 'TANGENT'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness_2"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: tangent, 1: add_3}, attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: 2.0000, 1: multiply}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract}, attrs={'operation': 'MULTIPLY'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness_1"], 1: 0.0000}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': add_4}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': curve_circle.outputs["Curve"], + 'Selection': endpoint_selection, + 'Position': combine_xyz_7 + }) + + endpoint_selection_1 = nw.new_node(Nodes.EndpointSelection, input_kwargs={'Start Size': 0}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: add_3}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': add_5}) + + set_position_1 = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': set_position, + 'Selection': endpoint_selection_1, + 'Position': combine_xyz_8 + }) + + index = nw.new_node(Nodes.Index) + + less_than = nw.new_node(Nodes.Math, input_kwargs={0: index, 1: 1.0100}, attrs={'operation': 'LESS_THAN'}) + + greater_than = nw.new_node(Nodes.Math, input_kwargs={0: index, 1: 0.9900}, + attrs={'operation': 'GREATER_THAN'}) + + op_and = nw.new_node(Nodes.BooleanMath, input_kwargs={0: less_than, 1: greater_than}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_4, 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_9 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_5, 'Y': add_4}) + + set_position_2 = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': set_position_1, + 'Selection': op_and, + 'Position': combine_xyz_9 + }) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': curve_line, + 'Profile Curve': set_position_2, + 'Fill Caps': True + }) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_1, 'Y': add_4, 'Z': add}) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_4}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_6}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_2}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract, 'Y': add_3, 'Z': add}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_1}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: add_3}, attrs={'operation': 'MULTIPLY'}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: multiply_7}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_6}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_1, 'Translation': combine_xyz_3}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, transform_1]}) + + multiply_8 = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_11 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_8}) + + transform_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry, 'Translation': combine_xyz_11}) + + combine_xyz_12 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add}) + + curve_line_1 = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_12}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': set_position_2, 'Scale': (-1.0000, 1.0000, 1.0000)}) + + curve_to_mesh_1 = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': curve_line_1, + 'Profile Curve': transform_2, + 'Fill Caps': True + }) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [curve_to_mesh, transform_4, curve_to_mesh_1]}) + + merge_by_distance = nw.new_node(Nodes.MergeByDistance, + input_kwargs={'Geometry': join_geometry_1, 'Distance': 0.0001}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': merge_by_distance}) + + subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': realize_instances, 'Level': 4}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': subdivide_mesh}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_ramped_edge', singleton=False, type='GeometryNodeTree') +def nodegroup_ramped_edge(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'thickness_2', 0.5000), ('NodeSocketFloat', 'width', 0.5000), + ('NodeSocketFloat', 'thickness_1', 0.5000), ('NodeSocketFloat', 'ramp_angle', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}) + + combine_xyz_10 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_10}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': 3, 'Radius': 0.0100}) + + endpoint_selection = nw.new_node(Nodes.EndpointSelection, input_kwargs={'End Size': 0}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["ramp_angle"], 1: 0.0000}) + + tangent = nw.new_node(Nodes.Math, input_kwargs={0: add_2}, attrs={'operation': 'TANGENT'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness_2"], 1: 0.0000}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: tangent, 1: add_3}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: subtract}, + attrs={'operation': 'SUBTRACT'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness_1"], 1: 0.0000}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_1, 'Y': add_4}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': curve_circle.outputs["Curve"], + 'Selection': endpoint_selection, + 'Position': combine_xyz_7 + }) + + endpoint_selection_1 = nw.new_node(Nodes.EndpointSelection, input_kwargs={'Start Size': 0}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: add_3}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_1, 'Y': add_5}) + + set_position_1 = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': set_position, + 'Selection': endpoint_selection_1, + 'Position': combine_xyz_8 + }) + + index = nw.new_node(Nodes.Index) + + less_than = nw.new_node(Nodes.Math, input_kwargs={0: index, 1: 1.0100}, attrs={'operation': 'LESS_THAN'}) + + greater_than = nw.new_node(Nodes.Math, input_kwargs={0: index, 1: 0.9900}, + attrs={'operation': 'GREATER_THAN'}) + + op_and = nw.new_node(Nodes.BooleanMath, input_kwargs={0: less_than, 1: greater_than}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_9 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_2, 'Y': add_4}) + + set_position_2 = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': set_position_1, + 'Selection': op_and, + 'Position': combine_xyz_9 + }) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': curve_line, + 'Profile Curve': set_position_2, + 'Fill Caps': True + }) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_1, 'Y': add_4, 'Z': add}) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_4}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_3}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_2}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract, 'Y': add_3, 'Z': add}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_1}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1}, attrs={'operation': 'MULTIPLY'}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_3}, attrs={'operation': 'MULTIPLY'}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: multiply_5}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_4, 'Y': add_6}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_1, 'Translation': combine_xyz_3}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, transform_1]}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_11 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_6}) + + transform_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry, 'Translation': combine_xyz_11}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [curve_to_mesh, transform_4]}) + + merge_by_distance = nw.new_node(Nodes.MergeByDistance, + input_kwargs={'Geometry': join_geometry_1, 'Distance': 0.0001}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': merge_by_distance}) + + subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': realize_instances, 'Level': 4}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_7}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': subdivide_mesh, 'Translation': combine_xyz_4}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_2}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_panel_edge_frame', singleton=False, type='GeometryNodeTree') +def nodegroup_panel_edge_frame(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'vertical_edge', None), + ('NodeSocketFloat', 'door_width', 0.5000), ('NodeSocketFloat', 'door_height', 0.0000), + ('NodeSocketGeometry', 'horizontal_edge', None)]) + + multiply_add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["door_width"], 2: 0.0010}, + attrs={'operation': 'MULTIPLY_ADD'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: multiply_add, 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + transform_7 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': group_input.outputs["horizontal_edge"], + 'Translation': (0.0000, -0.0001, 0.0000), + 'Scale': (0.9999, 1.0000, 1.0000) + }) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_add, 1: 1.0000}, + attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: -0.0001}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["door_height"], 1: 0.0001}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Z': add_1}) + + transform_3 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': transform_7, + 'Translation': combine_xyz_2, + 'Rotation': (0.0000, -1.5708, 0.0000) + }) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: 0.0001}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_2}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': transform_7, + 'Translation': combine_xyz_1, + 'Rotation': (0.0000, 1.5708, 0.0000) + }) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_add}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': group_input.outputs["vertical_edge"], + 'Translation': combine_xyz + }) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Scale': (-1.0000, 1.0000, 1.0000)}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_3, transform_2, transform_1, transform]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Value': multiply, 'Geometry': join_geometry_1}, + attrs={'is_active_output': True}) + + +def geometry_door_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + door_height = nw.new_node(Nodes.Value, label='door_height') + door_height.outputs[0].default_value = kwargs['door_height'] + + door_edge_thickness_2 = nw.new_node(Nodes.Value, label='door_edge_thickness_2') + door_edge_thickness_2.outputs[0].default_value = kwargs['edge_thickness_2'] + + door_edge_width = nw.new_node(Nodes.Value, label='door_edge_width') + door_edge_width.outputs[0].default_value = kwargs['edge_width'] + + door_edge_thickness_1 = nw.new_node(Nodes.Value, label='door_edge_thickness_1') + door_edge_thickness_1.outputs[0].default_value = kwargs['edge_thickness_1'] + + door_edge_ramp_angle = nw.new_node(Nodes.Value, label='door_edge_ramp_angle') + door_edge_ramp_angle.outputs[0].default_value = kwargs['edge_ramp_angle'] + + ramped_edge = nw.new_node(nodegroup_ramped_edge().name, input_kwargs={ + 'height': door_height, + 'thickness_2': door_edge_thickness_2, + 'width': door_edge_width, + 'thickness_1': door_edge_thickness_1, + 'ramp_angle': door_edge_ramp_angle + }) + + door_width = nw.new_node(Nodes.Value, label='door_width') + door_width.outputs[0].default_value = kwargs['door_width'] + + ramped_edge_1 = nw.new_node(nodegroup_ramped_edge().name, input_kwargs={ + 'height': door_width, + 'thickness_2': door_edge_thickness_2, + 'width': door_edge_width, + 'thickness_1': door_edge_thickness_1, + 'ramp_angle': door_edge_ramp_angle + }) + + panel_edge_frame = nw.new_node(nodegroup_panel_edge_frame().name, input_kwargs={ + 'vertical_edge': ramped_edge, + 'door_width': door_width, + 'door_height': door_height, + 'horizontal_edge': ramped_edge_1 + }) + + add = nw.new_node(Nodes.Math, input_kwargs={0: panel_edge_frame.outputs["Value"], 1: 0.0001}) + + mid_board_thickness = nw.new_node(Nodes.Value, label='mid_board_thickness') + mid_board_thickness.outputs[0].default_value = kwargs['board_thickness'] + + if kwargs['has_mid_ramp']: + mid_board = nw.new_node(nodegroup_mid_board(material=kwargs['board_material']).name, input_kwargs={ + 'height': door_height, + 'thickness': mid_board_thickness, + 'width': door_width + }) + else: + mid_board = nw.new_node(nodegroup_mid_board_001(material=kwargs['board_material']).name, input_kwargs={ + 'height': door_height, + 'thickness': mid_board_thickness, + 'width': door_width + }) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': add, 'Y': -0.0001, 'Z': mid_board.outputs["mid_height"]}) + + frame = [panel_edge_frame.outputs["Geometry"]] + if kwargs['has_mid_ramp']: + double_rampled_edge = nw.new_node(nodegroup_double_rampled_edge().name, input_kwargs={ + 'height': door_width, + 'thickness_2': door_edge_thickness_2, + 'width': door_edge_width, + 'thickness_1': door_edge_thickness_1, + 'ramp_angle': door_edge_ramp_angle + }) + + transform_5 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': double_rampled_edge, + 'Translation': combine_xyz_5, + 'Rotation': (0.0000, 1.5708, 0.0000) + }) + frame.append(transform_5) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': frame}) + + set_material_2 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': join_geometry_1, 'Material': kwargs['frame_material'] + }) + + knob_raduis = nw.new_node(Nodes.Value, label='knob_raduis') + knob_raduis.outputs[0].default_value = kwargs['knob_R'] + + know_length = nw.new_node(Nodes.Value, label='know_length') + know_length.outputs[0].default_value = kwargs['knob_length'] + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: door_height}, attrs={'operation': 'MULTIPLY'}) + + knob_handle = nw.new_node(nodegroup_knob_handle().name, input_kwargs={ + 'Radius': knob_raduis, + 'thickness_1': door_edge_thickness_1, + 'thickness_2': door_edge_thickness_2, + 'length': know_length, + 'knob_mid_height': multiply, + 'edge_width': door_edge_width, + 'door_width': door_width + }) + + set_material_3 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': knob_handle, 'Material': kwargs['frame_material']}) + + attach_gadgets = [] + + for h in kwargs['attach_height']: + attach_height = nw.new_node(Nodes.Value, label='attach_height') + attach_height.outputs[0].default_value = h + + attach = nw.new_node(nodegroup_node_group().name, + input_kwargs={'attach_height': attach_height, 'door_width': door_width}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': attach, 'Material': get_shelf_material('metal')}) + attach_gadgets.append(set_material_1) + + geos = [set_material_2, set_material_3, mid_board.outputs["Geometry"]] + attach_gadgets + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': geos}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: door_width, 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry, 'Translation': combine_xyz}) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': transform}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': realize_instances_1}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': triangulate, + 'Scale': (-1.0 if kwargs['door_left_hinge'] else 1.0, 1.0000, 1.0000) + }) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_1, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_2}, + attrs={'is_active_output': True}) + + +def geometry_cabinet_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + right_door_info = nw.new_node(Nodes.ObjectInfo, input_kwargs={'Object': kwargs['door'][0]}) + left_door_info = nw.new_node(Nodes.ObjectInfo, input_kwargs={'Object': kwargs['door'][1]}) + shelf_info = nw.new_node(Nodes.ObjectInfo, input_kwargs={'Object': kwargs['shelf']}) + + doors = [] + transform_r = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': right_door_info.outputs['Geometry'], + 'Translation': kwargs['door_hinge_pos'][0], + 'Rotation': (0, 0, kwargs['door_open_angle']) + }) + doors.append(transform_r) + if len(kwargs['door_hinge_pos']) > 1: + transform_l = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': left_door_info.outputs['Geometry'], + 'Translation': kwargs['door_hinge_pos'][1], + 'Rotation': (0, 0, kwargs['door_open_angle']) + }) + doors.append(transform_l) + + attaches = [] + for pos in kwargs['attach_pos']: + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': (0.0006, 0.0200, 0.04500)}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': -0.0100}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': (0.0005, 0.0340, 0.0200)}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, cube_1]}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': join_geometry, + 'Translation': (0.0000, -0.0170, 0.0000) + }) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_1, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + transform_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_2, 'Translation': pos}) + + attaches.append(transform_3) + + join_geometry_a = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': attaches}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': join_geometry_a, 'Material': get_shelf_material('metal')}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={ + 'Geometry': [shelf_info.outputs['Geometry']] + doors + [set_material] + }) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, + attrs={'is_active_output': True}) + + +class CabinetDoorBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(CabinetDoorBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = {} + + def get_asset_params(self, i=0): + params = self.params.copy() + if params.get('door_height', None) is None: + params['door_height'] = uniform(0.7, 2.2) + if params.get('door_width', None) is None: + params['door_width'] = uniform(0.3, 0.4) + if params.get('edge_thickness_1', None) is None: + params['edge_thickness_1'] = uniform(0.01, 0.02) + if params.get('edge_width', None) is None: + params['edge_width'] = uniform(0.03, 0.05) + if params.get('edge_thickness_2', None) is None: + params['edge_thickness_2'] = uniform(0.005, 0.01) + if params.get('edge_ramp_angle', None) is None: + params['edge_ramp_angle'] = uniform(0.6, 0.8) + params['board_thickness'] = params['edge_thickness_1'] - 0.005 + if params.get('knob_R', None) is None: + params['knob_R'] = uniform(0.003, 0.006) + if params.get('knob_length', None) is None: + params['knob_length'] = uniform(0.018, 0.035) + if params.get('attach_height', None) is None: + gap = uniform(0.05, 0.15) + params['attach_height'] = [gap, params['door_height'] - gap] + if params.get('has_mid_ramp', None) is None: + params['has_mid_ramp'] = np.random.choice([True, False], p=[0.6, 0.4]) + if params.get('door_left_hinge', None) is None: + params['door_left_hinge'] = False + + if params.get('frame_material', None) is None: + params['frame_material'] = np.random.choice(['white', 'black_wood', 'wood'], p=[0.5, 0.2, 0.3]) + if params.get('board_material', None) is None: + if params['has_mid_ramp']: + lower_mat = np.random.choice([params['frame_material'], 'glass'], p=[0.7, 0.3]) + upper_mat = np.random.choice([lower_mat, 'glass'], p=[0.6, 0.4]) + params['board_material'] = [lower_mat, upper_mat] + else: + params['board_material'] = [params['frame_material']] + + params = self.get_material_func(params) + return params + + def get_material_func(self, params, randomness=True): + params['frame_material'] = get_shelf_material(params['frame_material']) + materials = [] + if not isinstance(params['board_material'], list): + params['board_material'] = [params['board_material']] + for mat in params['board_material']: + materials.append(get_shelf_material(mat)) + params['board_material'] = materials + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), + scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, geometry_door_nodes, apply=True, attributes=[], input_kwargs=obj_params) + + if params.get('ret_params', False): + return obj, obj_params + + return obj + + +class CabinetDoorIkeaFactory(CabinetDoorBaseFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(CabinetDoorIkeaFactory, self).__init__(factory_seed, coarse=coarse) + self.params = { + 'edge_thickness_1': 0.012, + 'edge_thickness_2': 0.008, + 'board_thickness': 0.006, + 'edge_width': 0.02, + 'edge_ramp_angle': 0.5, + 'knob_R': 0.004, + 'knob_length': 0.03, + 'has_mid_ramp': False, + 'attach_height': 0.08 + } + + def get_asset_params(self, i=0): + params = self.params.copy() + if params.get('door_height', None) is None: + params['door_height'] = uniform(0.7, 2.2) + if params.get('door_width', None) is None: + params['door_width'] = uniform(0.3, 0.4) + if params.get('door_left_hinge', None) is None: + params['door_left_hinge'] = False + + params['attach_height'] = [params['door_height'] - params['attach_height'], params['attach_height']] + params = self.get_material_func(params) + return params + + +class CabinetBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(CabinetBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.shelf_params = {} + self.door_params = {} + self.mat_params = {} + self.shelf_fac = LargeShelfBaseFactory(factory_seed) + self.door_fac = CabinetDoorBaseFactory(factory_seed) + + def sample_params(self): + # Update fac params + pass + + def get_material_params(self): + with FixedSeed(self.factory_seed): + params = self.mat_params.copy() + if params.get('frame_material', None) is None: + params['frame_material'] = np.random.choice(['white', 'black_wood', 'wood'], p=[0.5, 0.2, 0.3]) + return params + + def get_shelf_params(self, i=0): + params = self.shelf_params.copy() + if params.get('shelf_cell_width', None) is None: + params['shelf_cell_width'] = [ + np.random.choice([0.76, 0.36], p=[0.5, 0.5]) * np.clip(normal(1., 0.1), 0.75, 1.25)] + if params.get('shelf_cell_height', None) is None: + num_v_cells = randint(3, 7) + shelf_cell_height = [] + for i in range(num_v_cells): + shelf_cell_height.append(0.3 * np.clip(normal(1., 0.06), 0.75, 1.25)) + params['shelf_cell_height'] = shelf_cell_height + if params.get('frame_material', None) is None: + params['frame_material'] = self.mat_params['frame_material'] + + return params + + def get_door_params(self, i=0): + params = self.door_params.copy() + + # get door params + shelf_width = self.shelf_params['shelf_width'] + self.shelf_params['side_board_thickness'] * 2 + if params.get('door_width', None) is None: + if shelf_width < 0.55: + params['door_width'] = shelf_width + params['num_door'] = 1 + else: + params['door_width'] = shelf_width / 2. - 0.0005 + params['num_door'] = 2 + if params.get('door_height', None) is None: + params['door_height'] = (self.shelf_params['division_board_z_translation'][-1] - + self.shelf_params['division_board_z_translation'][0] + self.shelf_params[ + 'division_board_thickness']) + if len(self.shelf_params['division_board_z_translation']) > 5 and np.random.choice([True, False], + p=[0.5, 0.5]): + params['door_height'] = (self.shelf_params['division_board_z_translation'][3] - + self.shelf_params['division_board_z_translation'][0] + + self.shelf_params['division_board_thickness']) + if params.get('frame_material', None) is None: + params['frame_material'] = self.mat_params['frame_material'] + + return params + + def get_cabinet_params(self, i=0): + params = dict() + + shelf_width = self.shelf_params['shelf_width'] + self.shelf_params['side_board_thickness'] * 2 + if self.door_params['num_door'] == 1: + params['door_hinge_pos'] = [(self.shelf_params['shelf_depth'] / 2. + 0.0025, -shelf_width / 2., + self.shelf_params['bottom_board_height'])] + params['door_open_angle'] = 0 + params['attach_pos'] = [( + self.shelf_params['shelf_depth'] / 2., -self.shelf_params['shelf_width'] / 2., + self.shelf_params['bottom_board_height'] + z) for z in self.door_params['attach_height']] + elif self.door_params['num_door'] == 2: + params['door_hinge_pos'] = [(self.shelf_params['shelf_depth'] / 2. + 0.008, -shelf_width / 2., + self.shelf_params['bottom_board_height']), ( + self.shelf_params['shelf_depth'] / 2. + 0.008, shelf_width / 2., + self.shelf_params['bottom_board_height'])] + params['door_open_angle'] = 0 + params['attach_pos'] = [( + self.shelf_params['shelf_depth'] / 2., -self.shelf_params['shelf_width'] / 2., + self.shelf_params['bottom_board_height'] + z) for z in self.door_params['attach_height']] + [( + self.shelf_params['shelf_depth'] / 2., self.shelf_params['shelf_width'] / 2., + self.shelf_params['bottom_board_height'] + z) for z in self.door_params['attach_height']] + else: + raise NotImplementedError + + return params + + def get_cabinet_components(self, i): + # update material params + self.sample_params() + self.mat_params = self.get_material_params() + + # create shelf + shelf_params = self.get_shelf_params(i=i) + self.shelf_fac.params = shelf_params + shelf, shelf_params = self.shelf_fac.create_asset(i=i, ret_params=True) + shelf.name = 'cabinet_frame' + self.shelf_params = shelf_params + + # create doors + door_params = self.get_door_params(i=i) + self.door_fac.params = door_params + self.door_fac.params['door_left_hinge'] = False + right_door, door_obj_params = self.door_fac.create_asset(i=i, ret_params=True) + right_door.name = 'cabinet_right_door' + self.door_fac.params = door_obj_params + self.door_fac.params['door_left_hinge'] = True + left_door, _ = self.door_fac.create_asset(i=i, ret_params=True) + left_door.name = 'cabinet_left_door' + self.door_params = door_obj_params + + return shelf, right_door, left_door + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), + scale=(1, 1, 1)) + obj = bpy.context.active_object + + shelf, right_door, left_door = self.get_cabinet_components(i=i) + + # create cabinet + cabinet_params = self.get_cabinet_params(i=i) + surface.add_geomod(obj, geometry_cabinet_nodes, attributes=[], input_kwargs={ + 'door': [right_door, left_door], + 'shelf': shelf, + 'door_hinge_pos': cabinet_params['door_hinge_pos'], + 'door_open_angle': cabinet_params['door_open_angle'], + 'attach_pos': cabinet_params['attach_pos'] + }) + butil.delete([shelf, left_door, right_door]) + return obj + + +class CabinetFactory(CabinetBaseFactory): + def sample_params(self): + params = dict() + params['Dimensions'] = (uniform(0.25, 0.35), uniform(0.3, 0.7), uniform(0.9, 1.8)) + + params['bottom_board_height'] = 0.083 + params['shelf_depth'] = params['Dimensions'][0] - 0.01 + num_h = int((params['Dimensions'][2] - 0.083) / 0.3) + params['shelf_cell_height'] = [(params['Dimensions'][2] - 0.083) / num_h for _ in range(num_h)] + params['shelf_cell_width'] = [params['Dimensions'][1]] + self.shelf_params = self.shelf_fac.sample_params() + diff --git a/infinigen/assets/shelves/cell_shelf.py b/infinigen/assets/shelves/cell_shelf.py new file mode 100644 index 000000000..781e36496 --- /dev/null +++ b/infinigen/assets/shelves/cell_shelf.py @@ -0,0 +1,900 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint +import numpy as np +import bpy + +from infinigen.assets.materials import metal +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory + +from infinigen.core.util import blender as butil, math as mu +from infinigen.core import tagging, tags as t + +from infinigen.assets.shelves.utils import nodegroup_tagged_cube +from infinigen.assets.materials.shelf_shaders import ( + shader_shelves_white, shader_shelves_white_sampler, + shader_shelves_black_wood, shader_shelves_black_wood_sampler, + shader_shelves_wood, shader_shelves_wood_sampler, + shader_shelves_white_metallic, shader_shelves_white_metallic_sampler, + shader_shelves_black_metallic, shader_shelves_black_metallic_sampler) + +from infinigen.assets.utils.object import new_bbox +from infinigen.core.util.math import FixedSeed + +@node_utils.to_nodegroup('nodegroup_screw_head', singleton=False, type='GeometryNodeTree') +def nodegroup_screw_head(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + cylinder = nw.new_node('GeometryNodeMeshCylinder', input_kwargs={'Radius': 0.0050, 'Depth': 0.0010}) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Z', 0.5000), + ('NodeSocketFloat', 'leg', 0.5000), + ('NodeSocketFloat', 'X', 0.5000), + ('NodeSocketFloat', 'external', 0.5000), + ('NodeSocketFloat', 'depth', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["external"], 1: 0.0000}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["X"], 1: add}, + attrs={'operation': 'SUBTRACT'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: subtract}, attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Z"], 1: group_input.outputs["leg"]}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: 2.0000}, attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: multiply_2}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': multiply_1, 'Z': add_2}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz}) + + subtract_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["depth"], 1: multiply_1}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': subtract_1, 'Z': add_2}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz_1}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': subtract_1, 'Z': add_2}) + + transform_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz_2}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': multiply_1, 'Z': add_2}) + + transform_5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz_3}) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_2, transform_3, transform_4, transform_5]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_3}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_base_frame', singleton=False, type='GeometryNodeTree') +def nodegroup_base_frame(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'leg_height', 0.5000), + ('NodeSocketFloat', 'leg_size', 0.5000), + ('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'bottom_x', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["leg_size"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["leg_height"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add, 'Z': add_1}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["bottom_x"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add_2}, attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract, 'Y': multiply_1, 'Z': multiply_2}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_1}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': multiply_1, 'Z': multiply_2}) + + transform_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_2}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: 0.0000}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: 0.0000}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract, 'Y': subtract_1, 'Z': multiply_2}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_3}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': subtract_1, 'Z': multiply_2}) + + transform_5 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_4}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: 2.0000}, attrs={'operation': 'MULTIPLY'}) + + subtract_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: multiply_4}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_2, 'Y': add, 'Z': add}) + + cube_1 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_5, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + subtract_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_1, 'Z': subtract_3}) + + transform_6 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_1, 'Translation': combine_xyz_6}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: 0.0000}) + + subtract_4 = nw.new_node(Nodes.Math, input_kwargs={0: add_5, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': subtract_4, 'Z': subtract_3}) + + transform_7 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_1, 'Translation': combine_xyz_7}) + + subtract_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: multiply_4}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': subtract_5, 'Z': add}) + + cube_2 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_8, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + subtract_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: add}, attrs={'operation': 'SUBTRACT'}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_6}, attrs={'operation': 'MULTIPLY'}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_5}, attrs={'operation': 'MULTIPLY'}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_6, 1: add}) + + subtract_7 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_9 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_5, 'Y': add_6, 'Z': subtract_7}) + + transform_8 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_2, 'Translation': combine_xyz_9}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_5, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_10 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_7, 'Y': add_6, 'Z': subtract_7}) + + transform_9 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_2, 'Translation': combine_xyz_10}) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={ + 'Geometry': [transform_2, transform_3, transform_4, transform_5, transform_6, + transform_7, transform_8, transform_9]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_3}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_back_board', singleton=False, type='GeometryNodeTree') +def nodegroup_back_board(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'X', 0.0000), + ('NodeSocketFloat', 'Z', 0.5000), + ('NodeSocketFloat', 'leg', 0.5000), + ('NodeSocketFloat', 'external', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Z"], 1: 0.0000}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["X"], 'Y': 0.01, 'Z': add}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_4, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: group_input.outputs["leg"]}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: group_input.outputs["external"]}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add_2}) + + transform_6 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_5}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_6}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_attach_gadget', singleton=False, type='GeometryNodeTree') +def nodegroup_attach_gadget(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'z', 0.5000), + ('NodeSocketFloat', 'base_leg', 0.5000), + ('NodeSocketFloat', 'x', 0.5000), + ('NodeSocketFloat', 'thickness', 0.5000), + ('NodeSocketFloat', 'size', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["size"], 1: 0.0000}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': 0.0010, 'Z': add}) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_4}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["x"]}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: multiply, 1: group_input.outputs["thickness"]}, + attrs={'operation': 'SUBTRACT'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["base_leg"], 1: group_input.outputs["z"]}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_1 , 1: group_input.outputs["thickness"]}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: -0.02}) + + subtract_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_2, 'Z': subtract_2}) + + transform_6 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_5}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_1, 'Z': subtract_2}) + + transform_7 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_6}) + + join_geometry_5 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_6, transform_7]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_5}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_h_division_placement', singleton=False, type='GeometryNodeTree') +def nodegroup_h_division_placement(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'cell_size', 0.5000), + ('NodeSocketFloat', 'leg_height', 0.5000), + ('NodeSocketFloat', 'division_board_thickness', 0.5000), + ('NodeSocketFloat', 'external_board_thickness', 0.5000), + ('NodeSocketFloat', 'index', 0.5000)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"]}, attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["index"], 1: 0.0000}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: group_input.outputs["cell_size"]}, + attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: -1.0000}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["external_board_thickness"], 1: 0.0000}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: add_2}, attrs={'operation': 'MULTIPLY'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: multiply_2}) + + add_4 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["division_board_thickness"], + 1: group_input.outputs["leg_height"]}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_2}, attrs={'operation': 'MULTIPLY'}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: multiply_3}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: add_5}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply, 'Z': add_6}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Vector': combine_xyz}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_h_division_board', singleton=False, type='GeometryNodeTree') +def nodegroup_h_division_board(nw: NodeWrangler, tag_support=False): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'cell_size', 0.5000), + ('NodeSocketFloat', 'horizontal_cell_num', 0.5000), + ('NodeSocketFloat', 'division_board_thickness', 0.5000), + ('NodeSocketFloat', 'depth', 0.0000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["horizontal_cell_num"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: group_input.outputs["cell_size"]}, + attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: -1.0000}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: add_1, 1: group_input.outputs["division_board_thickness"]}, + attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: multiply_1}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["division_board_thickness"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': add_2, 'Y': group_input.outputs["depth"], 'Z': add_3}) + if tag_support: + cube = nw.new_node(nodegroup_tagged_cube().name, input_kwargs={'Size': combine_xyz}) + else: + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': cube}, attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_v_division_board_placement', singleton=False, type='GeometryNodeTree') +def nodegroup_v_division_board_placement(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'base_leg', 0.5000), + ('NodeSocketFloat', 'external_thickness', 0.5000), + ('NodeSocketFloat', 'side_z', 0.5000), + ('NodeSocketFloat', 'index', 0.5000), + ('NodeSocketFloat', 'h_cell_num', 0.5000), + ('NodeSocketFloat', 'division_thickness', 0.5000), + ('NodeSocketFloat', 'cell_size', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["h_cell_num"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: -1.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={1: add_1}, attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["index"], 1: 0.0000}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: add_2}, attrs={'operation': 'SUBTRACT'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: subtract}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: add_3, 1: group_input.outputs["division_thickness"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: add_2}, attrs={'operation': 'SUBTRACT'}) + + multiply_3 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["cell_size"], 1: subtract_1}, + attrs={'operation': 'MULTIPLY'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: multiply_3}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"]}, + attrs={'operation': 'MULTIPLY'}) + + add_5 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["base_leg"], 1: group_input.outputs["external_thickness"]}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["side_z"]}, + attrs={'operation': 'MULTIPLY'}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_5, 1: multiply_5}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_4, 'Y': multiply_4, 'Z': add_6}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Vector': combine_xyz_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_v_division_board', singleton=False, type='GeometryNodeTree') +def nodegroup_v_division_board(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'division_board_thickness', 0.0000), + ('NodeSocketFloat', 'depth', 0.0000), + ('NodeSocketFloat', 'cell_size', 0.5000), + ('NodeSocketFloat', 'vertical_cell_num', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["vertical_cell_num"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["cell_size"], 1: add}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: 1.0000}, attrs={'operation': 'SUBTRACT'}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: subtract, 1: group_input.outputs["division_board_thickness"]}, + attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: multiply_1}) + + add_200 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: -0.001}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["division_board_thickness"], + 'Y': add_200, 'Z': add_1}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': cube, 'Value': add_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_top_bottom_board', singleton=False, type='GeometryNodeTree') +def nodegroup_top_bottom_board(nw: NodeWrangler, tag_support=False): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'base_leg_height', 0.5000), + ('NodeSocketFloat', 'horizontal_cell_num', 0.5000), + ('NodeSocketFloat', 'vertical_cell_num', 0.5000), + ('NodeSocketFloat', 'cell_size', 0.5000), + ('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'division_board_thickness', 0.5000), + ('NodeSocketFloat', 'external_board_thickness', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["external_board_thickness"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: 2.0000}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["division_board_thickness"], 1: 0.0000}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["horizontal_cell_num"], 1: 0.0000}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: -1.0000}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: add_3}, attrs={'operation': 'MULTIPLY'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: multiply_1}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["cell_size"], 1: 0.0000}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_5, 1: add_2}, attrs={'operation': 'MULTIPLY'}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: multiply_2}) + + add_7 = nw.new_node(Nodes.Math, input_kwargs={0: add_6, 1: 0.0020}) + + add_8 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: 0.0000}) + + add_9 = nw.new_node(Nodes.Math, input_kwargs={0: add_8, 1: 0.0000}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_7, 'Y': add_9, 'Z': add}) + + if tag_support: + cube_1 = nw.new_node(nodegroup_tagged_cube().name, input_kwargs={'Size': combine_xyz_3}) + else: + cube_1 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_3, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_8}, attrs={'operation': 'MULTIPLY'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + add_10 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_4, 1: group_input.outputs["base_leg_height"]}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_3, 'Z': add_10}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_1, 'Translation': combine_xyz}) + + add_11 = nw.new_node(Nodes.Math, input_kwargs={0: add_10, 1: add}) + + add_12 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["vertical_cell_num"], 1: 0.0000}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_12, 1: add_5}, attrs={'operation': 'MULTIPLY'}) + + add_13 = nw.new_node(Nodes.Math, input_kwargs={0: add_11, 1: multiply_5}) + + add_14 = nw.new_node(Nodes.Math, input_kwargs={0: add_12, 1: -1.0000}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: add_14}, attrs={'operation': 'MULTIPLY'}) + + add_15 = nw.new_node(Nodes.Math, input_kwargs={0: add_13, 1: multiply_6}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_3, 'Z': add_15}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_1, 'Translation': combine_xyz_1}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_2, transform]}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': join_geometry_1, 'x': add_7}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_side_board', singleton=False, type='GeometryNodeTree') +def nodegroup_side_board(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'base_leg_height', 0.5000), + ('NodeSocketFloat', 'horizontal_cell_num', 0.5000), + ('NodeSocketFloat', 'vertical_cell_num', 0.5000), + ('NodeSocketFloat', 'cell_size', 0.5000), + ('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'division_thickness', 0.5000), + ('NodeSocketFloat', 'external_thickness', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["external_thickness"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: 0.0000}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["vertical_cell_num"], 1: 0.0000}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: 1.0000}, attrs={'operation': 'SUBTRACT'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["division_thickness"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: add_3}, attrs={'operation': 'MULTIPLY'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["cell_size"], 1: 0.0000}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: add_4}, attrs={'operation': 'MULTIPLY'}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: multiply_1}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_5}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: add_4, 1: group_input.outputs["horizontal_cell_num"]}, + attrs={'operation': 'MULTIPLY'}) + + subtract_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["horizontal_cell_num"], 1: 1.0000}, + attrs={'operation': 'SUBTRACT'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: subtract_1}, attrs={'operation': 'MULTIPLY'}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: multiply_3}) + + add_7 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: add_6}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={1: add_7}, attrs={'operation': 'MULTIPLY'}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_5}, attrs={'operation': 'MULTIPLY'}) + + add_8 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_6, 1: group_input.outputs["base_leg_height"]}) + + add_9 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["external_thickness"], 1: add_8}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_4, 'Y': multiply_5, 'Z': add_9}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_1}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_4, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_7, 'Y': multiply_5, 'Z': add_9}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_2}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, transform_1]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, + attrs={'is_active_output': True}) + + +def geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + base_leg_height = nw.new_node(Nodes.Value, label='base_leg_height') + base_leg_height.outputs[0].default_value = kwargs['base_leg_height'] + + horizontal_cell_num = nw.new_node(Nodes.Integer, label='horizontal_cell_num') + horizontal_cell_num.integer = kwargs['horizontal_cell_num'] + + vertical_cell_num = nw.new_node(Nodes.Integer, label='vertical_cell_num') + vertical_cell_num.integer = kwargs['vertical_cell_num'] + + cell_size = nw.new_node(Nodes.Value, label='cell_size') + cell_size.outputs[0].default_value = kwargs['cell_size'] + + depth = nw.new_node(Nodes.Value, label='depth') + depth.outputs[0].default_value = kwargs['depth'] + + division_board_thickness = nw.new_node(Nodes.Value, label='division_board_thickness') + division_board_thickness.outputs[0].default_value = kwargs['division_board_thickness'] + + external_board_thickness = nw.new_node(Nodes.Value, label='external_board_thickness') + external_board_thickness.outputs[0].default_value = kwargs['external_board_thickness'] + + sideboard = nw.new_node(nodegroup_side_board().name, + input_kwargs={'base_leg_height': base_leg_height, + 'horizontal_cell_num': horizontal_cell_num, + 'vertical_cell_num': vertical_cell_num, 'cell_size': cell_size, + 'depth': depth, 'division_thickness': division_board_thickness, + 'external_thickness': external_board_thickness}) + + topbottomboard = nw.new_node(nodegroup_top_bottom_board(tag_support=kwargs.get('tag_support', False)).name, + input_kwargs={'base_leg_height': base_leg_height, + 'horizontal_cell_num': horizontal_cell_num, + 'vertical_cell_num': vertical_cell_num, 'cell_size': cell_size, + 'depth': depth, 'division_board_thickness': division_board_thickness, + 'external_board_thickness': external_board_thickness}) + + vdivisionboard = nw.new_node(nodegroup_v_division_board().name, + input_kwargs={'division_board_thickness': division_board_thickness, 'depth': depth, + 'cell_size': cell_size, 'vertical_cell_num': vertical_cell_num}) + + all_components = [sideboard, topbottomboard.outputs["Geometry"]] + + v_division_boards = [] + for i in range(1, kwargs['horizontal_cell_num']): + v_division_index = nw.new_node(Nodes.Integer, label='VDivisionIndex') + v_division_index.integer = i + + vdivisionboardplacement = nw.new_node(nodegroup_v_division_board_placement().name, + input_kwargs={'depth': depth, 'base_leg': base_leg_height, + 'external_thickness': external_board_thickness, + 'side_z': vdivisionboard.outputs["Value"], + 'index': v_division_index, 'h_cell_num': horizontal_cell_num, + 'division_thickness': division_board_thickness, + 'cell_size': cell_size}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': vdivisionboard.outputs["Mesh"], + 'Translation': vdivisionboardplacement}) + v_division_boards.append(transform_1) + + if len(v_division_boards) > 0: + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': v_division_boards}) + all_components.append(join_geometry_1) + + hdivisionboard = nw.new_node(nodegroup_h_division_board(tag_support=kwargs.get('tag_support', False)).name, + input_kwargs={'cell_size': cell_size, 'horizontal_cell_num': horizontal_cell_num, + 'division_board_thickness': division_board_thickness, 'depth': depth}) + + h_division_boards = [] + for j in range(1, kwargs['vertical_cell_num']): + h_division_index = nw.new_node(Nodes.Integer, label='HDivisionIndex') + h_division_index.integer = j + + hdivisionplacement = nw.new_node(nodegroup_h_division_placement().name, + input_kwargs={'depth': depth, 'cell_size': cell_size, + 'leg_height': base_leg_height, + 'division_board_thickness': external_board_thickness, + 'external_board_thickness': division_board_thickness, + 'index': h_division_index}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': hdivisionboard, 'Translation': hdivisionplacement}) + h_division_boards.append(transform) + + if len(h_division_boards) > 0: + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': h_division_boards}) + all_components.append(join_geometry) + + if kwargs['has_backboard']: + backboard = nw.new_node(nodegroup_back_board().name, + input_kwargs={'X': topbottomboard.outputs["x"], 'Z': vdivisionboard.outputs["Value"], + 'leg': base_leg_height, 'external': external_board_thickness}) + all_components.append(backboard) + else: + attach_square_size = nw.new_node(Nodes.Value, label='attach_square_size') + attach_square_size.outputs[0].default_value = kwargs['attachment_size'] + + attachgadget = nw.new_node(nodegroup_attach_gadget().name, + input_kwargs={'z': vdivisionboard.outputs["Value"], 'base_leg': base_leg_height, + 'x': topbottomboard.outputs["x"], + 'thickness': external_board_thickness, + 'size': attach_square_size}) + all_components.append(attachgadget) + + join_geometry_4 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': all_components}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_4}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': realize_instances, + 'Material': surface.shaderfunc_to_material(kwargs['wood_material'])}) + + base_leg_size = nw.new_node(Nodes.Value, label='base_leg_size') + base_leg_size.outputs[0].default_value = kwargs['base_leg_size'] + + merge_components = [set_material_1] + if kwargs['has_base_frame']: + baseframe = nw.new_node(nodegroup_base_frame().name, + input_kwargs={'leg_height': base_leg_height, 'leg_size': base_leg_size, 'depth': depth, + 'bottom_x': topbottomboard.outputs["x"]}) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': baseframe}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': realize_instances_1, + 'Material': surface.shaderfunc_to_material(kwargs['base_material'])}) + merge_components.append(set_material) + + screwhead = nw.new_node(nodegroup_screw_head().name, + input_kwargs={'Z': vdivisionboard.outputs["Value"], 'leg': base_leg_height, + 'X': topbottomboard.outputs["x"], 'external': external_board_thickness, + 'depth': depth}) + + realize_instances_2 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': screwhead}) + + set_material_2 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': realize_instances_2, + 'Material': surface.shaderfunc_to_material(metal.get_shader())}) + merge_components.append(set_material_2) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': merge_components}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': join_geometry_2}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': triangulate, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, + attrs={'is_active_output': True}) + + +class CellShelfBaseFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(CellShelfBaseFactory, self).__init__(factory_seed, coarse=coarse) + with FixedSeed(factory_seed): + self.params = self.sample_params() + self.params = self.get_asset_params(self.params) + + def get_asset_params(self, params): + + if params is None: + params = {} + + if params.get('depth', None) is None: + params['depth'] = np.clip(normal(0.39, 0.05), 0.29, 0.49) + if params.get('cell_size', None) is None: + params['cell_size'] = np.clip(normal(0.335, 0.03), 0.26, 0.40) + if params.get('vertical_cell_num', None) is None: + params['vertical_cell_num'] = randint(1, 7) + if params.get('horizontal_cell_num', None) is None: + params['horizontal_cell_num'] = randint(1, 7) + if params.get('division_board_thickness', None) is None: + params['division_board_thickness'] = np.clip(normal(0.015, 0.005), 0.008, 0.022) + if params.get('external_board_thickness', None) is None: + params['external_board_thickness'] = np.clip(normal(0.04, 0.005), 0.028, 0.052) + if params.get('has_backboard', None) is None: + params['has_backboard'] = False + if params.get('has_base_frame', None) is None: + params['has_base_frame'] = np.random.choice([True, False], p=[0.4, 0.6]) + if params['has_base_frame']: + if params.get('base_leg_height', None) is None: + params['base_leg_height'] = np.clip(normal(0.174, 0.03), 0.1, 0.25) + if params.get('base_leg_size', None) is None: + params['base_leg_size'] = np.clip(normal(0.035, 0.007), 0.02, 0.05) + if params.get('base_material', None) is None: + params['base_material'] = np.random.choice(['black', 'white'], p=[0.4, 0.6]) + else: + params['base_leg_height'] = 0.0 + params['base_leg_size'] = 0.0 + params['base_material'] = 'white' + if params.get('attachment_size', None) is None: + params['attachment_size'] = np.clip(normal(0.05, 0.02), 0.02, 0.1) + if params.get('wood_material', None) is None: + params['wood_material'] = np.random.choice(['black_wood', 'white', 'wood'], p=[0.3, 0.2, 0.5]) + params['tag_support'] = True + params = self.get_material_func(params, randomness=True) + return params + + def get_material_func(self, params, randomness=True): + if params['wood_material'] == 'white': + if randomness: + params['wood_material'] = lambda x: shader_shelves_white(x, **shader_shelves_white_sampler()) + else: + params['wood_material'] = shader_shelves_white + elif params['wood_material'] == 'black_wood': + if randomness: + params['wood_material'] = lambda x: shader_shelves_black_wood(x, **shader_shelves_black_wood_sampler()) + else: + params['wood_material'] = shader_shelves_black_wood + elif params['wood_material'] == 'wood': + if randomness: + params['wood_material'] = lambda x: shader_shelves_wood(x, **shader_shelves_wood_sampler()) + else: + params['wood_material'] = shader_shelves_wood + else: + raise NotImplementedError + + if params['base_material'] == 'white': + if randomness: + params['base_material'] = lambda x: shader_shelves_white_metallic(x, **shader_shelves_white_metallic_sampler()) + else: + params['base_material'] = shader_shelves_white_metallic + elif params['base_material'] == 'black': + if randomness: + params['base_material'] = lambda x: shader_shelves_black_metallic(x, **shader_shelves_black_metallic_sampler()) + else: + params['base_material'] = shader_shelves_black_metallic + else: + raise NotImplementedError + + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.params + surface.add_geomod(obj, geometry_nodes, attributes=[], input_kwargs=obj_params, apply=True) + tagging.tag_system.relabel_obj(obj) + + return obj + + +class CellShelfFactory(CellShelfBaseFactory): + + def sample_params(self): + params = dict() + params['Dimensions'] = (uniform(0.3, 0.45), + uniform(2 * 0.35, 6 * 0.35), + uniform(1 * 0.35, 6 * 0.35)) + h_cell_num = int(params['Dimensions'][1] / 0.35) + params['cell_size'] = params['Dimensions'][1] / h_cell_num + params['horizontal_cell_num'] = h_cell_num + params['vertical_cell_num'] = max(int(params['Dimensions'][2] / params['cell_size']), 1) + params['depth'] = params['Dimensions'][0] + params['has_base_frame'] = False + params['Dimensions'] = list(params['Dimensions']) + params['Dimensions'][2] = params['vertical_cell_num'] * params['cell_size'] + return params + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + x,y,z = self.params['Dimensions'][0], self.params['Dimensions'][1], self.params['Dimensions'][2] + return new_bbox(0, x, -y/2 * 1.1, y/2 * 1.1, 0, z + (self.params['vertical_cell_num'] - 1) * self.params['division_board_thickness'] + 2 * self.params['external_board_thickness'] ) + +class TVStandFactory(CellShelfFactory): + + def sample_params(self): # TODO HACK copied code just following the pattern to get this working + params = dict() + params['Dimensions'] = ( + uniform(0.3, 0.45), + uniform(2 * 0.35, 6 * 0.35), + uniform(0.3, 0.5) + ) + h_cell_num = int(params['Dimensions'][1] / 0.35) + params['cell_size'] = params['Dimensions'][1] / h_cell_num + params['horizontal_cell_num'] = h_cell_num + params['vertical_cell_num'] = max(int(params['Dimensions'][2] / params['cell_size']), 1) + params['depth'] = params['Dimensions'][0] + params['has_base_frame'] = False + params['Dimensions'] = list(params['Dimensions']) + params['Dimensions'][2] = params['vertical_cell_num'] * params['cell_size'] + return params \ No newline at end of file diff --git a/infinigen/assets/shelves/countertop.py b/infinigen/assets/shelves/countertop.py new file mode 100644 index 000000000..087503dce --- /dev/null +++ b/infinigen/assets/shelves/countertop.py @@ -0,0 +1,154 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +import shapely +from numpy.random import uniform + +from infinigen.assets.materials import marble, ceramic +from infinigen.assets.materials.woods import wood_tile +from infinigen.assets.utils.decorate import read_normal, read_center, select_faces +from infinigen.assets.utils.mesh import separate_selected, snap_mesh +from infinigen.assets.utils.object import join_objects +from infinigen.assets.utils.shapes import obj2polygon, safe_polygon2obj, buffer, dissolve_limited +from infinigen.core.placement.factory import AssetFactory, make_asset_collection +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.random import random_general as rg + +from infinigen.core.util import blender as butil + + +class CountertopFactory(AssetFactory): + surfaces = 'weighted_choice', (5, marble), (2, ceramic), (2, wood_tile) + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + self.surface = rg(self.surfaces) + self.thickness = uniform(.02, .06) + self.extrusion = 0 if uniform() < .4 else uniform(.02, .03) + self.h_snap = .5 + self.v_snap = .5 + self.v_merge = .1 + self.z_range = .5, 1.5 + self.surface = rg(self.surfaces) + + @staticmethod + def generate_shelves(): + from .kitchen_cabinet import KitchenCabinetFactory + from .simple_desk import SimpleDeskFactory + shelves = make_asset_collection( + [ + KitchenCabinetFactory(np.random.randint(1e7)), + SimpleDeskFactory(np.random.randint(1e7))], 10 + ) + for s in shelves.objects: + s.location = *uniform(-1, 1, 2), uniform(0, .5) + s.rotation_euler[-1] = np.pi / 2 * np.random.randint(4) + return shelves + + def create_asset(self, shelves=None, **params) -> bpy.types.Object: + if shelves is None: + shelves_generated = True + shelves = self.generate_shelves() + else: + shelves_generated = False + geoms, zs = [], [] + for s in shelves.objects: + t = deep_clone_obj(s) + z = read_center(t)[:, -1] + max_z = np.max(z[(self.z_range[0] < z) & (z < self.z_range[1])]) + selection = (read_normal(t)[:, -1] > .5) & (z - 1e-2 < max_z) & (max_z < z + 1e-2) + select_faces(t, selection) + r = separate_selected(t, True) + r.location = s.location + r.rotation_euler = s.rotation_euler + butil.apply_transform(r, True) + p = self.rebuffer(obj2polygon(r), self.h_snap) + q = buffer(p, self.extrusion) + geoms.append(q) + zs.append(max_z + s.location[-1]) + butil.delete([r, t]) + indices = np.argsort(zs) + geoms_ = [geoms[i] for i in indices] + zs_ = [zs[i] for i in indices] + geoms, zs = [], [] + for i in range(len(indices)): + if i == 0: + geoms.append(geoms_[i]) + zs.append(zs_[i]) + elif zs_[i] < zs[-1] + self.v_merge: + geoms[-1] = self.rebuffer(geoms[-1].union(geoms_[i]), self.h_snap) + else: + geoms.append(geoms_[i]) + zs.append(zs_[i]) + groups = [] + for i in range(len(geoms)): + for j in range(i): + if geoms[i].distance(geoms[j]) <= self.h_snap and zs[i] - zs[j] < self.v_snap: + group = next(g for g in groups if j in g) + group.add(i) + break + else: + groups.append({i}) + objs = [] + for group in groups: + n = len(group) + geoms_ = [geoms[i] for i in group] + zs_ = [zs[i] for i in group] + geom_unions = [self.rebuffer(shapely.union_all(geoms_[i:]), self.h_snap / 2) for i in + range(n)] + geom_unions.append(shapely.Point()) + shapes = [self.rebuffer(geom_unions[i].difference(geom_unions[i + 1]), -1e-4) for i in range(n)] + for s, z in zip(shapes, zs_): + if s.area > 0: + o = safe_polygon2obj(self.rebuffer(s, -1e-4).buffer(0)) + if o is not None: + o.location[-1] = z + butil.apply_transform(o, True) + objs.append(o) + ss = [] + for i in range(n - 1, -1, -1): + for j in range(i - 1, -1, -1): + s = buffer(shapes[i], 1e-4).intersection(buffer(shapes[j], 1e-4)) + ss.append(s) + for c in ss[:-1]: + s = s.difference(buffer(c, 1e-4)) + if s.area == 0: + continue + o = safe_polygon2obj(s) + if o is None: + continue + butil.modify_mesh(o, 'WELD', merge_threshold=5e-4) + o.location[-1] = zs_[i] + with butil.ViewportMode(o, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={'value': (0, 0, zs_[j] - zs_[i])} + ) + objs.append(o) + obj = join_objects(objs) + snap_mesh(obj, 2e-2) + dissolve_limited(obj) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.normals_make_consistent(inside=False) + butil.modify_mesh( + obj, 'SOLIDIFY', thickness=self.thickness, use_even_offset=True, offset=1, use_quality_normals=True + ) + + if shelves_generated: + for s in shelves.objects: + s.parent = obj + return objs[0] + + @staticmethod + def rebuffer(shape, distance): + return shape.buffer(distance, join_style='mitre', cap_style='flat').buffer( + -distance, join_style='mitre', cap_style='flat' + ) + + def finalize_assets(self, assets): + self.surface.apply(assets) diff --git a/infinigen/assets/shelves/doors.py b/infinigen/assets/shelves/doors.py new file mode 100644 index 000000000..78dc111c6 --- /dev/null +++ b/infinigen/assets/shelves/doors.py @@ -0,0 +1,739 @@ +# 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: Lingjie Mei + +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t +import bpy + +from infinigen.assets.materials.shelf_shaders import ( + shader_shelves_white, shader_shelves_white_sampler, + shader_shelves_black_wood, shader_shelves_black_wood_sampler, + shader_shelves_wood, shader_shelves_wood_sampler, + shader_glass) + +@node_utils.to_nodegroup('nodegroup_node_group', singleton=False, type='GeometryNodeTree') +def nodegroup_node_group(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': (0.0120, 0.00060, 0.0400)}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', input_kwargs={'Vertices': 64, 'Radius': 0.0100, 'Depth': 0.00050}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': (0.0050, 0.0000, 0.0000), + 'Rotation': (1.5708, 0.0000, 0.0000)}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': (0.0200, 0.0006, 0.0120)}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cube_1, 'Translation': (0.0080, 0.0000, 0.0000)}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [cube, transform, transform_1]}) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'attach_height', 0.1000), + ('NodeSocketFloat', 'door_width', 0.5000)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["door_width"]}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: 0.0181}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract, 'Z': group_input.outputs["attach_height"]}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry_1, 'Translation': combine_xyz}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_2}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_knob_handle', singleton=False, type='GeometryNodeTree') +def nodegroup_knob_handle(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'Radius', 0.0100), + ('NodeSocketFloat', 'thickness_1', 0.5000), + ('NodeSocketFloat', 'thickness_2', 0.5000), + ('NodeSocketFloat', 'length', 0.5000), + ('NodeSocketFloat', 'knob_mid_height', 0.0000), + ('NodeSocketFloat', 'edge_width', 0.5000), + ('NodeSocketFloat', 'door_width', 0.5000)]) + + add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["thickness_2"], 1: group_input.outputs["thickness_1"]}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: group_input.outputs["length"]}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Vertices': 64, 'Radius': group_input.outputs["Radius"], 'Depth': add_1}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["door_width"], 1: group_input.outputs["edge_width"]}, + attrs={'operation': 'SUBTRACT'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: -0.005}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': add_2, 'Y': multiply_1, 'Z': group_input.outputs["knob_mid_height"]}) + + transform_6 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz_6, + 'Rotation': (1.5708, 0.0000, 0.0000)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_6}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_mid_board', singleton=False, type='GeometryNodeTree') +def nodegroup_mid_board(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'thickness', 0.5000), + ('NodeSocketFloat', 'width', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: -0.0001}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"]}, attrs={'operation': 'MULTIPLY'}) + + multiply_k = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: 0.5000}, attrs={'operation': 'MULTIPLY'}) + + add_k = nw.new_node(Nodes.Math, input_kwargs={0: multiply_k, 1: 0.004}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: -0.0001}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_2}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_3, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_k, 'Z': multiply_1}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_4}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_4, + 'Material': surface.shaderfunc_to_material(kwargs['material'][0])}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_2}) + + cube_1 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_7, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: 1.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_k, 'Z': multiply_2}) + + transform_7 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_1, 'Translation': combine_xyz_8}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_7, + 'Material': surface.shaderfunc_to_material(kwargs['material'][1])}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material, set_material_1]}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_1}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': realize_instances, 'mid_height': multiply}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_mid_board_001', singleton=False, type='GeometryNodeTree') +def nodegroup_mid_board_001(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'thickness', 0.5000), + ('NodeSocketFloat', 'width', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: -0.0001}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"], 1: 0.0000}) + + multiply_k = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: 0.5000}, attrs={'operation': 'MULTIPLY'}) + + add_k = nw.new_node(Nodes.Math, input_kwargs={0: multiply_k, 1: 0.004}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 1.0000}, + attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: -0.0001}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_2}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_3, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_k, 'Z': multiply_1}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_4}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': transform_4, + 'Material': surface.shaderfunc_to_material(kwargs['material'][0])}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': set_material}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_1}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': realize_instances, 'mid_height': multiply}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_double_rampled_edge', singleton=False, type='GeometryNodeTree') +def nodegroup_double_rampled_edge(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'thickness_2', 0.5000), + ('NodeSocketFloat', 'width', 0.5000), + ('NodeSocketFloat', 'thickness_1', 0.5000), + ('NodeSocketFloat', 'ramp_angle', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}) + + combine_xyz_10 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_10}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': 3, 'Radius': 0.0100}) + + endpoint_selection = nw.new_node(Nodes.EndpointSelection, input_kwargs={'End Size': 0}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: 0.0000}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["ramp_angle"], 1: 0.0000}) + + tangent = nw.new_node(Nodes.Math, input_kwargs={0: add_2}, attrs={'operation': 'TANGENT'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness_2"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: tangent, 1: add_3}, attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: 2.0000, 1: multiply}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract}, attrs={'operation': 'MULTIPLY'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness_1"], 1: 0.0000}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': add_4}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': curve_circle.outputs["Curve"], 'Selection': endpoint_selection, + 'Position': combine_xyz_7}) + + endpoint_selection_1 = nw.new_node(Nodes.EndpointSelection, input_kwargs={'Start Size': 0}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: add_3}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': add_5}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': set_position, 'Selection': endpoint_selection_1, + 'Position': combine_xyz_8}) + + index = nw.new_node(Nodes.Index) + + less_than = nw.new_node(Nodes.Math, input_kwargs={0: index, 1: 1.0100}, attrs={'operation': 'LESS_THAN'}) + + greater_than = nw.new_node(Nodes.Math, input_kwargs={0: index, 1: 0.9900}, attrs={'operation': 'GREATER_THAN'}) + + op_and = nw.new_node(Nodes.BooleanMath, input_kwargs={0: less_than, 1: greater_than}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_4, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_9 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_5, 'Y': add_4}) + + set_position_2 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': set_position_1, 'Selection': op_and, + 'Position': combine_xyz_9}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': curve_line, 'Profile Curve': set_position_2, 'Fill Caps': True}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_1, 'Y': add_4, 'Z': add}) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_4}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_6}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_2}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract, 'Y': add_3, 'Z': add}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_1}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: add_3}, attrs={'operation': 'MULTIPLY'}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: multiply_7}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_6}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_1, 'Translation': combine_xyz_3}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, transform_1]}) + + multiply_8 = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_11 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_8}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry, 'Translation': combine_xyz_11}) + + combine_xyz_12 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add}) + + curve_line_1 = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_12}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': set_position_2, 'Scale': (-1.0000, 1.0000, 1.0000)}) + + curve_to_mesh_1 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': curve_line_1, 'Profile Curve': transform_2, 'Fill Caps': True}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [curve_to_mesh, transform_4, curve_to_mesh_1]}) + + merge_by_distance = nw.new_node(Nodes.MergeByDistance, + input_kwargs={'Geometry': join_geometry_1, 'Distance': 0.0001}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': merge_by_distance}) + + subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': realize_instances, 'Level': 4}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': subdivide_mesh}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_ramped_edge', singleton=False, type='GeometryNodeTree') +def nodegroup_ramped_edge(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'thickness_2', 0.5000), + ('NodeSocketFloat', 'width', 0.5000), + ('NodeSocketFloat', 'thickness_1', 0.5000), + ('NodeSocketFloat', 'ramp_angle', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}) + + combine_xyz_10 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_10}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': 3, 'Radius': 0.0100}) + + endpoint_selection = nw.new_node(Nodes.EndpointSelection, input_kwargs={'End Size': 0}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["ramp_angle"], 1: 0.0000}) + + tangent = nw.new_node(Nodes.Math, input_kwargs={0: add_2}, attrs={'operation': 'TANGENT'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness_2"], 1: 0.0000}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: tangent, 1: add_3}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: subtract}, attrs={'operation': 'SUBTRACT'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness_1"], 1: 0.0000}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_1, 'Y': add_4}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': curve_circle.outputs["Curve"], 'Selection': endpoint_selection, + 'Position': combine_xyz_7}) + + endpoint_selection_1 = nw.new_node(Nodes.EndpointSelection, input_kwargs={'Start Size': 0}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: add_3}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_1, 'Y': add_5}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': set_position, 'Selection': endpoint_selection_1, + 'Position': combine_xyz_8}) + + index = nw.new_node(Nodes.Index) + + less_than = nw.new_node(Nodes.Math, input_kwargs={0: index, 1: 1.0100}, attrs={'operation': 'LESS_THAN'}) + + greater_than = nw.new_node(Nodes.Math, input_kwargs={0: index, 1: 0.9900}, attrs={'operation': 'GREATER_THAN'}) + + op_and = nw.new_node(Nodes.BooleanMath, input_kwargs={0: less_than, 1: greater_than}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_9 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_2, 'Y': add_4}) + + set_position_2 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': set_position_1, 'Selection': op_and, + 'Position': combine_xyz_9}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': curve_line, 'Profile Curve': set_position_2, 'Fill Caps': True}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_1, 'Y': add_4, 'Z': add}) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_4}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_3}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_2}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract, 'Y': add_3, 'Z': add}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_1}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1}, attrs={'operation': 'MULTIPLY'}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_3}, attrs={'operation': 'MULTIPLY'}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_4, 1: multiply_5}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_4, 'Y': add_6}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_1, 'Translation': combine_xyz_3}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, transform_1]}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_11 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_6}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry, 'Translation': combine_xyz_11}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [curve_to_mesh, transform_4]}) + + merge_by_distance = nw.new_node(Nodes.MergeByDistance, + input_kwargs={'Geometry': join_geometry_1, 'Distance': 0.0001}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': merge_by_distance}) + + subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': realize_instances, 'Level': 4}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_7}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': subdivide_mesh, 'Translation': combine_xyz_4}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_2}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_panel_edge_frame', singleton=False, type='GeometryNodeTree') +def nodegroup_panel_edge_frame(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'vertical_edge', None), + ('NodeSocketFloat', 'door_width', 0.5000), + ('NodeSocketFloat', 'door_height', 0.0000), + ('NodeSocketGeometry', 'horizontal_edge', None)]) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["door_width"], 2: 0.0010}, + attrs={'operation': 'MULTIPLY_ADD'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: multiply_add, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + transform_7 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': group_input.outputs["horizontal_edge"], + 'Translation': (0.0000, -0.0001, 0.0000), + 'Scale': (0.9999, 1.0000, 1.0000)}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_add, 1: 1.0000}, attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: -0.0001}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["door_height"], 1: 0.0001}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Z': add_1}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_7, 'Translation': combine_xyz_2, + 'Rotation': (0.0000, -1.5708, 0.0000)}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: 0.0001}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_2}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_7, 'Translation': combine_xyz_1, + 'Rotation': (0.0000, 1.5708, 0.0000)}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_add}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': group_input.outputs["vertical_edge"], 'Translation': combine_xyz}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform, 'Scale': (-1.0000, 1.0000, 1.0000)}) + + # transform_1 = nw.new_node(Nodes.FlipFaces, input_kwargs={'Mesh': transform_1}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_3, transform_2, transform_1, transform]}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Value': multiply, 'Geometry': join_geometry_1}, + attrs={'is_active_output': True}) + + +def geometry_door_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + door_height = nw.new_node(Nodes.Value, label='door_height') + door_height.outputs[0].default_value = kwargs['door_height'] + + door_edge_thickness_2 = nw.new_node(Nodes.Value, label='door_edge_thickness_2') + door_edge_thickness_2.outputs[0].default_value = kwargs['edge_thickness_2'] + + door_edge_width = nw.new_node(Nodes.Value, label='door_edge_width') + door_edge_width.outputs[0].default_value = kwargs['edge_width'] + + door_edge_thickness_1 = nw.new_node(Nodes.Value, label='door_edge_thickness_1') + door_edge_thickness_1.outputs[0].default_value = kwargs['edge_thickness_1'] + + door_edge_ramp_angle = nw.new_node(Nodes.Value, label='door_edge_ramp_angle') + door_edge_ramp_angle.outputs[0].default_value = kwargs['edge_ramp_angle'] + + ramped_edge = nw.new_node(nodegroup_ramped_edge().name, + input_kwargs={'height': door_height, 'thickness_2': door_edge_thickness_2, + 'width': door_edge_width, 'thickness_1': door_edge_thickness_1, + 'ramp_angle': door_edge_ramp_angle}) + + door_width = nw.new_node(Nodes.Value, label='door_width') + door_width.outputs[0].default_value = kwargs['door_width'] + + ramped_edge_1 = nw.new_node(nodegroup_ramped_edge().name, + input_kwargs={'height': door_width, 'thickness_2': door_edge_thickness_2, + 'width': door_edge_width, 'thickness_1': door_edge_thickness_1, + 'ramp_angle': door_edge_ramp_angle}) + + panel_edge_frame = nw.new_node(nodegroup_panel_edge_frame().name, + input_kwargs={'vertical_edge': ramped_edge, 'door_width': door_width, + 'door_height': door_height, 'horizontal_edge': ramped_edge_1}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: panel_edge_frame.outputs["Value"], 1: 0.0001}) + + mid_board_thickness = nw.new_node(Nodes.Value, label='mid_board_thickness') + mid_board_thickness.outputs[0].default_value = kwargs['board_thickness'] + + if kwargs['has_mid_ramp']: + mid_board = nw.new_node(nodegroup_mid_board(material=kwargs['panel_material']).name, + input_kwargs={'height': door_height, 'thickness': mid_board_thickness, + 'width': door_width}) + else: + mid_board = nw.new_node(nodegroup_mid_board_001(material=kwargs['panel_material']).name, + input_kwargs={'height': door_height, 'thickness': mid_board_thickness, + 'width': door_width}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': add, 'Y': -0.0001, 'Z': mid_board.outputs["mid_height"]}) + + frame = [panel_edge_frame.outputs["Geometry"]] + if kwargs['has_mid_ramp']: + double_rampled_edge = nw.new_node(nodegroup_double_rampled_edge().name, + input_kwargs={'height': door_width, 'thickness_2': door_edge_thickness_2, + 'width': door_edge_width, 'thickness_1': door_edge_thickness_1, + 'ramp_angle': door_edge_ramp_angle}) + + transform_5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': double_rampled_edge, 'Translation': combine_xyz_5, + 'Rotation': (0.0000, 1.5708, 0.0000)}) + frame.append(transform_5) + + knob_raduis = nw.new_node(Nodes.Value, label='knob_raduis') + knob_raduis.outputs[0].default_value = kwargs['knob_R'] + + know_length = nw.new_node(Nodes.Value, label='know_length') + know_length.outputs[0].default_value = kwargs['knob_length'] + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: door_height}, attrs={'operation': 'MULTIPLY'}) + + knob_handle = nw.new_node(nodegroup_knob_handle().name, + input_kwargs={'Radius': knob_raduis, 'thickness_1': door_edge_thickness_1, + 'thickness_2': door_edge_thickness_2, 'length': know_length, + 'knob_mid_height': multiply, + 'edge_width': door_edge_width, 'door_width': door_width}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': frame + [knob_handle]}) + + set_material_3 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': join_geometry_1, + 'Material': surface.shaderfunc_to_material(kwargs['frame_material'])}) + + geos = [set_material_3, mid_board.outputs["Geometry"]] + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': geos}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: door_width, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry, 'Translation': combine_xyz}) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': transform}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': realize_instances_1}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': triangulate, + 'Scale': (-1.0 if kwargs['door_left_hinge'] else 1.0, 1.0000, 1.0000)}) + + if kwargs['door_left_hinge']: + transform_1 = nw.new_node(Nodes.FlipFaces, input_kwargs={'Mesh': transform_1}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_1, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_2}, + attrs={'is_active_output': True}) + + +class CabinetDoorBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(CabinetDoorBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = {} + + def get_asset_params(self, i=0): + params = self.params.copy() + if params.get('door_height', None) is None: + params['door_height'] = uniform(0.7, 2.2) + if params.get('door_width', None) is None: + params['door_width'] = uniform(0.3, 0.4) + if params.get('edge_thickness_1', None) is None: + params['edge_thickness_1'] = uniform(0.01, 0.018) + if params.get('edge_width', None) is None: + params['edge_width'] = uniform(0.03, 0.05) + if params.get('edge_thickness_2', None) is None: + params['edge_thickness_2'] = uniform(0.005, 0.008) + if params.get('edge_ramp_angle', None) is None: + params['edge_ramp_angle'] = uniform(0.6, 0.8) + params['board_thickness'] = params['edge_thickness_1'] - 0.005 + if params.get('knob_R', None) is None: + params['knob_R'] = uniform(0.003, 0.006) + if params.get('knob_length', None) is None: + params['knob_length'] = uniform(0.018, 0.035) + if params.get('attach_height', None) is None: + gap = uniform(0.05, 0.15) + params['attach_height'] = [gap, params['door_height'] - gap] + if params.get('has_mid_ramp', None) is None: + params['has_mid_ramp'] = np.random.choice([True, False], p=[0.6, 0.4]) + if params.get('door_left_hinge', None) is None: + params['door_left_hinge'] = False + + if params.get('frame_material', None) is None: + params['frame_material'] = np.random.choice(['white', 'black_wood', 'wood'], p=[0.5, 0.2, 0.3]) + if params.get('panel_material', None) is None: + if params['has_mid_ramp']: + lower_mat = np.random.choice([params['frame_material'], 'glass'], p=[0.7, 0.3]) + upper_mat = np.random.choice([lower_mat, 'glass'], p=[0.6, 0.4]) + params['panel_material'] = [lower_mat, upper_mat] + else: + params['panel_material'] = [params['frame_material']] + + params = self.get_material_func(params) + return params + + def get_material_func(self, params, randomness=True): + white_wood_params = shader_shelves_white_sampler() + black_wood_params = shader_shelves_black_wood_sampler() + normal_wood_params = shader_shelves_wood_sampler() + if params['frame_material'] == 'white': + if randomness: + params['frame_material'] = lambda x: shader_shelves_white(x, **white_wood_params) + else: + params['frame_material'] = shader_shelves_white + elif params['frame_material'] == 'black_wood': + if randomness: + params['frame_material'] = lambda x: shader_shelves_black_wood(x, **black_wood_params, z_axis_texture=True) + else: + params['frame_material'] = lambda x: shader_shelves_black_wood(x, z_axis_texture=True) + elif params['frame_material'] == 'wood': + if randomness: + params['frame_material'] = lambda x: shader_shelves_wood(x, **normal_wood_params, z_axis_texture=True) + else: + params['frame_material'] = lambda x: shader_shelves_wood(x, z_axis_texture=True) + + materials = [] + if not isinstance(params['panel_material'], list): + params['panel_material'] = [params['board_material']] + for mat in params['panel_material']: + if mat == 'white': + if randomness: + mat = lambda x: shader_shelves_white(x, **white_wood_params) + else: + mat = shader_shelves_white + elif mat == 'black_wood': + if randomness: + mat = lambda x: shader_shelves_black_wood(x, **black_wood_params, z_axis_texture=True) + else: + mat = lambda x: shader_shelves_black_wood(x, z_axis_texture=True) + elif mat == 'wood': + if randomness: + mat = lambda x: shader_shelves_wood(x, **normal_wood_params, z_axis_texture=True) + else: + mat = lambda x: shader_shelves_wood(x, z_axis_texture=True) + elif mat == 'glass': + if randomness: + mat = lambda x: shader_glass(x) + else: + mat = shader_glass + materials.append(mat) + params['panel_material'] = materials + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, geometry_door_nodes, apply=True, attributes=[], input_kwargs=obj_params) + tagging.tag_system.relabel_obj(obj) + + if params.get('ret_params', False): + return obj, obj_params + + return obj + diff --git a/infinigen/assets/shelves/drawers.py b/infinigen/assets/shelves/drawers.py new file mode 100644 index 000000000..7393f9819 --- /dev/null +++ b/infinigen/assets/shelves/drawers.py @@ -0,0 +1,416 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint + +from infinigen.assets.materials import metal +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core.tagging import tag_object, tag_nodegroup + +import bpy +from infinigen.assets.materials.shelf_shaders import ( + shader_shelves_white, shader_shelves_white_sampler, + shader_shelves_black_wood, shader_shelves_black_wood_sampler, + shader_shelves_wood, shader_shelves_wood_sampler, + shader_glass) + + +@node_utils.to_nodegroup('nodegroup_board_rail', singleton=False, type='GeometryNodeTree') +def nodegroup_board_rail(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + cylinder_1 = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Vertices': 64, 'Radius': 0.0040, 'Depth': 0.0050}) + + store_named_attribute_2 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cylinder_1.outputs["Mesh"], 'Name': 'uv_map', + 3: cylinder_1.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'width', 0.0000), + ('NodeSocketFloat', 'thickness', 0.5000), + ('NodeSocketFloat', 'depth', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: 0.0200}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_1}) + + transform_5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_2, 'Translation': combine_xyz_3, + 'Rotation': (0.0000, 1.5708, 0.0000)}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: 0.0300}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': 0.0020, 'Y': subtract, 'Z': group_input.outputs["width"]}) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', + 3: cube.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Vertices': 64, 'Radius': 0.0030, 'Depth': subtract}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Name': 'uv_map', + 3: cylinder.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_1}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_1, 'Translation': combine_xyz_1, + 'Rotation': (1.5708, 0.0000, 0.0000)}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_1, 'Scale': (1.0000, 1.0000, -1.0000)}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_2, transform_1]}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_5, transform, join_geometry_2]}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"]}, + attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: 0.0030}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: 0.0200}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_2, 'Y': multiply_3, 'Z': add_3}) + + transform_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry_1, 'Translation': combine_xyz_2}) + + transform_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_3, 'Scale': (-1.0000, 1.0000, 1.0000)}) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_4, transform_3]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_3}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_kallax_drawer_frame', singleton=False, type='GeometryNodeTree') +def nodegroup_kallax_drawer_frame(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'thickness', 0.5000), + ('NodeSocketFloat', 'width', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: 0.0000}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_2}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 4, 'Vertices Y': 4, 'Vertices Z': 4}) + + store_named_attribute_1 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', + 3: cube.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add_3}, attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: -0.0001}) + + multiply_add = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 2: 0.0100}, attrs={'operation': 'MULTIPLY_ADD'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': add_4, 'Z': multiply_add}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_1, 'Translation': combine_xyz_1}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform, 'Scale': (-1.0000, 1.0000, 1.0000)}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: -0.0001}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: add_5}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_6, 'Y': add_1, 'Z': add}) + + cube_1 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_2, 'Vertices X': 4, 'Vertices Y': 4, 'Vertices Z': 4}) + + store_named_attribute_2 = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_1.outputs["Mesh"], 'Name': 'uv_map', + 3: cube_1.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + multiply_add_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: -0.5000, 2: -0.0001}, + attrs={'operation': 'MULTIPLY_ADD'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_add_1, 'Z': 0.0100}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute_2, 'Translation': combine_xyz_3}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_3, 'Y': add, 'Z': add_2}) + + cube_2 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_4, 'Vertices X': 4, 'Vertices Y': 4, 'Vertices Z': 4}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube_2.outputs["Mesh"], 'Name': 'uv_map', + 3: cube_2.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + multiply_add_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: -1.0000, 2: multiply_2}, + attrs={'operation': 'MULTIPLY_ADD'}) + + multiply_add_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 2: 0.0100}, attrs={'operation': 'MULTIPLY_ADD'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_add_2, 'Z': multiply_add_3}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute, 'Translation': combine_xyz_5}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_1, transform, transform_2, transform_3]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_door_knob', singleton=False, type='GeometryNodeTree') +def nodegroup_door_knob(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'Radius', 0.0040), + ('NodeSocketFloat', 'length', 0.5000), + ('NodeSocketFloat', 'z', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["length"], 1: 0.0000}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Vertices': 64, 'Radius': group_input.outputs["Radius"], 'Depth': add}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Name': 'uv_map', + 3: cylinder.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: 0.0001}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["z"], 1: 0.0000}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_2}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_1, 'Z': multiply_1}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute, 'Translation': combine_xyz_2, + 'Rotation': (1.5708, 0.0000, 0.0000)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_drawer_door_board', singleton=False, type='GeometryNodeTree') +def nodegroup_drawer_door_board(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'thickness', 0.5000), + ('NodeSocketFloat', 'width', 0.5000), + ('NodeSocketFloat', 'height', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"], 1: 0.0000}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_2}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cube.outputs["Mesh"], 'Name': 'uv_map', + 3: cube.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_2}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply, 'Z': multiply_1}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': store_named_attribute, 'Translation': combine_xyz_1}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, + attrs={'is_active_output': True}) + + +def geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + door_thickness = nw.new_node(Nodes.Value, label='door_thickness') + door_thickness.outputs[0].default_value = kwargs['drawer_board_thickness'] + + drawer_board_width = nw.new_node(Nodes.Value, label='drawer_board_width') + drawer_board_width.outputs[0].default_value = kwargs['drawer_board_width'] + + drawer_board_height = nw.new_node(Nodes.Value, label='drawer_board_height') + drawer_board_height.outputs[0].default_value = kwargs['drawer_board_height'] + + drawer_door_board = nw.new_node(nodegroup_drawer_door_board().name, + input_kwargs={'thickness': door_thickness, 'width': drawer_board_width, + 'height': drawer_board_height}) + + knob_radius = nw.new_node(Nodes.Value, label='knob_radius') + knob_radius.outputs[0].default_value = kwargs['knob_radius'] + + knob_length = nw.new_node(Nodes.Value, label='knob_length') + knob_length.outputs[0].default_value = kwargs['knob_length'] + + door_knob = nw.new_node(nodegroup_door_knob().name, + input_kwargs={'Radius': knob_radius, 'length': knob_length, 'z': drawer_board_height}) + + drawer_depth = nw.new_node(Nodes.Value, label='drawer_depth') + drawer_depth.outputs[0].default_value = kwargs['drawer_depth'] - kwargs['drawer_board_thickness'] + + drawer_side_height = nw.new_node(Nodes.Value, label='drawer_side_height') + drawer_side_height.outputs[0].default_value = kwargs['drawer_side_height'] + + drawer_width = nw.new_node(Nodes.Value, label='drawer_width') + drawer_width.outputs[0].default_value = kwargs['drawer_width'] + + kallax_drawer_frame = nw.new_node(nodegroup_kallax_drawer_frame().name, + input_kwargs={'depth': drawer_depth, 'height': drawer_side_height, + 'thickness': door_thickness, 'width': drawer_width}) + + side_tilt_width = nw.new_node(Nodes.Value, label='side_tilt_width') + side_tilt_width.outputs[0].default_value = kwargs['side_tilt_width'] + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={ + 'Geometry': [door_knob, drawer_door_board, kallax_drawer_frame]}) + + set_material_2 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': join_geometry, + 'Material': surface.shaderfunc_to_material(kwargs['frame_material'])}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': set_material_2}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': realize_instances}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': triangulate, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + group_output_1 = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, + attrs={'is_active_output': True}) + + +class CabinetDrawerBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(CabinetDrawerBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = {} + + def get_asset_params(self, i=0): + params = self.params.copy() + if params.get('drawer_board_thickness', None) is None: + params['drawer_board_thickness'] = uniform(0.005, 0.01) + if params.get('drawer_board_width', None) is None: + params['drawer_board_width'] = uniform(0.3, 0.7) + if params.get('drawer_board_height', None) is None: + params['drawer_board_height'] = uniform(0.25, 0.4) + if params.get('drawer_depth', None) is None: + params['drawer_depth'] = uniform(0.3, 0.4) + if params.get('drawer_side_height', None) is None: + params['drawer_side_height'] = uniform(0.05, 0.2) + if params.get('drawer_width', None) is None: + params['drawer_width'] = params['drawer_board_width'] - uniform(0.015, 0.025) + if params.get('side_tilt_width', None) is None: + params['side_tilt_width'] = uniform(0.02, 0.03) + if params.get('knob_radius', None) is None: + params['knob_radius'] = uniform(0.003, 0.006) + if params.get('knob_length', None) is None: + params['knob_length'] = uniform(0.018, 0.035) + + if params.get('frame_material', None) is None: + params['frame_material'] = np.random.choice(['white', 'black_wood', 'wood'], p=[0.5, 0.2, 0.3]) + if params.get('knob_material', None) is None: + params['knob_material'] = np.random.choice([params['frame_material'], 'metal'], p=[0.5, 0.5]) + + params = self.get_material_func(params) + return params + + def get_material_func(self, params, randomness=True): + white_wood_params = shader_shelves_white_sampler() + black_wood_params = shader_shelves_black_wood_sampler() + normal_wood_params = shader_shelves_wood_sampler() + if params['frame_material'] == 'white': + if randomness: + params['frame_material'] = lambda x: shader_shelves_white(x, **white_wood_params) + else: + params['frame_material'] = shader_shelves_white + elif params['frame_material'] == 'black_wood': + if randomness: + params['frame_material'] = lambda x: shader_shelves_black_wood(x, **black_wood_params, z_axis_texture=True) + else: + params['frame_material'] = lambda x: shader_shelves_black_wood(x, z_axis_texture=True) + elif params['frame_material'] == 'wood': + if randomness: + params['frame_material'] = lambda x: shader_shelves_wood(x, **normal_wood_params, z_axis_texture=True) + else: + params['frame_material'] = lambda x: shader_shelves_wood(x, z_axis_texture=True) + + if params['knob_material'] == 'metal': + params['knob_material'] = metal.get_shader() + else: + params['knob_material'] = params['frame_material'] + + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, geometry_nodes, apply=True, attributes=[], input_kwargs=obj_params) + + if params.get('ret_params', False): + return obj, obj_params + + return obj diff --git a/infinigen/assets/shelves/kitchen_cabinet.py b/infinigen/assets/shelves/kitchen_cabinet.py new file mode 100644 index 000000000..6dea292a0 --- /dev/null +++ b/infinigen/assets/shelves/kitchen_cabinet.py @@ -0,0 +1,328 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint + +from infinigen.assets.materials.shelf_shaders import get_shelf_material +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t + +from infinigen.core.util.math import FixedSeed + +import bpy +from infinigen.assets.shelves.utils import nodegroup_tagged_cube, blender_rotate +from infinigen.assets.shelves.large_shelf import LargeShelfBaseFactory, LargeShelfFactory +from infinigen.assets.shelves.doors import CabinetDoorBaseFactory +from infinigen.assets.shelves.drawers import CabinetDrawerBaseFactory +from infinigen.assets.materials.shelf_shaders import ( + shader_shelves_white, shader_shelves_white_sampler, + shader_shelves_black_wood, shader_shelves_black_wood_sampler, + shader_shelves_wood, shader_shelves_wood_sampler +) +from infinigen.assets.utils.object import new_bbox + + +def geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + cabinets = [] + for i, component in enumerate(kwargs['components']): + frame_info = nw.new_node(Nodes.ObjectInfo, input_kwargs={'Object': component[0]}) + + attachments = [] + if component[1] == 'door': + right_door_info = nw.new_node(Nodes.ObjectInfo, input_kwargs={'Object': component[2][0]}) + left_door_info = nw.new_node(Nodes.ObjectInfo, input_kwargs={'Object':component[2][1]}) + + transform_r = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': right_door_info.outputs['Geometry'], + 'Translation': component[2][2]['door_hinge_pos'][0], + 'Rotation': (0, 0, component[2][2]['door_open_angle'])}) + attachments.append(transform_r) + if len(component[2][2]['door_hinge_pos']) > 1: + transform_l = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': left_door_info.outputs['Geometry'], + 'Translation': component[2][2]['door_hinge_pos'][1], + 'Rotation': (0, 0, component[2][2]['door_open_angle'])}) + attachments.append(transform_l) + elif component[1] == 'drawer': + + for j, drawer in enumerate(component[2]): + drawer_info = nw.new_node(Nodes.ObjectInfo, input_kwargs={'Object': drawer[0]}) + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': drawer_info.outputs['Geometry'], + 'Translation': drawer[1]['drawer_hinge_pos']}) + attachments.append(transform) + else: + continue + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': attachments}) + #[frame_info.outputs['Geometry']]}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry, + 'Translation': (0, kwargs['y_translations'][i], 0)}) + cabinets.append(transform) + + try: + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': cabinets}) + except TypeError: + import pdb; pdb.set_trace() + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': join_geometry_1}, attrs={'is_active_output': True}) + + +class KitchenCabinetBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(KitchenCabinetBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.frame_params = {} + self.material_params = {} + self.cabinet_widths = [] + self.frame_fac = LargeShelfBaseFactory(factory_seed) + self.door_fac = CabinetDoorBaseFactory(factory_seed) + self.drawer_fac = CabinetDrawerBaseFactory(factory_seed) + self.drawer_only = False + with FixedSeed(factory_seed): + self.params = self.sample_params() + + def sample_params(self): + pass + + def get_material_params(self): + with FixedSeed(self.factory_seed): + params = self.material_params.copy() + if params.get('frame_material', None) is None: + with FixedSeed(self.factory_seed): + params['frame_material'] = np.random.choice(['white', 'black_wood', 'wood'], p=[0.4, 0.3, 0.3]) + params['board_material'] = params['frame_material'] + return self.get_material_func(params, randomness=True) + + def get_material_func(self, params, randomness=True): + with FixedSeed(self.factory_seed): + white_wood_params = shader_shelves_white_sampler() + black_wood_params = shader_shelves_black_wood_sampler() + normal_wood_params = shader_shelves_wood_sampler() + if params['frame_material'] == 'white': + if randomness: + params['frame_material'] = lambda x: shader_shelves_white(x, **white_wood_params) + else: + params['frame_material'] = shader_shelves_white + elif params['frame_material'] == 'black_wood': + if randomness: + params['frame_material'] = lambda x: shader_shelves_black_wood(x, **black_wood_params, z_axis_texture=True) + else: + params['frame_material'] = lambda x: shader_shelves_black_wood(x, z_axis_texture=True) + elif params['frame_material'] == 'wood': + if randomness: + params['frame_material'] = lambda x: shader_shelves_wood(x, **normal_wood_params, z_axis_texture=True) + else: + params['frame_material'] = lambda x: shader_shelves_wood(x, z_axis_texture=True) + + if params['board_material'] == 'white': + if randomness: + params['board_material'] = lambda x: shader_shelves_white(x, **white_wood_params) + else: + params['board_material'] = shader_shelves_white + elif params['board_material'] == 'black_wood': + if randomness: + params['board_material'] = lambda x: shader_shelves_black_wood(x, **black_wood_params) + else: + params['board_material'] = shader_shelves_black_wood + elif params['board_material'] == 'wood': + if randomness: + params['board_material'] = lambda x: shader_shelves_wood(x, **normal_wood_params) + else: + params['board_material'] = shader_shelves_wood + + params['panel_meterial'] = params['frame_material'] + params['knob_material'] = params['frame_material'] + return params + + def get_frame_params(self, width, i=0): + params = self.frame_params.copy() + params['shelf_cell_width'] = [width] + params.update(self.material_params.copy()) + return params + + def get_attach_params(self, attach_type, i=0): + param_sets = [] + if attach_type == 'none': + pass + elif attach_type == 'door': + params = dict() + shelf_width = self.frame_params['shelf_width'] + self.frame_params['side_board_thickness'] * 2 + if shelf_width <= 0.6: + params['door_width'] = shelf_width + params['has_mid_ramp'] = False + params['edge_thickness_1'] = 0.01 + params['door_hinge_pos'] = [(self.frame_params['shelf_depth'] / 2. + 0.0025, -shelf_width / 2., + self.frame_params['bottom_board_height'])] + params['door_open_angle'] = 0 + else: + params['door_width'] = shelf_width / 2. - 0.0005 + params['has_mid_ramp'] = False + params['edge_thickness_1'] = 0.01 + params['door_hinge_pos'] = [(self.frame_params['shelf_depth'] / 2. + 0.008, -shelf_width / 2., + self.frame_params['bottom_board_height']), + (self.frame_params['shelf_depth'] / 2. + 0.008, shelf_width / 2., + self.frame_params['bottom_board_height'])] + params['door_open_angle'] = 0 + + params['door_height'] = (self.frame_params['division_board_z_translation'][-1] - + self.frame_params['division_board_z_translation'][0] + + self.frame_params['division_board_thickness']) + params.update(self.material_params.copy()) + param_sets.append(params) + elif attach_type == 'drawer': + for i, h in enumerate(self.frame_params['shelf_cell_height']): + params = dict() + drawer_h = (self.frame_params['division_board_z_translation'][i+1] + - self.frame_params['division_board_z_translation'][i] + - self.frame_params['division_board_thickness']) + drawer_depth = self.frame_params['shelf_depth'] + params['drawer_board_width'] = self.frame_params['shelf_width'] + params['drawer_board_height'] = drawer_h + params['drawer_depth'] = drawer_depth + params['drawer_hinge_pos'] = (self.frame_params['shelf_depth'] / 2., 0, + (self.frame_params['division_board_thickness'] / 2. + + self.frame_params['division_board_z_translation'][i])) + params.update(self.material_params.copy()) + param_sets.append(params) + else: + raise NotImplementedError + + return param_sets + + def get_cabinet_params(self, i=0): + x_translations = [] + accum_w, thickness = 0, self.frame_params.get('side_board_thickness', 0.005) # instructed by Beining + for w in self.cabinet_widths: + accum_w += thickness + w / 2. + x_translations.append(accum_w) + accum_w += thickness + w / 2. + 0.0005 + return x_translations + + def create_cabinet_components(self, i, drawer_only=False): + # update material params + self.material_params = self.get_material_params() + + components = [] + for k, w in enumerate(self.cabinet_widths): + # create frame + frame_params = self.get_frame_params(w, i=i) + self.frame_fac.params = frame_params + frame, frame_params = self.frame_fac.create_asset(i=i, ret_params=True) + frame.name = f'cabinet_frame_{k}' + self.frame_params = frame_params + + # create attach + if drawer_only: + attach_type = np.random.choice(['drawer', 'door'], p=[0.5, 0.5]) + else: + attach_type = np.random.choice(['drawer', 'door', 'none'], p=[0.4, 0.4, 0.2]) + + attach_params = self.get_attach_params(attach_type, i=i) + if attach_type == 'door': + self.door_fac.params = attach_params[0] + self.door_fac.params['door_left_hinge'] = False + right_door, door_obj_params = self.door_fac.create_asset(i=i, ret_params=True) + right_door.name = f'cabinet_right_door_{k}' + self.door_fac.params = door_obj_params + self.door_fac.params['door_left_hinge'] = True + left_door, _ = self.door_fac.create_asset(i=i, ret_params=True) + left_door.name = f'cabinet_left_door_{k}' + components.append([frame, 'door', [right_door, left_door, attach_params[0]]]) + + elif attach_type == 'drawer': + drawers = [] + for j, p in enumerate(attach_params): + self.drawer_fac.params = p + drawer = self.drawer_fac.create_asset(i=i) + drawer.name = f'drawer_{k}_layer{j}' + drawers.append([drawer, p]) + components.append([frame, 'drawer', drawers]) + + elif attach_type == 'none': + components.append([frame, 'none']) + + else: + raise NotImplementedError + + return components + + def create_asset(self, i=0, **params): + components = self.create_cabinet_components(i=i, drawer_only=self.drawer_only) + cabinet_params = self.get_cabinet_params(i=i) + join_objs = [] + + contain_attach = False + for com in components: + if com[1] == 'none': + continue + else: + contain_attach = True + + if contain_attach: + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + surface.add_geomod(obj, geometry_nodes, attributes=[], input_kwargs={ + 'components': components, + 'y_translations': cabinet_params + }, apply=True) + + join_objs += [obj] + + for i, c in enumerate(components): + if c[1] == 'door': + butil.delete(c[2][:-1]) + elif c[1] == 'drawer': + butil.delete([x[0] for x in c[2]]) + c[0].location = (0, cabinet_params[i], 0) + butil.apply_transform(c[0], loc=True) + join_objs.append(c[0]) + + #butil.delete(c[:1]) + obj = butil.join_objects(join_objs) + tagging.tag_system.relabel_obj(obj) + + return obj + + +class KitchenCabinetFactory(KitchenCabinetBaseFactory): + def __init__(self, factory_seed, params={}, coarse=False, dimensions=None, drawer_only=False): + self.dimensions = dimensions + super().__init__(factory_seed, params, coarse) + self.drawer_only = drawer_only + + def sample_params(self): + params = dict() + if self.dimensions is None: + dimensions = ( + uniform(0.25, 0.35), + uniform(1.0, 4.0), + uniform(0.5, 1.3)) + self.dimensions = dimensions + else: + dimensions = self.dimensions + params['Dimensions'] = dimensions + + params['bottom_board_height'] = 0.06 + params['shelf_depth'] = params['Dimensions'][0] - 0.01 + num_h = int((params['Dimensions'][2] - 0.06) / 0.3) + params['shelf_cell_height'] = [(params['Dimensions'][2] - 0.06) / num_h for _ in range(num_h)] + + self.frame_params = params + + n_cells= max(int(params['Dimensions'][1] / 0.45),1) + intervals = np.random.uniform(0.55, 1.0, size=(n_cells,)) + intervals = intervals / intervals.sum() * params['Dimensions'][1] + self.cabinet_widths = intervals.tolist() + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + x,y,z = self.dimensions + return new_bbox(-x/2 * 1.2, x/2 * 1.2, 0, y * 1.1, 0, (z + 0.06) * 1.03) diff --git a/infinigen/assets/shelves/kitchen_space.py b/infinigen/assets/shelves/kitchen_space.py new file mode 100644 index 000000000..66c65efdb --- /dev/null +++ b/infinigen/assets/shelves/kitchen_space.py @@ -0,0 +1,220 @@ +# 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: Yiming Zuo, Stamatis Alexandropoulos + +import bpy +import bpy +import mathutils +from mathutils import Vector +from numpy.random import uniform, normal, randint, choice + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface +from infinigen.core import tagging, tags as t + +from infinigen.assets.utils.object import new_bbox + +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory + +from infinigen.assets.shelves.kitchen_cabinet import KitchenCabinetFactory +from infinigen.assets.table_decorations.sink import SinkFactory +from infinigen.assets.wall_decorations.range_hood import RangeHoodFactory + +from infinigen.core.util import blender as butil + +from infinigen.assets.tables.table_top import nodegroup_generate_table_top +from infinigen.assets.materials.table_materials import shader_marble +from infinigen.core.constraints.example_solver.room.constants import WALL_HEIGHT, WALL_THICKNESS + +def nodegroup_tag_cube(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + + index = nw.new_node(Nodes.Index) + + equal = nw.new_node(Nodes.Compare, input_kwargs={2: index, 3: 5}, attrs={'data_type': 'INT', 'operation': 'EQUAL'}) + + cube = tagging.tag_nodegroup(nw, group_input.outputs['Geometry'], t.Subpart.SupportSurface, selection=equal) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': cube}, attrs={'is_active_output': True}) + +def geometry_nodes_add_cabinet_top(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + + value = nw.new_node(Nodes.Value) + value.outputs[0].default_value = 0.0500 + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': group_input.outputs["Geometry"]}) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': bounding_box.outputs["Max"]}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': bounding_box.outputs["Min"]}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["X"], 1: separate_xyz.outputs["X"]}, + attrs={'operation': 'SUBTRACT'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: 1.4140}, attrs={'operation': 'MULTIPLY'}) + + subtract_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["Y"], 1: separate_xyz.outputs["Y"]}, + attrs={'operation': 'SUBTRACT'}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: subtract}, attrs={'operation': 'DIVIDE'}) + + generatetabletop = nw.new_node(nodegroup_generate_table_top().name, + input_kwargs={'Thickness': value, 'N-gon': 4, 'Profile Width': multiply, 'Aspect Ratio': divide, 'Fillet Ratio': 0.0100, 'Fillet Radius Vertical': 0.0100}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': generatetabletop, 'Material': surface.shaderfunc_to_material(shader_marble)}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: separate_xyz_1.outputs["Y"]}) + + divide_1 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: 2.0000}, attrs={'operation': 'DIVIDE'}) + + separate_xyz_2 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': bounding_box.outputs["Max"]}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': divide_1, 'Z': separate_xyz_2.outputs["Z"]}) + + transform_geometry = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': set_material, 'Translation': combine_xyz}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [group_input.outputs["Geometry"], transform_geometry]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, attrs={'is_active_output': True}) + +def geometry_node_to_tagged_bbox(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': group_input.outputs["Geometry"]}) + + transform_geometry = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': bounding_box, 'Scale': (0.9700, 0.9700, 1.000)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_geometry}, attrs={'is_active_output': True}) + +def geometry_node_to_bbox(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': group_input.outputs["Geometry"]}) + + transform_geometry = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': bounding_box, 'Scale': (0.9700, 0.9700, 1.000)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_geometry}, attrs={'is_active_output': True}) + +class KitchenSpaceFactory(AssetFactory): + def __init__( + self, + factory_seed, + coarse=False, + dimensions=None, + island=False + ): + super(KitchenSpaceFactory, self).__init__(factory_seed, coarse=coarse) + + with FixedSeed(factory_seed): + + if dimensions is None: + dimensions = Vector(( + uniform(0.7, 1), + uniform(1.7, 5), + uniform(2.3, WALL_HEIGHT - WALL_THICKNESS) + )) + + self.island = island + if self.island: + dimensions.x *= uniform(1.5, 2) + + self.dimensions = dimensions + + self.params = self.sample_parameters(dimensions) + + + def sample_parameters(self, dimensions): + self.cabinet_bottom_height = uniform(0.8, 1.0) + self.cabinet_top_height = uniform(0.8, 1.0) + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + x, y, z = self.dimensions + box = new_bbox(-x/2 * 1.08, x/2 * 1.08, 0, y, 0, self.cabinet_bottom_height + 0.13) + surface.add_geomod(box, nodegroup_tag_cube, apply=True) + + if not self.island: + box_top = new_bbox(-x/2, x*0.16, 0, y, z - self.cabinet_top_height - 0.1, z) + box = butil.join_objects([box, box_top]) + + return box + + def create_asset(self, **params): + x, y, z = self.dimensions + parts = [] + + + cabinet_bottom_height = self.cabinet_bottom_height + cabinet_top_height = self.cabinet_top_height + + cabinet_bottom_factory = KitchenCabinetFactory(self.factory_seed, dimensions=(x, y-0.15, cabinet_bottom_height), drawer_only=True) + cabinet_bottom = cabinet_bottom_factory(i=0) + parts.append(cabinet_bottom) + + surface.add_geomod(cabinet_bottom, geometry_nodes_add_cabinet_top, apply=True) + + if not self.island: + # top + top_mid_width = uniform(1.0, 1.3) + cabinet_top_width = (y - top_mid_width) / 2.0 - 0.05 + + cabinet_top_factory = KitchenCabinetFactory(self.factory_seed, dimensions=(x / 2.0, cabinet_top_width, cabinet_top_height), drawer_only=False) + cabinet_top_left = cabinet_top_factory(i=0) + cabinet_top_right = cabinet_top_factory(i=1) + + cabinet_top_left.location = (-x/4.0, 0.0, z-cabinet_top_height) + cabinet_top_right.location = (-x/4.0, y - cabinet_top_width, z-cabinet_top_height) + + # hood / cab + # mid_style = choice(['range_hood', 'cabinet']) + # mid_style = 'range_hood' + mid_style = choice(['cabinet']) + if mid_style == 'range_hood': + range_hood_factory = RangeHoodFactory(self.factory_seed, dimensions=(x*0.66, top_mid_width + 0.15, cabinet_top_height)) + top_mid = range_hood_factory(i=0) + top_mid.location = (-x*0.5, y/2.0, z-cabinet_top_height+0.05) + + elif mid_style == 'cabinet': + cabinet_top_mid_factory = KitchenCabinetFactory(self.factory_seed, dimensions=(x*0.66, top_mid_width, cabinet_top_height * 0.8), drawer_only=False) + top_mid = cabinet_top_mid_factory(i=0) + top_mid.location = (-x/6.0, y/2.0 - top_mid_width / 2.0, z-(cabinet_top_height * 0.8)) + + else: + raise NotImplementedError + + # parts += [sink, cabinet_top_left, cabinet_top_right, top_mid] + parts += [cabinet_top_left, cabinet_top_right, top_mid] + + kitchen_space = butil.join_objects(parts)#[cabinet_bottom, sink, cabinet_top_left, cabinet_top_right, top_mid]) + + if not self.island: + kitchen_space.dimensions = self.dimensions + butil.apply_transform(kitchen_space) + + tagging.tag_system.relabel_obj(kitchen_space) + + return kitchen_space + +class KitchenIslandFactory(KitchenSpaceFactory): + + def __init__(self, factory_seed): + + super(KitchenIslandFactory, self).__init__( + factory_seed=factory_seed, + island=True, + ) \ No newline at end of file diff --git a/infinigen/assets/shelves/large_shelf.py b/infinigen/assets/shelves/large_shelf.py new file mode 100644 index 000000000..2ba1950ae --- /dev/null +++ b/infinigen/assets/shelves/large_shelf.py @@ -0,0 +1,616 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t + +import bpy +from infinigen.assets.shelves.utils import nodegroup_tagged_cube +from infinigen.assets.materials.shelf_shaders import ( + shader_shelves_white, shader_shelves_white_sampler, + shader_shelves_black_wood, shader_shelves_black_wood_sampler, + shader_shelves_wood, shader_shelves_wood_sampler, + shader_shelves_white_metallic, shader_shelves_white_metallic_sampler, + shader_shelves_black_metallic, shader_shelves_black_metallic_sampler) + + +@node_utils.to_nodegroup('nodegroup_screw_head', singleton=False, type='GeometryNodeTree') +def nodegroup_screw_head(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'Depth', 0.0050), + ('NodeSocketFloatDistance', 'Radius', 1.0000), + ('NodeSocketFloat', 'division_thickness', 0.5000), + ('NodeSocketFloat', 'width', 0.5000), + ('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'screw_width_gap', 0.5000), + ('NodeSocketFloat', 'screw_depth_gap', 0.0000)]) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Radius': group_input.outputs["Radius"], + 'Depth': group_input.outputs["Depth"]}, + attrs={'fill_type': 'TRIANGLE_FAN'}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cylinder.outputs["Mesh"]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"]}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: multiply, 1: group_input.outputs["screw_width_gap"]}, + attrs={'operation': 'SUBTRACT'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"]}, + attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["screw_width_gap"], 1: 0.0000}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: add}, attrs={'operation': 'SUBTRACT'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + multiply_3 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["division_thickness"], 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract, 'Y': multiply_2, 'Z': multiply_3}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform, 'Translation': combine_xyz}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract, 'Y': subtract_1, 'Z': multiply_3}) + + transform_6 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform, 'Translation': combine_xyz_4}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_1, transform_6]}) + + transform_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry_2, 'Scale': (-1.0000, 1.0000, 1.0000)}) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_4, join_geometry_2]}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_3}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': realize_instances}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_attachment', singleton=False, type='GeometryNodeTree') +def nodegroup_attachment(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'attach_thickness', 0.0000), + ('NodeSocketFloat', 'attach_length', 0.0000), + ('NodeSocketFloat', 'attach_z_translation', 0.0000), + ('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'width', 0.5000), + ('NodeSocketFloat', 'attach_gap', 0.5000), + ('NodeSocketFloat', 'attach_width', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["attach_width"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["attach_length"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': add, 'Y': add_1, 'Z': group_input.outputs["attach_thickness"]}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"]}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: multiply, 1: group_input.outputs["attach_gap"]}, + attrs={'operation': 'SUBTRACT'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: add}, attrs={'operation': 'SUBTRACT'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: multiply_2}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': subtract_1, 'Y': add_2, + 'Z': group_input.outputs["attach_z_translation"]}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_1}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform, 'Scale': (-1.0000, 1.0000, 1.0000)}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_1, transform]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_division_board', singleton=False, type='GeometryNodeTree') +def nodegroup_division_board(nw: NodeWrangler, material, tag_support=False): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'thickness', 0.0000), + ('NodeSocketFloat', 'width', 0.0000), + ('NodeSocketFloat', 'depth', 0.0000), + ('NodeSocketFloat', 'z_translation', 0.0000), + ('NodeSocketFloat', 'x_translation', 0.0000), + ('NodeSocketFloat', 'screw_depth', 0.0000), + ('NodeSocketFloat', 'screw_radius', 0.0000), + ('NodeSocketFloat', 'screw_width_gap', 0.0000), + ('NodeSocketFloat', 'screw_depth_gap', 0.0000)]) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["width"], 'Y': group_input.outputs["depth"], + 'Z': group_input.outputs["thickness"]}) + + if tag_support: + cube = nw.new_node(nodegroup_tagged_cube().name, input_kwargs={'Size': combine_xyz}) + else: + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 10, 'Vertices Y': 10, 'Vertices Z': 10}) + + screw_head = nw.new_node(nodegroup_screw_head().name, + input_kwargs={'Depth': group_input.outputs["screw_depth"], + 'Radius': group_input.outputs["screw_radius"], + 'division_thickness': group_input.outputs["thickness"], + 'width': group_input.outputs["width"], 'depth': group_input.outputs["depth"], + 'screw_width_gap': group_input.outputs["screw_width_gap"], + 'screw_depth_gap': group_input.outputs["screw_depth_gap"]}) + + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [cube, screw_head]}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["x_translation"], + 'Z': group_input.outputs["z_translation"]}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry, 'Translation': combine_xyz_1}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_bottom_board', singleton=False, type='GeometryNodeTree') +def nodegroup_bottom_board(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'thickness', 0.0000), + ('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'y_gap', 0.5000), + ('NodeSocketFloat', 'x_translation', 0.0000), + ('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'width', 0.0000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["width"], 'Y': group_input.outputs["thickness"], + 'Z': add}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 10, 'Vertices Y': 10, 'Vertices Z': 10}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"]}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: group_input.outputs["y_gap"]}, + attrs={'operation': 'SUBTRACT'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["x_translation"], 'Y': subtract, + 'Z': multiply_1}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_1}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_back_board', singleton=False, type='GeometryNodeTree') +def nodegroup_back_board(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'width', 0.0000), + ('NodeSocketFloat', 'thickness', 0.5000), + ('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'depth', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["width"], 'Y': add, 'Z': add_1}) + + cube_2 = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz_4, 'Vertices X': 10, 'Vertices Y': 10, 'Vertices Z': 10}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + multiply_add = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: -0.5000, 2: multiply}, + attrs={'operation': 'MULTIPLY_ADD'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_add, 'Z': multiply_1}) + + transform_5 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_2, 'Translation': combine_xyz_5}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_5}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_side_board', singleton=False, type='GeometryNodeTree') +def nodegroup_side_board(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'board_thickness', 0.5000), + ('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'x_translation', 0.0000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["board_thickness"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: 0.0000}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_2}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 10, 'Vertices Y': 10, 'Vertices Z': 10}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: 0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["x_translation"], 'Z': multiply}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_1}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, + attrs={'is_active_output': True}) + + +def geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + side_board_thickness = nw.new_node(Nodes.Value, label='side_board_thickness') + side_board_thickness.outputs[0].default_value = kwargs['side_board_thickness'] + + shelf_depth = nw.new_node(Nodes.Value, label='shelf_depth') + shelf_depth.outputs[0].default_value = kwargs['shelf_depth'] + + add = nw.new_node(Nodes.Math, input_kwargs={0: shelf_depth, 1: 0.0040}) + + shelf_height = nw.new_node(Nodes.Value, label='shelf_height') + shelf_height.outputs[0].default_value = kwargs['shelf_height'] + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: shelf_height, 1: 0.0020}) + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: shelf_height, 1: -0.0010}) + side_boards = [] + + for x in kwargs['side_board_x_translation']: + side_board_x_translation = nw.new_node(Nodes.Value, label='side_board_x_translation') + side_board_x_translation.outputs[0].default_value = x + + side_board = nw.new_node(nodegroup_side_board().name, + input_kwargs={'board_thickness': side_board_thickness, + 'depth': add, 'height': add_1, + 'x_translation': side_board_x_translation}) + side_boards.append(side_board) + + shelf_width = nw.new_node(Nodes.Value, label='shelf_width') + shelf_width.outputs[0].default_value = kwargs['shelf_width'] + + backboard_thickness = nw.new_node(Nodes.Value, label='backboard_thickness') + backboard_thickness.outputs[0].default_value = kwargs['backboard_thickness'] + + add_side = nw.new_node(Nodes.Math, input_kwargs={0: shelf_width, 1: kwargs['side_board_thickness'] * 2}) + back_board = nw.new_node(nodegroup_back_board().name, + input_kwargs={'width': add_side, 'thickness': backboard_thickness, + 'height': add_2, 'depth': shelf_depth}) + + bottom_board_y_gap = nw.new_node(Nodes.Value, label='bottom_board_y_gap') + bottom_board_y_gap.outputs[0].default_value = kwargs['bottom_board_y_gap'] + + bottom_board_height = nw.new_node(Nodes.Value, label='bottom_board_height') + bottom_board_height.outputs[0].default_value = kwargs['bottom_board_height'] + + bottom_boards = [] + for i in range(len(kwargs['shelf_cell_width'])): + + bottom_gap_x_translation = nw.new_node(Nodes.Value, label='bottom_gap_x_translation') + bottom_gap_x_translation.outputs[0].default_value = kwargs['bottom_gap_x_translation'][i] + + shelf_cell_width = nw.new_node(Nodes.Value, label='shelf_cell_width') + shelf_cell_width.outputs[0].default_value = kwargs['shelf_cell_width'][i] + + bottomboard = nw.new_node(nodegroup_bottom_board().name, + input_kwargs={'thickness': side_board_thickness, 'depth': shelf_depth, + 'y_gap': bottom_board_y_gap, 'x_translation': bottom_gap_x_translation, + 'height': bottom_board_height, 'width': shelf_cell_width}) + + bottom_boards.append(bottomboard) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [back_board] + side_boards + bottom_boards}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': realize_instances, + 'Material': surface.shaderfunc_to_material(kwargs['frame_material'])}) + + division_board_thickness = nw.new_node(Nodes.Value, label='division_board_thickness') + division_board_thickness.outputs[0].default_value = kwargs['division_board_thickness'] + + division_boards = [] + for i in range(len(kwargs['shelf_cell_width'])): + for j in range(len(kwargs['division_board_z_translation'])): + + division_board_z_translation = nw.new_node(Nodes.Value, label='division_board_z_translation') + division_board_z_translation.outputs[0].default_value = kwargs['division_board_z_translation'][j] + + division_board_x_translation = nw.new_node(Nodes.Value, label='division_board_x_translation') + division_board_x_translation.outputs[0].default_value = kwargs['division_board_x_translation'][i] + + shelf_cell_width = nw.new_node(Nodes.Value, label='shelf_cell_width') + shelf_cell_width.outputs[0].default_value = kwargs['shelf_cell_width'][i] + + screw_depth_head = nw.new_node(Nodes.Value, label='screw_depth_head') + screw_depth_head.outputs[0].default_value = kwargs['screw_depth_head'] + + screw_head_radius = nw.new_node(Nodes.Value, label='screw_head_radius') + screw_head_radius.outputs[0].default_value = kwargs['screw_head_radius'] + + screw_width_gap = nw.new_node(Nodes.Value, label='screw_width_gap') + screw_width_gap.outputs[0].default_value = kwargs['screw_width_gap'] + + screw_depth_gap = nw.new_node(Nodes.Value, label='screw_depth_gap') + screw_depth_gap.outputs[0].default_value = kwargs['screw_depth_gap'] + + division_board = nw.new_node(nodegroup_division_board(material=kwargs['board_material'], + tag_support=kwargs.get('tag_support', False)).name, + input_kwargs={'thickness': division_board_thickness, + 'width': shelf_cell_width, + 'depth': shelf_depth, + 'z_translation': division_board_z_translation, + 'x_translation': division_board_x_translation, + 'screw_depth': screw_depth_head, + 'screw_radius': screw_head_radius, + 'screw_width_gap': screw_width_gap, + 'screw_depth_gap': screw_depth_gap}) + division_boards.append(division_board) + + attach_thickness = nw.new_node(Nodes.Value, label='attach_thickness') + attach_thickness.outputs[0].default_value = kwargs['attach_thickness'] + + attach_length = nw.new_node(Nodes.Value, label='attach_length') + attach_length.outputs[0].default_value = kwargs['attach_length'] + + attach_z_translation = nw.new_node(Nodes.Value, label='attach_z_translation') + attach_z_translation.outputs[0].default_value = kwargs['attach_z_translation'] + + attach_gap = nw.new_node(Nodes.Value, label='attach_gap') + attach_gap.outputs[0].default_value = kwargs['attach_gap'] + + attach_width = nw.new_node(Nodes.Value, label='attach_width') + attach_width.outputs[0].default_value = kwargs['attach_width'] + + join_geometry_k = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': division_boards}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': join_geometry_k, + 'Material': surface.shaderfunc_to_material(kwargs['board_material'])}) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [set_material, set_material_1]}) + + realize_instances_3 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_3}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': realize_instances_3}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': triangulate, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, + attrs={'is_active_output': True}) + + +class LargeShelfBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(LargeShelfBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = {} + + def sample_params(self): + return self.params.copy() + + def get_asset_params(self, i=0): + params = self.sample_params() + if params.get('shelf_depth', None) is None: + params['shelf_depth'] = np.clip(normal(0.26, 0.03), 0.18, 0.36) + if params.get('side_board_thickness', None) is None: + params['side_board_thickness'] = np.clip(normal(0.02, 0.002), 0.015, 0.025) + if params.get('back_board_thickness', None) is None: + params['backboard_thickness'] = 0.01 + if params.get('bottom_board_y_gap', None) is None: + params['bottom_board_y_gap'] = uniform(0.01, 0.05) + if params.get('bottom_board_height', None) is None: + params['bottom_board_height'] = (np.clip(normal(0.083, 0.01), 0.05, 0.11) * + np.random.choice([1., 0.], p=[0.8, 0.2])) + if params.get('division_board_thickness', None) is None: + params['division_board_thickness'] = np.clip(normal(0.02, 0.002), 0.015, 0.025) + if params.get('screw_depth_head', None) is None: + params['screw_depth_head'] = uniform(0.001, 0.004) + if params.get('screw_head_radius', None) is None: + params['screw_head_radius'] = uniform(0.001, 0.004) + if params.get('screw_width_gap', None) is None: + params['screw_width_gap'] = uniform(0.0, 0.02) + if params.get('screw_depth_gap', None) is None: + params['screw_depth_gap'] = uniform(0.025, 0.06) + if params.get('attach_length', None) is None: + params['attach_length'] = uniform(0.05, 0.1) + if params.get('attach_width', None) is None: + params['attach_width'] = uniform(0.01, 0.025) + if params.get('attach_thickness', None) is None: + params['attach_thickness'] = uniform(0.002, 0.005) + if params.get('attach_gap', None) is None: + params['attach_gap'] = uniform(0.0, 0.05) + if params.get('shelf_cell_width', None) is None: + num_h_cells = randint(1, 4) + shelf_cell_width = [] + for i in range(num_h_cells): + shelf_cell_width.append(np.random.choice([0.76, 0.36], p=[0.5, 0.5]) * + np.clip(normal(1., 0.1), 0.75, 1.25)) + params['shelf_cell_width'] = shelf_cell_width + if params.get('shelf_cell_height', None) is None: + num_v_cells = randint(3, 8) + shelf_cell_height = [] + for i in range(num_v_cells): + shelf_cell_height.append(0.3 * np.clip(normal(1., 0.1), 0.75, 1.25)) + params['shelf_cell_height'] = shelf_cell_height + + params = self.update_translation_params(params) + if params.get('frame_material', None) is None: + params['frame_material'] = np.random.choice(['white', 'black_wood', 'wood'], p=[0.4, 0.3, 0.3]) + if params.get('board_material', None) is None: + params['board_material'] = params['frame_material'] + + params = self.get_material_func(params) + params['tag_support'] = True + return params + + def get_material_func(self, params, randomness=True): + white_wood_params = shader_shelves_white_sampler() + black_wood_params = shader_shelves_black_wood_sampler() + normal_wood_params = shader_shelves_wood_sampler() + if params['frame_material'] == 'white': + if randomness: + params['frame_material'] = lambda x: shader_shelves_white(x, **white_wood_params) + else: + params['frame_material'] = shader_shelves_white + elif params['frame_material'] == 'black_wood': + if randomness: + params['frame_material'] = lambda x: shader_shelves_black_wood(x, **black_wood_params, z_axis_texture=True) + else: + params['frame_material'] = lambda x: shader_shelves_black_wood(x, z_axis_texture=True) + elif params['frame_material'] == 'wood': + if randomness: + params['frame_material'] = lambda x: shader_shelves_wood(x, **normal_wood_params, z_axis_texture=True) + else: + params['frame_material'] = lambda x: shader_shelves_wood(x, z_axis_texture=True) + + if params['board_material'] == 'white': + if randomness: + params['board_material'] = lambda x: shader_shelves_white(x, **white_wood_params) + else: + params['board_material'] = shader_shelves_white + elif params['board_material'] == 'black_wood': + if randomness: + params['board_material'] = lambda x: shader_shelves_black_wood(x, **black_wood_params) + else: + params['board_material'] = shader_shelves_black_wood + elif params['board_material'] == 'wood': + if randomness: + params['board_material'] = lambda x: shader_shelves_wood(x, **normal_wood_params) + else: + params['board_material'] = shader_shelves_wood + + return params + + def update_translation_params(self, params): + cell_widths = params['shelf_cell_width'] + cell_heights = params['shelf_cell_height'] + side_thickness = params['side_board_thickness'] + div_thickness = params['division_board_thickness'] + + # get shelf_width and shelf_height + width = (len(cell_widths) - 1) * side_thickness * 2 + (len(cell_widths) - 1) * 0.001 + height = (len(cell_heights) + 1) * div_thickness + params['bottom_board_height'] + for w in cell_widths: + width += w + for h in cell_heights: + height += h + + params['shelf_width'] = width + params['shelf_height'] = height + params['attach_z_translation'] = height - div_thickness + + # get side_board_x_translation + dist = - (width + side_thickness) / 2. + side_board_x_translation = [dist] + + for w in cell_widths: + dist += side_thickness + w + side_board_x_translation.append(dist) + dist += side_thickness + 0.001 + side_board_x_translation.append(dist) + side_board_x_translation = side_board_x_translation[:-1] + + # get division_board_z_translation + dist = params['bottom_board_height'] + div_thickness / 2. + division_board_z_translation = [dist] + for h in cell_heights: + dist += h + div_thickness + division_board_z_translation.append(dist) + + # get division_board_x_translation + division_board_x_translation = [] + for i in range(len(cell_widths)): + division_board_x_translation.append((side_board_x_translation[2 * i] + side_board_x_translation[2 * i+1]) / 2.) + + params['side_board_x_translation'] = side_board_x_translation + params['division_board_x_translation'] = division_board_x_translation + params['division_board_z_translation'] = division_board_z_translation + params['bottom_gap_x_translation'] = division_board_x_translation + + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, geometry_nodes, attributes=[], apply=True, input_kwargs=obj_params) + + if params.get('ret_params', False): + return obj, obj_params + + tagging.tag_system.relabel_obj(obj) + + return obj + + +class LargeShelfFactory(LargeShelfBaseFactory): + def sample_params(self): + params = dict() + params['Dimensions'] = ( + uniform(0.25, 0.35), + uniform(0.3, 2.0), + uniform(0.9, 2.0) + ) + + params['bottom_board_height'] = 0.083 + params['shelf_depth'] = params['Dimensions'][0] - 0.01 + num_h = int((params['Dimensions'][2] - 0.083) / 0.3) + params['shelf_cell_height'] = [(params['Dimensions'][2] - 0.083) / num_h for _ in range(num_h)] + num_v = max(int(params['Dimensions'][1] / 0.5), 1) + params['shelf_cell_width'] = [params['Dimensions'][1] / num_v for _ in range(num_v)] + return params diff --git a/infinigen/assets/shelves/simple_bookcase.py b/infinigen/assets/shelves/simple_bookcase.py new file mode 100644 index 000000000..6ea9796a9 --- /dev/null +++ b/infinigen/assets/shelves/simple_bookcase.py @@ -0,0 +1,518 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint + +from infinigen.assets.materials.shelf_shaders import get_shelf_material +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t + +import bpy +from infinigen.assets.shelves.utils import nodegroup_tagged_cube + + +@node_utils.to_nodegroup('nodegroup_attach_gadget', singleton=False, type='GeometryNodeTree') +def nodegroup_attach_gadget(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'division_thickness', 0.5000), + ('NodeSocketFloat', 'height', 0.5000), ('NodeSocketFloat', 'attach_thickness', 0.5000), + ('NodeSocketFloat', 'attach_width', 0.5000), ('NodeSocketFloat', 'attach_back_len', 0.5000), + ('NodeSocketFloat', 'attach_top_len', 0.5000), ('NodeSocketFloat', 'depth', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["attach_width"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["attach_top_len"], 1: 0.0000}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["attach_thickness"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_2}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: 0.0000}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: add_1}, attrs={'operation': 'SUBTRACT'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["height"], + 1: group_input.outputs["division_thickness"] + }, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply, 'Z': subtract_1}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_2}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["attach_back_len"], 1: 0.0000}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_2, 'Z': add_4}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={ + 'Size': combine_xyz_1, + 'Vertices X': 5, + 'Vertices Y': 5, + 'Vertices Z': 5 + }) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_4}, attrs={'operation': 'MULTIPLY'}) + + subtract_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: multiply_2}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_1, 'Z': subtract_2}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_1, 'Translation': combine_xyz_3}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'attach1': transform, 'attach2': transform_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_screw_head', singleton=False, type='GeometryNodeTree') +def nodegroup_screw_head(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloatDistance', 'Depth', 0.0050), + ('NodeSocketFloatDistance', 'Radius', 1.0000), ('NodeSocketFloat', 'bottom_gap', 0.5000), + ('NodeSocketFloat', 'division_thickness', 0.5000), ('NodeSocketFloat', 'width', 0.5000), + ('NodeSocketFloat', 'height', 0.5000), ('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'screw_gap', 0.5000)]) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', input_kwargs={ + 'Radius': group_input.outputs["Radius"], + 'Depth': group_input.outputs["Depth"] + }, attrs={'fill_type': 'TRIANGLE_FAN'}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': cylinder.outputs["Mesh"], + 'Rotation': (0.0000, 1.5708, 0.0000) + }) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"]}, + attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["screw_gap"], 1: 0.0000}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: add}, attrs={'operation': 'SUBTRACT'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["division_thickness"]}, + attrs={'operation': 'MULTIPLY'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: multiply_2}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': subtract, 'Z': subtract_1}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Translation': combine_xyz_1}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: group_input.outputs["bottom_gap"]}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': subtract, 'Z': add_1}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform, 'Translation': combine_xyz}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': multiply, 'Y': multiply_3, 'Z': subtract_1}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Translation': combine_xyz_2}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: add_1}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: add_2}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Z': multiply_4}) + + transform_5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Translation': combine_xyz_3}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': multiply_3, 'Z': add_1}) + + transform_6 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Translation': combine_xyz_4}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={ + 'Geometry': [transform_2, transform_1, transform_3, transform_5, transform_6] + }) + + transform_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry_2, 'Scale': (-1.0000, 1.0000, 1.0000)}) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_4, join_geometry_2]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_3}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_back_board', singleton=False, type='GeometryNodeTree') +def nodegroup_back_board(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'width', 0.0000), + ('NodeSocketFloat', 'thickness', 0.5000), ('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'depth', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["width"], 'Y': add, 'Z': add_1}) + + cube_2 = nw.new_node(Nodes.MeshCube, input_kwargs={ + 'Size': combine_xyz_4, + 'Vertices X': 10, + 'Vertices Y': 10, + 'Vertices Z': 10 + }) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: 0.0000}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + multiply_add = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: -0.5000, 2: multiply}, + attrs={'operation': 'MULTIPLY_ADD'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_add, 'Z': multiply_1}) + + transform_5 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube_2, 'Translation': combine_xyz_5}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_5}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_division_board', singleton=False, type='GeometryNodeTree') +def nodegroup_division_board(nw: NodeWrangler, tag_support=False): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'board_thickness', 0.0000), + ('NodeSocketFloat', 'depth', 0.5000), ('NodeSocketFloat', 'width', 0.5000), + ('NodeSocketFloat', 'side_thickness', 0.5000)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["side_thickness"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: multiply}, + attrs={'operation': 'SUBTRACT'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: 0.0000}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': subtract, + 'Y': add, + 'Z': group_input.outputs["board_thickness"] + }) + + if tag_support: + cube_1 = nw.new_node(nodegroup_tagged_cube().name, input_kwargs={'Size': combine_xyz_3}) + else: + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={ + 'Size': combine_xyz_3, + 'Vertices X': 10, + 'Vertices Y': 10, + 'Vertices Z': 10 + }) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': cube_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_division_boards', singleton=False, type='GeometryNodeTree') +def nodegroup_division_boards(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'thickness', 0.5000), + ('NodeSocketFloat', 'height', 0.5000), ('NodeSocketFloat', 'gap', 0.5000), + ('NodeSocketGeometry', 'Geometry', None)]) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, + input_kwargs={'Geometry': group_input.outputs["Geometry"]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"]}, + attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["gap"], 1: multiply}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': realize_instances_1, 'Translation': combine_xyz_1}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: multiply}, + attrs={'operation': 'SUBTRACT'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: add}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_1}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': realize_instances_1, 'Translation': combine_xyz_2}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': subtract}) + + transform_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': realize_instances_1, 'Translation': combine_xyz}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={ + 'board1': transform_2, + 'board2': transform_3, + 'board3': transform_4 + }, attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_side_board', singleton=False, type='GeometryNodeTree') +def nodegroup_side_board(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'board_thickness', 0.5000), + ('NodeSocketFloat', 'depth', 0.5000), ('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'width', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["board_thickness"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["depth"], 1: 0.0000}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["height"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1, 'Z': add_2}) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 10, 'Vertices Y': 10, 'Vertices Z': 10}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"], 1: 0.0000}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: add}, attrs={'operation': 'SUBTRACT'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_2, 1: 0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Z': multiply_1}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_1}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: 0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_2, 'Z': multiply_1}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_2}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, transform_1]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_1}, + attrs={'is_active_output': True}) + + +def geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + side_board_thickness = nw.new_node(Nodes.Value, label='side_board_thickness') + side_board_thickness.outputs[0].default_value = kwargs['side_board_thickness'] + + shelf_depth = nw.new_node(Nodes.Value, label='shelf_depth') + shelf_depth.outputs[0].default_value = kwargs['depth'] + + shelf_height = nw.new_node(Nodes.Value, label='shelf_height') + shelf_height.outputs[0].default_value = kwargs['height'] + + shelf_width = nw.new_node(Nodes.Value, label='shelf_width') + shelf_width.outputs[0].default_value = kwargs['width'] + + side_board = nw.new_node(nodegroup_side_board().name, input_kwargs={ + 'board_thickness': side_board_thickness, + 'depth': shelf_depth, + 'height': shelf_height, + 'width': shelf_width + }) + + division_board_thickness = nw.new_node(Nodes.Value, label='division_board_thickness') + division_board_thickness.outputs[0].default_value = kwargs['division_board_thickness'] + + bottom_gap = nw.new_node(Nodes.Value, label='bottom_gap') + bottom_gap.outputs[0].default_value = kwargs['bottom_gap'] + + division_board = nw.new_node(nodegroup_division_board(tag_support=kwargs['tag_support']).name, + input_kwargs={ + 'board_thickness': division_board_thickness, + 'depth': shelf_depth, + 'width': shelf_width, + 'side_thickness': side_board_thickness + }) + + division_boards = nw.new_node(nodegroup_division_boards().name, input_kwargs={ + 'thickness': division_board_thickness, + 'height': shelf_height, + 'gap': bottom_gap, + 'Geometry': division_board + }) + + backboard_thickness = nw.new_node(Nodes.Value, label='backboard_thickness') + backboard_thickness.outputs[0].default_value = kwargs['backboard_thickness'] + + back_board = nw.new_node(nodegroup_back_board().name, input_kwargs={ + 'width': shelf_width, + 'thickness': backboard_thickness, + 'height': shelf_height, + 'depth': shelf_depth + }) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={ + 'Geometry': [side_board, division_boards.outputs["board1"], division_boards.outputs["board2"], + back_board, division_boards.outputs["board3"]] + }) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry}) + + set_material = nw.new_node(Nodes.SetMaterial, input_kwargs={ + 'Geometry': realize_instances, + 'Material': kwargs['frame_material'] + }) + + screw_depth_head = nw.new_node(Nodes.Value, label='screw_depth_head') + screw_depth_head.outputs[0].default_value = kwargs['screw_head_depth'] + + screw_head_radius = nw.new_node(Nodes.Value, label='screw_head_radius') + screw_head_radius.outputs[0].default_value = kwargs['screw_head_radius'] + + screw_head_gap = nw.new_node(Nodes.Value, label='screw_head_gap') + screw_head_gap.outputs[0].default_value = kwargs['screw_head_dist'] + + screw_head = nw.new_node(nodegroup_screw_head().name, input_kwargs={ + 'Depth': screw_depth_head, + 'Radius': screw_head_radius, + 'bottom_gap': bottom_gap, + 'division_thickness': division_board_thickness, + 'width': shelf_width, + 'height': shelf_height, + 'depth': shelf_depth, + 'screw_gap': screw_head_gap + }) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': screw_head}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, input_kwargs={ + 'Geometry': realize_instances_1, + 'Material': kwargs['metal_material'] + }) + + attach_thickness = nw.new_node(Nodes.Value, label='attach_thickness') + attach_thickness.outputs[0].default_value = kwargs['attach_thickness'] + + attach_width = nw.new_node(Nodes.Value, label='attach_width') + attach_width.outputs[0].default_value = kwargs['attach_width'] + + attach_back_length = nw.new_node(Nodes.Value, label='attach_back_length') + attach_back_length.outputs[0].default_value = kwargs['attach_back_length'] + + attach_top_length = nw.new_node(Nodes.Value, label='attach_top_length') + attach_top_length.outputs[0].default_value = kwargs['attach_top_length'] + + attach_gadget = nw.new_node(nodegroup_attach_gadget().name, input_kwargs={ + 'division_thickness': division_board_thickness, + 'height': shelf_height, + 'attach_thickness': attach_thickness, + 'attach_width': attach_width, + 'attach_back_len': attach_back_length, + 'attach_top_len': attach_top_length, + 'depth': shelf_depth + }) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={ + 'Geometry': [attach_gadget.outputs["attach1"], attach_gadget.outputs["attach2"]] + }) + + realize_instances_2 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_2}) + + set_material_2 = nw.new_node(Nodes.SetMaterial, input_kwargs={ + 'Geometry': realize_instances_2, + 'Material': kwargs['metal_material'] + }) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [set_material, set_material_1, set_material_2]}) + + realize_instances_3 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_1}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': realize_instances_3}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': triangulate, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, + attrs={'is_active_output': True}) + + +class SimpleBookcaseBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(SimpleBookcaseBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = params + + def sample_params(self): + return self.params.copy() + + def get_asset_params(self, i=0): + params = self.sample_params() + if params.get('depth', None) is None: + params['depth'] = np.clip(normal(0.3, 0.05), 0.15, 0.45) + if params.get('width', None) is None: + params['width'] = np.clip(normal(0.5, 0.1), 0.25, 0.75) + if params.get('height', None) is None: + params['height'] = np.clip(normal(0.8, 0.1), 0.5, 1.0) + params['side_board_thickness'] = uniform(0.005, 0.03) + params['division_board_thickness'] = np.clip(normal(0.015, 0.005), 0.005, 0.025) + params['bottom_gap'] = np.clip(normal(0.14, 0.05), 0.0, 0.2) + params['backboard_thickness'] = uniform(0.01, 0.02) + params['screw_head_depth'] = uniform(0.002, 0.008) + params['screw_head_radius'] = uniform(0.003, 0.008) + params['screw_head_dist'] = uniform(0.03, 0.1) + params['attach_thickness'] = uniform(0.002, 0.005) + params['attach_width'] = uniform(0.01, 0.04) + params['attach_top_length'] = uniform(0.03, 0.1) + params['attach_back_length'] = uniform(0.02, 0.05) + params['frame_material'] = get_shelf_material('white') + params['metal_material'] = get_shelf_material('metal') + params['tag_support'] = True + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), + scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, geometry_nodes, apply=True, attributes=[], input_kwargs=obj_params) + tagging.tag_system.relabel_obj(obj) + + return obj + + +class SimpleBookcaseFactory(SimpleBookcaseBaseFactory): + def sample_params(self): + params = dict() + params['Dimensions'] = (uniform(0.25, 0.4), uniform(0.5, 0.7), uniform(0.7, 0.9)) + params['depth'] = params['Dimensions'][0] - 0.015 + params['width'] = params['Dimensions'][1] + params['height'] = params['Dimensions'][2] + return params diff --git a/infinigen/assets/shelves/simple_desk.py b/infinigen/assets/shelves/simple_desk.py new file mode 100644 index 000000000..440ae9fe5 --- /dev/null +++ b/infinigen/assets/shelves/simple_desk.py @@ -0,0 +1,267 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t + +import bpy +from infinigen.assets.shelves.utils import nodegroup_tagged_cube +from infinigen.assets.materials.shelf_shaders import ( + shader_shelves_white, shader_shelves_white_sampler, + shader_shelves_black_wood, shader_shelves_black_wood_sampler, + shader_shelves_white_metallic, shader_shelves_white_metallic_sampler, + shader_shelves_black_metallic, shader_shelves_black_metallic_sampler) + + +@node_utils.to_nodegroup('nodegroup_table_legs', singleton=False, type='GeometryNodeTree') +def nodegroup_table_legs(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'thickness', 0.5000), + ('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloatDistance', 'radius', 0.0200), + ('NodeSocketFloat', 'width', 0.5000), + ('NodeSocketFloat', 'depth', 0.5000), + ('NodeSocketFloat', 'dist', 0.5000)]) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["height"], 1: group_input.outputs["thickness"]}, + attrs={'operation': 'SUBTRACT'}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Radius': group_input.outputs["radius"], 'Depth': subtract, 'Vertices': 128}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["width"]}, attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["dist"], 1: 0.0000}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: add}, attrs={'operation': 'SUBTRACT'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={1: group_input.outputs["depth"]}, + attrs={'operation': 'MULTIPLY'}) + + subtract_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: add}, attrs={'operation': 'SUBTRACT'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_1, 'Y': subtract_2, 'Z': multiply_2}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz_2}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': subtract_2, 'Z': multiply_2}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz_3}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_2, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_1, 'Y': multiply_4, 'Z': multiply_2}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz_4}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': multiply_4, 'Z': multiply_2}) + + transform_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz_5}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform, transform_2, transform_3, transform_4]}) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry_1}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': realize_instances_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_table_top', singleton=False, type='GeometryNodeTree') +def nodegroup_table_top(nw: NodeWrangler, tag_support=True): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'depth', 0.0000), + ('NodeSocketFloat', 'width', 0.0000), + ('NodeSocketFloat', 'height', 0.5000), + ('NodeSocketFloat', 'thickness', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["thickness"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["width"], 'Y': group_input.outputs["depth"], + 'Z': add}) + + if tag_support: + cube = nw.new_node(nodegroup_tagged_cube().name, input_kwargs={'Size': combine_xyz}) + + else: + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 10, 'Vertices Y': 10, 'Vertices Z': 10}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["height"], 1: multiply}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': subtract}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_1}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_1}, + attrs={'is_active_output': True}) + + +def geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + table_depth = nw.new_node(Nodes.Value, label='table_depth') + table_depth.outputs[0].default_value = kwargs['depth'] + + table_width = nw.new_node(Nodes.Value, label='table_width') + table_width.outputs[0].default_value = kwargs['width'] + + table_height = nw.new_node(Nodes.Value, label='table_height') + table_height.outputs[0].default_value = kwargs['height'] + + top_thickness = nw.new_node(Nodes.Value, label='top_thickness') + top_thickness.outputs[0].default_value = kwargs['thickness'] + + table_top = nw.new_node(nodegroup_table_top(tag_support=True).name, + input_kwargs={'depth': table_depth, 'width': table_width, 'height': table_height, + 'thickness': top_thickness}) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': table_top, + 'Material': surface.shaderfunc_to_material(kwargs['top_material'])}) + + leg_radius = nw.new_node(Nodes.Value, label='leg_radius') + leg_radius.outputs[0].default_value = kwargs['leg_radius'] + + leg_center_to_edge = nw.new_node(Nodes.Value, label='leg_center_to_edge') + leg_center_to_edge.outputs[0].default_value = kwargs['leg_dist'] + + table_legs = nw.new_node(nodegroup_table_legs().name, + input_kwargs={'thickness': top_thickness, 'height': table_height, 'radius': leg_radius, + 'width': table_width, 'depth': table_depth, 'dist': leg_center_to_edge}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': table_legs, + 'Material': surface.shaderfunc_to_material(kwargs['leg_material'])}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material, set_material_1]}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': realize_instances}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': triangulate, 'Rotation': (0.0000, 0.0000, 1.5708)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, + attrs={'is_active_output': True}) + + +class SimpleDeskBaseFactory(AssetFactory): + + def __init__(self, factory_seed, params={}, coarse=False): + super(SimpleDeskBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = params + + def sample_params(self): + return self.params.copy() + + def get_asset_params(self, i=0): + params = self.sample_params() + if params.get('depth', None) is None: + params['depth'] = np.clip(normal(0.6, 0.05), 0.45, 0.7) + if params.get('width', None) is None: + params['width'] = np.clip(normal(1.0, 0.1), 0.7, 1.3) + if params.get('height', None) is None: + params['height'] = np.clip(normal(0.73, 0.05), 0.6, 0.83) + if params.get('top_material', None) is None: + params['top_material'] = np.random.choice(['white', 'black_wood']) + if params.get('leg_material', None) is None: + params['leg_material'] = np.random.choice(['white', 'black']) + if params.get('leg_radius', None) is None: + params['leg_radius'] = uniform(0.01, 0.025) + if params.get('leg_dist', None) is None: + params['leg_dist'] = uniform(0.035, 0.07) + if params.get('thickness', None) is None: + params['thickness'] = uniform(0.01, 0.03) + + params = self.get_material_func(params) + return params + + def get_material_func(self, params, randomness=True): + if params['top_material'] == 'white': + if randomness: + params['top_material'] = lambda x: shader_shelves_white(x, **shader_shelves_white_sampler()) + else: + params['top_material'] = shader_shelves_white + elif params['top_material'] == 'black_wood': + if randomness: + params['top_material'] = lambda x: shader_shelves_black_wood(x, **shader_shelves_black_wood_sampler()) + else: + params['top_material'] = shader_shelves_black_wood + else: + raise NotImplementedError + + if params['leg_material'] == 'white': + if randomness: + params['leg_material'] = lambda x: shader_shelves_white_metallic(x, **shader_shelves_white_metallic_sampler()) + else: + params['leg_material'] = shader_shelves_white_metallic + elif params['leg_material'] == 'black': + if randomness: + params['leg_material'] = lambda x: shader_shelves_black_metallic(x, **shader_shelves_black_metallic_sampler()) + else: + params['leg_material'] = shader_shelves_black_metallic + else: + raise NotImplementedError + + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, geometry_nodes, attributes=[], apply=True, input_kwargs=obj_params) + tagging.tag_system.relabel_obj(obj) + + return obj + + +class SimpleDeskFactory(SimpleDeskBaseFactory): + def sample_params(self): + params = dict() + params['Dimensions'] = (uniform(0.5, 0.75), + uniform(0.8, 2), + uniform(0.6, 0.8)) + params['depth'] = params['Dimensions'][0] + params['width'] = params['Dimensions'][1] + params['height'] = params['Dimensions'][2] + return params + +class SidetableDeskFactory(SimpleDeskBaseFactory): + def sample_params(self): + params = dict() + w = 0.55 * normal(1, 0.1) + params['Dimensions'] = (w, w, w * normal(1, 0.05)) + params['depth'] = params['Dimensions'][0] + params['width'] = params['Dimensions'][1] + params['height'] = params['Dimensions'][2] + return params + diff --git a/infinigen/assets/shelves/single_cabinet.py b/infinigen/assets/shelves/single_cabinet.py new file mode 100644 index 000000000..14dc77257 --- /dev/null +++ b/infinigen/assets/shelves/single_cabinet.py @@ -0,0 +1,235 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t + +import bpy +from infinigen.assets.shelves.utils import nodegroup_tagged_cube, blender_rotate +from infinigen.assets.shelves.large_shelf import LargeShelfBaseFactory +from infinigen.assets.shelves.doors import CabinetDoorBaseFactory + +from infinigen.core.util.math import FixedSeed +from infinigen.assets.utils.object import new_bbox + +def geometry_cabinet_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + right_door_info = nw.new_node(Nodes.ObjectInfo, input_kwargs={'Object': kwargs['door'][0]}) + left_door_info = nw.new_node(Nodes.ObjectInfo, input_kwargs={'Object': kwargs['door'][1]}) + shelf_info = nw.new_node(Nodes.ObjectInfo, input_kwargs={'Object': kwargs['shelf']}) + + doors = [] + transform_r = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': right_door_info.outputs['Geometry'], + 'Translation': kwargs['door_hinge_pos'][0], + 'Rotation': (0, 0, kwargs['door_open_angle'])}) + doors.append(transform_r) + if len(kwargs['door_hinge_pos']) > 1: + transform_l = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': left_door_info.outputs['Geometry'], + 'Translation': kwargs['door_hinge_pos'][1], + 'Rotation': (0, 0, kwargs['door_open_angle'])}) + doors.append(transform_l) + + attaches = [] + for pos in kwargs['attach_pos']: + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': (0.0006, 0.0200, 0.04500)}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': -0.0100}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz}) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': (0.0005, 0.0340, 0.0200)}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, cube_1]}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry, 'Translation': (0.0000, -0.0170, 0.0000)}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_1, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_2, 'Translation': pos}) + + attaches.append(transform_3) + + join_geometry_a = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': attaches}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': doors + [join_geometry_a]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, + attrs={'is_active_output': True}) + + +class SingleCabinetBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(SingleCabinetBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.shelf_params = {} + self.door_params = {} + self.mat_params = {} + self.shelf_fac = LargeShelfBaseFactory(factory_seed) + self.door_fac = CabinetDoorBaseFactory(factory_seed) + with FixedSeed(factory_seed): + self.params = self.sample_params() + + def sample_params(self): + # Update fac params + pass + + def get_material_params(self): + with FixedSeed(self.factory_seed): + params = self.mat_params.copy() + if params.get('frame_material', None) is None: + params['frame_material'] = np.random.choice(['white', 'black_wood', 'wood'], p=[0.5, 0.2, 0.3]) + return params + + def get_shelf_params(self, i=0): + params = self.shelf_params.copy() + if params.get('shelf_cell_width', None) is None: + params['shelf_cell_width'] = [np.random.choice([0.76, 0.36], p=[0.5, 0.5]) * + np.clip(normal(1., 0.1), 0.75, 1.25)] + if params.get('shelf_cell_height', None) is None: + num_v_cells = randint(3, 7) + shelf_cell_height = [] + for i in range(num_v_cells): + shelf_cell_height.append(0.3 * np.clip(normal(1., 0.06), 0.75, 1.25)) + params['shelf_cell_height'] = shelf_cell_height + if params.get('frame_material', None) is None: + params['frame_material'] = self.mat_params['frame_material'] + + return params + + def get_door_params(self, i=0): + params = self.door_params.copy() + + # get door params + shelf_width = self.shelf_params['shelf_width'] + self.shelf_params['side_board_thickness'] * 2 + if params.get('door_width', None) is None: + if shelf_width < 0.55: + params['door_width'] = shelf_width + params['num_door'] = 1 + else: + params['door_width'] = shelf_width / 2. - 0.0005 + params['num_door'] = 2 + if params.get('door_height', None) is None: + params['door_height'] = (self.shelf_params['division_board_z_translation'][-1] - + self.shelf_params['division_board_z_translation'][0] + + self.shelf_params['division_board_thickness']) + if len(self.shelf_params['division_board_z_translation']) > 5 and \ + np.random.choice([True, False], p=[0.5, 0.5]): + params['door_height'] = (self.shelf_params['division_board_z_translation'][3] - + self.shelf_params['division_board_z_translation'][0] + + self.shelf_params['division_board_thickness']) + if params.get('frame_material', None) is None: + params['frame_material'] = self.mat_params['frame_material'] + + return params + + def get_cabinet_params(self, i=0): + params = dict() + + shelf_width = self.shelf_params['shelf_width'] + self.shelf_params['side_board_thickness'] * 2 + if self.door_params['num_door'] == 1: + params['door_hinge_pos'] = [(self.shelf_params['shelf_depth'] / 2. + 0.0025, -shelf_width / 2., + self.shelf_params['bottom_board_height'])] + params['door_open_angle'] = 0 + params['attach_pos'] = [ + (self.shelf_params['shelf_depth'] / 2., -self.shelf_params['shelf_width'] / 2., + self.shelf_params['bottom_board_height'] + z) for z in self.door_params['attach_height'] + ] + elif self.door_params['num_door'] == 2: + params['door_hinge_pos'] = [(self.shelf_params['shelf_depth'] / 2. + 0.008, -shelf_width / 2., + self.shelf_params['bottom_board_height']), + (self.shelf_params['shelf_depth'] / 2. + 0.008, shelf_width / 2., + self.shelf_params['bottom_board_height'])] + params['door_open_angle'] = 0 + params['attach_pos'] = [ + (self.shelf_params['shelf_depth'] / 2., -self.shelf_params['shelf_width'] / 2., + self.shelf_params['bottom_board_height'] + z) for z in self.door_params['attach_height'] + ] + [ + (self.shelf_params['shelf_depth'] / 2., self.shelf_params['shelf_width'] / 2., + self.shelf_params['bottom_board_height'] + z) for z in self.door_params['attach_height'] + ] + else: + raise NotImplementedError + + return params + + def get_cabinet_components(self, i): + # update material params + self.mat_params = self.get_material_params() + + # create shelf + shelf_params = self.get_shelf_params(i=i) + self.shelf_fac.params = shelf_params + shelf, shelf_params = self.shelf_fac.create_asset(i=i, ret_params=True) + shelf.name = 'cabinet_frame' + self.shelf_params = shelf_params + + # create doors + door_params = self.get_door_params(i=i) + self.door_fac.params = door_params + self.door_fac.params['door_left_hinge'] = False + right_door, door_obj_params = self.door_fac.create_asset(i=i, ret_params=True) + right_door.name = 'cabinet_right_door' + self.door_fac.params = door_obj_params + self.door_fac.params['door_left_hinge'] = True + left_door, _ = self.door_fac.create_asset(i=i, ret_params=True) + left_door.name = 'cabinet_left_door' + self.door_params = door_obj_params + + return shelf, right_door, left_door + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + shelf, right_door, left_door = self.get_cabinet_components(i=i) + + # create cabinet + cabinet_params = self.get_cabinet_params(i=i) + surface.add_geomod(obj, geometry_cabinet_nodes, attributes=[], apply=True, input_kwargs={ + 'door': [right_door, left_door], + 'shelf': shelf, + 'door_hinge_pos': cabinet_params['door_hinge_pos'], + 'door_open_angle': cabinet_params['door_open_angle'], + 'attach_pos': cabinet_params['attach_pos'] + }) + butil.delete([left_door, right_door]) + obj = butil.join_objects([shelf, obj]) + + tagging.tag_system.relabel_obj(obj) + return obj + + +class SingleCabinetFactory(SingleCabinetBaseFactory): + def sample_params(self): + params = dict() + params['Dimensions'] = ( + uniform(0.25, 0.35), + uniform(0.3, 0.7), + uniform(0.9, 1.8) + ) + + params['bottom_board_height'] = 0.083 + params['shelf_depth'] = params['Dimensions'][0] - 0.01 + num_h = int((params['Dimensions'][2] - 0.083) / 0.3) + params['shelf_cell_height'] = [(params['Dimensions'][2] - 0.083) / num_h for _ in range(num_h)] + params['shelf_cell_width'] = [params['Dimensions'][1]] + self.shelf_params = params + self.dims = params['Dimensions'] + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + x,y,z = self.dims + return new_bbox(-x/2 * 1.2, x/2 * 1.2, -y/2 * 1.2, y/2 * 1.2, 0, (z + 0.083) * 1.02) diff --git a/infinigen/assets/shelves/triangle_shelf.py b/infinigen/assets/shelves/triangle_shelf.py new file mode 100644 index 000000000..3eb60ef98 --- /dev/null +++ b/infinigen/assets/shelves/triangle_shelf.py @@ -0,0 +1,870 @@ +# 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: Beining Han + +from numpy.random import uniform, normal, randint + +from infinigen.assets.materials.shelf_shaders import get_shelf_material +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t + +import bpy + +from infinigen.assets.shelves.utils import nodegroup_tagged_cube + + +@node_utils.to_nodegroup('nodegroup_table_profile', singleton=False, type='GeometryNodeTree') +def nodegroup_table_profile(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketInt', 'Profile N-gon', 4), + ('NodeSocketFloat', 'Profile Width', 1.0000), ('NodeSocketFloat', 'Profile Aspect Ratio', 1.0000), + ('NodeSocketFloat', 'Profile Fillet Ratio', 0.2000)]) + + value = nw.new_node(Nodes.Value) + value.outputs[0].default_value = 0.7071 + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={ + 'Resolution': group_input.outputs["Profile N-gon"], + 'Radius': value + }) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: 3.1416, 1: group_input.outputs["Profile N-gon"]}, + attrs={'operation': 'DIVIDE'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': divide}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle.outputs["Curve"], 'Rotation': combine_xyz_1}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["Profile Aspect Ratio"], + 1: group_input.outputs["Profile Width"] + }, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': group_input.outputs["Profile Width"], + 'Y': multiply, + 'Z': 1.0000 + }) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_2, 'Scale': combine_xyz}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["Profile Width"], + 1: group_input.outputs["Profile Fillet Ratio"] + }, attrs={'operation': 'MULTIPLY'}) + + fillet_curve_1 = nw.new_node('GeometryNodeFilletCurve', input_kwargs={ + 'Curve': transform_1, + 'Count': 4, + 'Radius': multiply_1, + 'Limit Radius': True + }, attrs={'mode': 'POLY'}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Output': fillet_curve_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_curve_to_board', singleton=False, type='GeometryNodeTree') +def nodegroup_curve_to_board(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Profile Curve', None), + ('NodeSocketGeometry', 'Shape Curve', None), ('NodeSocketFloat', 'Height', 0.5000)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_1}) + + set_curve_tilt = nw.new_node(Nodes.SetCurveTilt, input_kwargs={'Curve': curve_line, 'Tilt': 3.1416}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, + input_kwargs={'Curve': set_curve_tilt, 'Count': 128, 'Length': 0.0500}) + + spline_parameter_1 = nw.new_node(Nodes.SplineParameter) + + capture_attribute = nw.new_node(Nodes.CaptureAttribute, input_kwargs={ + 'Geometry': resample_curve, + 2: spline_parameter_1.outputs["Factor"] + }) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': capture_attribute.outputs["Geometry"], + 'Profile Curve': group_input.outputs["Shape Curve"], + 'Fill Caps': True + }) + + position_1 = nw.new_node(Nodes.InputPosition) + + separate_xyz_2 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position_1}) + + sample_curve = nw.new_node(Nodes.SampleCurve, input_kwargs={ + 'Curve': group_input.outputs["Profile Curve"], + 'Factor': capture_attribute.outputs[2] + }, attrs={'mode': 'FACTOR'}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': sample_curve.outputs["Position"]}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz.outputs["X"], 'Y': separate_xyz.outputs["Y"]}) + + length = nw.new_node(Nodes.VectorMath, input_kwargs={0: combine_xyz}, attrs={'operation': 'LENGTH'}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["X"], 1: length.outputs["Value"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Y"], 1: length.outputs["Value"]}, + attrs={'operation': 'MULTIPLY'}) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + attribute_statistic = nw.new_node(Nodes.AttributeStatistic, input_kwargs={ + 'Geometry': group_input.outputs["Profile Curve"], + 2: separate_xyz_1.outputs["Z"] + }) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={ + 'Value': separate_xyz.outputs["Z"], + 1: attribute_statistic.outputs["Min"], + 2: attribute_statistic.outputs["Max"], + 3: multiply, + 4: 0.0000 + }) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': multiply_1, + 'Y': multiply_2, + 'Z': map_range.outputs["Result"] + }) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': curve_to_mesh, 'Position': combine_xyz_2}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': set_position}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_leg_straight', singleton=False, type='GeometryNodeTree') +def nodegroup_leg_straight(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Profile Curve', None), + ('NodeSocketFloat', 'Height', 0.5000), ('NodeSocketInt', 'N-gon', 0), + ('NodeSocketFloat', 'Profile Width', 0.5000), ('NodeSocketFloat', 'Aspect Ratio', 0.5000), + ('NodeSocketFloat', 'Fillet Ratio', 0.2000), ('NodeSocketInt', 'Resolution', 128)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_1}) + + set_curve_tilt = nw.new_node(Nodes.SetCurveTilt, input_kwargs={'Curve': curve_line, 'Tilt': 3.1416}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, input_kwargs={ + 'Curve': set_curve_tilt, + 'Count': group_input.outputs["Resolution"], + 'Length': 0.0500 + }) + + spline_parameter_1 = nw.new_node(Nodes.SplineParameter) + + capture_attribute = nw.new_node(Nodes.CaptureAttribute, input_kwargs={ + 'Geometry': resample_curve, + 2: spline_parameter_1.outputs["Factor"] + }) + + tableprofile = nw.new_node(nodegroup_table_profile().name, input_kwargs={ + 'Profile N-gon': group_input.outputs["N-gon"], + 'Profile Width': group_input.outputs["Profile Width"], + 'Profile Aspect Ratio': group_input.outputs["Aspect Ratio"], + 'Profile Fillet Ratio': group_input.outputs["Fillet Ratio"] + }) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': capture_attribute.outputs["Geometry"], + 'Profile Curve': tableprofile, + 'Fill Caps': True + }) + + position_1 = nw.new_node(Nodes.InputPosition) + + separate_xyz_2 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position_1}) + + sample_curve = nw.new_node(Nodes.SampleCurve, input_kwargs={ + 'Curve': group_input.outputs["Profile Curve"], + 'Factor': capture_attribute.outputs[2] + }, attrs={'mode': 'FACTOR'}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': sample_curve.outputs["Position"]}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz.outputs["X"], 'Y': separate_xyz.outputs["Y"]}) + + length = nw.new_node(Nodes.VectorMath, input_kwargs={0: combine_xyz}, attrs={'operation': 'LENGTH'}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["X"], 1: length.outputs["Value"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Y"], 1: length.outputs["Value"]}, + attrs={'operation': 'MULTIPLY'}) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + attribute_statistic = nw.new_node(Nodes.AttributeStatistic, input_kwargs={ + 'Geometry': group_input.outputs["Profile Curve"], + 2: separate_xyz_1.outputs["Z"] + }) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={ + 'Value': separate_xyz.outputs["Z"], + 1: attribute_statistic.outputs["Min"], + 2: attribute_statistic.outputs["Max"], + 3: multiply, + 4: 0.0000 + }) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': multiply_1, + 'Y': multiply_2, + 'Z': map_range.outputs["Result"] + }) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': curve_to_mesh, 'Position': combine_xyz_2}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Mesh': set_position, 'Profile Curve': tableprofile}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_curve_board', singleton=False, type='GeometryNodeTree') +def nodegroup_curve_board(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + curve_line = nw.new_node(Nodes.CurveLine, + input_kwargs={'Start': (1.0000, 0.0000, -1.0000), 'End': (1.0000, 0.0000, 1.0000)}) + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'Thickness', 0.5000), + ('NodeSocketFloat', 'Fillet Radius Vertical', 0.0000), ('NodeSocketFloat', 'width', 0.0000), + ('NodeSocketFloat', 'extrude_length', 0.0000)]) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["width"]}) + + curve_line_1 = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_3}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': group_input.outputs["width"]}) + + curve_line_2 = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_4}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': group_input.outputs["width"], + 'Y': group_input.outputs["extrude_length"] + }) + + curve_line_3 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz_3, 'End': combine_xyz_6}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': group_input.outputs["extrude_length"], + 'Y': group_input.outputs["width"] + }) + + curve_line_4 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz_4, 'End': combine_xyz_5}) + + curve_line_5 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz_6, 'End': combine_xyz_5}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={ + 'Geometry': [curve_line_1, curve_line_2, curve_line_3, curve_line_4, curve_line_5] + }) + + curve_to_mesh_1 = nw.new_node(Nodes.CurveToMesh, input_kwargs={'Curve': join_geometry_1}) + + merge_by_distance_1 = nw.new_node(Nodes.MergeByDistance, input_kwargs={'Geometry': curve_to_mesh_1}) + + mesh_to_curve = nw.new_node(Nodes.MeshToCurve, input_kwargs={'Mesh': merge_by_distance_1}) + + curve_to_board = nw.new_node(nodegroup_curve_to_board().name, input_kwargs={ + 'Profile Curve': curve_line, + 'Shape Curve': mesh_to_curve, + 'Height': group_input.outputs["Thickness"] + }) + + arc = nw.new_node('GeometryNodeCurveArc', + input_kwargs={'Resolution': 4, 'Radius': 0.7071, 'Sweep Angle': 4.7124}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': arc.outputs["Curve"], + 'Rotation': (0.0000, 0.0000, -0.7854) + }) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Rotation': (0.0000, 1.5708, 0.0000)}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_2, 'Translation': (0.0000, 0.5000, 0.0000)}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': group_input, 'Z': 1.0000}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_3, 'Scale': combine_xyz}) + + fillet_curve = nw.new_node('GeometryNodeFilletCurve', input_kwargs={ + 'Curve': transform_4, + 'Count': 8, + 'Radius': group_input, + 'Limit Radius': True + }, attrs={'mode': 'POLY'}) + + transform_6 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': fillet_curve, + 'Rotation': (1.5708, 1.5708, 0.0000), + 'Scale': group_input.outputs["Thickness"] + }) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={'Profile Curve': transform_6}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Thickness"], 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + transform_5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_to_mesh, 'Translation': combine_xyz_1}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [curve_to_board.outputs["Mesh"], transform_5]}) + + merge_by_distance = nw.new_node(Nodes.MergeByDistance, input_kwargs={'Geometry': join_geometry}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Thickness"]}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': merge_by_distance, 'Translation': combine_xyz_2}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_side_leg', singleton=False, type='GeometryNodeTree') +def nodegroup_side_leg(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + curve_line = nw.new_node(Nodes.CurveLine, + input_kwargs={'Start': (1.0000, 0.0000, -1.0000), 'End': (1.0000, 0.0000, 1.0000)}) + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'Thickness', 0.5000), + ('NodeSocketInt', 'N-gon', 0), ('NodeSocketFloat', 'Profile Width', 0.5000), + ('NodeSocketFloat', 'Aspect Ratio', 0.5000), ('NodeSocketFloat', 'Fillet Ratio', 0.2000), + ('NodeSocketFloat', 'Fillet Radius Vertical', 0.0000)]) + + legstraight = nw.new_node(nodegroup_leg_straight().name, input_kwargs={ + 'Profile Curve': curve_line, + 'Height': group_input.outputs["Thickness"], + 'N-gon': group_input.outputs["N-gon"], + 'Profile Width': group_input.outputs["Profile Width"], + 'Aspect Ratio': group_input.outputs["Aspect Ratio"], + 'Fillet Ratio': group_input.outputs["Fillet Ratio"] + }) + + arc = nw.new_node('GeometryNodeCurveArc', + input_kwargs={'Resolution': 4, 'Radius': 0.7071, 'Sweep Angle': 4.7124}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': arc.outputs["Curve"], + 'Rotation': (0.0000, 0.0000, -0.7854) + }) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Rotation': (0.0000, 1.5708, 0.0000)}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_2, 'Translation': (0.0000, 0.5000, 0.0000)}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': group_input, 'Z': 1.0000}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_3, 'Scale': combine_xyz}) + + fillet_curve = nw.new_node('GeometryNodeFilletCurve', input_kwargs={ + 'Curve': transform_4, + 'Count': 8, + 'Radius': group_input, + 'Limit Radius': True + }, attrs={'mode': 'POLY'}) + + transform_6 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': fillet_curve, + 'Rotation': (1.5708, 1.5708, 0.0000), + 'Scale': group_input.outputs["Thickness"] + }) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': legstraight.outputs["Profile Curve"], + 'Profile Curve': transform_6 + }) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Thickness"], 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + transform_5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_to_mesh, 'Translation': combine_xyz_1}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_5, legstraight.outputs["Mesh"]]}) + + merge_by_distance = nw.new_node(Nodes.MergeByDistance, input_kwargs={'Geometry': join_geometry}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Thickness"]}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': merge_by_distance, 'Translation': combine_xyz_2}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_side_boards', singleton=False, type='GeometryNodeTree') +def nodegroup_side_boards(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Y', 0.0000), ('NodeSocketFloat', 'Z', 0.0000), + ('NodeSocketFloat', 'x1', 0.5000), ('NodeSocketFloat', 'x2', 0.5000), + ('NodeSocketFloat', 'x3', 0.0010), ('NodeSocketFloat', 'x4', 0.5000), + ('NodeSocketFloat', 'x5', 0.5000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["x5"], 1: 0.0000}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': add, + 'Y': group_input.outputs["Y"], + 'Z': group_input.outputs["Z"] + }) + + cube = nw.new_node(Nodes.MeshCube, + input_kwargs={'Size': combine_xyz, 'Vertices X': 5, 'Vertices Y': 5, 'Vertices Z': 5}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: group_input.outputs["x3"]}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["x1"]}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["x2"], 1: multiply_1}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_1, 'Z': subtract}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_1}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["x4"], 1: multiply_1}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_1, 'Z': subtract_1}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube, 'Translation': combine_xyz_2}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, transform_1]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_shelf_boards', singleton=False, type='GeometryNodeTree') +def nodegroup_shelf_boards(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'Thickness', 0.0100), + ('NodeSocketFloat', 'Bottom_z', 0.0000), ('NodeSocketFloat', 'Mid_z', 0.0000), + ('NodeSocketFloat', 'Top_z', 0.0000), ('NodeSocketFloat', 'Board_width', 0.3000), + ('NodeSocketFloat', 'Leg_gap', 0.5000), ('NodeSocketFloat', 'extrude_length', 0.5000)]) + + curve_board = nw.new_node(nodegroup_curve_board().name, input_kwargs={ + 'Thickness': group_input.outputs["Thickness"], + 'Fillet Radius Vertical': 0.0100, + 'width': group_input.outputs["Board_width"], + 'extrude_length': group_input.outputs["extrude_length"] + }) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Leg_gap"], 1: 0.0000}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Z': group_input.outputs["Bottom_z"]}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': curve_board, + 'Translation': combine_xyz_1, + 'Rotation': (0.0000, 0.0000, -1.5708) + }) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Z': group_input.outputs["Mid_z"]}) + + transform_5 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': curve_board, + 'Translation': combine_xyz_4, + 'Rotation': (0.0000, 0.0000, -1.5708) + }) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Z': group_input.outputs["Top_z"]}) + + transform_6 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': curve_board, + 'Translation': combine_xyz_5, + 'Rotation': (0.0000, 0.0000, -1.5708) + }) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_1, transform_5, transform_6]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_screw_head', singleton=False, type='GeometryNodeTree') +def nodegroup_screw_head(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + cylinder = nw.new_node('GeometryNodeMeshCylinder', input_kwargs={'Radius': 0.004, 'Depth': 0.0030}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': cylinder.outputs["Mesh"], + 'Rotation': (1.5708, 0.0000, 0.0000) + }) + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'leg_width', 0.5000), + ('NodeSocketFloat', 'board_thickness', 0.5000), ('NodeSocketFloat', 'board_height', 0.5000), + ('NodeSocketFloat', 'leg_gap', 0.5000), ('NodeSocketFloat', 'board_width', 0.5000), + ('NodeSocketFloat', 'leg_depth', 0.0000)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["leg_width"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["leg_depth"]}, + attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: 0.0000, 1: multiply_1}, attrs={'operation': 'SUBTRACT'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["board_thickness"]}, + attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["board_height"], 1: multiply_2}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': subtract, 'Z': add}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform, 'Translation': combine_xyz}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["board_width"], 1: 0.0000}) + + divide1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["leg_depth"], 1: 0.5}, + attrs={'operation': 'MULTIPLY'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: divide1}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': add_2, 'Z': add}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Translation': combine_xyz_1}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["leg_gap"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: multiply_3}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: multiply}, attrs={'operation': 'SUBTRACT'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract_1, 'Y': subtract, 'Z': add}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Translation': combine_xyz_2}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_1, transform_2, transform_3]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_shelf_legs', singleton=False, type='GeometryNodeTree') +def nodegroup_shelf_legs(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'leg_gap', 0.5000), + ('NodeSocketFloat', 'leg_curve_ratio', 0.5000), ('NodeSocketFloat', 'leg_width', 0.5000), + ('NodeSocketFloat', 'leg_length', 0.5000), ('NodeSocketFloat', 'board_width', 0.5000), + ('NodeSocketFloat', 'leg_depth', 0.0000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["leg_width"], 1: 0.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["leg_length"], 1: 0.0000}) + + divide = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["leg_depth"], + 1: group_input.outputs["leg_length"] + }, attrs={'operation': 'DIVIDE'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["leg_curve_ratio"], 1: 0.0000}) + + side_leg = nw.new_node(nodegroup_side_leg().name, input_kwargs={ + 'Thickness': add, + 'N-gon': 4, + 'Profile Width': add_1, + 'Aspect Ratio': divide, + 'Fillet Ratio': add_2, + 'Fillet Radius Vertical': add_2 + }) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: add_1}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': side_leg, + 'Translation': combine_xyz, + 'Rotation': (0.0000, 1.5708, 0.0000) + }) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["board_width"], 1: 0.0000}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: add}, attrs={'operation': 'SUBTRACT'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["leg_gap"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: multiply_1}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_4}) + + transform_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Translation': combine_xyz_3}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_3}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Translation': combine_xyz_2}) + + transform_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_4, transform_2, transform_3]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_2}, + attrs={'is_active_output': True}) + + +def geometry_nodes(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + leg_gap = nw.new_node(Nodes.Value, label='leg_gap') + leg_gap.outputs[0].default_value = kwargs['leg_board_gap'] + + curvature_ratio = nw.new_node(Nodes.Value, label='curvature_ratio') + curvature_ratio.outputs[0].default_value = kwargs['leg_curvature_ratio'] + + leg_width = nw.new_node(Nodes.Value, label='leg_width') + leg_width.outputs[0].default_value = kwargs['leg_width'] + + leg_length = nw.new_node(Nodes.Value, label='leg_length') + leg_length.outputs[0].default_value = kwargs['leg_length'] + + leg_depth = nw.new_node(Nodes.Value, label='leg_depth') + leg_depth.outputs[0].default_value = kwargs['leg_depth'] + + board_width = nw.new_node(Nodes.Value, label='board_width') + board_width.outputs[0].default_value = kwargs['board_width'] + + shelf_legs = nw.new_node(nodegroup_shelf_legs().name, input_kwargs={ + 'leg_gap': leg_gap, + 'leg_curve_ratio': curvature_ratio, + 'leg_width': leg_width, + 'leg_length': leg_length, + 'board_width': board_width, + 'leg_depth': leg_depth + }) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': shelf_legs, 'Material': kwargs['leg_material']}) + + board_thickness = nw.new_node(Nodes.Value, label='board_thickness') + board_thickness.outputs[0].default_value = kwargs['board_thickness'] + + board_extrude_length = nw.new_node(Nodes.Value, label='board_extrude_length') + board_extrude_length.outputs[0].default_value = kwargs['board_extrude_length'] + + bottom_layer_height = nw.new_node(Nodes.Value, label='bottom_layer_height') + bottom_layer_height.outputs[0].default_value = kwargs['bottom_layer_height'] + + mid_layer_height = nw.new_node(Nodes.Value, label='mid_layer_height') + mid_layer_height.outputs[0].default_value = kwargs['mid_layer_height'] + + top_layer_height = nw.new_node(Nodes.Value, label='top_layer_height') + top_layer_height.outputs[0].default_value = kwargs['top_layer_height'] + + screwhead1 = nw.new_node(nodegroup_screw_head().name, input_kwargs={ + 'leg_width': leg_width, + 'board_thickness': board_thickness, + 'board_height': bottom_layer_height, + 'leg_gap': leg_gap, + 'board_width': board_width, + 'leg_depth': leg_depth + }) + + screwhead2 = nw.new_node(nodegroup_screw_head().name, input_kwargs={ + 'leg_width': leg_width, + 'board_thickness': board_thickness, + 'board_height': mid_layer_height, + 'leg_gap': leg_gap, + 'board_width': board_width, + 'leg_depth': leg_depth + }) + + screwhead3 = nw.new_node(nodegroup_screw_head().name, input_kwargs={ + 'leg_width': leg_width, + 'board_thickness': board_thickness, + 'board_height': top_layer_height, + 'leg_gap': leg_gap, + 'board_width': board_width, + 'leg_depth': leg_depth + }) + + join_geometry2 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [screwhead1, screwhead2, screwhead3]}) + + set_material_2 = nw.new_node(Nodes.SetMaterial, input_kwargs={ + 'Geometry': join_geometry2, + 'Material': get_shelf_material('metal') + }) + + shelf_boards = nw.new_node(nodegroup_shelf_boards().name, input_kwargs={ + 'Thickness': board_thickness, + 'Bottom_z': bottom_layer_height, + 'Mid_z': mid_layer_height, + 'Top_z': top_layer_height, + 'Board_width': board_width, + 'Leg_gap': leg_gap, + 'extrude_length': board_extrude_length + }) + + set_material_1 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': shelf_boards, 'Material': kwargs['board_material']}) + + side_board_height = nw.new_node(Nodes.Value, label='side_board_height') + side_board_height.outputs[0].default_value = kwargs['side_board_height'] + + side_boards = nw.new_node(nodegroup_side_boards().name, input_kwargs={ + 'Y': leg_depth, + 'Z': side_board_height, + 'x1': side_board_height, + 'x2': bottom_layer_height, + 'x3': leg_gap, + 'x4': top_layer_height, + 'x5': board_width + }) + + set_material_3 = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': side_boards, 'Material': kwargs['leg_material']}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={ + 'Geometry': [set_material, set_material_2, set_material_1, set_material_3] + }) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': join_geometry}) + + transform4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': realize_instances, 'Scale': (-1, 1, 1)}) + + triangulate = nw.new_node('GeometryNodeTriangulate', input_kwargs={'Mesh': transform4}) + + transform5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': triangulate, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform5}, + attrs={'is_active_output': True}) + + +class TriangleShelfBaseFactory(AssetFactory): + def __init__(self, factory_seed, params={}, coarse=False): + super(TriangleShelfBaseFactory, self).__init__(factory_seed, coarse=coarse) + self.params = {} + + def sample_params(self): + return self.params.copy() + + def get_asset_params(self, i=0): + params = self.sample_params() + if params.get('leg_board_gap', None) is None: + params['leg_board_gap'] = uniform(0.002, 0.005) + if params.get('leg_width', None) is None: + params['leg_width'] = uniform(0.01, 0.03) + if params.get('leg_depth', None) is None: + params['leg_depth'] = uniform(0.01, 0.02) + if params.get('leg_length', None) is None: + params['leg_length'] = np.clip(normal(0.6, 0.05), 0.45, 0.75) + if params.get('leg_curvature_ratio', None) is None: + params['leg_curvature_ratio'] = uniform(0.0, 0.02) + if params.get('board_thickness', None) is None: + params['board_thickness'] = uniform(0.01, 0.025) + if params.get('board_width', None) is None: + params['board_width'] = np.clip(normal(0.3, 0.03), 0.2, 0.4) + if params.get('board_extrude_length', None) is None: + params['board_extrude_length'] = uniform(0.03, 0.07) + if params.get('side_board_height', None) is None: + params['side_board_height'] = uniform(0.02, 0.04) + if params.get('bottom_layer_height', None) is None: + params['bottom_layer_height'] = uniform(0.05, 0.1) + if params.get('shelf_layer_height', None) is None: + params['top_layer_height'] = params['leg_length'] - uniform(0.02, 0.07) + if params.get('board_material', None) is None: + params['board_material'] = np.random.choice(['black_wood', 'wood', 'white'], p=[0.2, 0.6, 0.2]) + if params.get('leg_material', None) is None: + params['leg_material'] = np.random.choice(['black_wood', 'wood', 'white'], p=[0.2, 0.6, 0.2]) + params['mid_layer_height'] = (params['top_layer_height'] + params['bottom_layer_height']) / 2. + + params = self.get_material_func(params) + return params + + def get_material_func(self, params, randomness=True): + params['board_material'] = get_shelf_material(params['board_material']) + params['leg_material'] = get_shelf_material(params['leg_material'], z_axis_texture=True) + return params + + def create_asset(self, i=0, **params): + bpy.ops.mesh.primitive_plane_add(size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), + scale=(1, 1, 1)) + obj = bpy.context.active_object + + obj_params = self.get_asset_params(i) + surface.add_geomod(obj, geometry_nodes, attributes=[], input_kwargs=obj_params, apply=True) + tagging.tag_system.relabel_obj(obj) + + return obj + + +class TriangleShelfFactory(TriangleShelfBaseFactory): + def sample_params(self): + params = dict() + params['Dimensions'] = (uniform(0.25, 0.35), uniform(0.25, 0.35), uniform(0.5, 0.7)) + params['leg_length'] = params['Dimensions'][2] + params['board_width'] = params['Dimensions'][0] + return params + diff --git a/infinigen/assets/shelves/utils.py b/infinigen/assets/shelves/utils.py new file mode 100644 index 000000000..be55e31e6 --- /dev/null +++ b/infinigen/assets/shelves/utils.py @@ -0,0 +1,61 @@ +# 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: Beining Han + +import bpy +import numpy as np +from infinigen.core.util import blender as butil +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler, geometry_node_group_empty_new +from infinigen.core.nodes import node_utils +from infinigen.core import tagging, tags as t + +from infinigen.assets.utils.extract_nodegroup_parts import extract_nodegroup_geo + + +def get_nodegroup_assets(func, params): + bpy.ops.mesh.primitive_plane_add( + size=1, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + with butil.TemporaryObject(obj) as base_obj: + node_group_func = func(**params) + geo_outputs = [o for o in node_group_func.outputs if o.bl_socket_idname == 'NodeSocketGeometry'] + results = {o.name: extract_nodegroup_geo(base_obj, node_group_func, o.name, + ng_params={}) for o in geo_outputs} + + return results + +@node_utils.to_nodegroup('nodegroup_tagged_cube', singleton=False, type='GeometryNodeTree') +def nodegroup_tagged_cube(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketVectorTranslation', 'Size', (1.0000, 1.0000, 1.0000))]) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': group_input.outputs["Size"]}) + + index = nw.new_node(Nodes.Index) + + equal = nw.new_node(Nodes.Compare, input_kwargs={2: index, 3: 2}, attrs={'data_type': 'INT', 'operation': 'EQUAL'}) + + cube = tagging.tag_nodegroup(nw, cube, t.Subpart.SupportSurface, selection=equal) + + #subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, input_kwargs={'Mesh': cube, 'Level': 2}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': cube}, attrs={'is_active_output': True}) + + + +def blender_rotate(vec): + if isinstance(vec, tuple): + vec = list(vec) + if isinstance(vec, list): + vec = np.array(vec, dtype=np.float32) + if len(vec.shape) == 1: + vec = np.expand_dims(vec, axis=-1) + if vec.shape[0] == 3: + new_vec = np.array([[1, 0, 0], [0, 0, 1], [0, -1, 0]], dtype=np.float32) @ vec + return new_vec.squeeze() + if vec.shape[0] == 4: + new_vec = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, -1, 0, 0], [0, 0, 0, 1]], dtype=np.float32) @ vec + return new_vec.squeeze() diff --git a/infinigen/assets/small_plants/fern.py b/infinigen/assets/small_plants/fern.py index ab15536f3..b824a2560 100644 --- a/infinigen/assets/small_plants/fern.py +++ b/infinigen/assets/small_plants/fern.py @@ -21,7 +21,7 @@ from infinigen.core import surface from infinigen.assets.materials import simple_greenery -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup def random_pinnae_level2_curvature(): z_max_curvature = uniform(0.3, 0.45, (1,))[0] diff --git a/infinigen/assets/small_plants/leaf_general.py b/infinigen/assets/small_plants/leaf_general.py index 2305e6f62..8f3222f7a 100644 --- a/infinigen/assets/small_plants/leaf_general.py +++ b/infinigen/assets/small_plants/leaf_general.py @@ -16,7 +16,7 @@ C = bpy.context D = bpy.data -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class LeafFactory(AssetFactory): diff --git a/infinigen/assets/small_plants/leaf_heart.py b/infinigen/assets/small_plants/leaf_heart.py index e4efb4b9c..83cc15249 100644 --- a/infinigen/assets/small_plants/leaf_heart.py +++ b/infinigen/assets/small_plants/leaf_heart.py @@ -12,7 +12,7 @@ C = bpy.context D = bpy.data -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class LeafHeartFactory(AssetFactory): scale = 0.2 diff --git a/infinigen/assets/small_plants/num_leaf_grass.py b/infinigen/assets/small_plants/num_leaf_grass.py index 1a81f994d..567faabfe 100644 --- a/infinigen/assets/small_plants/num_leaf_grass.py +++ b/infinigen/assets/small_plants/num_leaf_grass.py @@ -16,7 +16,7 @@ from infinigen.assets.small_plants.leaf_heart import LeafHeartFactory from infinigen.assets.materials import simple_greenery import numpy as np -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_leafon_stem', singleton=False, type='GeometryNodeTree') def nodegroup_leaf_on_stem(nw: NodeWrangler, z_rotation=(0, 0, 0,), leaf_scale=1.0, leaf=None): diff --git a/infinigen/assets/small_plants/snake_plant.py b/infinigen/assets/small_plants/snake_plant.py index 917c8b5fc..b620e5a8c 100644 --- a/infinigen/assets/small_plants/snake_plant.py +++ b/infinigen/assets/small_plants/snake_plant.py @@ -14,7 +14,7 @@ from infinigen.core.placement.factory import AssetFactory import numpy as np from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_pedal_thickness', singleton=False, type='GeometryNodeTree') def nodegroup_pedal_thickness(nw: NodeWrangler): diff --git a/infinigen/assets/small_plants/spider_plant.py b/infinigen/assets/small_plants/spider_plant.py index 8a5cf8f47..3fc59c306 100644 --- a/infinigen/assets/small_plants/spider_plant.py +++ b/infinigen/assets/small_plants/spider_plant.py @@ -16,7 +16,7 @@ import numpy as np from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_set_leaf_countour', singleton=False, type='GeometryNodeTree') def nodegroup_set_leaf_countour(nw: NodeWrangler): diff --git a/infinigen/assets/small_plants/succulent.py b/infinigen/assets/small_plants/succulent.py index 31d081a46..6677108f1 100644 --- a/infinigen/assets/small_plants/succulent.py +++ b/infinigen/assets/small_plants/succulent.py @@ -16,7 +16,7 @@ import numpy as np from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_pedal_cross_contour_top', singleton=False, type='GeometryNodeTree') def nodegroup_pedal_cross_contour_top(nw: NodeWrangler): diff --git a/infinigen/assets/table_decorations/__init__.py b/infinigen/assets/table_decorations/__init__.py new file mode 100644 index 000000000..a58696a3a --- /dev/null +++ b/infinigen/assets/table_decorations/__init__.py @@ -0,0 +1,3 @@ +from .vase import VaseFactory +from .sink import SinkFactory, TapFactory +from .book import BookFactory, BookColumnFactory, BookStackFactory diff --git a/infinigen/assets/table_decorations/book.py b/infinigen/assets/table_decorations/book.py new file mode 100644 index 000000000..785abedce --- /dev/null +++ b/infinigen/assets/table_decorations/book.py @@ -0,0 +1,215 @@ +# 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: Lingjie Mei +import bpy +import bmesh +import numpy as np +import math +import trimesh +from numpy.random import uniform +from trimesh import proximity + +from infinigen.assets.materials import text +from infinigen.assets.utils.decorate import read_co, write_attribute, write_co +from infinigen.assets.utils.object import center, join_objects, new_bbox, new_cube, obj2trimesh +from infinigen.assets.utils.mesh import longest_ray +from infinigen.assets.utils.uv import wrap_front_back_side +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil + +from infinigen.assets.material_assignments import AssetList + +class BookFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(BookFactory, self).__init__(factory_seed, coarse) + self.rel_scale = log_uniform(1, 1.5) + self.skewness = log_uniform(1.3, 1.8) + self.unit = .0127 + self.is_paperback = uniform() < .5 + self.margin = uniform(.005, .01) + self.offset = 0 if uniform() < .5 else log_uniform(.002, .008) + self.thickness = uniform(.002, .003) + + materials = AssetList['BookFactory']() + self.surface = materials['surface'].assign_material() + self.cover_surface = materials['cover_surface'].assign_material() + if self.cover_surface == text.Text: + self.cover_surface = self.cover_surface(self.factory_seed) + + scratch_prob, edge_wear_prob = materials['wear_tear_prob'] + self.scratch, self.edge_wear = materials['wear_tear'] + self.scratch = None if uniform() > scratch_prob else self.scratch + self.edge_wear = None if uniform() > edge_wear_prob else self.edge_wear + + self.texture_shared = uniform() < .2 + + def create_asset(self, **params) -> bpy.types.Object: + width = int(log_uniform(.08, .15) * self.rel_scale / self.unit) * self.unit + height = int(width * self.skewness / self.unit) * self.unit + depth = uniform(.01, .02) * self.rel_scale + fn = self.make_paperback if self.is_paperback else self.make_hardcover + # noinspection PyArgumentList + obj = fn(width, height, depth) + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + + def make_paperback(self, width, height, depth): + paper = self.make_paper(depth, height, width) + obj = new_cube() + obj.location = width / 2, height / 2, depth / 2 + obj.scale = width / 2, height / 2, depth / 2 + butil.apply_transform(obj, True) + + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + geom = [] + for e in bm.edges: + u, v = e.verts + if u.co[0] > 0 and v.co[0] > 0 and u.co[-1] != v.co[-1]: + geom.append(e) + bmesh.ops.delete(bm, geom=geom, context='EDGES') + + self.make_cover(obj) + write_attribute(obj, 1, 'cover', 'FACE') + obj = join_objects([paper, obj]) + return obj + + def make_paper(self, depth, height, width): + paper = new_cube() + paper.location = width / 2, height / 2, depth / 2 + paper.scale = width / 2 - 1e-4, height / 2, depth / 2 - 1e-4 + butil.apply_transform(paper, True) + self.surface.apply(paper) + return paper + + def make_hardcover(self, width, height, depth): + paper = self.make_paper(depth, height, width) + obj = new_cube() + count = 8 + butil.modify_mesh(obj, 'ARRAY', count=count, relative_offset_displace=(0, 0, 1), + use_merge_vertices=True) + obj.location = 1, 1, 1 + butil.apply_transform(obj, loc=True) + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + geom = [] + for v in bm.verts: + if v.co[0] > 0 and 0 < v.co[-1] < count * 2: + geom.append(v) + bmesh.ops.delete(bm, geom=geom, context='VERTS') + obj.location = 0, - self.margin, 0 + obj.scale = (width + self.margin) / 2, height / 2 + self.margin, depth / 2 / count + butil.apply_transform(obj, True) + x, y, z = read_co(obj).T + ratio = np.minimum(z / depth, 1 - z / depth) + x -= 4 * ratio * (1 - ratio) * self.offset + write_co(obj, np.stack([x, y, z]).T) + self.make_cover(obj) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness) + write_attribute(obj, 1, 'cover', 'FACE') + obj = join_objects([paper, obj]) + return obj + + def make_cover(self, obj): + obj.rotation_euler[0] = np.pi / 2 + butil.apply_transform(obj) + wrap_front_back_side(obj, self.cover_surface, self.texture_shared) + obj.rotation_euler[0] = -np.pi / 2 + butil.apply_transform(obj) + + +class BookColumnFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(BookColumnFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.base_factories = [BookFactory(np.random.randint(1e5)) for _ in range(np.random.randint(1, 4))] + self.n_books = np.random.randint(10, 20) + self.max_angle = uniform(0, np.pi / 9) if uniform() < .7 else 0 + self.max_rel_scale = max(f.rel_scale for f in self.base_factories) + self.max_skewness = max(f.skewness for f in self.base_factories) + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + height = .15 * self.max_rel_scale * self.max_skewness + return new_bbox(0, (.02 + np.sin(self.max_angle) * height) * self.n_books * self.max_rel_scale, + -.15 * self.max_rel_scale, 0, 0, height) + + def create_asset(self, **params) -> bpy.types.Object: + books = [] + for i in range(self.n_books): + factory = np.random.choice(self.base_factories) + obj = factory.create_asset(i=i) + x, y, z = read_co(obj).T + obj.location = [-np.max(x), -np.min(y), -np.min(z)] + butil.apply_transform(obj, True) + if uniform() < .5: + obj.rotation_euler = np.pi / 2 - uniform(0, self.max_angle), 0, np.pi / 2 + else: + obj.location[-1] = -np.max(z) + butil.apply_transform(obj, True) + obj.rotation_euler = np.pi / 2 + uniform(0, self.max_angle), 0, np.pi / 2 + butil.apply_transform(obj) + if i > 0: + obj.location[0] = 10 + butil.apply_transform(obj, True) + dist = longest_ray(books[-1], obj, (-1, 0, 0)) + dist_ = longest_ray(obj, books[-1], (1, 0, 0)) + offset = np.minimum(np.min(dist), np.min(dist_)) + obj.location[0] = -offset + butil.apply_transform(obj, True) + books.append(obj) + obj = join_objects(books) + obj.location[0] = -np.min(read_co(obj)[:, 0]) + butil.apply_transform(obj, True) + return obj + +def rotate(theta, x, y): + return x * math.cos(theta) - y * math.sin(theta), x * math.sin(theta) + y * math.cos(theta) + +class BookStackFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(BookStackFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.base_factories = [BookFactory(np.random.randint(1e5)) for _ in range(np.random.randint(1, 4))] + self.n_books = int(log_uniform(5, 15)) + self.max_angle = uniform(np.pi / 9, np.pi / 6) if uniform() < .7 else 0 + self.max_rel_scale = max(f.rel_scale for f in self.base_factories) + self.max_skewness = max(f.skewness for f in self.base_factories) + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + x_lo = -.15 * self.max_rel_scale / 2 + x_hi = .15 * self.max_rel_scale / 2 + y_lo = -.15 * self.max_rel_scale / 2 * self.max_skewness + y_hi = .15 * self.max_rel_scale / 2 * self.max_skewness + + theta = self.max_angle + x_1, y_1 = rotate(theta, x_lo, y_lo) + x_2, y_2 = rotate(theta, x_lo, y_hi) + x_3, y_3 = rotate(theta, x_hi, y_lo) + x_4, y_4 = rotate(theta, x_hi, y_hi) + + return new_bbox(min(min([x_1,x_2,x_3,x_4]), x_lo ), max(max([x_1,x_2,x_3,x_4]), x_hi), + min(min([y_1,y_2,y_3,y_4]), y_lo), max(max([y_1,y_2,y_3,y_4]), y_hi), + 0, self.n_books * .02 * self.max_rel_scale * 0.8) + + def create_asset(self, **params) -> bpy.types.Object: + books = [] + offset = 0 + for i in range(self.n_books): + factory = np.random.choice(self.base_factories) + obj = factory.create_asset(i=i) + c = center(obj)[:-1] + obj.location = -c[0], -c[1], offset - np.min(read_co(obj)[:, -1]) + obj.rotation_euler[-1] = uniform(-self.max_angle, self.max_angle) + butil.apply_transform(obj, True) + offset = np.max(read_co(obj)[:, -1]) + books.append(obj) + return join_objects(books) diff --git a/infinigen/assets/table_decorations/sink.py b/infinigen/assets/table_decorations/sink.py new file mode 100644 index 000000000..343554bb2 --- /dev/null +++ b/infinigen/assets/table_decorations/sink.py @@ -0,0 +1,785 @@ +# 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: +# - Hongyu Wen: sink geometry +# - Meenal Parakh: material assignment +# - Stamatis Alexandropoulos: taps +# - Alexander Raistrick: placeholder, optimize detail, redo cutter + +import random + +import bpy +import mathutils + +import numpy as np +from numpy.random import uniform as U, normal as N, randint as RI + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface +from infinigen.core.util import blender as butil + +from infinigen.assets.utils import bbox_from_mesh +from infinigen.assets.utils.extract_nodegroup_parts import extract_nodegroup_geo + +from infinigen.core.util.math import FixedSeed +from infinigen.core import tagging, tags as t +from infinigen.core.placement.factory import AssetFactory +from infinigen.assets.material_assignments import AssetList + + +class SinkFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=[1., 1., 1.], upper_height=None): + super(SinkFactory, self).__init__(factory_seed, coarse=coarse) + + self.dimensions = dimensions + self.factory_seed = factory_seed + with FixedSeed(factory_seed): + self.params = self.sample_parameters(dimensions, upper_height=upper_height) + self.material_params, self.scratch, self.edge_wear = self.get_material_params() + self.params.update(self.material_params) + + self.tap_factory = TapFactory(factory_seed) + + def get_material_params(self): + material_assignments = AssetList['SinkFactory']() + params = { + "Sink": material_assignments['sink'].assign_material(), + "Tap": material_assignments['tap'].assign_material(), + } + wrapped_params = { + k: surface.shaderfunc_to_material(v) for k, v in params.items() + } + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = U() < scratch_prob + is_edge_wear = U() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + @staticmethod + def sample_parameters(dimensions, upper_height, use_default=False, open=False): + width = U(0.4, 1.0) + depth = U(0.4, 0.5) + curvature = U(1.0, 1.0) + if upper_height is None: + upper_height = U(0.2, 0.4) + lower_height = U(0.00, 0.01) + hole_radius = U(0.02, 0.05) + margin = U(0.02, 0.05) + watertap_margin = U(0.1, 0.12) + + params = { + 'Width': width, + 'Depth': depth, + 'Curvature': curvature, + 'Upper Height': upper_height, + 'Lower Height': lower_height, + 'HoleRadius': hole_radius, + 'Margin': margin, + 'WaterTapMargin': watertap_margin, + 'ProtrudeAboveCounter': U(0.01, 0.025), + } + return params + + def _extract_geo_results(self): + + params = self.params.copy() + params.pop('ProtrudeAboveCounter') + + with butil.TemporaryObject(butil.spawn_vert()) as temp: + obj = extract_nodegroup_geo( + temp, nodegroup_sink_geometry(), 'Geometry', ng_params=params + ) + cutter = extract_nodegroup_geo( + temp, nodegroup_sink_geometry(), 'Cutter', ng_params=params + ) + + return obj, cutter + + def create_placeholder(self, i, **kwargs) -> bpy.types.Object: + + obj, cutter = self._extract_geo_results() + butil.delete(cutter) + + min_corner, max_corner = butil.bounds(obj) + min_corner[-1] = max_corner[-1] - self.params['ProtrudeAboveCounter'] + top_slice_placeholder = bbox_from_mesh.box_from_corners(min_corner, max_corner) + + butil.delete(obj) + + return top_slice_placeholder + + def create_asset(self,i, placeholder, state=None, **params): + + obj, cutter = self._extract_geo_results() + tagging.tag_system.relabel_obj(obj) + + cutter.parent = obj + cutter.name = repr(self) + f'.spawn_placeholder({i}).cutter' + cutter.hide_render = True + + tap_loc = (-self.params['Depth'] / 2, 0, self.params['Upper Height']) + tap = self.tap_factory.spawn_asset(i, loc=tap_loc, rot=(0,0,0)) + tap.parent = obj + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + +class TapFactory(AssetFactory): + + def __init__(self, factory_seed): + super().__init__(factory_seed) + with FixedSeed(factory_seed): + self.params, self.scratch, self.edge_wear = self.get_material_params() + + + @staticmethod + def tap_parameters(): + params = { + 'base_width' : U(0.570,0.630), + 'tap_head': U(0.7,1.1), + 'roation_z': U(5.5,7.0), + 'tap_height': U(0.5,1), + 'base_radius': U(0.0,0.3), + 'Switch': True if U()>0.5 else False, + 'Y': U(-0.5, -0.06), + 'hand_type': True if U()>0.2 else False, + 'hands_length_x': U(0.750,1.25), + 'hands_length_Y': U(0.950, 1.550), + 'one_side': True if U()>0.5 else False, + 'different_type': True if U()>0.8 else False + } + return params + + + def get_material_params(self): + material_assignments = AssetList['TapFactory']() + tap_material = material_assignments['tap'].assign_material() + + wrapped_params = { + 'Tap': surface.shaderfunc_to_material(tap_material) + } + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = U() < scratch_prob + is_edge_wear = U() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + def create_asset(self, **_): + obj = butil.spawn_cube() + butil.modify_mesh(obj, 'NODES', node_group=nodegroup_water_tap(), ng_inputs=self.params, apply=True) + obj.scale = (0.4,)*3 + obj.rotation_euler.z += np.pi + butil.apply_transform(obj) + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + + +@node_utils.to_nodegroup('nodegroup_handle', singleton=False, type='GeometryNodeTree') +def nodegroup_handle(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + bezier_segment = nw.new_node(Nodes.CurveBezierSegment, input_kwargs={ + 'Start': (0.0000, 0.0000, 0.0000), + 'Start Handle': (0.0000, 0.0000, 0.7000), + 'End Handle': (0.2000, 0.0000, 0.7000), + 'End': (1.0000, 0.0000, 0.9000) + }) + + spline_parameter = nw.new_node(Nodes.SplineParameter) + + float_curve = nw.new_node(Nodes.FloatCurve, input_kwargs={'Value': spline_parameter.outputs["Factor"]}) + node_utils.assign_curve(float_curve.mapping.curves[0], [(0.0000, 0.9750), (1.0000, 0.1625)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: float_curve, 1: 1.3000}, + attrs={'operation': 'MULTIPLY'}) + + set_curve_radius = nw.new_node(Nodes.SetCurveRadius, + input_kwargs={'Curve': bezier_segment, 'Radius': multiply}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': 0.2000}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': set_curve_radius, + 'Profile Curve': curve_circle.outputs["Curve"], + 'Fill Caps': True + }) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + map_range = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': separate_xyz.outputs["X"], 1: 0.2000, 3: 1.0000, 4: 2.5000}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz.outputs["Y"], 1: map_range.outputs["Result"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': separate_xyz.outputs["X"], + 'Y': multiply_1, + 'Z': separate_xyz.outputs["Z"] + }) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': curve_to_mesh, 'Position': combine_xyz}) + + subdivision_surface = nw.new_node(Nodes.SubdivisionSurface, input_kwargs={'Mesh': set_position, 'Level': 2}) + + set_shade_smooth = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': subdivision_surface}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_shade_smooth}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_water_tap', singleton=False, type='GeometryNodeTree') +def nodegroup_water_tap(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'base_width', U(0.2,0.3)), + ('NodeSocketFloat', 'tap_head', U(0.7,1.1)), + ('NodeSocketFloat', 'roation_z',U(5.5,7.0)), + ('NodeSocketFloat', 'tap_height', U(0.5,1)), + ('NodeSocketFloatDistance', 'base_radius', U(0.0,0.1)), + ('NodeSocketBool', 'Switch',True if U()>0.5 else False), + ('NodeSocketFloat', 'Y', U(-0.5, -0.06)), + ('NodeSocketBool', 'hand_type', True if U()>0.2 else False), + ('NodeSocketFloat', 'hands_length_x', U(0.750,1.25)), + ('NodeSocketFloat', 'hands_length_Y', U(0.950, 1.550)), + ('NodeSocketBool', 'one_side', True if U()>0.5 else False), + ('NodeSocketBool', 'different_type', True if U()>0.8 else False), + ('NodeSocketBool', 'length_one_side', True if U()>0.8 else False)]) + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketMaterial', 'Tap', None)]) + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': 0.0500}) + + fill_curve_1 = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': curve_circle.outputs["Curve"]}) + + extrude_mesh_1 = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={'Mesh': fill_curve_1, 'Offset Scale': 0.1500}) + + quadrilateral = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', + input_kwargs={'Width': 0.2000, 'Height': 0.7000}) + + fillet_curve = nw.new_node('GeometryNodeFilletCurve', + input_kwargs={'Curve': quadrilateral, 'Count': 19, 'Radius': 0.1000}, + attrs={'mode': 'POLY'}) + + fill_curve = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': fillet_curve}) + + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={'Mesh': fill_curve, 'Offset Scale': 0.0500}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': (0.0000, 0.0000, 0.6000)}) + + curve_circle_1 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': 0.0300}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': curve_line, 'Profile Curve': curve_circle_1.outputs["Curve"]}) + + curve_circle_2 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': 0.2000}) + + transform_geometry = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle_2.outputs["Curve"], 'Translation': (0.0000, 0.2000, 0.0000)}) + + transform_geometry_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_geometry, 'Rotation': (-1.5708, 1.5708, 0.0000), 'Scale': (1.0000, 0.7000, 1.0000)}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 0.2000, 'Y': group_input.outputs["Y"]}) + + bezier_segment = nw.new_node(Nodes.CurveBezierSegment, + input_kwargs={'Resolution': 177, 'Start': (0.0000, 0.0000, 0.0000), 'Start Handle': (0.0000, 1.2000, 0.0000), 'End Handle': combine_xyz_3, 'End': (-0.0500, 0.1000, 0.0000)}) + + trim_curve = nw.new_node(Nodes.TrimCurve, input_kwargs={'Curve': bezier_segment, 3: 0.6625, 5: 3.0000}) + + transform_geometry_6 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': trim_curve, 'Rotation': (1.5708, 0.0000, 2.5220), 'Scale': (5.2000, 0.5000, 7.8000)}) + + curve_circle_3 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': 0.0300}) + + curve_to_mesh_2 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': transform_geometry_6, 'Profile Curve': curve_circle_3.outputs["Curve"]}) + + switch = nw.new_node(Nodes.Switch, + input_kwargs={1: group_input.outputs["Switch"], 14: transform_geometry_1, 15: curve_to_mesh_2}) + + curve_to_mesh_1 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': switch.outputs[6], 'Profile Curve': curve_circle_1.outputs["Curve"]}) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + greater_than = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: -0.0100}, attrs={'operation': 'GREATER_THAN'}) + + switch_1 = nw.new_node(Nodes.Switch, + input_kwargs={0: group_input.outputs["Switch"], 2: greater_than, 3: 1.0000}, + attrs={'input_type': 'FLOAT'}) + + separate_geometry = nw.new_node(Nodes.SeparateGeometry, + input_kwargs={'Geometry': curve_to_mesh_1, 'Selection': switch_1.outputs["Output"]}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': 1.0000, 'Z': group_input.outputs["tap_head"]}) + + switch_2 = nw.new_node(Nodes.Switch, + input_kwargs={0: group_input.outputs["Switch"], 8: combine_xyz, 9: (1.0000, 1.0000, 1.0000)}, + attrs={'input_type': 'VECTOR'}) + + transform_geometry_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': separate_geometry.outputs["Selection"], 'Translation': (0.0000, 0.0000, 0.6000), 'Scale': switch_2.outputs[3]}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [curve_to_mesh, transform_geometry_2]}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["roation_z"]}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': 1.0000, 'Z': group_input.outputs["tap_height"]}) + + transform_geometry_5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry, 'Rotation': combine_xyz_1, 'Scale': combine_xyz_2}) + + handle = nw.new_node(nodegroup_handle().name) + + transform_geometry_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': handle, 'Translation': (0.0000, -0.2000, 0.0000), 'Rotation': (0.0000, 0.0000, 3.6652), 'Scale': (0.3000, 0.3000, 0.3000)}) + + transform_geometry_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': handle, 'Translation': (0.0000, 0.2000, 0.0000), 'Rotation': (0.0000, 0.0000, 2.6180), 'Scale': (0.3000, 0.3000, 0.3000)}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_geometry_4, transform_geometry_3]}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Vertices': 41, 'Side Segments': 39, 'Radius': 0.0300, 'Depth': 0.1000}) + + transform_geometry_7 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': (0.0000, 0.0500, 0.1000), 'Rotation': (1.5708, 0.0000, 0.0000)}) + + switch_5 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["one_side"], 14: transform_geometry_7}) + + transform_geometry_8 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': (0.0000, -0.0500, 0.1000), 'Rotation': (1.5708, 0.0000, 0.0000)}) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [switch_5.outputs[6], transform_geometry_8]}) + + cylinder_1 = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Vertices': 41, 'Side Segments': 39, 'Radius': 0.0050, 'Depth': 0.1000}) + + transform_geometry_9 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder_1.outputs["Mesh"], 'Translation': (0.0000, 0.0800, 0.1500), 'Scale': (1.0000, 1.0000, 1.1000)}) + + switch_4 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["one_side"], 14: transform_geometry_9}) + + transform_geometry_10 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder_1.outputs["Mesh"], 'Translation': (0.0000, -0.0800, 0.1500), 'Rotation': (0.0000, 0.0000, 0.0855), 'Scale': (1.0000, 1.0000, 1.1000)}) + + transform_geometry_17 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_geometry_10, 'Translation': (0.0000, -0.0100, -0.0050), 'Scale': (4.1000, 1.0000, 1.0000)}) + + switch_8 = nw.new_node(Nodes.Switch, + input_kwargs={1: group_input.outputs["length_one_side"], 14: transform_geometry_10, 15: transform_geometry_17}) + + switch_7 = nw.new_node(Nodes.Switch, + input_kwargs={1: group_input.outputs["one_side"], 14: transform_geometry_10, 15: switch_8.outputs[6]}) + + join_geometry_4 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [switch_4.outputs[6], switch_7.outputs[6]]}) + + join_geometry_5 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [join_geometry_3, join_geometry_4]}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["hands_length_x"], 'Y': group_input.outputs["hands_length_Y"], 'Z': 1.0000}) + + transform_geometry_11 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry_5, 'Scale': combine_xyz_4}) + + switch_3 = nw.new_node(Nodes.Switch, + input_kwargs={1: group_input.outputs["hand_type"], 14: join_geometry_2, 15: transform_geometry_11}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': 0.0500}) + + fill_curve = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': curve_circle.outputs["Curve"]}) + + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={'Mesh': fill_curve, 'Offset Scale': 0.1500}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_geometry_5, switch_3.outputs[6], extrude_mesh.outputs["Mesh"]]}) + + bezier_segment_1 = nw.new_node(Nodes.CurveBezierSegment, + input_kwargs={'Resolution': 54, 'Start': (0.0000, 0.0000, 0.0000), 'Start Handle': (0.0000, 0.0000, 0.7000), 'End Handle': (0.2000, 0.0000, 0.7000), 'End': (1.0000, 0.0000, 0.9000)}) + + spline_parameter = nw.new_node(Nodes.SplineParameter) + + float_curve = nw.new_node(Nodes.FloatCurve, input_kwargs={'Value': spline_parameter.outputs["Factor"]}) + node_utils.assign_curve(float_curve.mapping.curves[0], [(0.0000, 0.9750), (0.6295, 0.4125), (1.0000, 0.1625)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: float_curve, 1: 1.3000}, attrs={'operation': 'MULTIPLY'}) + + set_curve_radius = nw.new_node(Nodes.SetCurveRadius, input_kwargs={'Curve': bezier_segment_1, 'Radius': multiply}) + + curve_circle_4 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': 0.1000}) + + curve_to_mesh_3 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': set_curve_radius, 'Profile Curve': curve_circle_4.outputs["Curve"], 'Fill Caps': True}) + + position_1 = nw.new_node(Nodes.InputPosition) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position_1}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': separate_xyz_1.outputs["X"], 1: 0.2000, 3: 1.0000, 4: 2.5000}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["Y"], 1: map_range.outputs["Result"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': separate_xyz_1.outputs["X"], 'Y': multiply_1, 'Z': separate_xyz_1.outputs["Z"]}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': curve_to_mesh_3, 'Position': combine_xyz_5, 'Offset': (0.0000, 0.0000, 0.0000)}) + + subdivision_surface = nw.new_node(Nodes.SubdivisionSurface, input_kwargs={'Mesh': set_position, 'Level': 1}) + + set_shade_smooth = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': subdivision_surface}) + + transform_geometry_12 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': set_shade_smooth, 'Translation': (0.0000, 0.0000, 0.1000), 'Rotation': (0.0000, 0.0000, 0.6807), 'Scale': (0.4000, 0.4000, 0.3000)}) + + curve_circle_5 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': 307, 'Radius': 0.0550}) + + fill_curve_2 = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': curve_circle_5.outputs["Curve"]}) + + extrude_mesh_2 = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={'Mesh': fill_curve_2, 'Offset Scale': 0.1500}) + + cylinder_2 = nw.new_node('GeometryNodeMeshCylinder', input_kwargs={'Vertices': 100, 'Radius': 0.0100, 'Depth': 0.7000}) + + set_position_1 = nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': cylinder_2.outputs["Mesh"]}) + + transform_geometry_13 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': set_position_1, 'Translation': (0.3000, 0.0000, 0.2500), 'Rotation': (0.0000, -2.0420, 0.0000), 'Scale': (1.7000, 3.1000, 1.0000)}) + + cylinder_3 = nw.new_node('GeometryNodeMeshCylinder', input_kwargs={'Vertices': 318, 'Radius': 0.0200, 'Depth': 0.0300}) + + transform_geometry_14 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cylinder_3.outputs["Mesh"], 'Translation': (0.5950, 0.0000, 0.3800)}) + + join_geometry_7 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_geometry_13, transform_geometry_14]}) + + transform_geometry_15 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry_7, 'Scale': (0.9000, 1.0000, 1.0000)}) + + join_geometry_8 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_geometry_12, extrude_mesh_2.outputs["Mesh"], transform_geometry_15]}) + + transform_geometry_16 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry_8, 'Rotation': (0.0000, 0.0000, 3.1416)}) + + switch_6 = nw.new_node(Nodes.Switch, + input_kwargs={1: group_input.outputs["different_type"], 14: join_geometry_1, 15: transform_geometry_16}) + + quadrilateral = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', + input_kwargs={'Width': group_input.outputs["base_width"], 'Height': 0.7000}) + + fillet_curve = nw.new_node(Nodes.FilletCurve, + input_kwargs={'Curve': quadrilateral, 'Count': 19, 'Radius': group_input.outputs["base_radius"]}, + attrs={'mode': 'POLY'}) + + fill_curve_1 = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': fillet_curve}) + + extrude_mesh_1 = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={'Mesh': fill_curve_1, 'Offset Scale': 0.0500}) + + join_geometry_6 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [switch_6.outputs[6], extrude_mesh_1.outputs["Mesh"]]}) + + set_material = nw.new_node(Nodes.SetMaterial, input_kwargs={ + 'Geometry': join_geometry_6, + 'Material': group_input.outputs["Tap"] + }) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_material}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_sink_geometry', singleton=False, type='GeometryNodeTree') +def nodegroup_sink_geometry(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloatDistance', 'Width', 2.0000), + ('NodeSocketFloatDistance', 'Depth', 2.0000), ('NodeSocketFloat', 'Curvature', 0.9500), + ('NodeSocketFloat', 'Upper Height', 1.0000), ('NodeSocketFloat', 'Lower Height', -0.0500), + ('NodeSocketFloatDistance', 'HoleRadius', 0.1000), ('NodeSocketFloat', 'Margin', 0.5000), + ('NodeSocketFloat', 'WaterTapMargin', 0.5000), + ('NodeSocketMaterial', 'Tap', None), + ('NodeSocketMaterial', 'Sink', None),]) + + reroute_3 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Depth"]}) + + reroute_2 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Width"]}) + + quadrilateral = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', + input_kwargs={'Width': reroute_3, 'Height': reroute_2}) + + minimum = nw.new_node(Nodes.Math, input_kwargs={0: reroute_3, 1: reroute_2}, attrs={'operation': 'MINIMUM'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: minimum, 1: 0.1000}, attrs={'operation': 'MULTIPLY'}) + + # inside of sink curve + sink_interior_border = nw.new_node('GeometryNodeFilletCurve', + input_kwargs={'Curve': quadrilateral, 'Count': 50, 'Radius': multiply}, + attrs={'mode': 'POLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': group_input.outputs["Curvature"], + 'Y': group_input.outputs["Curvature"] + }) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': sink_interior_border, 'Scale': combine_xyz_1}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': group_input.outputs["HoleRadius"]}) + + join_geometry_4 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_1, curve_circle.outputs["Curve"]]}) + + fill_curve_1 = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': join_geometry_4}) + + #fill_curve_1 = tagging.tag_nodegroup(nw, fill_curve_1, t.Subpart.SupportSurface) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Lower Height"]}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': reroute}) + + transform_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': fill_curve_1, 'Translation': combine_xyz_2}) + + extrude_mesh_2 = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={ + 'Mesh': transform_2, + 'Offset Scale': -0.0100, + 'Individual': False + }) + + transform_5 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': curve_circle.outputs["Curve"], + 'Scale': (0.7000, 0.7000, 1.0000) + }) + + join_geometry_6 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [curve_circle.outputs["Curve"], transform_5]}) + + fill_curve_4 = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': join_geometry_6}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: reroute, 1: -0.0100}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add}) + + transform_6 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': fill_curve_4, 'Translation': combine_xyz_4}) + + extrude_mesh_4 = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={ + 'Mesh': transform_6, + 'Offset Scale': group_input.outputs["Lower Height"], + 'Individual': False + }) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Lower Height"], 1: -0.0100}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add_1}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_6}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': curve_line, + 'Profile Curve': curve_circle.outputs["Curve"] + }) + + transform_7 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_to_mesh, 'Translation': combine_xyz_2}) + + join_geometry_5 = nw.new_node(Nodes.JoinGeometry, input_kwargs={ + 'Geometry': [extrude_mesh_2.outputs["Mesh"], transform_2, extrude_mesh_4.outputs["Mesh"], transform_7] + }) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': sink_interior_border, 'Scale': (0.9900, 0.9900, 1.0000)}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform, sink_interior_border]}) + + fill_curve = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': join_geometry}) + + extrude_mesh_1 = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={ + 'Mesh': fill_curve, + 'Offset Scale': group_input.outputs["Lower Height"] + }) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + less_than = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: 0.0000}, + attrs={'operation': 'LESS_THAN'}) + + position_1 = nw.new_node(Nodes.InputPosition) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position_1}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["X"], 1: group_input.outputs["Curvature"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_1.outputs["Y"], 1: group_input.outputs["Curvature"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': multiply_1, 'Y': multiply_2, 'Z': separate_xyz_1.outputs["Z"]}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': extrude_mesh_1.outputs["Mesh"], + 'Selection': less_than, + 'Position': combine_xyz + }) + + add_2 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Width"], 1: group_input.outputs["Margin"]}) + + add_3 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["Margin"]}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: group_input.outputs["WaterTapMargin"]}) + + quadrilateral_1 = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', + input_kwargs={'Width': add_4, 'Height': add_2}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["WaterTapMargin"], 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3}) + + transform_8 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': quadrilateral_1, 'Translation': combine_xyz_7}) + + fillet_curve_1 = nw.new_node('GeometryNodeFilletCurve', + input_kwargs={'Curve': transform_8, 'Count': 10, 'Radius': multiply}, + attrs={'mode': 'POLY'}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [sink_interior_border, fillet_curve_1]}) + + fill_curve_2 = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': join_geometry_2}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Lower Height"], 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Upper Height"], 1: multiply_4}) + + extrude_mesh_3 = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={'Mesh': fill_curve_2, 'Offset Scale': add_5}) + + reroute_1 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Lower Height"]}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': reroute_1}) + + transform_3 = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': extrude_mesh_3.outputs["Mesh"], + 'Translation': combine_xyz_3 + }) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': transform_3}) + + #watertap = nw.new_node(nodegroup_water_tap().name, input_kwargs={'Tap': group_input.outputs['Tap']}) + + add_6 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["Depth"], + 1: group_input.outputs["WaterTapMargin"] + }) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: add_6, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': multiply_5, 'Z': group_input.outputs["Upper Height"]}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={ + 'Geometry': [join_geometry_5, set_position, join_geometry_3]#, transform_geometry] + }) + + set_material = nw.new_node(Nodes.SetMaterial, input_kwargs={ + 'Geometry': join_geometry_1, + 'Material': group_input.outputs["Sink"] + }) + + add_7 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["WaterTapMargin"], 1: group_input.outputs["Margin"]}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: add_7, 1: 2.5600}, attrs={'operation': 'DIVIDE'}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': divide}) + + set_position_1 = nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': set_material, 'Offset': combine_xyz_8}) + + # region CREATE CUTTER (manually added by araistrick post-fact) + + sink_interior_border_simplified = nw.new_node('GeometryNodeFilletCurve', + input_kwargs={ + 'Curve': quadrilateral, 'Count': 3, 'Radius': multiply + }, + attrs={'mode': 'POLY'} + ) + + scaled_sink_interior_border = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': sink_interior_border_simplified, + 'Scale': (1.01, 1.01, 1) #scale it up just a little to avoid zclip + }) + + fill_interior = nw.new_node( + Nodes.FillCurve, + input_kwargs={'Curve': scaled_sink_interior_border}, + attrs={'mode': 'NGONS'} + ) + + extrude_amt = nw.scalar_add( + group_input.outputs["Lower Height"], + group_input.outputs["Upper Height"], + 0.05 + ) + extrude = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={ + 'Mesh': fill_interior, + 'Offset Scale': extrude_amt + }) + + # same translation as set_position_1, to keep it in sync + setpos_move_cutter = nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': extrude, 'Offset': combine_xyz_8}) + + # endregion + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={ + 'Geometry': set_position_1, + 'Cutter': setpos_move_cutter + }) + + + +def geometry_node_to_bbox(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': group_input.outputs["Geometry"]}) + + transform_geometry = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': bounding_box, 'Scale': (0.100, 0.100, 0.1000)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_geometry}, attrs={'is_active_output': True}) \ No newline at end of file diff --git a/infinigen/assets/table_decorations/utils.py b/infinigen/assets/table_decorations/utils.py new file mode 100644 index 000000000..87ce885a7 --- /dev/null +++ b/infinigen/assets/table_decorations/utils.py @@ -0,0 +1,324 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +@node_utils.to_nodegroup('nodegroup_star_profile', singleton=False, type='GeometryNodeTree') +def nodegroup_star_profile(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'Resolution', 64), + ('NodeSocketInt', 'Points', 64), + ('NodeSocketFloatDistance', 'Inner Radius', 0.9000)]) + + star = nw.new_node('GeometryNodeCurveStar', + input_kwargs={'Points': group_input.outputs["Points"], 'Inner Radius': group_input.outputs["Inner Radius"], 'Outer Radius': 1.0000}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, + input_kwargs={'Curve': star.outputs["Curve"], 'Count': group_input.outputs["Resolution"]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Curve': resample_curve}, attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_flip_index', singleton=False, type='GeometryNodeTree') +def nodegroup_flip_index(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + index = nw.new_node(Nodes.Index) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'V Resolution', 0), + ('NodeSocketInt', 'U Resolution', 0)]) + + modulo = nw.new_node(Nodes.Math, + input_kwargs={0: index, 1: group_input.outputs["V Resolution"]}, + attrs={'operation': 'MODULO'}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: modulo, 1: group_input.outputs["U Resolution"]}, + attrs={'operation': 'MULTIPLY'}) + + divide = nw.new_node(Nodes.Math, + input_kwargs={0: index, 1: group_input.outputs["V Resolution"]}, + attrs={'operation': 'DIVIDE'}) + + floor = nw.new_node(Nodes.Math, input_kwargs={0: divide}, attrs={'operation': 'FLOOR'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: floor}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Index': add}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_cylinder_side', singleton=False, type='GeometryNodeTree') +def nodegroup_cylinder_side(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'U Resolution', 32), + ('NodeSocketInt', 'V Resolution', 0)]) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["V Resolution"], 1: 1.0000}, + attrs={'operation': 'SUBTRACT'}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Vertices': group_input.outputs["U Resolution"], 'Side Segments': subtract}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Name': 'uv_map', 3: cylinder.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': store_named_attribute, 'Top': cylinder.outputs["Top"], 'Side': cylinder.outputs["Side"], 'Bottom': cylinder.outputs["Bottom"]}, + attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_shifted_circle', singleton=False, type='GeometryNodeTree') +def nodegroup_shifted_circle(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'Resolution', 32), + ('NodeSocketFloatDistance', 'Radius', 1.0000), + ('NodeSocketFloat', 'Z', 0.0000), + ('NodeSocketFloat', 'Rot Z', 0.0000)]) + + curve_circle_3 = nw.new_node(Nodes.CurveCircle, + input_kwargs={'Resolution': group_input.outputs["Resolution"], 'Radius': group_input.outputs["Radius"]}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Z"]}) + + radians = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Rot Z"]}, attrs={'operation': 'RADIANS'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': radians}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle_3.outputs["Curve"], 'Translation': combine_xyz, 'Rotation': combine_xyz_1}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_3}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_lofting', singleton=False, type='GeometryNodeTree') +def nodegroup_lofting(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Profile Curves', None), + ('NodeSocketInt', 'U Resolution', 32), + ('NodeSocketInt', 'V Resolution', 32), + ('NodeSocketBool', 'Use Nurb', False)]) + + cylinderside = nw.new_node(nodegroup_cylinder_side().name, + input_kwargs={'U Resolution': group_input.outputs["U Resolution"], 'V Resolution': group_input.outputs["V Resolution"]}) + + index = nw.new_node(Nodes.Index) + + evaluate_on_domain = nw.new_node(Nodes.EvaluateonDomain, input_kwargs={1: index}, attrs={'data_type': 'INT', 'domain': 'CURVE'}) + + equal = nw.new_node(Nodes.Compare, + input_kwargs={2: evaluate_on_domain.outputs[1]}, + attrs={'data_type': 'INT', 'operation': 'EQUAL'}) + + curve_line = nw.new_node(Nodes.CurveLine) + + domain_size = nw.new_node(Nodes.DomainSize, input_kwargs={'Geometry': group_input.outputs["Profile Curves"]}, attrs={'component': 'CURVE'}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': curve_line, 'Count': domain_size.outputs["Spline Count"]}) + + instance_on_points_1 = nw.new_node(Nodes.InstanceOnPoints, + input_kwargs={'Points': group_input.outputs["Profile Curves"], 'Selection': equal, 'Instance': resample_curve}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': instance_on_points_1}) + + position = nw.new_node(Nodes.InputPosition) + + flipindex = nw.new_node(nodegroup_flip_index().name, + input_kwargs={'V Resolution': domain_size.outputs["Spline Count"], 'U Resolution': group_input.outputs["U Resolution"]}) + + sample_index_2 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': group_input.outputs["Profile Curves"], 3: position, 'Index': flipindex}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': realize_instances, 'Position': sample_index_2.outputs[2]}) + + set_spline_type_1 = nw.new_node(Nodes.SplineType, input_kwargs={'Curve': set_position}, attrs={'spline_type': 'CATMULL_ROM'}) + + set_spline_type = nw.new_node(Nodes.SplineType, input_kwargs={'Curve': set_position}, attrs={'spline_type': 'NURBS'}) + + switch = nw.new_node(Nodes.Switch, + input_kwargs={1: group_input.outputs["Use Nurb"], 14: set_spline_type_1, 15: set_spline_type}) + + resample_curve_1 = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': switch.outputs[6], 'Count': group_input.outputs["V Resolution"]}) + + position_1 = nw.new_node(Nodes.InputPosition) + + flipindex_1 = nw.new_node(nodegroup_flip_index().name, + input_kwargs={'V Resolution': group_input.outputs["U Resolution"], 'U Resolution': group_input.outputs["V Resolution"]}) + + sample_index_3 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': resample_curve_1, 3: position_1, 'Index': flipindex_1}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': cylinderside.outputs["Geometry"], 'Position': sample_index_3.outputs[2]}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': set_position_1, 'Top': cylinderside.outputs["Top"], 'Side': cylinderside.outputs["Side"], 'Bottom': cylinderside.outputs["Bottom"]}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_lofting_poly', singleton=False, type='GeometryNodeTree') +def nodegroup_lofting_poly(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Profile Curves', None), + ('NodeSocketInt', 'U Resolution', 32), + ('NodeSocketInt', 'V Resolution', 32), + ('NodeSocketBool', 'Use Nurb', False)]) + + reroute_2 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["V Resolution"]}) + + cylinderside_001 = nw.new_node(nodegroup_cylinder_side().name, + input_kwargs={'U Resolution': group_input.outputs["U Resolution"], 'V Resolution': reroute_2}) + + index = nw.new_node(Nodes.Index) + + evaluate_on_domain = nw.new_node(Nodes.EvaluateonDomain, input_kwargs={1: index}, attrs={'domain': 'CURVE', 'data_type': 'INT'}) + + equal = nw.new_node(Nodes.Compare, + input_kwargs={2: evaluate_on_domain.outputs[1]}, + attrs={'operation': 'EQUAL', 'data_type': 'INT'}) + + curve_line = nw.new_node(Nodes.CurveLine) + + domain_size = nw.new_node(Nodes.DomainSize, + input_kwargs={'Geometry': group_input.outputs["Profile Curves"]}, + attrs={'component': 'CURVE'}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': curve_line, 'Count': domain_size.outputs["Spline Count"]}) + + instance_on_points_1 = nw.new_node(Nodes.InstanceOnPoints, + input_kwargs={'Points': group_input.outputs["Profile Curves"], 'Selection': equal, 'Instance': resample_curve}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': instance_on_points_1}) + + position = nw.new_node(Nodes.InputPosition) + + flipindex_001 = nw.new_node(nodegroup_flip_index().name, + input_kwargs={'V Resolution': domain_size.outputs["Spline Count"], 'U Resolution': group_input.outputs["U Resolution"]}) + + sample_index_2 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': group_input.outputs["Profile Curves"], 3: position, 'Index': flipindex_001}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': realize_instances, 'Position': sample_index_2.outputs[2]}) + + set_spline_type_1 = nw.new_node(Nodes.SplineType, input_kwargs={'Curve': set_position}) + + set_spline_type = nw.new_node(Nodes.SplineType, input_kwargs={'Curve': set_position}, attrs={'spline_type': 'NURBS'}) + + switch = nw.new_node(Nodes.Switch, + input_kwargs={1: group_input.outputs["Use Nurb"], 14: set_spline_type_1, 15: set_spline_type}) + + resample_curve_1 = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': switch.outputs[6], 'Count': reroute_2}) + + position_1 = nw.new_node(Nodes.InputPosition) + + flipindex_001_1 = nw.new_node(nodegroup_flip_index().name, + input_kwargs={'V Resolution': group_input.outputs["U Resolution"], 'U Resolution': reroute_2}) + + sample_index_3 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': resample_curve_1, 3: position_1, 'Index': flipindex_001_1}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': cylinderside_001.outputs["Geometry"], 'Position': sample_index_3.outputs[2]}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': set_position_1, 'Top': cylinderside_001.outputs["Top"], 'Side': cylinderside_001.outputs["Side"], 'Bottom': cylinderside_001.outputs["Bottom"]}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_warp_around_curve', singleton=False, type='GeometryNodeTree') +def nodegroup_warp_around_curve(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Geometry', None), + ('NodeSocketGeometry', 'Curve', None), + ('NodeSocketInt', 'Curve Resolution', 1024)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Curve Resolution"], 1: 1.0000}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': group_input.outputs["Curve"], 'Count': add}) + + position_1 = nw.new_node(Nodes.InputPosition) + + position_2 = nw.new_node(Nodes.InputPosition) + + separate_xyz_3 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position_2}) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': group_input.outputs["Geometry"]}) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': bounding_box.outputs["Min"]}) + + separate_xyz_2 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': bounding_box.outputs["Max"]}) + + map_range = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': separate_xyz_3.outputs["Z"], 1: separate_xyz_1.outputs["Z"], 2: separate_xyz_2.outputs["Z"]}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Curve Resolution"], 1: map_range.outputs["Result"]}, + attrs={'operation': 'MULTIPLY'}) + + round = nw.new_node(Nodes.Math, input_kwargs={0: multiply}, attrs={'operation': 'ROUND'}) + + sample_index_3 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': resample_curve, 3: position_1, 'Index': round}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + normal = nw.new_node(Nodes.InputNormal) + + sample_index_5 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': resample_curve, 3: normal, 'Index': round}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + scale = nw.new_node(Nodes.VectorMath, + input_kwargs={0: sample_index_5.outputs[2], 'Scale': separate_xyz.outputs["X"]}, + attrs={'operation': 'SCALE'}) + + curve_tangent = nw.new_node(Nodes.CurveTangent) + + sample_index_4 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': resample_curve, 3: curve_tangent, 'Index': round}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + cross_product = nw.new_node(Nodes.VectorMath, + input_kwargs={0: sample_index_4.outputs[2], 1: sample_index_5.outputs[2]}, + attrs={'operation': 'CROSS_PRODUCT'}) + + scale_1 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: cross_product.outputs["Vector"], 'Scale': separate_xyz.outputs["Y"]}, + attrs={'operation': 'SCALE'}) + + add_1 = nw.new_node(Nodes.VectorMath, input_kwargs={0: scale.outputs["Vector"], 1: scale_1.outputs["Vector"]}) + + add_2 = nw.new_node(Nodes.VectorMath, input_kwargs={0: sample_index_3.outputs[2], 1: add_1.outputs["Vector"]}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': group_input.outputs["Geometry"], 'Position': add_2.outputs["Vector"]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_position}, attrs={'is_active_output': True}) diff --git a/infinigen/assets/table_decorations/vase.py b/infinigen/assets/table_decorations/vase.py new file mode 100644 index 000000000..437153ed5 --- /dev/null +++ b/infinigen/assets/table_decorations/vase.py @@ -0,0 +1,301 @@ +# 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: Yiming Zuo + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint, choice, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +import infinigen.core.util.blender as butil + +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory + +from infinigen.assets.table_decorations.utils import nodegroup_lofting, nodegroup_star_profile +from infinigen.assets.material_assignments import AssetList + +class VaseFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=None): + super(VaseFactory, self).__init__(factory_seed, coarse=coarse) + + if dimensions is None: + z = uniform(0.17, 0.5) + x = z * uniform(0.3, 0.6) + dimensions = (x, x, z) + self.dimensions = dimensions + + with FixedSeed(factory_seed): + self.params = self.sample_parameters(dimensions) + self.material_params, self.scratch, self.edge_wear = self.get_material_params() + + self.params.update(self.material_params) + + def get_material_params(self): + material_assignments = AssetList['VaseFactory']() + params = { + 'Material': material_assignments['surface'].assign_material(), + } + wrapped_params = { + k: surface.shaderfunc_to_material(v) for k, v in params.items() + } + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = uniform() < scratch_prob + is_edge_wear = uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + @staticmethod + def sample_parameters(dimensions): + # all in meters + if dimensions is None: + z = uniform(0.25, 0.40) + x = uniform(0.2, 0.4) * z + dimensions = (x, x, z) + + x, y, z = dimensions + + U_resolution = 64 + V_resolution = 64 + + neck_scale = uniform(0.2, 0.8) + + parameters = { + 'Profile Inner Radius': choice([1.0, uniform(0.8, 1.0)]), + 'Profile Star Points': randint(16, U_resolution // 2 + 1), + 'U_resolution': U_resolution, + 'V_resolution': V_resolution, + 'Height': z, + 'Diameter': x, + 'Top Scale': neck_scale * uniform(0.8, 1.2), + 'Neck Mid Position': uniform(0.7, 0.95), + 'Neck Position': 0.5 * neck_scale + 0.5 + uniform(-0.05, 0.05), + 'Neck Scale': neck_scale, + 'Shoulder Position': uniform(0.3, 0.7), + 'Shoulder Thickness': uniform(0.1, 0.25), + 'Foot Scale': uniform(0.4, 0.6), + 'Foot Height': uniform(0.01, 0.1), + 'Material': choice(['glass', 'ceramic']) + } + + return parameters + + def create_asset(self, **params): + bpy.ops.mesh.primitive_plane_add(size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), + scale=(1, 1, 1)) + obj = bpy.context.active_object + + surface.add_geomod(obj, geometry_vases, apply=True, input_kwargs=self.params) + butil.modify_mesh(obj, 'SOLIDIFY', apply=True, thickness=.002) + butil.modify_mesh(obj, 'SUBSURF', apply=True, levels=2, render_levels=2) + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + + +@node_utils.to_nodegroup('nodegroup_vase_profile', singleton=False, type='GeometryNodeTree') +def nodegroup_vase_profile(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Profile Curve', None), + ('NodeSocketFloat', 'Height', 0.0000), ('NodeSocketFloat', 'Diameter', 0.0000), + ('NodeSocketFloat', 'Top Scale', 0.0000), ('NodeSocketFloat', 'Neck Mid Position', 0.0000), + ('NodeSocketFloat', 'Neck Position', 0.5000), ('NodeSocketFloat', 'Neck Scale', 0.0000), + ('NodeSocketFloat', 'Shoulder Position', 0.0000), ('NodeSocketFloat', 'Shoulder Thickness', 0.0000), + ('NodeSocketFloat', 'Foot Scale', 0.0000), ('NodeSocketFloat', 'Foot Height', 0.0000)]) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Height"]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["Top Scale"], + 1: group_input.outputs["Diameter"] + }, attrs={'operation': 'MULTIPLY'}) + + neck_top = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': group_input.outputs["Profile Curve"], + 'Translation': combine_xyz_1, + 'Scale': multiply + }) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["Height"], + 1: group_input.outputs["Neck Position"] + }, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_1}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["Diameter"], + 1: group_input.outputs["Neck Scale"] + }, attrs={'operation': 'MULTIPLY'}) + + neck = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': group_input.outputs["Profile Curve"], + 'Translation': combine_xyz, + 'Scale': multiply_2 + }) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: 1.0000, 1: group_input.outputs["Neck Position"]}, + attrs={'use_clamp': True, 'operation': 'SUBTRACT'}) + + multiply_add = nw.new_node(Nodes.Math, input_kwargs={ + 0: subtract, + 1: group_input.outputs["Neck Mid Position"], + 2: group_input.outputs["Neck Position"] + }, attrs={'operation': 'MULTIPLY_ADD'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_add, 1: group_input.outputs["Height"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_3}) + + add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Neck Scale"], 1: group_input.outputs["Top Scale"]}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: 2.0000}, attrs={'operation': 'DIVIDE'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Diameter"], 1: divide}, + attrs={'operation': 'MULTIPLY'}) + + neck_middle = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': group_input.outputs["Profile Curve"], + 'Translation': combine_xyz_2, + 'Scale': multiply_4 + }) + + neck_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [neck, neck_middle, neck_top]}) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={ + 'Value': group_input.outputs["Shoulder Position"], + 3: group_input.outputs["Foot Height"], + 4: group_input.outputs["Neck Position"] + }) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["Neck Position"], + 1: group_input.outputs["Foot Height"] + }, attrs={'operation': 'SUBTRACT'}) + + multiply_5 = nw.new_node(Nodes.Math, + input_kwargs={0: subtract_1, 1: group_input.outputs["Shoulder Thickness"]}, + attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: map_range.outputs["Result"], 1: multiply_5}) + + minimum = nw.new_node(Nodes.Math, input_kwargs={0: add_1, 1: group_input.outputs["Neck Position"]}, + attrs={'operation': 'MINIMUM'}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: minimum, 1: group_input.outputs["Height"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_6}) + + body_top = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': group_input.outputs["Profile Curve"], + 'Translation': combine_xyz_3, + 'Scale': group_input.outputs["Diameter"] + }) + + subtract_2 = nw.new_node(Nodes.Math, input_kwargs={0: map_range.outputs["Result"], 1: multiply_5}, + attrs={'operation': 'SUBTRACT'}) + + maximum = nw.new_node(Nodes.Math, input_kwargs={0: subtract_2, 1: group_input.outputs["Foot Height"]}, + attrs={'operation': 'MAXIMUM'}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: maximum, 1: group_input.outputs["Height"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_7}) + + body_bottom = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': group_input.outputs["Profile Curve"], + 'Translation': combine_xyz_5, + 'Scale': group_input.outputs["Diameter"] + }) + + body_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [body_bottom, body_top]}) + + multiply_8 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["Foot Height"], + 1: group_input.outputs["Height"] + }, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_8}) + + multiply_9 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["Diameter"], + 1: group_input.outputs["Foot Scale"] + }, attrs={'operation': 'MULTIPLY'}) + + foot_top = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': group_input, + 'Translation': combine_xyz_4, + 'Scale': multiply_9 + }) + + foot_bottom = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': group_input, 'Scale': multiply_9}) + + foot_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [foot_bottom, foot_top]}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [foot_geometry, body_geometry, neck_geometry]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_2}, + attrs={'is_active_output': True}) + + +def geometry_vases(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + starprofile = nw.new_node(nodegroup_star_profile().name, input_kwargs={ + 'Resolution': kwargs['U_resolution'], + 'Points': kwargs['Profile Star Points'], + 'Inner Radius': kwargs['Profile Inner Radius'] + }) + + vaseprofile = nw.new_node(nodegroup_vase_profile().name, input_kwargs={ + 'Profile Curve': starprofile.outputs["Curve"], + 'Height': kwargs['Height'], + 'Diameter': kwargs['Diameter'], + 'Top Scale': kwargs['Top Scale'], + 'Neck Mid Position': kwargs['Neck Mid Position'], + 'Neck Position': kwargs['Neck Position'], + 'Neck Scale': kwargs['Neck Scale'], + 'Shoulder Position': kwargs['Shoulder Position'], + 'Shoulder Thickness': kwargs['Shoulder Thickness'], + 'Foot Scale': kwargs['Foot Scale'], + 'Foot Height': kwargs['Foot Height'] + }) + + lofting = nw.new_node(nodegroup_lofting().name, + input_kwargs={'Profile Curves': vaseprofile, 'U Resolution': 64, 'V Resolution': 64}) + + delete_geometry = nw.new_node(Nodes.DeleteGeometry, input_kwargs={ + 'Geometry': lofting.outputs["Geometry"], + 'Selection': lofting.outputs["Top"] + }) + + set_material = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': delete_geometry, 'Material': kwargs['Material']}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_material}, + attrs={'is_active_output': True}) + + diff --git a/infinigen/assets/tables/__init__.py b/infinigen/assets/tables/__init__.py new file mode 100644 index 000000000..96d9cfa0b --- /dev/null +++ b/infinigen/assets/tables/__init__.py @@ -0,0 +1,7 @@ +# 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: Lingjie Mei +from .cocktail_table import TableCocktailFactory +from .dining_table import TableDiningFactory, SideTableFactory, CoffeeTableFactory +from .table_top import TableTopFactory diff --git a/infinigen/assets/tables/cocktail_table.py b/infinigen/assets/tables/cocktail_table.py new file mode 100644 index 000000000..ec7e30f7d --- /dev/null +++ b/infinigen/assets/tables/cocktail_table.py @@ -0,0 +1,269 @@ +# 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: +# - Yiming Zuo: primary author +# - Alexander Raistrick: implement placeholder + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint, choice + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.surface import NoApply +from infinigen.core.util.color import color_category +from infinigen.core import surface +from infinigen.core import tagging, tags as t + +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory + +from infinigen.assets.tables.table_utils import nodegroup_create_anchors, nodegroup_create_legs_and_strechers +from infinigen.assets.tables.table_top import nodegroup_generate_table_top + +from infinigen.assets.tables.legs.single_stand import nodegroup_generate_single_stand +from infinigen.assets.tables.legs.straight import nodegroup_generate_leg_straight +from infinigen.assets.tables.legs.wheeled import nodegroup_wheeled_leg + +from infinigen.assets.tables.strechers import nodegroup_strecher + +from infinigen.core.util.random import log_uniform +from infinigen.assets.material_assignments import AssetList + +@node_utils.to_nodegroup('geometry_create_legs', singleton=False, type='GeometryNodeTree') +def geometry_create_legs(nw: NodeWrangler, **kwargs): + + createanchors = nw.new_node(nodegroup_create_anchors().name, + input_kwargs={'Profile N-gon': kwargs['Leg Number'], 'Profile Width': kwargs['Leg Placement Top Relative Scale']*kwargs['Top Profile Width'], 'Profile Aspect Ratio': 1.0000}) + + if kwargs['Leg Style'] == "single_stand": + leg = nw.new_node(nodegroup_generate_single_stand(**kwargs).name, + input_kwargs={'Leg Height': kwargs['Leg Height'], + 'Leg Diameter': kwargs['Leg Diameter'], + 'Resolution': 64}) + + leg = nw.new_node(nodegroup_create_legs_and_strechers().name, + input_kwargs={'Anchors': createanchors, + 'Keep Legs': True, + 'Leg Instance': leg, + 'Table Height': kwargs['Top Height'], + 'Leg Bottom Relative Scale': kwargs['Leg Placement Bottom Relative Scale'], + 'Align Leg X rot': True + }) + + elif kwargs['Leg Style'] == "straight": + leg = nw.new_node(nodegroup_generate_leg_straight(**kwargs).name, + input_kwargs={'Leg Height': kwargs['Leg Height'], + 'Leg Diameter': kwargs['Leg Diameter'], + 'Resolution': 32, + 'N-gon': kwargs['Leg NGon'], + 'Fillet Ratio': 0.1}) + + strecher = nw.new_node(nodegroup_strecher().name, + input_kwargs={'Profile Width': kwargs['Leg Diameter'] * 0.5}) + + leg = nw.new_node(nodegroup_create_legs_and_strechers().name, + input_kwargs={ + 'Anchors': createanchors, + 'Keep Legs': True, + 'Leg Instance': leg, + 'Table Height': kwargs['Top Height'], + 'Strecher Instance': strecher, + 'Strecher Index Increment': kwargs['Strecher Increament'], + 'Strecher Relative Position': kwargs['Strecher Relative Pos'], + 'Leg Bottom Relative Scale': kwargs['Leg Placement Bottom Relative Scale'], + 'Align Leg X rot': True + }) + + elif kwargs['Leg Style'] == "wheeled": + leg = nw.new_node(nodegroup_wheeled_leg(**kwargs).name, + input_kwargs={ + 'Joint Height': kwargs['Leg Joint Height'], + 'Leg Diameter': kwargs['Leg Diameter'], + 'Top Height': kwargs['Top Height'], + 'Wheel Width': kwargs['Leg Wheel Width'], + 'Wheel Rotation': kwargs['Leg Wheel Rot'], + 'Pole Length': kwargs['Leg Pole Length'], + 'Leg Number': kwargs['Leg Pole Number'], + }) + + else: + raise NotImplementedError + + leg = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': leg, 'Material': kwargs['LegMaterial']}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': leg}, attrs={'is_active_output': True}) + +def geometry_assemble_table(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + generatetabletop = nw.new_node(nodegroup_generate_table_top().name, + input_kwargs={ + 'Thickness': kwargs['Top Thickness'], + 'N-gon': kwargs['Top Profile N-gon'], + 'Profile Width': kwargs['Top Profile Width'], + 'Aspect Ratio': kwargs['Top Profile Aspect Ratio'], + 'Fillet Ratio': kwargs['Top Profile Fillet Ratio'], + 'Fillet Radius Vertical': kwargs['Top Vertical Fillet Ratio'], + }) + + tabletop_instance = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': generatetabletop, + 'Translation': (0.0000, 0.0000, kwargs['Top Height'])}) + + tabletop_instance = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': tabletop_instance, 'Material': kwargs['TopMaterial']}) + + legs = nw.new_node(geometry_create_legs(**kwargs).name) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [tabletop_instance, legs]}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': generatetabletop.outputs["Curve"]}) + fill_curve = nw.new_node(Nodes.FillCurve, input_kwargs={'Curve': resample_curve}) + + voff = kwargs['Top Height'] + kwargs['Top Thickness'] + extrude_mesh = nw.new_node(Nodes.ExtrudeMesh, input_kwargs={'Mesh': fill_curve, 'Offset Scale': -voff, 'Individual': False}) + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [extrude_mesh.outputs["Mesh"], fill_curve]}) + transform_geometry_1 = nw.new_node( + Nodes.Transform, input_kwargs={ + 'Geometry': join_geometry_1, 'Translation': (0, 0, voff) + } + ) + switch = nw.new_node(Nodes.Switch, input_kwargs={1: kwargs['is_placeholder'], 14: join_geometry, 15: transform_geometry_1}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': switch}, attrs={'is_active_output': True}) + + +class TableCocktailFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=None): + super(TableCocktailFactory, self).__init__(factory_seed, coarse=coarse) + + self.dimensions = dimensions + + with FixedSeed(factory_seed): + self.params = self.sample_parameters(dimensions) + from infinigen.assets.clothes import blanket + from infinigen.assets.scatters.clothes import ClothesCover + # self.clothes_scatter = ClothesCover(factory_fn=blanket.BlanketFactory, width=log_uniform(.8, 1.2), + # size=uniform(.8, 1.2)) if uniform() < .3 else NoApply() + self.clothes_scatter = NoApply() + self.material_params, self.scratch, self.edge_wear = self.get_material_params() + + self.params.update(self.material_params) + + def get_material_params(self): + material_assignments = AssetList['TableCocktailFactory']() + params = { + "TopMaterial": material_assignments['top'].assign_material(), + "LegMaterial": material_assignments['leg'].assign_material(), + } + wrapped_params = { + k: surface.shaderfunc_to_material(v) for k, v in params.items() + } + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = uniform() < scratch_prob + is_edge_wear = uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + @staticmethod + def sample_parameters(dimensions): + # all in meters + if dimensions is None: + x = uniform(0.5, 0.8) + z = uniform(1.0, 1.5) + dimensions = ( + x, x, z + ) + + x, y, z = dimensions + + NGon = choice([4, 32]) + if NGon >= 32: + round_table = True + else: + round_table = False + + leg_style = choice(['straight', 'single_stand']) + if leg_style == "single_stand": + leg_number = 1 + leg_diameter = uniform(0.7*x, 0.9*x) + + leg_curve_ctrl_pts = [(0.0, uniform(0.1, 0.2)), + (0.5, uniform(0.1, 0.2)), (0.9, uniform(0.2, 0.3)), (1.0, 1.0)] + + elif leg_style == "straight": + leg_diameter = uniform(0.05, 0.07) + + if round_table: + leg_number = choice([3, 4]) + else: + leg_number = NGon + + leg_curve_ctrl_pts = [(0.0, 1.0), (0.4, uniform(0.85, 0.95)), (1.0, uniform(0.4, 0.6))] + + else: + raise NotImplementedError + + top_thickness = uniform(0.02, 0.05) + + parameters = { + 'Top Profile N-gon': 32 if round_table else 4, + 'Top Profile Width': x if round_table else 1.414 * x, + 'Top Profile Aspect Ratio': 1.0, + 'Top Profile Fillet Ratio': 0.499 if round_table else uniform(0.0, 0.05), + 'Top Thickness': top_thickness, + 'Top Vertical Fillet Ratio': uniform(0.1, 0.3), + # 'Top Material': choice(['marble', 'tiled_wood', 'plastic', 'glass']), + 'Height': z, + 'Top Height': z - top_thickness, + 'Leg Number': leg_number, + 'Leg Style': leg_style, + 'Leg NGon': choice([4, 32]), + 'Leg Placement Top Relative Scale': 0.7, + 'Leg Placement Bottom Relative Scale': uniform(1.1, 1.3), + 'Leg Height': 1.0, + 'Leg Diameter': leg_diameter, + 'Leg Curve Control Points': leg_curve_ctrl_pts, + # 'Leg Material': choice(['metal', 'wood', 'glass']), + 'Strecher Relative Pos': uniform(0.2, 0.6), + 'Strecher Increament': choice([0, 1, 2]) + } + + return parameters + + def _execute_geonodes(self, is_placeholder): + + bpy.ops.mesh.primitive_plane_add( + size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + kwargs = {**self.params, 'is_placeholder': is_placeholder} + surface.add_geomod(obj, geometry_assemble_table, apply=True, input_kwargs=kwargs) + tagging.tag_system.relabel_obj(obj) + + return obj + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + return self._execute_geonodes(is_placeholder=True) + + def create_asset(self, **_): + return self._execute_geonodes(is_placeholder=False) + + def finalize_assets(self, assets): + self.clothes_scatter.apply(assets) + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) diff --git a/infinigen/assets/tables/dining_table.py b/infinigen/assets/tables/dining_table.py new file mode 100644 index 000000000..9c956b499 --- /dev/null +++ b/infinigen/assets/tables/dining_table.py @@ -0,0 +1,312 @@ +# 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: Yiming Zuo + +from collections.abc import Iterable + +import bpy +import bpy +import mathutils +import numpy as np +from numpy.random import uniform, normal, randint, choice + +# from infinigen.assets.materials import metal, metal_shader_list +# from infinigen.assets.materials.leather_and_fabrics import fabric +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.surface import NoApply +from infinigen.core.util.color import color_category +from infinigen.core import surface +from infinigen.core import tagging, tags as t + +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory + +from infinigen.assets.tables.table_utils import nodegroup_create_anchors, nodegroup_create_legs_and_strechers +from infinigen.assets.tables.table_top import nodegroup_generate_table_top + +from infinigen.assets.tables.legs.single_stand import nodegroup_generate_single_stand +from infinigen.assets.tables.legs.straight import nodegroup_generate_leg_straight +from infinigen.assets.tables.legs.square import nodegroup_generate_leg_square + +from infinigen.assets.tables.strechers import nodegroup_strecher + +from infinigen.core.util.random import log_uniform +from infinigen.assets.material_assignments import AssetList + + +@node_utils.to_nodegroup('geometry_create_legs', singleton=False, type='GeometryNodeTree') +def geometry_create_legs(nw: NodeWrangler, **kwargs): + createanchors = nw.new_node(nodegroup_create_anchors().name, input_kwargs={ + 'Profile N-gon': kwargs['Leg Number'], + 'Profile Width': kwargs['Leg Placement Top Relative Scale'] * kwargs['Top Profile Width'], + 'Profile Aspect Ratio': kwargs['Top Profile Aspect Ratio'] + }) + + if kwargs['Leg Style'] == "single_stand": + leg = nw.new_node(nodegroup_generate_single_stand(**kwargs).name, input_kwargs={ + 'Leg Height': kwargs['Leg Height'], + 'Leg Diameter': kwargs['Leg Diameter'], + 'Resolution': 64 + }) + + leg = nw.new_node(nodegroup_create_legs_and_strechers().name, input_kwargs={ + 'Anchors': createanchors, + 'Keep Legs': True, + 'Leg Instance': leg, + 'Table Height': kwargs['Top Height'], + 'Leg Bottom Relative Scale': kwargs['Leg Placement Bottom Relative Scale'], + 'Align Leg X rot': True + }) + + elif kwargs['Leg Style'] == "straight": + leg = nw.new_node(nodegroup_generate_leg_straight(**kwargs).name, input_kwargs={ + 'Leg Height': kwargs['Leg Height'], + 'Leg Diameter': kwargs['Leg Diameter'], + 'Resolution': 32, + 'N-gon': kwargs['Leg NGon'], + 'Fillet Ratio': 0.1 + }) + + strecher = nw.new_node(nodegroup_strecher().name, + input_kwargs={'Profile Width': kwargs['Leg Diameter'] * 0.5}) + + leg = nw.new_node(nodegroup_create_legs_and_strechers().name, input_kwargs={ + 'Anchors': createanchors, + 'Keep Legs': True, + 'Leg Instance': leg, + 'Table Height': kwargs['Top Height'], + 'Strecher Instance': strecher, + 'Strecher Index Increment': kwargs['Strecher Increament'], + 'Strecher Relative Position': kwargs['Strecher Relative Pos'], + 'Leg Bottom Relative Scale': kwargs['Leg Placement Bottom Relative Scale'], + 'Align Leg X rot': True + }) + + elif kwargs['Leg Style'] == "square": + leg = nw.new_node(nodegroup_generate_leg_square(**kwargs).name, input_kwargs={ + 'Height': kwargs['Leg Height'], + 'Width': 0.707 * kwargs['Leg Placement Top Relative Scale'] * kwargs['Top Profile Width'] * kwargs[ + 'Top Profile Aspect Ratio'], + 'Has Bottom Connector': (kwargs['Strecher Increament'] > 0), + 'Profile Width': kwargs['Leg Diameter'] + }) + + leg = nw.new_node(nodegroup_create_legs_and_strechers().name, input_kwargs={ + 'Anchors': createanchors, + 'Keep Legs': True, + 'Leg Instance': leg, + 'Table Height': kwargs['Top Height'], + 'Leg Bottom Relative Scale': kwargs['Leg Placement Bottom Relative Scale'], + 'Align Leg X rot': True + }) + + else: + raise NotImplementedError + + leg = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': leg, 'Material': kwargs['LegMaterial']}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': leg}, + attrs={'is_active_output': True}) + + +def geometry_assemble_table(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + generatetabletop = nw.new_node(nodegroup_generate_table_top().name, input_kwargs={ + 'Thickness': kwargs['Top Thickness'], + 'N-gon': kwargs['Top Profile N-gon'], + 'Profile Width': kwargs['Top Profile Width'], + 'Aspect Ratio': kwargs['Top Profile Aspect Ratio'], + 'Fillet Ratio': kwargs['Top Profile Fillet Ratio'], + 'Fillet Radius Vertical': kwargs['Top Vertical Fillet Ratio'] + }) + + tabletop_instance = nw.new_node(Nodes.Transform, input_kwargs={ + 'Geometry': generatetabletop, + 'Translation': (0.0000, 0.0000, kwargs['Top Height']) + }) + + tabletop_instance = nw.new_node(Nodes.SetMaterial, + input_kwargs={'Geometry': tabletop_instance, 'Material': kwargs['TopMaterial']}) + + legs = nw.new_node(geometry_create_legs(**kwargs).name) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [tabletop_instance, legs]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, + attrs={'is_active_output': True}) + + +class TableDiningFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=None): + super(TableDiningFactory, self).__init__(factory_seed, coarse=coarse) + + self.dimensions = dimensions + + with FixedSeed(factory_seed): + self.params = self.sample_parameters(dimensions) + from infinigen.assets.clothes import blanket + + from infinigen.assets.scatters.clothes import ClothesCover + # self.clothes_scatter = ClothesCover(factory_fn=blanket.BlanketFactory, width=log_uniform(.8, 1.2), + # size=uniform(.8, 1.2)) if uniform() < .3 else NoApply() + self.clothes_scatter = NoApply() + self.material_params, self.scratch, self.edge_wear = self.get_material_params() + + self.params.update(self.material_params) + + def get_material_params(self): + material_assignments = AssetList['TableDiningFactory']() + params = { + "TopMaterial": material_assignments['top'].assign_material(), + "LegMaterial": material_assignments['leg'].assign_material(), + } + wrapped_params = { + k: surface.shaderfunc_to_material(v) for k, v in params.items() + } + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = uniform() < scratch_prob + is_edge_wear = uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return wrapped_params, scratch, edge_wear + + + @staticmethod + def sample_parameters(dimensions): + + if dimensions is None: + + width = uniform(0.91, 1.16) + + if uniform() < 0.7: + # oblong + length = uniform(1.4, 2.8) + else: + # approx square + length = width * normal(1, 0.1) + + dimensions = ( + length, + width, + uniform(0.65, 0.85) + ) + + # all in meters + x, y, z = dimensions + + NGon = 4 + + leg_style = choice(['straight', 'single_stand', 'square'], p=[0.5, 0.1, 0.4]) + # leg_style = choice(['straight']) + + if leg_style == "single_stand": + leg_number = 2 + leg_diameter = uniform(0.22 * x, 0.28 * x) + + leg_curve_ctrl_pts = [(0.0, uniform(0.1, 0.2)), (0.5, uniform(0.1, 0.2)), (0.9, uniform(0.2, 0.3)), + (1.0, 1.0)] + + top_scale = uniform(0.6, 0.7) + bottom_scale = 1.0 + + elif leg_style == "square": + leg_number = 2 + leg_diameter = uniform(0.07, 0.10) + + leg_curve_ctrl_pts = None + + top_scale = 0.8 + bottom_scale = 1.0 + + elif leg_style == "straight": + leg_diameter = uniform(0.05, 0.07) + + leg_number = 4 + + leg_curve_ctrl_pts = [(0.0, 1.0), (0.4, uniform(0.85, 0.95)), (1.0, uniform(0.4, 0.6))] + + top_scale = 0.8 + bottom_scale = uniform(1.0, 1.2) + + else: + raise NotImplementedError + + top_thickness = uniform(0.03, 0.06) + + parameters = { + 'Top Profile N-gon': NGon, + 'Top Profile Width': 1.414 * x, + 'Top Profile Aspect Ratio': y / x, + 'Top Profile Fillet Ratio': uniform(0.0, 0.02), + 'Top Thickness': top_thickness, + 'Top Vertical Fillet Ratio': uniform(0.1, 0.3), + # 'Top Material': choice(['marble', 'tiled_wood', 'metal', 'fabric'], p=[.3, .3, .2, .2]), + 'Height': z, + 'Top Height': z - top_thickness, + 'Leg Number': leg_number, + 'Leg Style': leg_style, + 'Leg NGon': 4, + 'Leg Placement Top Relative Scale': top_scale, + 'Leg Placement Bottom Relative Scale': bottom_scale, + 'Leg Height': 1.0, + 'Leg Diameter': leg_diameter, + 'Leg Curve Control Points': leg_curve_ctrl_pts, + # 'Leg Material': choice(['metal', 'wood', 'glass', 'plastic']), + 'Strecher Relative Pos': uniform(0.2, 0.6), + 'Strecher Increament': choice([0, 1, 2]) + } + + return parameters + + def create_asset(self, **params): + bpy.ops.mesh.primitive_plane_add(size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), + scale=(1, 1, 1)) + obj = bpy.context.active_object + + # surface.add_geomod(obj, geometry_assemble_table, apply=False, input_kwargs=self.params) + surface.add_geomod(obj, geometry_assemble_table, apply=True, input_kwargs=self.params) + tagging.tag_system.relabel_obj(obj) + assert tagging.tagged_face_mask(obj, {t.Subpart.SupportSurface}).sum() != 0 + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + + #def finalize_assets(self, assets): + # self.clothes_scatter.apply(assets) + +class SideTableFactory(TableDiningFactory): + + def __init__(self, factory_seed, coarse=False, dimensions=None): + if dimensions is None: + w = 0.55 * normal(1, 0.05) + h = 0.95 * w * normal(1, 0.05) + dimensions = (w, w, h) + super().__init__(factory_seed, coarse=coarse, dimensions=dimensions) + +class CoffeeTableFactory(TableDiningFactory): + + def __init__(self, factory_seed, coarse=False, dimensions=None): + if dimensions is None: + dimensions = ( + uniform(1, 1.5), + uniform(0.6, 0.9), + uniform(0.4, 0.5) + ) + super().__init__(factory_seed, coarse=coarse, dimensions=dimensions) + diff --git a/infinigen/assets/tables/legs/single_stand.py b/infinigen/assets/tables/legs/single_stand.py new file mode 100644 index 000000000..1425e9190 --- /dev/null +++ b/infinigen/assets/tables/legs/single_stand.py @@ -0,0 +1,34 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +from infinigen.assets.tables.table_utils import nodegroup_n_gon_cylinder, nodegroup_generate_radius_curve + +@node_utils.to_nodegroup('nodegroup_generate_single_stand', singleton=False, type='GeometryNodeTree') +def nodegroup_generate_single_stand(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Leg Height', 0.0000), + ('NodeSocketFloat', 'Leg Diameter', 1.0000), + ('NodeSocketInt', 'Resolution', 64)]) + + generateradiuscurve = nw.new_node(nodegroup_generate_radius_curve(kwargs['Leg Curve Control Points']).name, input_kwargs={'Resolution': group_input.outputs["Resolution"]}) + + ngoncylinder = nw.new_node(nodegroup_n_gon_cylinder().name, + input_kwargs={'Radius Curve': generateradiuscurve, 'Height': group_input.outputs["Leg Height"], 'N-gon': group_input.outputs["Resolution"], 'Profile Width': group_input.outputs["Leg Diameter"], 'Aspect Ratio': 1.0000, 'Fillet Ratio': 0.0000, 'Resolution': group_input.outputs["Resolution"]}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': ngoncylinder.outputs["Mesh"]}, + attrs={'is_active_output': True}) \ No newline at end of file diff --git a/infinigen/assets/tables/legs/square.py b/infinigen/assets/tables/legs/square.py new file mode 100644 index 000000000..7d99d728e --- /dev/null +++ b/infinigen/assets/tables/legs/square.py @@ -0,0 +1,74 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +from infinigen.assets.tables.table_utils import nodegroup_n_gon_profile, nodegroup_merge_curve + +@node_utils.to_nodegroup('nodegroup_generate_leg_square', singleton=False, type='GeometryNodeTree') +def nodegroup_generate_leg_square(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[ + ('NodeSocketFloat', 'Width', 0.0000), + ('NodeSocketFloat', 'Height', 0.0000), + ('NodeSocketFloatDistance', 'Fillet Radius', 0.0300), + ('NodeSocketBool', 'Has Bottom Connector', True), + ('NodeSocketInt', 'Profile N-gon', 4), + ('NodeSocketFloatDistance', 'Profile Width', 0.1000), + ('NodeSocketFloatDistance', 'Profile Aspect Ratio', 0.5000), + ('NodeSocketFloat', 'Profile Fillet Ratio', 0.1000)]) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Has Bottom Connector"], 1: 4.0000}) + + map_range = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': group_input.outputs["Has Bottom Connector"], 3: 4.7124, 4: 6.2832}) + + arc = nw.new_node('GeometryNodeCurveArc', + input_kwargs={'Resolution': add, 'Radius': 0.7071, 'Sweep Angle': map_range.outputs["Result"]}) + + mergecurve = nw.new_node(nodegroup_merge_curve().name, input_kwargs={'Curve': arc.outputs["Curve"]}) + + map_range_1 = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': group_input.outputs["Has Bottom Connector"], 3: 1.5708, 4: 3.1416}) + + set_curve_tilt = nw.new_node(Nodes.SetCurveTilt, input_kwargs={'Curve': mergecurve, 'Tilt': map_range_1.outputs["Result"]}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': set_curve_tilt, 'Rotation': (0.0000, 0.0000, -0.7854)}) + + transform_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform, 'Translation': (0.0000, 0.0000, -0.5000), 'Rotation': (1.5708, 0.0000, 0.0000)}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Width"], 'Y': 1.0000, 'Z': group_input.outputs["Height"]}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_1, 'Scale': combine_xyz}) + + set_curve_radius = nw.new_node(Nodes.SetCurveRadius, input_kwargs={'Curve': transform_2, 'Radius': 1.0000}) + + fillet_curve = nw.new_node(Nodes.FilletCurve, + input_kwargs={'Curve': set_curve_radius, 'Count': 8, 'Radius': group_input.outputs["Fillet Radius"], 'Limit Radius': True}, + attrs={'mode': 'POLY'}) + + ngonprofile = nw.new_node(nodegroup_n_gon_profile().name, + input_kwargs={'Profile N-gon': group_input.outputs["Profile N-gon"], 'Profile Width': group_input.outputs["Profile Width"], 'Profile Aspect Ratio': group_input.outputs["Profile Aspect Ratio"], 'Profile Fillet Ratio': group_input.outputs["Profile Fillet Ratio"]}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': fillet_curve, 'Profile Curve': ngonprofile, 'Fill Caps': True}) + + transform_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': curve_to_mesh, 'Rotation': (0.0000, 0.0000, 1.5708)}) + + set_shade_smooth = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': transform_3, 'Shade Smooth': False}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_shade_smooth}, attrs={'is_active_output': True}) \ No newline at end of file diff --git a/infinigen/assets/tables/legs/straight.py b/infinigen/assets/tables/legs/straight.py new file mode 100644 index 000000000..0f619d1e5 --- /dev/null +++ b/infinigen/assets/tables/legs/straight.py @@ -0,0 +1,36 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +from infinigen.assets.tables.table_utils import nodegroup_n_gon_cylinder, nodegroup_generate_radius_curve + +@node_utils.to_nodegroup('nodegroup_generate_leg_straight', singleton=False, type='GeometryNodeTree') +def nodegroup_generate_leg_straight(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Leg Height', 0.0000), + ('NodeSocketFloat', 'Leg Diameter', 1.0000), + ('NodeSocketInt', 'Resolution', 0), + ('NodeSocketInt', 'N-gon', 32), + ('NodeSocketFloat', 'Fillet Ratio', 0.0100)]) + + generateradiuscurve = nw.new_node(nodegroup_generate_radius_curve(kwargs['Leg Curve Control Points']).name, input_kwargs={'Resolution': group_input.outputs["Resolution"]}) + + ngoncylinder = nw.new_node(nodegroup_n_gon_cylinder().name, + input_kwargs={'Radius Curve': generateradiuscurve, 'Height': group_input.outputs["Leg Height"], 'N-gon': group_input.outputs["N-gon"], 'Profile Width': group_input.outputs["Leg Diameter"], 'Aspect Ratio': 1.0000, 'Fillet Ratio': group_input.outputs["Fillet Ratio"], 'Resolution': group_input.outputs["Resolution"]}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': ngoncylinder.outputs["Mesh"]}, + attrs={'is_active_output': True}) \ No newline at end of file diff --git a/infinigen/assets/tables/legs/wheeled.py b/infinigen/assets/tables/legs/wheeled.py new file mode 100644 index 000000000..874914234 --- /dev/null +++ b/infinigen/assets/tables/legs/wheeled.py @@ -0,0 +1,217 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +from infinigen.assets.tables.table_top import nodegroup_capped_cylinder +from infinigen.assets.tables.table_utils import nodegroup_arc_top, nodegroup_n_gon_cylinder, nodegroup_align_bottom_to_floor, nodegroup_create_anchors, nodegroup_create_legs_and_strechers + +@node_utils.to_nodegroup('nodegroup_chair_wheel', singleton=False, type='GeometryNodeTree') +def nodegroup_chair_wheel(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Arc Sweep Angle', 240.0000), + ('NodeSocketFloat', 'Wheel Width', 0.0000), + ('NodeSocketFloat', 'Wheel Rotation', 0.5000), + ('NodeSocketFloat', 'Pole Width', 0.0000), + ('NodeSocketFloat', 'Pole Aspect Ratio', 0.6000), + ('NodeSocketFloat', 'Pole Length', 3.0000)]) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': group_input.outputs["Wheel Width"]}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Wheel Width"], 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz_1, 'End': combine_xyz_2}) + + value = nw.new_node(Nodes.Value) + value.outputs[0].default_value = 0.0200 + + value_1 = nw.new_node(Nodes.Value) + value_1.outputs[0].default_value = 0.5000 + + cappedcylinder = nw.new_node(nodegroup_capped_cylinder().name, + input_kwargs={'Thickness': value, 'Radius': value_1, 'Cap Relative Scale': 0.0100}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: value, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_1}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': cappedcylinder, 'Translation': combine_xyz, 'Rotation': (-1.5708, 0.0000, 0.0000)}) + + position = nw.new_node(Nodes.InputPosition) + + align_euler_to_vector = nw.new_node(Nodes.AlignEulerToVector, input_kwargs={'Vector': position}, attrs={'axis': 'Y'}) + + instance_on_points = nw.new_node(Nodes.InstanceOnPoints, + input_kwargs={'Points': curve_line, 'Instance': transform, 'Rotation': align_euler_to_vector}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: value_1, 1: 0.0800}) + + arctop = nw.new_node(nodegroup_arc_top().name, + input_kwargs={'Diameter': add, 'Sweep Angle': group_input.outputs["Arc Sweep Angle"]}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Wheel Width"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + quadrilateral = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', input_kwargs={'Width': multiply_2, 'Height': 0.0200}) + + fillet_curve = nw.new_node('GeometryNodeFilletCurve', + input_kwargs={'Curve': quadrilateral, 'Count': 4, 'Radius': 0.0300, 'Limit Radius': True}, + attrs={'mode': 'POLY'}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={'Curve': arctop, 'Profile Curve': fillet_curve, 'Fill Caps': True}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: value_1, 1: 0.1000}, attrs={'operation': 'MULTIPLY'}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: value_1, 1: 0.4000}, attrs={'operation': 'MULTIPLY'}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Side Segments': 8, 'Fill Segments': 4, 'Radius': multiply_3, 'Depth': multiply_4}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: value_1, 1: 0.4400}, attrs={'operation': 'MULTIPLY'}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: value_1, 1: 0.4500}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_5, 'Z': multiply_6}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz_3}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [instance_on_points, curve_to_mesh, transform_2]}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_5, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_7}) + + transform_6 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry, 'Translation': combine_xyz_4}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Pole Length"], 1: 0.1500}, + attrs={'operation': 'SUBTRACT'}) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Pole Width"], 1: -0.3535, 2: -0.3000}, + attrs={'operation': 'MULTIPLY_ADD'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': subtract, 'Z': multiply_add}) + + radians = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Wheel Rotation"]}, attrs={'operation': 'RADIANS'}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': radians}) + + transform_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_6, 'Translation': combine_xyz_5, 'Rotation': combine_xyz_6}) + + curve_line_1 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': (1.0000, 0.0000, -1.0000), 'End': (1.0000, 0.0000, 1.0000)}) + + ngoncylinder = nw.new_node(nodegroup_n_gon_cylinder().name, + input_kwargs={'Radius Curve': curve_line_1, 'Height': group_input.outputs["Pole Length"], 'N-gon': 4, 'Profile Width': group_input.outputs["Pole Width"], 'Aspect Ratio': group_input.outputs["Pole Aspect Ratio"], 'Fillet Ratio': 0.1500, 'Resolution': 32}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': ngoncylinder.outputs["Mesh"], 'Rotation': (0.0000, -1.5708, 0.0000)}) + + subdivision_surface_1 = nw.new_node(Nodes.SubdivisionSurface, input_kwargs={'Mesh': transform_3, 'Level': 0}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_4, subdivision_surface_1]}) + + value_2 = nw.new_node(Nodes.Value) + value_2.outputs[0].default_value = 0.1500 + + transform_geometry = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry_1, 'Scale': value_2}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_geometry}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_wheeled_leg', singleton=False, type='GeometryNodeTree') +def nodegroup_wheeled_leg(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Joint Height', 0.0000), + ('NodeSocketFloat', 'Leg Diameter', 0.0000), + ('NodeSocketFloat', 'Top Height', 0.0000), + ('NodeSocketFloat', 'Arc Sweep Angle', 240.0000), + ('NodeSocketFloat', 'Wheel Width', 0.1300), + ('NodeSocketFloat', 'Wheel Rotation', 0.5000), + ('NodeSocketFloat', 'Pole Length', 1.8000), + ('NodeSocketInt', 'Leg Number', 5)]) + + value_1 = nw.new_node(Nodes.Value) + value_1.outputs[0].default_value = 0.0010 + + createanchors = nw.new_node(nodegroup_create_anchors().name, + input_kwargs={'Profile N-gon': group_input.outputs["Leg Number"], 'Profile Width': value_1, 'Profile Aspect Ratio': 1.0000}) + + chair_wheel = nw.new_node(nodegroup_chair_wheel().name, + input_kwargs={'Arc Sweep Angle': group_input.outputs["Arc Sweep Angle"], 'Wheel Width': group_input.outputs["Wheel Width"], 'Wheel Rotation': group_input.outputs["Wheel Rotation"], 'Pole Width': 0.5000, 'Pole Length': group_input.outputs["Pole Length"]}) + + transform_geometry = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': chair_wheel, 'Rotation': (0.0000, 1.5708, 0.0000)}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: 2.0000, 1: value_1}, attrs={'operation': 'DIVIDE'}) + + createlegsandstrechers = nw.new_node(nodegroup_create_legs_and_strechers().name, + input_kwargs={'Anchors': createanchors, 'Keep Legs': True, 'Leg Instance': transform_geometry, 'Table Height': 0.0250, 'Leg Bottom Relative Scale': divide, 'Strecher Index Increment': 1, 'Strecher Relative Position': 1.0000, 'Leg Bottom Offset': 0.0250, 'Align Leg X rot': True}) + + alignbottomtofloor = nw.new_node(nodegroup_align_bottom_to_floor().name, input_kwargs={'Geometry': createlegsandstrechers}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Leg Diameter"]}, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Joint Height"], 1: alignbottomtofloor.outputs["Offset"]}, + attrs={'operation': 'SUBTRACT'}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', input_kwargs={'Vertices': 64, 'Radius': multiply, 'Depth': subtract}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: subtract}, attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: alignbottomtofloor.outputs["Offset"]}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add}) + + transform_geometry_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Translation': combine_xyz_1}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: 0.0025}, attrs={'operation': 'SUBTRACT'}) + + subtract_2 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Top Height"], 1: group_input.outputs["Joint Height"]}, + attrs={'operation': 'SUBTRACT'}) + + cylinder_1 = nw.new_node('GeometryNodeMeshCylinder', input_kwargs={'Vertices': 64, 'Radius': subtract_1, 'Depth': subtract_2}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={1: subtract_2}, attrs={'operation': 'MULTIPLY'}) + + subtract_3 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Top Height"], 1: multiply_2}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': subtract_3}) + + transform_geometry_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cylinder_1.outputs["Mesh"], 'Translation': combine_xyz_2}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [alignbottomtofloor.outputs["Geometry"], transform_geometry_2, transform_geometry_3]}) + + # multiply_3 = nw.new_node(Nodes.Math, + # input_kwargs={0: group_input.outputs["Top Height"], 1: -1.0000}, + # attrs={'operation': 'MULTIPLY'}) + + # combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_3}) + + # transform_geometry_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': join_geometry, 'Translation': combine_xyz_3}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, attrs={'is_active_output': True}) \ No newline at end of file diff --git a/infinigen/assets/tables/lofting.py b/infinigen/assets/tables/lofting.py new file mode 100644 index 000000000..7156e4944 --- /dev/null +++ b/infinigen/assets/tables/lofting.py @@ -0,0 +1,298 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + + + +@node_utils.to_nodegroup('nodegroup_flip_index', singleton=False, type='GeometryNodeTree') +def nodegroup_flip_index(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + index = nw.new_node(Nodes.Index) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'V Resolution', 0), + ('NodeSocketInt', 'U Resolution', 0)]) + + modulo = nw.new_node(Nodes.Math, + input_kwargs={0: index, 1: group_input.outputs["V Resolution"]}, + attrs={'operation': 'MODULO'}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: modulo, 1: group_input.outputs["U Resolution"]}, + attrs={'operation': 'MULTIPLY'}) + + divide = nw.new_node(Nodes.Math, + input_kwargs={0: index, 1: group_input.outputs["V Resolution"]}, + attrs={'operation': 'DIVIDE'}) + + floor = nw.new_node(Nodes.Math, input_kwargs={0: divide}, attrs={'operation': 'FLOOR'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: floor}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Index': add}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_cylinder_side', singleton=False, type='GeometryNodeTree') +def nodegroup_cylinder_side(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'U Resolution', 32), + ('NodeSocketInt', 'V Resolution', 0)]) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["V Resolution"], 1: 1.0000}, + attrs={'operation': 'SUBTRACT'}) + + cylinder = nw.new_node('GeometryNodeMeshCylinder', + input_kwargs={'Vertices': group_input.outputs["U Resolution"], 'Side Segments': subtract}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': cylinder.outputs["Mesh"], 'Name': 'uv_map', 3: cylinder.outputs["UV Map"]}, + attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': store_named_attribute, 'Top': cylinder.outputs["Top"], 'Side': cylinder.outputs["Side"], 'Bottom': cylinder.outputs["Bottom"]}, + attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_shifted_circle', singleton=False, type='GeometryNodeTree') +def nodegroup_shifted_circle(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'Resolution', 32), + ('NodeSocketFloatDistance', 'Radius', 1.0000), + ('NodeSocketFloat', 'Z', 0.0000), + ('NodeSocketFloat', 'Rot Z', 0.0000)]) + + curve_circle_3 = nw.new_node(Nodes.CurveCircle, + input_kwargs={'Resolution': group_input.outputs["Resolution"], 'Radius': group_input.outputs["Radius"]}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Z"]}) + + radians = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Rot Z"]}, attrs={'operation': 'RADIANS'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': radians}) + + transform_3 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curve_circle_3.outputs["Curve"], 'Translation': combine_xyz, 'Rotation': combine_xyz_1}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_3}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_shifted_square', singleton=False, type='GeometryNodeTree') +def nodegroup_shifted_square(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'Resolution', 10), + ('NodeSocketFloatDistance', 'Width', 1.0000), + ('NodeSocketFloat', 'Z', 0.0000), + ('NodeSocketFloat', 'Rot Z', 0.5000)]) + + quadrilateral = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', + input_kwargs={'Width': group_input.outputs["Width"], 'Height': group_input.outputs["Width"]}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': quadrilateral, 'Count': group_input.outputs["Resolution"]}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Z"]}) + + radians = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Rot Z"]}, attrs={'operation': 'RADIANS'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': radians}) + + transform_geometry = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': resample_curve, 'Translation': combine_xyz, 'Rotation': combine_xyz_1}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Curve': transform_geometry}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_lofting', singleton=False, type='GeometryNodeTree') +def nodegroup_lofting(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Profile Curves', None), + ('NodeSocketInt', 'U Resolution', 32), + ('NodeSocketInt', 'V Resolution', 32), + ('NodeSocketBool', 'Use Nurb', False)]) + + cylinderside = nw.new_node(nodegroup_cylinder_side().name, + input_kwargs={'U Resolution': group_input.outputs["U Resolution"], 'V Resolution': group_input}) + + index = nw.new_node(Nodes.Index) + + evaluate_on_domain = nw.new_node(Nodes.EvaluateonDomain, input_kwargs={1: index}, attrs={'domain': 'CURVE', 'data_type': 'INT'}) + + equal = nw.new_node(Nodes.Compare, + input_kwargs={2: evaluate_on_domain.outputs[1]}, + attrs={'data_type': 'INT', 'operation': 'EQUAL'}) + + curve_line = nw.new_node(Nodes.CurveLine) + + domain_size = nw.new_node(Nodes.DomainSize, input_kwargs={'Geometry': group_input}, attrs={'component': 'CURVE'}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': curve_line, 'Count': domain_size.outputs["Spline Count"]}) + + instance_on_points_1 = nw.new_node(Nodes.InstanceOnPoints, + input_kwargs={'Points': group_input, 'Selection': equal, 'Instance': resample_curve}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': instance_on_points_1}) + + position = nw.new_node(Nodes.InputPosition) + + flipindex = nw.new_node(nodegroup_flip_index().name, + input_kwargs={'V Resolution': domain_size.outputs["Spline Count"], 'U Resolution': group_input.outputs["U Resolution"]}) + + sample_index_2 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': group_input, 3: position, 'Index': flipindex}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': realize_instances, 'Position': sample_index_2.outputs[2]}) + + set_spline_type_1 = nw.new_node(Nodes.SplineType, input_kwargs={'Curve': set_position}, attrs={'spline_type': 'CATMULL_ROM'}) + + set_spline_type = nw.new_node(Nodes.SplineType, input_kwargs={'Curve': set_position}, attrs={'spline_type': 'NURBS'}) + + switch = nw.new_node(Nodes.Switch, + input_kwargs={1: group_input.outputs["Use Nurb"], 14: set_spline_type_1, 15: set_spline_type}) + + resample_curve_1 = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': switch.outputs[6], 'Count': group_input}) + + position_1 = nw.new_node(Nodes.InputPosition) + + flipindex_1 = nw.new_node(nodegroup_flip_index().name, + input_kwargs={'V Resolution': group_input.outputs["U Resolution"], 'U Resolution': group_input}) + + sample_index_3 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': resample_curve_1, 3: position_1, 'Index': flipindex_1}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': cylinderside.outputs["Geometry"], 'Position': sample_index_3.outputs[2]}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': set_position_1, 'Top': cylinderside.outputs["Top"], 'Side': cylinderside.outputs["Side"], 'Bottom': cylinderside.outputs["Bottom"]}, + attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_warp_around_curve', singleton=False, type='GeometryNodeTree') +def nodegroup_warp_around_curve(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Geometry', None), + ('NodeSocketGeometry', 'Curve', None), + ('NodeSocketInt', 'U Resolution', 32), + ('NodeSocketInt', 'V Resolution', 32), + ('NodeSocketFloat', 'Radius', 1.0000)]) + + resample_curve = nw.new_node(Nodes.ResampleCurve, + input_kwargs={'Curve': group_input.outputs["Curve"], 'Count': group_input.outputs["V Resolution"]}) + + position_1 = nw.new_node(Nodes.InputPosition) + + index = nw.new_node(Nodes.Index) + + divide = nw.new_node(Nodes.Math, + input_kwargs={0: index, 1: group_input.outputs["U Resolution"]}, + attrs={'operation': 'DIVIDE'}) + + floor = nw.new_node(Nodes.Math, input_kwargs={0: divide}, attrs={'operation': 'FLOOR'}) + + sample_index_3 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': resample_curve, 3: position_1, 'Index': floor}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + normal = nw.new_node(Nodes.InputNormal) + + sample_index_5 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': resample_curve, 3: normal, 'Index': floor}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + scale = nw.new_node(Nodes.VectorMath, + input_kwargs={0: sample_index_5.outputs[2], 'Scale': separate_xyz.outputs["X"]}, + attrs={'operation': 'SCALE'}) + + curve_tangent = nw.new_node(Nodes.CurveTangent) + + sample_index_4 = nw.new_node(Nodes.SampleIndex, + input_kwargs={'Geometry': resample_curve, 3: curve_tangent, 'Index': floor}, + attrs={'data_type': 'FLOAT_VECTOR'}) + + cross_product = nw.new_node(Nodes.VectorMath, + input_kwargs={0: sample_index_4.outputs[2], 1: sample_index_5.outputs[2]}, + attrs={'operation': 'CROSS_PRODUCT'}) + + scale_1 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: cross_product.outputs["Vector"], 'Scale': separate_xyz.outputs["Y"]}, + attrs={'operation': 'SCALE'}) + + add = nw.new_node(Nodes.VectorMath, input_kwargs={0: scale.outputs["Vector"], 1: scale_1.outputs["Vector"]}) + + scale_2 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: add.outputs["Vector"], 'Scale': group_input.outputs["Radius"]}, + attrs={'operation': 'SCALE'}) + + add_1 = nw.new_node(Nodes.VectorMath, input_kwargs={0: sample_index_3.outputs[2], 1: scale_2.outputs["Vector"]}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': group_input.outputs["Geometry"], 'Position': add_1.outputs["Vector"]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_position}, attrs={'is_active_output': True}) + +def geometry_nodes(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + integer = nw.new_node(Nodes.Integer) + integer.integer = 32 + + shiftedsquare = nw.new_node(nodegroup_shifted_square().name, input_kwargs={'Resolution': integer}) + + shiftedcircle = nw.new_node(nodegroup_shifted_circle().name, input_kwargs={'Resolution': integer, 'Radius': 0.9200, 'Z': 2.5600}) + + transform_geometry = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': shiftedcircle, 'Rotation': (0.0000, 0.0000, 0.7854)}) + + shiftedsquare_1 = nw.new_node(nodegroup_shifted_square().name, input_kwargs={'Resolution': integer, 'Z': 10.0000}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: integer, 1: 2.0000}, attrs={'operation': 'DIVIDE'}) + + star = nw.new_node('GeometryNodeCurveStar', + input_kwargs={'Points': divide, 'Inner Radius': 0.5000, 'Outer Radius': 0.6600}) + + transform_geometry_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': star.outputs["Curve"], 'Translation': (0.0000, 0.0000, 7.6000), 'Rotation': (0.0000, 0.0000, 0.7854)}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [shiftedsquare, transform_geometry, shiftedsquare_1, transform_geometry_1]}) + + v_resolution = nw.new_node(Nodes.Integer, label='V Resolution') + v_resolution.integer = 64 + + lofting = nw.new_node(nodegroup_lofting().name, + input_kwargs={'Profile Curves': join_geometry, 'U Resolution': integer, 'V Resolution': v_resolution}) + + object_info = nw.new_node(Nodes.ObjectInfo, input_kwargs={'Object': bpy.data.objects['BezierCurve']}) + + warparoundcurve = nw.new_node(nodegroup_warp_around_curve().name, + input_kwargs={'Geometry': lofting.outputs["Geometry"], 'Curve': object_info.outputs["Geometry"], 'U Resolution': integer, 'V Resolution': v_resolution}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': warparoundcurve}, attrs={'is_active_output': True}) + + + +def apply(obj, selection=None, **kwargs): + surface.add_geomod(obj, geometry_nodes, selection=selection, attributes=[]) +apply(bpy.context.active_object) \ No newline at end of file diff --git a/infinigen/assets/tables/strechers.py b/infinigen/assets/tables/strechers.py new file mode 100644 index 000000000..aa20b8987 --- /dev/null +++ b/infinigen/assets/tables/strechers.py @@ -0,0 +1,33 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +from infinigen.assets.tables.table_utils import nodegroup_n_gon_cylinder + +@node_utils.to_nodegroup('nodegroup_strecher', singleton=False, type='GeometryNodeTree') +def nodegroup_strecher(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': (1.0000, 0.0000, 1.0000), 'End': (1.0000, 0.0000, -1.0000)}) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'N-gon', 32), + ('NodeSocketFloat', 'Profile Width', 0.200)]) + + ngoncylinder = nw.new_node(nodegroup_n_gon_cylinder().name, + input_kwargs={'Radius Curve': curve_line, 'Height': 1.0000, 'N-gon': group_input.outputs["N-gon"], 'Profile Width': group_input.outputs["Profile Width"], 'Aspect Ratio': 1.0000, 'Resolution': 64}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': ngoncylinder.outputs["Mesh"]}, + attrs={'is_active_output': True}) \ No newline at end of file diff --git a/infinigen/assets/tables/table_top.py b/infinigen/assets/tables/table_top.py new file mode 100644 index 000000000..6aa594807 --- /dev/null +++ b/infinigen/assets/tables/table_top.py @@ -0,0 +1,174 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory + +from infinigen.assets.tables.table_utils import nodegroup_n_gon_cylinder, nodegroup_create_cap +from infinigen.core.tagging import tag_nodegroup +from infinigen.core import tags as t + +@node_utils.to_nodegroup('nodegroup_capped_cylinder', singleton=False, type='GeometryNodeTree') +def nodegroup_capped_cylinder(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Thickness', 0.5000), + ('NodeSocketFloat', 'Radius', 0.2000), + ('NodeSocketFloatDistance', 'Cap Flatness', 4.0000), + ('NodeSocketFloat', 'Fillet Radius Vertical', 0.4000), + ('NodeSocketFloat', 'Cap Relative Scale', 1.0000), + ('NodeSocketFloat', 'Cap Relative Z Offset', 0.0000), + ('NodeSocketInt', 'Resolution', 64)]) + + create_cap = nw.new_node(nodegroup_create_cap().name, + input_kwargs={'Radius': group_input.outputs["Cap Flatness"], 'Resolution': group_input.outputs["Resolution"]}, + label='CreateCap') + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Thickness"], 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply, 1: group_input.outputs["Cap Relative Z Offset"]}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Radius"], 1: 0.5}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: group_input.outputs["Cap Relative Scale"]}) + + transform_5 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': create_cap, 'Translation': combine_xyz_5, 'Scale': add_1}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Radius"], 1: 1.0}, attrs={'operation': 'MULTIPLY'}) + + generatetabletop = nw.new_node(nodegroup_generate_table_top().name, + input_kwargs={'Thickness': multiply, 'N-gon': group_input.outputs["Resolution"], 'Profile Width': multiply_2, 'Aspect Ratio': 1.0000, 'Fillet Ratio': 0.0000, 'Fillet Radius Vertical': group_input.outputs["Fillet Radius Vertical"]}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_5, generatetabletop]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry_2}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_generate_table_top', singleton=False, type='GeometryNodeTree') +def nodegroup_generate_table_top(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': (1.0000, 0.0000, 1.0000), 'End': (1.0000, 0.0000, -1.0000)}) + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloat', 'Thickness', 0.5000), + ('NodeSocketInt', 'N-gon', 0), + ('NodeSocketFloat', 'Profile Width', 0.5000), + ('NodeSocketFloat', 'Aspect Ratio', 0.5000), + ('NodeSocketFloat', 'Fillet Ratio', 0.2000), + ('NodeSocketFloat', 'Fillet Radius Vertical', 0.0000)]) + + ngoncylinder = nw.new_node(nodegroup_n_gon_cylinder().name, + input_kwargs={'Radius Curve': curve_line, 'Height': group_input.outputs["Thickness"], 'N-gon': group_input.outputs["N-gon"], 'Profile Width': group_input.outputs["Profile Width"], 'Aspect Ratio': group_input.outputs["Aspect Ratio"], 'Fillet Ratio': group_input.outputs["Fillet Ratio"], 'Profile Resolution': 512, 'Resolution': 10}) + + arc = nw.new_node('GeometryNodeCurveArc', input_kwargs={'Resolution': 4, 'Radius': 0.7071, 'Sweep Angle': 4.7124}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': arc.outputs["Curve"], 'Rotation': (0.0000, 0.0000, -0.7854)}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform, 'Rotation': (0.0000, 1.5708, 0.0000)}) + + transform_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_2, 'Translation': (0.0000, 0.5000, 0.0000)}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': group_input.outputs["Fillet Radius Vertical"], 'Z': 1.0000}) + + transform_4 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_3, 'Scale': combine_xyz}) + + fillet_curve = nw.new_node('GeometryNodeFilletCurve', + input_kwargs={'Curve': transform_4, 'Count': 8, 'Radius': group_input.outputs["Fillet Radius Vertical"], 'Limit Radius': True}, + attrs={'mode': 'POLY'}) + + transform_6 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': fillet_curve, 'Rotation': (1.5708, 1.5708, 0.0000), 'Scale': group_input.outputs["Thickness"]}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': ngoncylinder.outputs["Profile Curve"], 'Profile Curve': transform_6}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Thickness"], 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + transform_5 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': curve_to_mesh, 'Translation': combine_xyz_1}) + + index = nw.new_node(Nodes.Index) + + equal = nw.new_node(Nodes.Compare, input_kwargs={'A': index, 'B': 0}, attrs={'data_type': 'INT', 'operation': 'EQUAL'}) + + cap = tag_nodegroup(nw, ngoncylinder.outputs["Caps"], t.Subpart.SupportSurface, selection=equal) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [transform_5, cap]}) + + flip_faces = nw.new_node(Nodes.FlipFaces, input_kwargs={'Mesh': join_geometry}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Thickness"]}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': flip_faces, 'Translation': combine_xyz_2}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={ + 'Geometry': transform_1, + 'Curve': ngoncylinder.outputs["Profile Curve"], + }) + +def geometry_generate_table_top_wrapper(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'Profile N-gon', kwargs['Profile N-gon']), + ('NodeSocketFloat', 'Profile Width', kwargs['Profile Width']), + ('NodeSocketFloat', 'Profile Aspect Ratio', kwargs['Profile Aspect Ratio']), + ('NodeSocketFloat', 'Profile Fillet Ratio', kwargs['Profile Fillet Ratio']), + ('NodeSocketFloat', 'Thickness', kwargs['Thickness']), + ('NodeSocketFloat', 'Vertical Fillet Ratio', kwargs['Vertical Fillet Ratio'])] + ) + + generatetabletop = nw.new_node(nodegroup_generate_table_top().name, + input_kwargs={'Thickness': group_input.outputs["Thickness"], 'N-gon': group_input.outputs["Profile N-gon"], 'Profile Width': group_input.outputs["Profile Width"], 'Aspect Ratio': group_input.outputs["Profile Aspect Ratio"], 'Fillet Ratio': group_input.outputs["Profile Fillet Ratio"], 'Fillet Radius Vertical': group_input.outputs["Vertical Fillet Ratio"]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': generatetabletop}, attrs={'is_active_output': True}) + +class TableTopFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(TableTopFactory, self).__init__(factory_seed, coarse=coarse) + + with FixedSeed(factory_seed): + self.params = self.sample_parameters() + + @staticmethod + def sample_parameters(): + # all in meters + return { + 'Profile N-gon': 4, + 'Profile Width': 1.0, + 'Profile Aspect Ratio': 1.0, + 'Profile Fillet Ratio': 0.2000, + 'Thickness': 0.1000, + 'Vertical Fillet Ratio': 0.2000 + } + + def create_asset(self, **params): + + bpy.ops.mesh.primitive_plane_add( + size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + surface.add_geomod(obj, geometry_generate_table_top_wrapper, apply=False, input_kwargs=self.params) + + return obj \ No newline at end of file diff --git a/infinigen/assets/tables/table_utils.py b/infinigen/assets/tables/table_utils.py new file mode 100644 index 000000000..94703f79e --- /dev/null +++ b/infinigen/assets/tables/table_utils.py @@ -0,0 +1,514 @@ +# 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: Yiming Zuo + + +import bpy +import bpy +import mathutils +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +@node_utils.to_nodegroup('nodegroup_n_gon_profile', singleton=False, type='GeometryNodeTree') +def nodegroup_n_gon_profile(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'Profile N-gon', 4), + ('NodeSocketFloat', 'Profile Width', 1.0000), + ('NodeSocketFloat', 'Profile Aspect Ratio', 1.0000), + ('NodeSocketFloat', 'Profile Fillet Ratio', 0.2000)]) + + value = nw.new_node(Nodes.Value) + value.outputs[0].default_value = 0.5000 + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': group_input.outputs["Profile N-gon"], 'Radius': value}) + + divide = nw.new_node(Nodes.Math, + input_kwargs={0: 3.1416, 1: group_input.outputs["Profile N-gon"]}, + attrs={'operation': 'DIVIDE'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': divide}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': curve_circle.outputs["Curve"], 'Rotation': combine_xyz_1}) + + transform_2 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform, 'Rotation': (0.0000, 0.0000, -1.5708)}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Profile Aspect Ratio"], 1: group_input.outputs["Profile Width"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["Profile Width"], 'Y': multiply, 'Z': 1.0000}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_2, 'Scale': combine_xyz}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Profile Width"], 1: group_input.outputs["Profile Fillet Ratio"]}, + attrs={'operation': 'MULTIPLY'}) + + fillet_curve_1 = nw.new_node('GeometryNodeFilletCurve', + input_kwargs={'Curve': transform_1, 'Count': 8, 'Radius': multiply_1, 'Limit Radius': True}, + attrs={'mode': 'POLY'}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Output': fillet_curve_1}, attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_n_gon_cylinder', singleton=False, type='GeometryNodeTree') +def nodegroup_n_gon_cylinder(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Radius Curve', None), + ('NodeSocketFloat', 'Height', 0.5000), + ('NodeSocketInt', 'N-gon', 0), + ('NodeSocketFloat', 'Profile Width', 0.5000), + ('NodeSocketFloat', 'Aspect Ratio', 0.5000), + ('NodeSocketFloat', 'Fillet Ratio', 0.2000), + ('NodeSocketInt', 'Profile Resolution', 64), + ('NodeSocketInt', 'Resolution', 128)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_1}) + + set_curve_tilt = nw.new_node(Nodes.SetCurveTilt, input_kwargs={'Curve': curve_line, 'Tilt': 3.1416}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, + input_kwargs={'Curve': set_curve_tilt, 'Count': group_input.outputs["Resolution"]}) + + spline_parameter_1 = nw.new_node(Nodes.SplineParameter) + + capture_attribute = nw.new_node(Nodes.CaptureAttribute, + input_kwargs={'Geometry': resample_curve, 2: spline_parameter_1.outputs["Factor"]}) + + ngonprofile = nw.new_node(nodegroup_n_gon_profile().name, + input_kwargs={'Profile N-gon': group_input.outputs["N-gon"], 'Profile Width': group_input.outputs["Profile Width"], 'Profile Aspect Ratio': group_input.outputs["Aspect Ratio"], 'Profile Fillet Ratio': group_input.outputs["Fillet Ratio"]}) + + resample_curve_1 = nw.new_node(Nodes.ResampleCurve, + input_kwargs={'Curve': ngonprofile, 'Count': group_input.outputs["Profile Resolution"]}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': capture_attribute.outputs["Geometry"], 'Profile Curve': resample_curve_1, 'Fill Caps': True}) + + position_1 = nw.new_node(Nodes.InputPosition) + + separate_xyz_2 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position_1}) + + sample_curve = nw.new_node(Nodes.SampleCurve, + input_kwargs={'Curves': group_input.outputs["Radius Curve"], 'Factor': capture_attribute.outputs[2]}, + attrs={'use_all_curves': True}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': sample_curve.outputs["Position"]}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': separate_xyz.outputs["X"], 'Y': separate_xyz.outputs["Y"]}) + + length = nw.new_node(Nodes.VectorMath, input_kwargs={0: combine_xyz}, attrs={'operation': 'LENGTH'}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["X"], 1: length.outputs["Value"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, + input_kwargs={0: separate_xyz_2.outputs["Y"], 1: length.outputs["Value"]}, + attrs={'operation': 'MULTIPLY'}) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz_1 = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + attribute_statistic = nw.new_node(Nodes.AttributeStatistic, + input_kwargs={'Geometry': group_input.outputs["Radius Curve"], 2: separate_xyz_1.outputs["Z"]}) + + map_range = nw.new_node(Nodes.MapRange, + input_kwargs={'Value': separate_xyz.outputs["Z"], 1: attribute_statistic.outputs["Min"], 2: attribute_statistic.outputs["Max"], 3: multiply, 4: 0.0000}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_1, 'Y': multiply_2, 'Z': map_range.outputs["Result"]}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': curve_to_mesh, 'Position': combine_xyz_2}) + + index = nw.new_node(Nodes.Index) + + domain_size = nw.new_node(Nodes.DomainSize, input_kwargs={'Geometry': curve_to_mesh}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: domain_size.outputs["Face Count"], 1: 2.0000}, + attrs={'operation': 'SUBTRACT'}) + + less_than = nw.new_node(Nodes.Compare, + input_kwargs={2: index, 3: subtract}, + attrs={'operation': 'LESS_THAN', 'data_type': 'INT'}) + + delete_geometry = nw.new_node(Nodes.DeleteGeometry, + input_kwargs={'Geometry': curve_to_mesh, 'Selection': less_than}, + attrs={'domain': 'FACE'}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Mesh': set_position, 'Profile Curve': resample_curve_1, 'Caps': delete_geometry}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_generate_radius_curve', singleton=False, type='GeometryNodeTree') +def nodegroup_generate_radius_curve(nw: NodeWrangler, curve_control_points): + # Code generated using version 2.6.4 of the node_transpiler + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': (1.0000, 0.0000, 1.0000), 'End': (1.0000, 0.0000, -1.0000)}) + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketInt', 'Resolution', 128)]) + + resample_curve = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': curve_line, 'Count': group_input.outputs["Resolution"]}) + + position = nw.new_node(Nodes.InputPosition) + + spline_parameter = nw.new_node(Nodes.SplineParameter) + + float_curve = nw.new_node(Nodes.FloatCurve, input_kwargs={'Value': spline_parameter.outputs["Factor"]}) + node_utils.assign_curve(float_curve.mapping.curves[0], curve_control_points) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': float_curve, 'Y': 1.0000, 'Z': 1.0000}) + + multiply = nw.new_node(Nodes.VectorMath, input_kwargs={0: position, 1: combine_xyz_1}, attrs={'operation': 'MULTIPLY'}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': resample_curve, 'Position': multiply.outputs["Vector"]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_position}, attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_create_anchors', singleton=False, type='GeometryNodeTree') +def nodegroup_create_anchors(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'Profile N-gon', 0), + ('NodeSocketFloat', 'Profile Width', 0.5000), + ('NodeSocketFloat', 'Profile Aspect Ratio', 0.5000), + ('NodeSocketFloat', 'Profile Rotation', 0.0000)]) + + equal = nw.new_node(Nodes.Compare, + input_kwargs={2: group_input.outputs["Profile N-gon"], 3: 1}, + attrs={'operation': 'EQUAL', 'data_type': 'INT'}) + + equal_1 = nw.new_node(Nodes.Compare, + input_kwargs={2: group_input.outputs["Profile N-gon"], 3: 2}, + attrs={'operation': 'EQUAL', 'data_type': 'INT'}) + + ngonprofile = nw.new_node(nodegroup_n_gon_profile().name, + input_kwargs={'Profile N-gon': group_input.outputs["Profile N-gon"], 'Profile Width': group_input.outputs["Profile Width"], 'Profile Aspect Ratio': group_input.outputs["Profile Aspect Ratio"], 'Profile Fillet Ratio': 0.0000}) + + curve_to_points = nw.new_node(Nodes.CurveToPoints, input_kwargs={'Curve': ngonprofile}, attrs={'mode': 'EVALUATED'}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Profile Width"], 1: 0.3535}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Profile Width"], 1: -0.3535}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_1}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz, 'End': combine_xyz_1}) + + curve_to_points_1 = nw.new_node(Nodes.CurveToPoints, input_kwargs={'Curve': curve_line}, attrs={'mode': 'EVALUATED'}) + + switch_1 = nw.new_node(Nodes.Switch, + input_kwargs={1: equal_1, 14: curve_to_points.outputs["Points"], 15: curve_to_points_1.outputs["Points"]}) + + points = nw.new_node('GeometryNodePoints') + + switch = nw.new_node(Nodes.Switch, input_kwargs={1: equal, 14: switch_1.outputs[6], 15: points}) + + set_point_radius = nw.new_node(Nodes.SetPointRadius, input_kwargs={'Points': switch.outputs[6]}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Profile Rotation"]}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': set_point_radius, 'Rotation': combine_xyz_2}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform}, attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_create_legs_and_strechers', singleton=False, type='GeometryNodeTree') +def nodegroup_create_legs_and_strechers(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Anchors', None), + ('NodeSocketBool', 'Keep Legs', False), + ('NodeSocketGeometry', 'Leg Instance', None), + ('NodeSocketFloat', 'Table Height', 0.0000), + ('NodeSocketFloat', 'Leg Bottom Relative Scale', 0.0000), + ('NodeSocketFloat', 'Leg Bottom Relative Rotation', 0.0000), + ('NodeSocketBool', 'Keep Odd Strechers', True), + ('NodeSocketBool', 'Keep Even Strechers', True), + ('NodeSocketGeometry', 'Strecher Instance', None), + ('NodeSocketInt', 'Strecher Index Increment', 0), + ('NodeSocketFloat', 'Strecher Relative Position', 0.5000), + ('NodeSocketFloat', 'Leg Bottom Offset', 0.0000), + ('NodeSocketBool', 'Align Leg X rot', False)]) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Table Height"]}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': group_input.outputs["Anchors"], 'Translation': combine_xyz}) + + position = nw.new_node(Nodes.InputPosition) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Leg Bottom Offset"]}) + + subtract = nw.new_node(Nodes.VectorMath, input_kwargs={0: combine_xyz, 1: combine_xyz_3}, attrs={'operation': 'SUBTRACT'}) + + subtract_1 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: position, 1: subtract.outputs["Vector"]}, + attrs={'operation': 'SUBTRACT'}) + + vector_rotate = nw.new_node(Nodes.VectorRotate, + input_kwargs={'Vector': subtract_1.outputs["Vector"], 'Angle': group_input.outputs["Leg Bottom Relative Rotation"]}, + attrs={'rotation_type': 'Z_AXIS'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': group_input.outputs["Leg Bottom Relative Scale"], 'Y': group_input.outputs["Leg Bottom Relative Scale"], 'Z': 1.0000}) + + multiply = nw.new_node(Nodes.VectorMath, input_kwargs={0: vector_rotate, 1: combine_xyz_4}, attrs={'operation': 'MULTIPLY'}) + + subtract_2 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: position, 1: multiply.outputs["Vector"]}, + attrs={'operation': 'SUBTRACT'}) + + align_euler_to_vector = nw.new_node(Nodes.AlignEulerToVector, input_kwargs={'Vector': subtract_2}, attrs={'axis': 'Z'}) + + align_euler_to_vector_3 = nw.new_node(Nodes.AlignEulerToVector, + input_kwargs={'Rotation': align_euler_to_vector, 'Vector': position}, + attrs={'pivot_axis': 'Z'}) + + switch = nw.new_node(Nodes.Switch, + input_kwargs={0: group_input.outputs["Align Leg X rot"], 8: align_euler_to_vector, 9: align_euler_to_vector_3}, + attrs={'input_type': 'VECTOR'}) + + length = nw.new_node(Nodes.VectorMath, input_kwargs={0: subtract_2}, attrs={'operation': 'LENGTH'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': 1.0000, 'Z': length.outputs["Value"]}) + + instance_on_points = nw.new_node(Nodes.InstanceOnPoints, + input_kwargs={'Points': transform, 'Instance': group_input.outputs["Leg Instance"], 'Rotation': switch.outputs[3], 'Scale': combine_xyz_2}) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': instance_on_points}) + + switch_1 = nw.new_node(Nodes.Switch, input_kwargs={1: group_input.outputs["Keep Legs"], 15: realize_instances}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Strecher Relative Position"], 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + scale = nw.new_node(Nodes.VectorMath, input_kwargs={0: subtract_2, 'Scale': multiply_1}, attrs={'operation': 'SCALE'}) + + position_2 = nw.new_node(Nodes.InputPosition) + + add = nw.new_node(Nodes.VectorMath, input_kwargs={0: scale.outputs["Vector"], 1: position_2}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': transform, 'Position': add.outputs["Vector"]}) + + index = nw.new_node(Nodes.Index) + + modulo = nw.new_node(Nodes.Math, input_kwargs={0: index, 1: 2.0000}, attrs={'operation': 'MODULO'}) + + op_and = nw.new_node(Nodes.BooleanMath, input_kwargs={0: modulo, 1: group_input.outputs["Keep Odd Strechers"]}) + + op_not = nw.new_node(Nodes.BooleanMath, input_kwargs={0: modulo}, attrs={'operation': 'NOT'}) + + op_and_1 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: group_input.outputs["Keep Even Strechers"], 1: op_not}) + + op_or = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_and, 1: op_and_1}, attrs={'operation': 'OR'}) + + domain_size = nw.new_node(Nodes.DomainSize, input_kwargs={'Geometry': transform}, attrs={'component': 'POINTCLOUD'}) + + divide = nw.new_node(Nodes.Math, + input_kwargs={0: domain_size.outputs["Point Count"], 1: group_input.outputs["Strecher Index Increment"]}, + attrs={'operation': 'DIVIDE'}) + + equal = nw.new_node(Nodes.Compare, input_kwargs={0: divide, 1: 2.0000}, attrs={'operation': 'EQUAL'}) + + boolean = nw.new_node(Nodes.Boolean, attrs={'boolean': True}) + + index_1 = nw.new_node(Nodes.Index) + + divide_1 = nw.new_node(Nodes.Math, + input_kwargs={0: domain_size.outputs["Point Count"], 1: 2.0000}, + attrs={'operation': 'DIVIDE'}) + + less_than = nw.new_node(Nodes.Compare, + input_kwargs={2: index_1, 3: divide_1}, + attrs={'operation': 'LESS_THAN', 'data_type': 'INT'}) + + switch_2 = nw.new_node(Nodes.Switch, input_kwargs={0: equal, 6: boolean, 7: less_than}, attrs={'input_type': 'BOOLEAN'}) + + op_and_2 = nw.new_node(Nodes.BooleanMath, input_kwargs={0: op_or, 1: switch_2.outputs[2]}) + + position_1 = nw.new_node(Nodes.InputPosition) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: index, 1: group_input.outputs["Strecher Index Increment"]}) + + modulo_1 = nw.new_node(Nodes.Math, + input_kwargs={0: add_1, 1: domain_size.outputs["Point Count"]}, + attrs={'operation': 'MODULO'}) + + field_at_index = nw.new_node(Nodes.FieldAtIndex, input_kwargs={'Index': modulo_1, 3: position_1}, attrs={'data_type': 'FLOAT_VECTOR'}) + + subtract_3 = nw.new_node(Nodes.VectorMath, + input_kwargs={0: position_1, 1: field_at_index.outputs[2]}, + attrs={'operation': 'SUBTRACT'}) + + align_euler_to_vector_1 = nw.new_node(Nodes.AlignEulerToVector, input_kwargs={'Vector': subtract_3.outputs["Vector"]}, attrs={'axis': 'Z'}) + + align_euler_to_vector_2 = nw.new_node(Nodes.AlignEulerToVector, input_kwargs={'Rotation': align_euler_to_vector_1}, attrs={'pivot_axis': 'Z'}) + + length_1 = nw.new_node(Nodes.VectorMath, input_kwargs={0: subtract_3.outputs["Vector"]}, attrs={'operation': 'LENGTH'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': 1.0000, 'Y': 1.0000, 'Z': length_1.outputs["Value"]}) + + instance_on_points_1 = nw.new_node(Nodes.InstanceOnPoints, + input_kwargs={'Points': set_position, 'Selection': op_and_2, 'Instance': group_input.outputs["Strecher Instance"], 'Rotation': align_euler_to_vector_2, 'Scale': combine_xyz_1}) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': instance_on_points_1}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [switch_1.outputs[6], realize_instances_1]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': join_geometry}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_create_cap', singleton=False, type='GeometryNodeTree') +def nodegroup_create_cap(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'Radius', 1.0000), + ('NodeSocketInt', 'Resolution', 64)]) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Radius"], 1: 257.0000}, + attrs={'operation': 'MULTIPLY'}) + + uv_sphere = nw.new_node(Nodes.MeshUVSphere, + input_kwargs={'Segments': group_input.outputs["Resolution"], 'Rings': multiply, 'Radius': group_input.outputs["Radius"]}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': uv_sphere.outputs["Mesh"], 'Name': 'uv_map', 3: uv_sphere.outputs["UV Map"]}, + attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'CORNER'}) + + power = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Radius"], 1: 2.0000}, attrs={'operation': 'POWER'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: power, 1: 1.0000}, attrs={'operation': 'SUBTRACT'}) + + sqrt = nw.new_node(Nodes.Math, input_kwargs={0: subtract}, attrs={'operation': 'SQRT'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: sqrt, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_1}) + + transform = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': store_named_attribute, 'Translation': combine_xyz}) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + less_than = nw.new_node(Nodes.Compare, input_kwargs={0: separate_xyz.outputs["Z"]}, attrs={'operation': 'LESS_THAN'}) + + delete_geometry = nw.new_node(Nodes.DeleteGeometry, input_kwargs={'Geometry': transform, 'Selection': less_than}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Mesh': delete_geometry}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_arc_top', singleton=False, type='GeometryNodeTree') +def nodegroup_arc_top(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketFloatDistance', 'Diameter', 1.0000), + ('NodeSocketFloat', 'Sweep Angle', 180.0000)]) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Diameter"], 1: 2.0000}, attrs={'operation': 'DIVIDE'}) + + multiply_add = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Sweep Angle"], 2: -90.0000}, + attrs={'operation': 'MULTIPLY_ADD'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: multiply_add, 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + radians = nw.new_node(Nodes.Math, input_kwargs={0: multiply}, attrs={'operation': 'RADIANS'}) + + radians_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Sweep Angle"]}, attrs={'operation': 'RADIANS'}) + + arc = nw.new_node('GeometryNodeCurveArc', + input_kwargs={'Resolution': 32, 'Radius': divide, 'Start Angle': radians, 'Sweep Angle': radians_1}) + + transform_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': arc.outputs["Curve"], 'Rotation': (1.5708, 0.0000, 0.0000)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_1}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_align_bottom_to_floor', singleton=False, type='GeometryNodeTree') +def nodegroup_align_bottom_to_floor(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': group_input.outputs["Geometry"]}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': bounding_box.outputs["Min"]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Z"], 1: -1.0000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply}) + + transform_geometry_1 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': group_input.outputs["Geometry"], 'Translation': combine_xyz}) + + group_output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': transform_geometry_1, 'Offset': multiply}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_bent', singleton=False, type='GeometryNodeTree') +def nodegroup_bent(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketGeometry', 'Geometry', None), + ('NodeSocketFloat', 'Amount', -0.1000)]) + + position = nw.new_node(Nodes.InputPosition) + + length = nw.new_node(Nodes.VectorMath, input_kwargs={0: position}, attrs={'operation': 'LENGTH'}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + multiply = nw.new_node(Nodes.Math, + input_kwargs={0: length.outputs["Value"], 1: separate_xyz.outputs["X"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, + input_kwargs={0: multiply, 1: group_input.outputs["Amount"]}, + attrs={'operation': 'MULTIPLY'}) + + vector_rotate = nw.new_node(Nodes.VectorRotate, input_kwargs={'Vector': position, 'Angle': multiply_1}) + + set_position = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': group_input.outputs["Geometry"], 'Position': vector_rotate}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_position}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('nodegroup_merge_curve', singleton=False, type='GeometryNodeTree') +def nodegroup_merge_curve(nw: NodeWrangler): + # Code generated using version 2.6.4 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Curve', None)]) + + curve_to_mesh_1 = nw.new_node(Nodes.CurveToMesh, input_kwargs={'Curve': group_input.outputs["Curve"]}) + + merge_by_distance = nw.new_node(Nodes.MergeByDistance, input_kwargs={'Geometry': curve_to_mesh_1}) + + mesh_to_curve = nw.new_node(Nodes.MeshToCurve, input_kwargs={'Mesh': merge_by_distance}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Curve': mesh_to_curve}, attrs={'is_active_output': True}) \ No newline at end of file diff --git a/infinigen/assets/tableware/__init__.py b/infinigen/assets/tableware/__init__.py new file mode 100644 index 000000000..d1d3c46f8 --- /dev/null +++ b/infinigen/assets/tableware/__init__.py @@ -0,0 +1,22 @@ +# 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: Lingjie Mei +from .spoon import SpoonFactory +from .knife import KnifeFactory +from .chopsticks import ChopsticksFactory +from .fork import ForkFactory, SpatulaFactory +from .pan import PanFactory +from .pot import PotFactory +from .cup import CupFactory +from .wineglass import WineglassFactory +from .plate import PlateFactory +from .bowl import BowlFactory +from .fruit_container import FruitContainerFactory +from .bottle import BottleFactory +from .can import CanFactory +from .jar import JarFactory +from .food_bag import FoodBagFactory +from .food_box import FoodBoxFactory +from .lid import LidFactory +from .plant_container import PlantContainerFactory, LargePlantContainerFactory diff --git a/infinigen/assets/tableware/base.py b/infinigen/assets/tableware/base.py new file mode 100644 index 000000000..8940bc8f7 --- /dev/null +++ b/infinigen/assets/tableware/base.py @@ -0,0 +1,116 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.core.nodes.node_info import Nodes +from infinigen.core.nodes.node_wrangler import NodeWrangler + +from infinigen.core.placement.factory import AssetFactory +from infinigen.core import surface +from infinigen.core.util.math import FixedSeed + +from infinigen.core.util import blender as butil +from infinigen.assets.utils.decorate import read_co, write_attribute +from infinigen.assets.utils.misc import assign_material +from infinigen.assets.material_assignments import AssetList + + +class TablewareFactory(AssetFactory): + is_fragile = False + allow_transparent = False + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.thickness = .01 + material_assignments = AssetList['TablewareFactory'](fragile=self.is_fragile, + transparent=self.allow_transparent) + + self.surface = material_assignments['surface'].assign_material() + self.inside_surface = material_assignments['inside'].assign_material() + self.guard_surface = material_assignments['guard'].assign_material() + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + self.scratch, self.edge_wear = material_assignments['wear_tear'] + + self.scratch = None if uniform() > scratch_prob else self.scratch + self.edge_wear = None if uniform() > edge_wear_prob else self.edge_wear + + self.guard_depth = self.thickness + self.has_guard = False + self.has_inside = False + self.lower_thresh = uniform(.5, .8) + self.scale = 1. + self.metal_color = 'bw+natural' + + def create_asset(self, **params) -> bpy.types.Object: + raise NotImplementedError + + def add_guard(self, obj, selection): + if not self.has_guard: + selection = False + + def geo_guard(nw: NodeWrangler): + geometry = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + normal = nw.new_node(Nodes.InputNormal) + x = nw.separate(nw.new_node(Nodes.InputPosition))[0] + sel = surface.eval_argument(nw, selection, x=x, normal=normal) + geometry, top, side = nw.new_node( + Nodes.ExtrudeMesh, + input_args=[geometry, sel, None, self.guard_depth, + False] + ).outputs[:3] + guard = nw.boolean_math('OR', top, side) + geometry = nw.new_node( + Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': geometry, 'Name': 'guard', 'Value': guard}, + attrs={'domain': 'FACE'} + ) + nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) + + surface.add_geomod(obj, geo_guard, apply=True) + + @staticmethod + def make_double_sided(selection): + return lambda nw, x, normal: nw.boolean_math( + 'AND', + surface.eval_argument(nw, selection, x=x, normal=normal), + nw.compare( + 'GREATER_THAN', + nw.math('ABSOLUTE', nw.separate(normal)[-1]), + .8 + ) + ) + + def finalize_assets(self, assets): + assign_material(assets, []) + self.surface.apply(assets, metal_color=self.metal_color) + if self.has_inside: + self.inside_surface.apply(assets, selection='inside', clear=True, metal_color='bw+natural') + if self.has_guard: + self.guard_surface.apply(assets, selection='guard', metal_color=self.metal_color) + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + + def solidify_with_inside(self, obj, thickness): + max_z = np.max(read_co(obj)[:, -1]) + obj.vertex_groups.new(name='inside_') + butil.modify_mesh(obj, 'SOLIDIFY', thickness=thickness, offset=1, shell_vertex_group='inside_') + write_attribute(obj, 'inside_', 'inside', 'FACE') + + def inside(nw: NodeWrangler): + lower = nw.compare( + 'LESS_THAN', nw.separate(nw.new_node(Nodes.InputPosition))[-1], + max_z * self.lower_thresh + ) + inside = nw.compare('GREATER_THAN', surface.eval_argument(nw, 'inside'), .8) + return nw.boolean_math('AND', inside, lower) + + write_attribute(obj, inside, 'lower_inside', 'FACE') + obj.vertex_groups.remove(obj.vertex_groups['inside_']) diff --git a/infinigen/assets/tableware/bottle.py b/infinigen/assets/tableware/bottle.py new file mode 100644 index 000000000..7868fa4d4 --- /dev/null +++ b/infinigen/assets/tableware/bottle.py @@ -0,0 +1,146 @@ +# 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: Lingjie Mei +import bpy +import bmesh +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import read_co, subdivide_edge_ring, subsurf +from infinigen.assets.utils.draw import spin +from infinigen.assets.utils.object import join_objects, new_cylinder +from infinigen.assets.utils.uv import wrap_front_back +from infinigen.core.placement.factory import AssetFactory +from infinigen.assets.materials import text +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList + + +class BottleFactory(AssetFactory): + z_neck_offset = .05 + z_waist_offset = .15 + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.z_length = uniform(.15, .25) + self.x_length = self.z_length * uniform(.15, .25) + self.x_cap = uniform(.3, .35) + self.bottle_type = np.random.choice(['beer', 'bordeaux', 'champagne', 'coke', 'vintage']) + self.bottle_width = uniform(.002, .005) + self.z_waist = 0 + match self.bottle_type: + case 'beer': + self.z_neck = uniform(.5, .6) + self.z_cap = uniform(.05, .08) + neck_size = uniform(.06, .1) + neck_ratio = uniform(.4, .5) + self.x_anchors = [0, 1, 1, (neck_ratio + 1) / 2 + (1 - neck_ratio) / 2 * self.x_cap, + neck_ratio + (1 - neck_ratio) * self.x_cap, self.x_cap, self.x_cap, 0] + self.z_anchors = [0, 0, self.z_neck, self.z_neck + uniform(.6, .7) * neck_size, + self.z_neck + neck_size, 1 - self.z_cap, 1, 1] + self.is_vector = [0, 1, 1, 0, 1, 1, 1, 0] + case 'bordeaux': + self.z_neck = uniform(.6, .7) + self.z_cap = uniform(.1, .15) + neck_size = uniform(.1, .15) + self.x_anchors = 0, 1, 1, (1 + self.x_cap) / 2, self.x_cap, self.x_cap, 0 + self.z_anchors = [0, 0, self.z_neck, self.z_neck + uniform(.6, .7) * neck_size, + self.z_neck + neck_size, 1, 1] + self.is_vector = [0, 1, 1, 0, 1, 1, 0] + case 'champagne': + self.z_neck = uniform(.4, .5) + self.z_cap = uniform(.05, .08) + self.x_anchors = [0, 1, 1, 1, (1 + self.x_cap) / 2, self.x_cap, self.x_cap, 0] + self.z_anchors = [0, 0, self.z_neck, self.z_neck + uniform(.08, .1), + self.z_neck + uniform(.15, .18), 1 - self.z_cap, 1, 1] + self.is_vector = [0, 1, 1, 0, 0, 1, 1, 0] + case 'coke': + self.z_waist = uniform(.4, .5) + self.z_neck = self.z_waist + uniform(.2, .25) + self.z_cap = uniform(.05, .08) + self.x_anchors = [0, uniform(.85, .95), 1, uniform(.85, .95), 1, 1, self.x_cap, self.x_cap, + 0] + self.z_anchors = [0, 0, uniform(.08, .12), uniform(.18, .25), self.z_waist, self.z_neck, + 1 - self.z_cap, 1, 1] + self.is_vector = [0, 1, 0, 0, 1, 1, 1, 1, 0] + case 'vintage': + self.z_waist = uniform(.1, .15) + self.z_neck = uniform(.7, .75) + self.z_cap = uniform(.0, .08) + x_lower = uniform(.85, .95) + self.x_anchors = [0, x_lower, (x_lower + 1) / 2, 1, 1, (self.x_cap + 1) / 2, self.x_cap, + self.x_cap, 0] + self.z_anchors = [0, 0, self.z_waist - uniform(.1, .15), self.z_waist, self.z_neck, + self.z_neck + uniform(.1, .2), 1 - self.z_cap, 1, 1] + self.is_vector = [0, 1, 0, 1, 1, 0, 1, 1, 0] + + material_assignments = AssetList['BottleFactory']() + self.surface = material_assignments['surface'].assign_material() + self.wrap_surface = material_assignments['wrap_surface'].assign_material() + if self.wrap_surface == text.Text: + self.wrap_surface = text.Text(self.factory_seed, False) + + self.cap_surface = material_assignments['cap_surface'].assign_material() + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + self.scratch, self.edge_wear = material_assignments['wear_tear'] + self.scratch = None if uniform() > scratch_prob else self.scratch + self.edge_wear = None if uniform() > edge_wear_prob else self.edge_wear + + self.texture_shared = uniform() < .2 + self.cap_subsurf = uniform() < .5 + + def create_asset(self, **params) -> bpy.types.Object: + bottle = self.make_bottle() + wrap = self.make_wrap(bottle) + cap = self.make_cap() + obj = join_objects([bottle, wrap, cap]) + + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + + def make_bottle(self): + x_anchors = np.array(self.x_anchors) * self.x_length + z_anchors = np.array(self.z_anchors) * self.z_length + anchors = x_anchors, 0, z_anchors + obj = spin(anchors, np.nonzero(self.is_vector)[0]) + subsurf(obj, 1, True) + subsurf(obj, 1) + if self.bottle_width > 0: + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.bottle_width) + self.surface.apply(obj, translucent=True) + return obj + + def make_wrap(self, bottle): + obj = new_cylinder(vertices=128) + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + geom = [f for f in bm.faces if len(f.verts) > 4] + bmesh.ops.delete(bm, geom=geom, context='FACES_ONLY') + bmesh.update_edit_mesh(obj.data) + subdivide_edge_ring(obj, 16) + z_max = self.z_neck - uniform(.02, self.z_neck_offset) * (self.z_neck - self.z_waist) + z_min = self.z_waist + uniform(.02, self.z_waist_offset) * (self.z_neck - self.z_waist) + radius = np.max(read_co(bottle)[:, 0]) + 2e-3 + obj.scale = radius, radius, (z_max - z_min) * self.z_length + obj.location[-1] = z_min * self.z_length + butil.apply_transform(obj, True) + wrap_front_back(obj, self.wrap_surface, self.texture_shared) + return obj + + def make_cap(self): + obj = new_cylinder(vertices=128) + obj.scale = [(self.x_cap + .1) * self.x_length, (self.x_cap + .1) * self.x_length, + (self.z_cap + .01) * self.z_length] + obj.location[-1] = (1 - self.z_cap) * self.z_length + butil.apply_transform(obj, loc=True) + subsurf(obj, 1, self.cap_subsurf) + self.cap_surface.apply(obj) + return obj diff --git a/infinigen/assets/tableware/bowl.py b/infinigen/assets/tableware/bowl.py new file mode 100644 index 000000000..f0e3e1f94 --- /dev/null +++ b/infinigen/assets/tableware/bowl.py @@ -0,0 +1,51 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.tableware.base import TablewareFactory +from infinigen.assets.utils.decorate import subsurf, set_shade_smooth +from infinigen.assets.utils.draw import spin +from infinigen.assets.utils.object import new_bbox +from infinigen.core.util.random import log_uniform +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil + + +class BowlFactory(TablewareFactory): + allow_transparent = True + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.x_end = .5 + self.z_length = log_uniform(.4, .8) + self.z_bottom = log_uniform(.02, .05) + self.x_bottom = uniform(.2, .3) * self.x_end + self.x_mid = uniform(.8, .95) * self.x_end + self.has_guard = False + self.thickness = uniform(.01, .03) + self.has_inside = uniform(0, 1) < .5 + self.scale = log_uniform(.15, .4) + self.edge_wear = None + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + radius = self.x_end * self.scale + return new_bbox(-radius, radius, -radius, radius, 0, self.z_length * self.scale) + + def create_asset(self, **params) -> bpy.types.Object: + x_anchors = 0, self.x_bottom, self.x_bottom + 1e-3, self.x_bottom, self.x_mid, self.x_end + z_anchors = 0, 0, 0, self.z_bottom, self.z_length / 2, self.z_length + anchors = x_anchors, np.zeros_like(x_anchors), z_anchors + obj = spin(anchors, [2, 3], 16, 64) + subsurf(obj, 1) + self.solidify_with_inside(obj, self.thickness) + butil.modify_mesh(obj, 'BEVEL', width=self.thickness / 2, segments=np.random.randint(2, 5)) + obj.scale = [self.scale] * 3 + butil.apply_transform(obj) + subsurf(obj, 1) + set_shade_smooth(obj) + return obj diff --git a/infinigen/assets/tableware/can.py b/infinigen/assets/tableware/can.py new file mode 100644 index 000000000..c41d60528 --- /dev/null +++ b/infinigen/assets/tableware/can.py @@ -0,0 +1,97 @@ +# 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: Lingjie Mei +import bpy +import bmesh +import numpy as np +import shapely +from numpy.random import uniform +from shapely import Point, affinity + +from infinigen.assets.utils.decorate import write_co +from infinigen.assets.utils.object import join_objects, new_circle, new_cylinder +from infinigen.assets.utils.uv import wrap_four_sides +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.placement.factory import AssetFactory +from infinigen.core import surface +from infinigen.assets.materials import text +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList + + +class CanFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.x_length = log_uniform(.05, .1) + self.z_length = self.x_length * log_uniform(.5, 2.5) + self.shape = np.random.choice(['circle', 'rectangle']) + self.skewness = uniform(1, 2.5) if uniform() < .5 else 1 + + material_assignments = AssetList["CanFactory"]() + self.surface = material_assignments["surface"].assign_material() + self.wrap_surface = material_assignments["wrap_surface"].assign_material() + if self.wrap_surface == text.Text: + self.wrap_surface = text.Text(self.factory_seed, False) + + scratch_prob, edge_wear_prob = material_assignments["wear_tear_prob"] + self.scratch, self.edge_wear = material_assignments["wear_tear"] + self.scratch = None if uniform() > scratch_prob else self.scratch + self.edge_wear = None if uniform() > edge_wear_prob else self.edge_wear + + self.texture_shared = uniform() < .2 + + def create_asset(self, **params) -> bpy.types.Object: + coords = self.make_coords() + obj = new_circle(vertices=len(coords)) + write_co(obj, np.array([[x, y, 0] for x, y in coords])) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.edge_face_add() + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.z_length) + surface.add_geomod(obj, self.geo_cap, apply=True) + self.surface.apply(obj) + wrap = self.make_wrap(coords) + obj = join_objects([obj, wrap]) + return obj + + @staticmethod + def geo_cap(nw: NodeWrangler): + geometry = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + selection = nw.compare('GREATER_THAN', + nw.math('ABSOLUTE', nw.separate(nw.new_node(Nodes.InputNormal))[-1]), 1 - 1e-3) + geometry, top = nw.new_node(Nodes.ExtrudeMesh, [geometry, selection, None, 0]).outputs[:2] + geometry = nw.new_node(Nodes.ScaleElements, + input_kwargs={'Geometry': geometry, 'Selection': top, 'Scale': uniform(.96, .98) + }) + geometry = nw.new_node(Nodes.ExtrudeMesh, [geometry, top, None, -uniform(.005, .01)]).outputs[0] + nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) + + def make_coords(self): + match self.shape: + case 'circle': + p = Point(0, 0).buffer(self.x_length, quad_segs=64) + case _: + side = self.x_length * uniform(.2, .8) + p = shapely.box(-side, -side, side, side).buffer(self.x_length - side, quad_segs=16) + p = affinity.scale(p, yfact=1 / self.skewness) + coords = p.boundary.segmentize(.01).coords[:][:-1] + return coords + + def make_wrap(self, coords): + obj = new_cylinder(vertices=len(coords)) + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + geom = [f for f in bm.faces if len(f.verts) > 4] + bmesh.ops.delete(bm, geom=geom, context='FACES_ONLY') + bmesh.update_edit_mesh(obj.data) + lowest, highest = self.z_length * uniform(0, .1), self.z_length * uniform(.9, 1.) + write_co(obj, np.concatenate([np.array([[x, y, lowest], [x, y, highest]]) for x, y in coords])) + obj.scale = 1 + 1e-3, 1 + 1e-3, 1 + butil.apply_transform(obj) + wrap_four_sides(obj, self.wrap_surface, self.texture_shared) + return obj diff --git a/infinigen/assets/tableware/chopsticks.py b/infinigen/assets/tableware/chopsticks.py new file mode 100644 index 000000000..0eef1c331 --- /dev/null +++ b/infinigen/assets/tableware/chopsticks.py @@ -0,0 +1,81 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.tableware.base import TablewareFactory +from infinigen.assets.utils.decorate import subsurf, write_co +from infinigen.core.util.random import log_uniform +from infinigen.assets.utils.object import join_objects, new_grid +from infinigen.core.nodes.node_info import Nodes +from infinigen.core.nodes.node_wrangler import NodeWrangler +from infinigen.core import surface +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.math import FixedSeed + +from infinigen.core.util import blender as butil + + +class ChopsticksFactory(TablewareFactory): + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.y_length = uniform(.01, .02) + self.y_shrink = log_uniform(.2, .8) + self.is_square = uniform(0, 1) < .5 + self.has_guard = uniform(0, 1) < .4 + self.x_guard = uniform(.4, .9) + self.guard_depth = 0. + self.pre_level = 2 + self.scale = log_uniform(.2, .4) + + def create_asset(self, **params) -> bpy.types.Object: + obj = self.make_single() + if uniform(0, 1) < .6: + obj = self.make_parallel(obj) + else: + obj = self.make_crossed(obj) + return obj + + def make_parallel(self, obj): + distance = log_uniform(self.y_length, .04) + if uniform(0, 1) < .5: + other = deep_clone_obj(obj) + obj.location[1] = distance + obj.rotation_euler[-1] = uniform(0, np.pi / 8) + other.location[1] = -distance + other.rotation_euler[-1] = -uniform(0, np.pi / 8) + else: + obj.location[0] = -1 + butil.apply_transform(obj, loc=True) + other = deep_clone_obj(obj) + obj.location[1] = distance + obj.rotation_euler[-1] = -uniform(0, np.pi / 8) + other.location[1] = -distance + other.rotation_euler[-1] = uniform(0, np.pi / 8) + return join_objects([obj, other]) + + def make_crossed(self, obj): + other = deep_clone_obj(obj) + other.location = uniform(-.1, .2), uniform(-.2, .2), self.y_length + sign = np.sign(other.location[1]) + other.rotation_euler[-1] = -sign * log_uniform(np.pi / 8, np.pi / 4) + return join_objects([obj, other]) + + def make_single(self): + n = int(1 / self.y_length) + obj = new_grid(x_subdivisions=n - 1, y_subdivisions=1) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.y_length * 2) + l = np.linspace(self.y_shrink, 1, n) * self.y_length + x = np.concatenate([np.linspace(0, 1, n)] * 4) + y = np.concatenate([-l, l, -l, l]) + z = np.concatenate([l, l, -l, -l]) + write_co(obj, np.stack([x, y, z], -1)) + subsurf(obj, 2, self.is_square) + self.add_guard(obj, lambda nw, x: nw.compare('GREATER_THAN', x, self.x_guard)) + obj.scale = [self.scale] * 3 + butil.apply_transform(obj) + return obj diff --git a/infinigen/assets/tableware/cup.py b/infinigen/assets/tableware/cup.py new file mode 100644 index 000000000..7a6cc1ea9 --- /dev/null +++ b/infinigen/assets/tableware/cup.py @@ -0,0 +1,136 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.tableware.base import TablewareFactory +from infinigen.assets.materials import text +from infinigen.assets.utils.decorate import read_co, remove_vertices, subsurf, write_attribute +from infinigen.assets.utils.object import join_objects +from infinigen.assets.utils.draw import spin +from infinigen.assets.utils.uv import wrap_sides +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.random import log_uniform +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList + + +class CupFactory(TablewareFactory): + allow_transparent = True + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.x_end = .25 + self.is_short = uniform(0, 1) < .5 + if self.is_short: + self.is_profile_straight = uniform(0, 1) < .2 + self.x_lowest = log_uniform(.6, .9) + self.depth = log_uniform(.25, .5) + self.has_guard = uniform(0, 1) < .8 + else: + self.is_profile_straight = True + self.x_lowest = log_uniform(.9, 1.) + self.depth = log_uniform(.5, 1.) + self.has_guard = False + if self.is_profile_straight: + self.handle_location = uniform(.45, .65) + else: + self.handle_location = uniform(-.1, .3) + self.handle_type = 'shear' if uniform(0, 1) < .5 else 'round' + self.handle_radius = self.depth * uniform(.2, .4) + self.handle_inner_radius = self.handle_radius * log_uniform(.2, .3) + self.handle_taper_x = uniform(0, 2) + self.handle_taper_y = uniform(0, 2) + self.x_lower_ratio = log_uniform(.8, 1.) + self.thickness = log_uniform(.01, .04) + self.has_wrap = uniform() < .3 + self.has_wrap = True + self.wrap_margin = uniform(.1, .2) + + material_assignments = AssetList['CupFactory']() + self.surface = material_assignments['surface'].assign_material() + self.wrap_surface = material_assignments['wrap_surface'].assign_material() + if self.wrap_surface == text.Text: + self.wrap_surface = text.Text(self.factory_seed, False) + self.scratch = self.edge_wear = None + + self.has_inside = uniform(0, 1) < .5 + self.scale = log_uniform(.15, .3) + + def create_asset(self, **params) -> bpy.types.Object: + if self.is_profile_straight: + x_anchors = 0, self.x_lowest * self.x_end, self.x_end + z_anchors = 0, 0, self.depth + else: + x_anchors = 0, self.x_lowest * self.x_end, (self.x_lowest + self.x_lower_ratio * ( + 1 - self.x_lowest)) * self.x_end, self.x_end + z_anchors = 0, 0, self.depth * .5, self.depth + anchors = x_anchors, np.zeros_like(x_anchors), z_anchors + obj = spin(anchors, [1], 16) + subsurf(obj, 1) + butil.modify_mesh(obj, 'BEVEL', True, offset_type='PERCENT', width_pct=uniform(10, 50), segments=8) + if self.has_wrap: + wrap = self.make_wrap(obj) + else: + wrap = None + self.solidify_with_inside(obj, self.thickness) + handle_location = x_anchors[-2] * (1 - self.handle_location) + x_anchors[-1] * self.handle_location, \ + 0, \ + z_anchors[-2] * (1 - self.handle_location) + z_anchors[-1] * self.handle_location + angle_low = np.arctan((x_anchors[-1] - x_anchors[-2]) / (z_anchors[-1] - z_anchors[-2])) + angle_height = np.arctan((x_anchors[2] - x_anchors[1]) / (z_anchors[2] - z_anchors[1])) + handle_angle = uniform(angle_low, angle_height + 1e-3) + if self.has_guard: + obj = self.add_handle(obj, handle_location, handle_angle) + if self.has_wrap: + butil.select_none() + obj = join_objects([obj, wrap]) + obj.scale = [self.scale] * 3 + butil.apply_transform(obj) + return obj + + def add_handle(self, obj, handle_location, handle_angle): + bpy.ops.mesh.primitive_torus_add(location=handle_location, major_radius=self.handle_radius, + minor_radius=self.handle_inner_radius) + handle = bpy.context.active_object + handle.rotation_euler = np.pi / 2, handle_angle, 0 + butil.modify_mesh(handle, 'SIMPLE_DEFORM', deform_method='TAPER', angle=self.handle_taper_x, + deform_axis='X') + butil.modify_mesh(handle, 'SIMPLE_DEFORM', deform_method='TAPER', angle=self.handle_taper_y, + deform_axis='Y') + butil.modify_mesh(handle, 'BOOLEAN', object=obj, operation='DIFFERENCE') + butil.select_none() + objs = butil.split_object(handle) + i = np.argmax([np.max(read_co(o)[:, 0]) for o in objs]) + handle = objs[i] + objs.remove(handle) + butil.delete(objs) + subsurf(handle, 1) + write_attribute(handle, lambda nw: 1, "guard", "FACE") + return join_objects([obj, handle]) + + def make_wrap(self, obj): + butil.select_none() + obj = deep_clone_obj(obj) + remove_vertices(obj, lambda x, y, z: (z / self.depth < self.wrap_margin) | ( + z / self.depth > 1 - self.wrap_margin + uniform(.0, .1)) | ( + np.abs(np.arctan2(y, x)) < np.pi * self.wrap_margin)) + obj.scale = 1 + 1e-2, 1 + 1e-2, 1 + butil.apply_transform(obj) + write_attribute(obj, lambda nw: 1, "text", "FACE") + return obj + + def finalize_assets(self, assets): + super().finalize_assets(assets) + if self.has_wrap: + for obj in assets if isinstance(assets, list) else [assets]: + wrap_sides(obj, self.wrap_surface, 'u', 'v', 'z', selection='text') + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) \ No newline at end of file diff --git a/infinigen/assets/tableware/food_bag.py b/infinigen/assets/tableware/food_bag.py new file mode 100644 index 000000000..68e986d63 --- /dev/null +++ b/infinigen/assets/tableware/food_bag.py @@ -0,0 +1,83 @@ +# 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: Lingjie Mei +import bpy +import bmesh +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import text +from infinigen.assets.utils.decorate import geo_extension, read_co, subdivide_edge_ring, subsurf, write_co +from infinigen.assets.utils.object import new_base_cylinder +from infinigen.assets.utils.uv import wrap_front_back +from infinigen.core import surface +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.core.util.random import log_uniform +from infinigen.assets.material_assignments import AssetList + + +class FoodBagFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.length = uniform(.1, .3) + self.is_packet = uniform() < .6 + if self.is_packet: + self.width = self.length * log_uniform(.6, 1.) + self.depth = self.width * uniform(.5, .8) + self.curve_profile = uniform(2, 4) + else: + self.width = self.length * log_uniform(.2, .4) + self.depth = self.width * uniform(.6, 1.) + self.curve_profile = uniform(4, 8) + self.extrude_length = uniform(.05, .1) + material_assignments = AssetList["FoodBagFactory"]() + self.surface = material_assignments["surface"].assign_material() + if self.surface == text.Text: + self.surface = self.surface(self.factory_seed) + self.texture_shared = uniform() < .2 + + def create_asset(self, **params) -> bpy.types.Object: + obj = self.make_base() + self.add_seal(obj) + self.build_uv(obj) + subsurf(obj, 2) + surface.add_geomod(obj, geo_extension, input_kwargs={'musgrave_dimensions': '2D'}, apply=True) + return obj + + def make_base(self): + obj = new_base_cylinder() + subdivide_edge_ring(obj, 64) + obj.scale = self.width / 2, self.depth / 2, self.length / 2 + butil.apply_transform(obj) + x, y, z = read_co(obj).T + ratio = 1 - (2 * np.abs(z) / self.length) ** self.curve_profile + write_co(obj, np.stack([x, ratio * y, z], -1)) + butil.modify_mesh(obj, 'WELD', merge_threshold=1e-3) + return obj + + def add_seal(self, obj): + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + for i in [-1, 1]: + bpy.ops.mesh.select_all(action='DESELECT') + bm.verts.ensure_lookup_table() + indices = np.nonzero(read_co(obj)[:, -1] * i >= self.length / 2 - 1e-3)[0] + for idx in indices: + bm.verts[idx].select_set(True) + bm.select_flush(False) + bmesh.update_edit_mesh(obj.data) + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={'value': (0, 0, self.extrude_length * self.length * i)}) + + def build_uv(self, obj): + if not self.is_packet: + obj.rotation_euler[1] = np.pi / 2 + butil.apply_transform(obj) + wrap_front_back(obj, self.surface, self.texture_shared) + if not self.is_packet: + obj.rotation_euler[1] = -np.pi / 2 + butil.apply_transform(obj) diff --git a/infinigen/assets/tableware/food_box.py b/infinigen/assets/tableware/food_box.py new file mode 100644 index 000000000..e0b645d7a --- /dev/null +++ b/infinigen/assets/tableware/food_box.py @@ -0,0 +1,37 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials import text +from infinigen.assets.utils.object import new_cube +from infinigen.assets.utils.uv import wrap_six_sides +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil + + +class FoodBoxFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + dimensions = np.sort(log_uniform(.05, .3, 3)).tolist() + self.dimensions = np.array([dimensions[1], dimensions[0], dimensions[2]]) + self.surface = text.Text(self.factory_seed) + self.texture_shared = uniform() < .4 + + def create_placeholder(self, **params): + obj = new_cube() + obj.scale = self.dimensions / 2 + butil.apply_transform(obj) + return obj + + def create_asset(self, placeholder, **params) -> bpy.types.Object: + obj = butil.copy(placeholder) + wrap_six_sides(obj, self.surface, self.texture_shared) + butil.modify_mesh(obj, 'BEVEL', width=.001) + return obj diff --git a/infinigen/assets/tableware/fork.py b/infinigen/assets/tableware/fork.py new file mode 100644 index 000000000..86c3e949b --- /dev/null +++ b/infinigen/assets/tableware/fork.py @@ -0,0 +1,89 @@ +# 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: Lingjie Mei +import bpy +import bmesh +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import subsurf, write_co +from infinigen.core.util.random import log_uniform +from .base import TablewareFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.assets.utils.object import new_grid + + +class ForkFactory(TablewareFactory): + x_end = .15 + is_fragile = True + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.x_length = log_uniform(.4, .8) + self.x_tip = uniform(.15, .2) + self.y_length = log_uniform(.05, .08) + self.z_depth = log_uniform(.02, .04) + self.z_offset = uniform(.0, .05) + self.thickness = log_uniform(.008, .015) + self.has_guard = uniform(0, 1) < .4 + self.guard_type = 'round' if uniform(0, 1) < .6 else 'double' + self.n_cuts = np.random.randint(1, 3) if uniform(0, 1) < .3 else 3 + self.guard_depth = log_uniform(.2, 1.) * self.thickness + self.scale = log_uniform(.15, .25) + self.has_cut = True + + def create_asset(self, **params) -> bpy.types.Object: + x_anchors = np.array( + [self.x_tip, uniform(-.04, -.02), -.08, -.12, -self.x_end, -self.x_end - self.x_length, + -self.x_end - self.x_length * log_uniform(1.2, 1.4)]) + y_anchors = np.array([self.y_length * log_uniform(.8, 1.), self.y_length * log_uniform(1., 1.2), + self.y_length * log_uniform(.6, 1.), self.y_length * log_uniform(.2, .4), + log_uniform(.01, .02), log_uniform(.02, .05), log_uniform(.01, .02)]) + z_anchors = np.array([0, -self.z_depth, -self.z_depth, 0, self.z_offset, self.z_offset + uniform(-.02, .04), + self.z_offset + uniform(-.02, 0)]) + n = 2 * (self.n_cuts + 1) + obj = new_grid(x_subdivisions=len(x_anchors) - 1, y_subdivisions=n - 1) + x = np.concatenate([x_anchors] * n) + y = np.ravel(y_anchors[np.newaxis, :] * np.linspace(1, -1, n)[:, np.newaxis]) + z = np.concatenate([z_anchors] * n) + write_co(obj, np.stack([x, y, z], -1)) + if self.has_cut: + self.make_cuts(obj) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness) + subsurf(obj, 1) + selection = lambda nw, x: nw.compare('LESS_THAN', x, -self.x_end) + if self.guard_type == 'double': + selection = self.make_double_sided(selection) + self.add_guard(obj, selection) + subsurf(obj, 1) + obj.scale = [self.scale] * 3 + butil.apply_transform(obj) + return obj + + def make_cuts(self, obj): + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + front_verts = [] + for v in bm.verts: + if abs(v.co[0] - self.x_tip) < 1e-3: + front_verts.append(v) + front_verts = sorted(front_verts, key=lambda v: v.co[1]) + geom = [] + for f in bm.faces: + vs = list(v for v in f.verts if v in front_verts) + if len(vs) == 2: + if min(front_verts.index(vs[0]), front_verts.index(vs[1])) % 2 == 1: + geom.append(f) + bmesh.ops.delete(bm, geom=geom, context="FACES") + bmesh.update_edit_mesh(obj.data) + + +class SpatulaFactory(ForkFactory): + def __init__(self, factory_seed, coarse=False): + super(SpatulaFactory, self).__init__(factory_seed, coarse) + self.has_cut = False + self.z_depth = uniform(0, .05) + self.y_length = log_uniform(.08, .12) diff --git a/infinigen/assets/tableware/fruit_container.py b/infinigen/assets/tableware/fruit_container.py new file mode 100644 index 000000000..29b5638e1 --- /dev/null +++ b/infinigen/assets/tableware/fruit_container.py @@ -0,0 +1,71 @@ +# 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: Lingjie Mei +from collections.abc import Iterable, Sequence +from functools import cached_property +from statistics import mean + +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.fruits.general_fruit import FruitFactoryGeneralFruit +from infinigen.assets.tableware import BowlFactory, PotFactory +from infinigen.assets.utils.decorate import read_co, write_co +from infinigen.assets.utils.misc import make_normalized_factory, subclasses +from infinigen.core.placement.factory import AssetFactory, make_asset_collection +from infinigen.core.placement.instance_scatter import scatter_instances +from infinigen.core.util.math import FixedSeed + +from infinigen.core.util import blender as butil + +class FruitCover: + def __init__(self, factory_seed=0): + with FixedSeed(factory_seed): + fruit_factory_fns = list(subclasses(FruitFactoryGeneralFruit).difference([FruitFactoryGeneralFruit])) + fruit_factory_fn = make_normalized_factory(np.random.choice(fruit_factory_fns)) + self.col = make_asset_collection(fruit_factory_fn(np.random.randint(1e5)), name='fruit', n=5) + self.dimension = mean(mean(o.dimensions) for o in self.col.objects) + self.shrink_rate = max(self.dimension, 2.) + + def apply(self, obj, selection=None): + for obj in obj if isinstance(obj, Iterable) else [obj]: + scale = uniform(.06, .08) / self.shrink_rate + scatter_instances( + base_obj=obj, collection=self.col, density=1e3, + min_spacing=scale * self.dimension * uniform(.5, .7), scale=scale, + scale_rand=uniform(0.1, 0.3), selection=selection, + ground_offset=self.dimension * .2 * scale, apply_geo=True, realize=True + ) + + +class FruitContainerFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(FruitContainerFactory, self).__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + base_factory_fns = [BowlFactory, PotFactory] + probs = np.array([1, 1]) + base_factory_fn = np.random.choice(base_factory_fns, p=probs / probs.sum()) + self.base_factory = base_factory_fn(factory_seed, coarse) + self.cover_seed = factory_seed + + @cached_property + def cover(self): + return FruitCover(self.cover_seed) + + def create_placeholder(self, **params): + box = self.base_factory.create_placeholder(**params) + co = read_co(box) + co[co[:, -1] > .02, -1] += .05 + co[co[:, -1] < .02, -1] -= .01 + write_co(box, co) + butil.apply_transform(box) + return box + + def create_asset(self, **params) -> bpy.types.Object: + return self.base_factory.create_asset(**params) + + def finalize_assets(self, assets): + self.base_factory.finalize_assets(assets) + self.cover.apply(assets, selection='lower_inside') diff --git a/infinigen/assets/tableware/jar.py b/infinigen/assets/tableware/jar.py new file mode 100644 index 000000000..79a1e9598 --- /dev/null +++ b/infinigen/assets/tableware/jar.py @@ -0,0 +1,83 @@ +# 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: Lingjie Mei +import bpy +import bmesh +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import read_co, subsurf, write_attribute +from infinigen.assets.utils.object import join_objects, new_circle, new_cylinder +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList + +class JarFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.z_length = uniform(.15, .2) + self.x_length = uniform(.03, .06) + self.thickness = uniform(.002, .004) + self.n_base = np.random.choice([4, 6, 64]) + self.x_cap = uniform(.6, .9) * np.cos(np.pi / self.n_base) + self.z_cap = uniform(.05, .08) + self.z_neck = uniform(.15, .2) + + material_assignments = AssetList["JarFactory"]() + self.surface = material_assignments["surface"].assign_material() + self.cap_surface = material_assignments["cap_surface"].assign_material() + scratch_prob, edge_wear_prob = material_assignments["wear_tear_prob"] + self.scratch, self.edge_wear = material_assignments["wear_tear"] + self.scratch = None if uniform() > scratch_prob else self.scratch + self.edge_wear = None if uniform() > edge_wear_prob else self.edge_wear + + self.cap_subsurf = uniform() < .5 + + def create_asset(self, **params) -> bpy.types.Object: + obj = new_cylinder(vertices=self.n_base) + obj.scale = self.x_length, self.x_length, self.z_length + butil.apply_transform(obj, True) + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + geom = [f for f in bm.faces if f.normal[-1] > .5] + bmesh.ops.delete(bm, geom=geom, context='FACES_KEEP_BOUNDARY') + bmesh.update_edit_mesh(obj.data) + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.region_to_loop() + subsurf(obj, 2, True) + top = new_circle(location=(0, 0, 0)) + top.scale = [self.x_cap * self.x_length] * 3 + top.location[-1] = (1 + self.z_neck) * self.z_length + butil.apply_transform(top) + butil.select_none() + obj = join_objects([obj, top]) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.bridge_edge_loops(number_cuts=5, profile_shape_factor=uniform(0, .1)) + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.region_to_loop() + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={'value': (0, 0, self.z_cap * self.z_length)}) + subsurf(obj, 1) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness) + + cap = new_cylinder(vertices=64) + cap.scale = *([self.x_cap * self.x_length + 1e-3] * 2), self.z_cap * self.z_length + cap.location[-1] = (1 + self.z_neck + self.z_cap * uniform(.5, .8)) * self.z_length + butil.apply_transform(cap, True) + subsurf(obj, 1, self.cap_subsurf) + write_attribute(cap, 1, 'cap', 'FACE') + obj = join_objects([obj, cap]) + return obj + + def finalize_assets(self, assets): + self.surface.apply(assets, clear=uniform() < .5) + self.cap_surface.apply(assets, selection='cap') + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) diff --git a/infinigen/assets/tableware/knife.py b/infinigen/assets/tableware/knife.py new file mode 100644 index 000000000..39a753c35 --- /dev/null +++ b/infinigen/assets/tableware/knife.py @@ -0,0 +1,95 @@ +# 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: Lingjie Mei +import bpy +import bmesh +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import read_co, subsurf, write_co +from infinigen.core.util.random import log_uniform +from .base import TablewareFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.assets.utils.object import new_grid + + +class KnifeFactory(TablewareFactory): + x_end = .5 + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.x_length = log_uniform(.4, .7) + self.has_guard = uniform(0, 1) < .7 + if self.has_guard: + self.y_length = log_uniform(.1, .5) + self.y_guard = self.y_length * log_uniform(.2, .4) + else: + self.y_length = log_uniform(.1, .2) + self.y_guard = self.y_length * log_uniform(.3, .5) + self.x_guard = uniform(0, .2) + self.has_tip = uniform(0, 1) < .7 + self.thickness = log_uniform(.02, .03) + y_off_rand = uniform(0, 1) + self.y_offset = .2 if y_off_rand < 1 / 8 else .5 if y_off_rand < 1 / 4 else uniform(.2, .6) + self.guard_type = 'round' if uniform(0, 1) < .6 else 'double' + self.guard_depth = log_uniform(.2, 1.) * self.thickness + self.scale = log_uniform(.2, .3) + + def create_asset(self, **params) -> bpy.types.Object: + x_anchors = np.array( + [self.x_end, uniform(.5, .8) * self.x_end, uniform(.3, .4) * self.x_end, 1e-3, 0, -1e-3, -2e-3, + -self.x_end * self.x_length + 1e-3, -self.x_end * self.x_length]) + y_anchors = np.array( + [1e-3, self.y_length * log_uniform(.75, .95), self.y_length, self.y_length, self.y_length, + self.y_guard, self.y_guard, self.y_guard, self.y_guard]) + if not self.has_guard: + indices = [0, 1, 2, 4, 5, 7, 8] + x_anchors = x_anchors[indices] + y_anchors = y_anchors[indices] + if self.has_tip: + indices = [0] + list(range(len(x_anchors))) + x_anchors = x_anchors[indices] + x_anchors[0] += 1e-3 + y_anchors = y_anchors[indices] + y_anchors[1] += 3e-3 + + obj = new_grid(x_subdivisions=len(x_anchors) - 1, y_subdivisions=1) + x = np.concatenate([x_anchors] * 2) + y = np.concatenate([y_anchors, np.zeros_like(y_anchors)]) + y[0::len(y_anchors)] += self.y_offset * self.y_length + if self.has_tip: + y[1::len(y_anchors)] += self.y_offset * self.y_length + y[2::len(y_anchors)] += self.y_offset * (self.y_length - y_anchors[2]) + else: + y[1::len(y_anchors)] += self.y_offset * (self.y_length - y_anchors[1]) + z = np.concatenate([np.zeros_like(x_anchors)] * 2) + write_co(obj, np.stack([x, y, z], -1)) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness) + self.make_knife_tip(obj) + subsurf(obj, 1) + selection = lambda nw, x: nw.compare('LESS_THAN', x, -self.x_guard * self.x_length * self.x_end) + if self.guard_type == 'double': + selection = self.make_double_sided(selection) + self.add_guard(obj, selection) + subsurf(obj, 1) + obj.scale = [self.scale] * 3 + butil.apply_transform(obj) + return obj + + def make_knife_tip(self, obj): + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + for e in bm.edges: + u, v = e.verts + x0, y0, z0 = u.co + x1, y1, z1 = v.co + if x0 >= 0 and x1 >= 0 and abs(x0 - x1) < 2e-4: + if y0 > self.y_offset * self.y_length and y1 > self.y_offset * self.y_length: + bmesh.ops.pointmerge(bm, verts=[u, v], merge_co=(u.co + v.co) / 2) + bmesh.update_edit_mesh(obj.data) + bpy.ops.mesh.select_mode(type="EDGE") + bpy.ops.mesh.select_loose(extend=False) + bpy.ops.mesh.delete(type='EDGE') diff --git a/infinigen/assets/tableware/lid.py b/infinigen/assets/tableware/lid.py new file mode 100644 index 000000000..083c2249c --- /dev/null +++ b/infinigen/assets/tableware/lid.py @@ -0,0 +1,123 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import read_center, subsurf, write_co +from infinigen.assets.utils.draw import spin +from infinigen.assets.utils.object import join_objects, new_cylinder, new_line +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util import blender as butil +from infinigen.core.util.math import FixedSeed +from infinigen.assets.material_assignments import AssetList + + +class LidFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False): + super(LidFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.x_length = uniform(.08, .15) + self.z_height = self.x_length * uniform(0, .5) + self.thickness = uniform(.003, .005) + self.is_glass = uniform() < .5 + self.hardware_type = None + self.rim_height = uniform(1, 2) * self.thickness + self.handle_type = np.random.choice(['handle', 'knob']) + if self.handle_type == 'knob': + self.handle_height = self.x_length * uniform(.1, .15) + else: + self.handle_height = self.x_length * uniform(.2, .25) + self.handle_radius = self.x_length * uniform(.15, .25) + self.handle_width = self.x_length * uniform(.25, .3) + self.handle_subsurf_level = np.random.randint(0, 3) + + if self.is_glass: + material_assignments = AssetList['GlassLidFactory']() + else: + material_assignments = AssetList["LidFactory"]() + self.surface = material_assignments["surface"].assign_material() + self.rim_surface = material_assignments["rim_surface"].assign_material() + self.handle_surface = material_assignments["handle_surface"].assign_material() + + scratch_prob, edge_wear_prob = material_assignments["wear_tear_prob"] + self.scratch, self.edge_wear = material_assignments["wear_tear"] + self.scratch = None if uniform() > scratch_prob else self.scratch + self.edge_wear = None if uniform() > edge_wear_prob else self.edge_wear + + def create_asset(self, **params) -> bpy.types.Object: + x_anchors = 0, .01, self.x_length / 2, self.x_length + z_anchors = self.z_height, self.z_height, self.z_height * uniform(.7, .8), 0 + obj = spin((x_anchors, 0, z_anchors)) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness, offset=0) + butil.modify_mesh(obj, 'BEVEL', width=self.thickness / 2, segments=4) + self.surface.apply(obj, clear=True if self.is_glass else None, metal_color='bw+natural') + parts = [obj] + if self.is_glass: + parts.append(self.add_rim()) + match self.handle_type: + case 'handle': + parts.append(self.add_handle(obj)) + case _: + parts.append(self.add_knob()) + obj = join_objects(parts) + return obj + + def add_rim(self): + butil.select_none() + bpy.ops.mesh.primitive_torus_add( + major_radius=self.x_length, minor_radius=self.thickness / 2, + major_segments=128 + ) + obj = bpy.context.active_object + obj.scale[-1] = self.rim_height / self.thickness + butil.apply_transform(obj) + self.rim_surface.apply(obj) + return obj + + def add_handle(self, obj): + center = read_center(obj) + i = np.argmin(np.abs(center[:, :2] - np.array([self.handle_width, 0])[np.newaxis, :]).sum(-1)) + z_offset = center[i, -1] + obj = new_line(3) + write_co( + obj, np.array( + [[-self.handle_width, 0, 0], [-self.handle_width, 0, self.handle_height], + [self.handle_width, 0, self.handle_height], [self.handle_width, 0, 0]] + ) + ) + subsurf(obj, self.handle_subsurf_level) + butil.select_none() + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={'value': (0, self.thickness * 2, 0)}) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness, offset=0) + butil.modify_mesh(obj, 'BEVEL', width=self.thickness / 2, segments=4) + obj.location = 0, -self.thickness, z_offset + butil.apply_transform(obj, True) + self.handle_surface.apply(obj) + return obj + + def add_knob(self): + obj = new_cylinder() + obj.scale = *([self.thickness * uniform(1, 2)] * 2), self.handle_height + obj.location[-1] = self.z_height + butil.apply_transform(obj, True) + butil.modify_mesh(obj, 'BEVEL', width=self.thickness / 2, segments=4) + top = new_cylinder() + top.scale = self.handle_radius, self.handle_radius, self.thickness * uniform(1, 2) + top.location[-1] = self.z_height + self.handle_height + butil.apply_transform(top, True) + butil.modify_mesh(top, 'BEVEL', width=self.thickness / 2, segments=4) + obj = join_objects([obj, top]) + self.handle_surface.apply(obj) + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) diff --git a/infinigen/assets/tableware/pan.py b/infinigen/assets/tableware/pan.py new file mode 100644 index 000000000..5e9c53b1a --- /dev/null +++ b/infinigen/assets/tableware/pan.py @@ -0,0 +1,129 @@ +# 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: +# - Lingjie Mei +# - Karhan Kayan: fix cutter bug + +import bpy +import bmesh +import numpy as np +from numpy.random import uniform + +from infinigen.core.util.random import log_uniform +from .base import TablewareFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.assets.utils.decorate import subsurf +from infinigen.assets.utils.object import ( + join_objects, new_base_circle, new_base_cylinder, origin2lowest, +) +from infinigen.assets.material_assignments import AssetList +from ..utils.misc import assign_material + + +class PanFactory(TablewareFactory): + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.r_expand = 1 if uniform(0, 1) < .2 else log_uniform(1., 1.2) + self.depth = log_uniform(.3, .8) + if self.r_expand == 1: + self.r_mid = log_uniform(1., 1.3) + else: + self.r_mid = 1 + (self.r_expand - 1) * (uniform(.5, .85) if uniform(0, 1) < .5 else .5) + self.has_handle = True + self.has_handle_hole = uniform() < .6 + self.pre_level = 2 + self.x_handle = log_uniform(1.2, 2.) + self.z_handle = self.x_handle * uniform(0, .2) + self.z_handle_mid = uniform(.6, .8) * self.z_handle + self.s_handle = log_uniform(.8, 1.2) + self.thickness = log_uniform(.04, .06) + self.has_guard = uniform(0, 1) < .8 + self.x_guard = self.r_expand + uniform(0, .2) * self.x_handle + self.guard_type = 'round' + self.guard_depth = log_uniform(1., 2.) * self.thickness + material_assignments = AssetList['PanFactory']() + self.surface = material_assignments['surface'].assign_material() + self.inside_surface = material_assignments['inside'].assign_material() + if self.surface == self.inside_surface: + self.has_inside = uniform(0, 1) < .5 + else: + self.has_inside = True + self.metal_color = None + self.scale = log_uniform(.1, .15) + self.scratch = self.edge_wear = None + + def create_asset(self, **params) -> bpy.types.Object: + obj = self.make_base() + origin2lowest(obj, vertical=True) + obj.scale = [self.scale] * 3 + butil.apply_transform(obj) + return obj + + def make_base(self): + n = 4 * int(log_uniform(4, 8)) + base = new_base_circle(vertices=n) + middle = new_base_circle(vertices=n, ) + middle.location[-1] = self.depth / 2 + middle.scale = [self.r_mid] * 3 + upper = new_base_circle(vertices=n) + upper.location[-1] = self.depth + upper.scale = [self.r_expand] * 3 + butil.apply_transform(upper, loc=True) + obj = join_objects([base, middle, upper]) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.bridge_edge_loops() + bm = bmesh.from_edit_mesh(obj.data) + for v in bm.verts: + v.select_set(np.abs(v.co[-1]) < 1e-3) + bm.select_flush(False) + bmesh.update_edit_mesh(obj.data) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.fill_grid(use_interp_simple=True, offset=np.random.randint(n // 4)) + bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + obj.rotation_euler[-1] = np.pi / n + butil.apply_transform(obj) + if self.has_handle: + self.add_handle(obj) + self.solidify_with_inside(obj, self.thickness) + selection = lambda nw, x: nw.compare('GREATER_THAN', x, self.x_guard) + self.add_guard(obj, selection) + subsurf(obj, 1, True) + subsurf(obj, 3) + if self.has_handle_hole: + self.add_handle_hole(obj) + return obj + + def add_handle(self, obj): + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type="EDGE") + bm = bmesh.from_edit_mesh(obj.data) + bm.edges.ensure_lookup_table() + m = [] + for e in bm.edges: + u, v = e.verts + m.append(u.co[0] + v.co[0] + u.co[2] + v.co[2]) + ri = np.argmax(m) + for e in bm.edges: + e.select_set(e.index == ri) + bm.select_flush(False) + bmesh.update_edit_mesh(obj.data) + + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={'value': (self.x_handle * .5, 0, self.z_handle_mid)} + ) + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={'value': (self.x_handle * .5, 0, (self.z_handle - self.z_handle_mid))} + ) + bpy.ops.transform.resize(value=[self.s_handle] * 3) + bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={'value': (1e-3, 0, 0)}) + + def add_handle_hole(self, obj): + cutter = new_base_cylinder() + cutter.scale = *([uniform(.06, .1)] * 2), 1 + cutter.location[0] = self.r_expand + uniform(.8, .9) * self.x_handle + butil.modify_mesh(obj, 'BOOLEAN', object=cutter, operation='DIFFERENCE') + butil.delete(cutter) diff --git a/infinigen/assets/tableware/plant_container.py b/infinigen/assets/tableware/plant_container.py new file mode 100644 index 000000000..f2eb58358 --- /dev/null +++ b/infinigen/assets/tableware/plant_container.py @@ -0,0 +1,129 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.cactus import CactusFactory +from infinigen.assets.monocot import MonocotFactory +from infinigen.assets.mushroom import MushroomFactory +from infinigen.assets.small_plants import FernFactory, SnakePlantFactory, SpiderPlantFactory, SucculentFactory +from infinigen.assets.tableware import PotFactory +from infinigen.assets.utils.decorate import ( + read_edge_center, read_edge_direction, remove_vertices, + select_edges, subsurf, +) +from infinigen.assets.utils.object import center, join_objects, new_bbox, origin2lowest +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList +from infinigen.core.constraints.example_solver.room.constants import WALL_HEIGHT, WALL_THICKNESS + + +class PlantPotFactory(PotFactory): + def __init__(self, factory_seed, coarse=False): + super(PlantPotFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.has_handle = self.has_bar = self.has_guard = False + self.depth = log_uniform(.5, 1.) + self.r_expand = uniform(1.1, 1.3) + alpha = uniform(.5, .8) + self.r_mid = (self.r_expand - 1) * alpha + 1 + material_assignments = AssetList["PlantContainerFactory"]() + self.surface = material_assignments["surface"].assign_material() + self.scale = log_uniform(.08, .12) + + +class PlantContainerFactory(AssetFactory): + plant_factories = [CactusFactory, MushroomFactory, FernFactory, SucculentFactory, SpiderPlantFactory, + SnakePlantFactory] + + def __init__(self, factory_seed, coarse=False): + super(PlantContainerFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.base_factory = PlantPotFactory(self.factory_seed, coarse) + self.dirt_ratio = uniform(.7, .8) + material_assignments = AssetList["PlantContainerFactory"]() + self.dirt_surface = material_assignments["dirt_surface"].assign_material() + fn = np.random.choice(self.plant_factories) + self.plant_factory = fn(self.factory_seed) + self.side_size = self.base_factory.scale * self.base_factory.r_expand + self.top_size = uniform(.4, .6) + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + return new_bbox( + -self.side_size, + self.side_size, + -self.side_size, + self.side_size, + -.02, + self.base_factory.depth * self.base_factory.scale + self.top_size + ) + + def create_asset(self, i, **params) -> bpy.types.Object: + obj = self.base_factory.create_asset(i=i, **params) + horizontal = np.abs(read_edge_direction(obj)[:, -1]) < .1 + + edge_center = read_edge_center(obj) + z = edge_center[:, -1] + dirt_z = self.dirt_ratio * self.base_factory.depth * self.base_factory.scale + idx = np.argmin(np.abs(z - dirt_z) - horizontal * 10) + radius = np.sqrt((edge_center[idx] ** 2)[:2].sum()) + + selection = np.zeros_like(z).astype(bool) + selection[idx] = True + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type="EDGE") + select_edges(obj, selection) + bpy.ops.mesh.loop_multi_select(ring=False) + bpy.ops.mesh.duplicate_move() + bpy.ops.mesh.separate(type='SELECTED') + + dirt_ = bpy.context.selected_objects[-1] + butil.select_none() + self.base_factory.finalize_assets(obj) + with butil.ViewportMode(dirt_, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.fill_grid() + subsurf(dirt_, 3) + self.dirt_surface.apply(dirt_) + butil.apply_modifiers(dirt_) + + remove_vertices(dirt_, lambda x, y, z: np.sqrt(x ** 2 + y ** 2) > radius * 0.92) + dirt_.location[-1] -= .02 + + plant = self.plant_factory.spawn_asset(i=i, loc=(0, 0, 0), rot=(0, 0, 0)) + origin2lowest(plant, approximate=True) + self.plant_factory.finalize_assets(plant) + + scale = np.min( + np.array([self.side_size, self.side_size, self.top_size]) / np.max( + np.abs(np.array(plant.bound_box)), 0 + ) + ) + plant.scale = [scale] * 3 + plant.location[-1] = dirt_z + + obj = join_objects([obj, plant, dirt_]) + return obj + + +class LargePlantContainerFactory(PlantContainerFactory): + plant_factories = [MonocotFactory] + + def __init__(self, factory_seed, coarse=False): + super(LargePlantContainerFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.base_factory.depth = log_uniform(1., 1.5) + self.base_factory.scale = log_uniform(.15, .25) + self.side_size = self.base_factory.scale * uniform(1.5, 2.) * self.base_factory.r_expand + self.top_size = uniform(1, 1.5) + # if WALL_HEIGHT - 2*WALL_THICKNESS < 3: + # self.top_size = uniform(1.5, WALL_HEIGHT - 2*WALL_THICKNESS) + # else: + # self.top_size = uniform(1.5, 3) + # print(f"{self.side_size=} {self.top_size=} {WALL_THICKNESS=} {WALL_HEIGHT=}") diff --git a/infinigen/assets/tableware/plate.py b/infinigen/assets/tableware/plate.py new file mode 100644 index 000000000..3bcc8756c --- /dev/null +++ b/infinigen/assets/tableware/plate.py @@ -0,0 +1,44 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.tableware.base import TablewareFactory +from infinigen.assets.utils.decorate import subsurf +from infinigen.assets.utils.draw import spin +from infinigen.core.util.random import log_uniform +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil + + +class PlateFactory(TablewareFactory): + allow_transparent = True + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.x_end = .5 + self.z_length = log_uniform(.05, .2) + self.x_mid = uniform(.3, 1.) * self.x_end + self.z_mid = uniform(.3, .8) * self.z_length + self.has_guard = False + self.pre_level = 1 + self.thickness = uniform(.01, .03) + self.has_inside = uniform(0, 1) < .2 + self.scale = log_uniform(.2, .4) + self.scratch = self.edge_wear = None + + def create_asset(self, **params) -> bpy.types.Object: + x_anchors = 0, self.x_mid, self.x_mid, self.x_end + z_anchors = 0, 0, self.z_mid, self.z_length + anchors = x_anchors, np.zeros_like(x_anchors), z_anchors + obj = spin(anchors, [1, 2], 4, 16) + butil.modify_mesh(obj, 'SUBSURF', render_levels=self.pre_level, levels=self.pre_level) + self.solidify_with_inside(obj, self.thickness) + subsurf(obj, 2) + obj.scale = [self.scale] * 3 + butil.apply_transform(obj) + return obj diff --git a/infinigen/assets/tableware/pot.py b/infinigen/assets/tableware/pot.py new file mode 100644 index 000000000..be7e89a3c --- /dev/null +++ b/infinigen/assets/tableware/pot.py @@ -0,0 +1,100 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import read_co, write_attribute, subsurf +from infinigen.assets.utils.object import join_objects, new_bbox +from infinigen.core.util.random import log_uniform +from . import PanFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil + + +class PotFactory(PanFactory): + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.depth = log_uniform(.6, 2.) + self.r_expand = 1 + self.r_mid = 1 + self.has_bar = uniform(0, 1) < .5 + self.has_handle = not self.has_handle + self.has_guard = not self.has_bar + self.bar_height = self.depth * uniform(.75, .85) + self.bar_radius = log_uniform(.2, .3) + self.bar_x = 1 + uniform(-self.bar_radius, self.bar_radius) * .05 + self.bar_inner_radius = log_uniform(.2, .4) * self.bar_radius + scale = log_uniform(.6, 1.5) + self.bar_scale = log_uniform(.6, 1.) * scale, 1 * scale, log_uniform(.6, 1.2) * scale + self.bar_taper = log_uniform(.3, .8) + self.bar_y_rotation = uniform(-np.pi / 6, 0) + self.bar_x_offset = self.bar_radius * uniform(-.1, .1) + + self.guard_type = 'round' + self.guard_depth = log_uniform(.5, 1.) * self.thickness + self.scale = log_uniform(.1, .15) + + def post_init(self): + self.has_handle = not self.has_bar + self.has_guard = not self.has_bar + + self.bar_x = 1 + uniform(-self.bar_radius, self.bar_radius) * .05 + self.bar_inner_radius = log_uniform(.2, .4) * self.bar_radius + self.bar_x_offset = self.bar_radius * uniform(-.1, .1) + + def create_asset(self, **params) -> bpy.types.Object: + obj = self.make_base() + if self.has_bar: + self.add_bar(obj) + obj.scale = [self.scale] * 3 + butil.apply_transform(obj) + return obj + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + if self.has_bar: + radius_ = 1 + self.bar_x_offset + self.bar_radius + self.bar_inner_radius + self.thickness + obj = new_bbox(-radius_, radius_, -1 - self.thickness, 1 + self.thickness, 0, self.depth) + elif self.has_handle: + obj = new_bbox( + -1 - self.thickness, 1 + self.thickness + self.x_handle, -1 - self.thickness, 1 + self.thickness, 0, + self.depth + ) + else: + obj = new_bbox( + -1 - self.thickness, 1 + self.thickness, -1 - self.thickness, 1 + self.thickness, 0, self.depth + ) + obj.scale = (self.scale,) * 3 + butil.apply_transform(obj) + return obj + + def add_bar(self, obj): + bars = [] + for side in [-1, 1]: + bpy.ops.mesh.primitive_torus_add( + location=(side * (1 + self.bar_x_offset), 0, self.bar_height), + major_radius=self.bar_radius, minor_radius=self.bar_inner_radius + ) + bar = bpy.context.active_object + bar.scale = self.bar_scale + butil.modify_mesh( + bar, 'SIMPLE_DEFORM', deform_method='TAPER', angle=self.bar_taper, + deform_axis='X' + ) + bar.rotation_euler = 0, self.bar_y_rotation, 0 if side == 1 else np.pi + butil.apply_transform(bar) + + butil.modify_mesh(bar, 'BOOLEAN', object=obj, operation='DIFFERENCE') + butil.select_none() + objs = butil.split_object(bar) + i = np.argmax([np.max(read_co(o)[:, 0] * side) for o in objs]) + bar = objs[i] + objs.remove(bar) + butil.delete(objs) + subsurf(bar, 1) + write_attribute(bar, lambda nw: 1, "guard", "FACE") + bars.append(bar) + return join_objects([obj, *bars]) diff --git a/infinigen/assets/tableware/spoon.py b/infinigen/assets/tableware/spoon.py new file mode 100644 index 000000000..5570a1733 --- /dev/null +++ b/infinigen/assets/tableware/spoon.py @@ -0,0 +1,58 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.utils.decorate import subsurf, write_co +from infinigen.core.util.random import log_uniform +from .base import TablewareFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil +from infinigen.assets.utils.object import new_grid + + +class SpoonFactory(TablewareFactory): + x_end = .15 + is_fragile = True + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.x_length = log_uniform(.2, .8) + self.y_length = log_uniform(.06, .12) + self.z_depth = log_uniform(.08, .25) + self.z_offset = uniform(.0, .05) + self.thickness = log_uniform(.008, .015) + self.has_guard = uniform(0, 1) < .4 + self.guard_type = 'round' if uniform(0, 1) < .6 else 'double' + self.guard_depth = log_uniform(.2, 1.) * self.thickness + self.scale = log_uniform(.15, .25) + + def create_asset(self, **params) -> bpy.types.Object: + x_anchors = np.array([log_uniform(.07, .25), 0, -.08, -.12, -self.x_end, -self.x_end - self.x_length, + -self.x_end - self.x_length * log_uniform(1.2, 1.4)]) + y_anchors = np.array([self.y_length * log_uniform(.1, .8), self.y_length * log_uniform(1., 1.2), + self.y_length * log_uniform(.6, 1.), self.y_length * log_uniform(.2, .4), + log_uniform(.01, .02), log_uniform(.02, .05), log_uniform(.01, .02)]) + z_anchors = np.array( + [0, 0, 0, 0, self.z_offset, self.z_offset + uniform(-.02, .04), self.z_offset + uniform(-.02, 0)]) + obj = new_grid(x_subdivisions=len(x_anchors) - 1, y_subdivisions=2) + x = np.concatenate([x_anchors] * 3) + y = np.concatenate([y_anchors, np.zeros_like(y_anchors), -y_anchors]) + z = np.concatenate([z_anchors] * 3) + x[len(x_anchors)] += .02 + z[len(x_anchors) + 1] = -self.z_depth + write_co(obj, np.stack([x, y, z], -1)) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness) + subsurf(obj, 1) + selection = lambda nw, x: nw.compare('LESS_THAN', x, -self.x_end) + if self.guard_type == 'double': + selection = self.make_double_sided(selection) + self.add_guard(obj, selection) + subsurf(obj, 2) + obj.scale = [self.scale] * 3 + butil.apply_transform(obj) + return obj diff --git a/infinigen/assets/tableware/wineglass.py b/infinigen/assets/tableware/wineglass.py new file mode 100644 index 000000000..463efae45 --- /dev/null +++ b/infinigen/assets/tableware/wineglass.py @@ -0,0 +1,50 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.tableware.base import TablewareFactory +from infinigen.assets.materials import glass +from infinigen.assets.utils.decorate import subsurf +from infinigen.assets.utils.draw import spin +from infinigen.core.util.random import log_uniform +from infinigen.core.util.math import FixedSeed +from infinigen.core.util import blender as butil + + +class WineglassFactory(TablewareFactory): + + def __init__(self, factory_seed, coarse=False): + super().__init__(factory_seed, coarse) + with FixedSeed(factory_seed): + self.x_end = .25 + self.z_length = log_uniform(.6, 2.) + self.z_cup = uniform(.3, .6) * self.z_length + self.z_mid = self.z_cup + uniform(.3, .5) * (self.z_length - self.z_cup) + self.x_neck = log_uniform(.01, .02) + self.x_top = self.x_end * log_uniform(1, 1.4) + self.x_mid = self.x_top * log_uniform(.9, 1.2) + self.has_guard = False + self.thickness = uniform(.01, .03) + self.surface = glass + self.scale = log_uniform(.1, .3) + + def create_asset(self, **params) -> bpy.types.Object: + z_bottom = self.z_length * log_uniform(.01, .05) + x_anchors = self.x_end, self.x_end / 2, self.x_neck, self.x_neck, self.x_mid, self.x_top + z_anchors = 0, z_bottom / 2, z_bottom, self.z_cup, self.z_mid, self.z_length + anchors = x_anchors, np.zeros_like(x_anchors), z_anchors + obj = spin(anchors, [0, 1, 2, 3], 4, 16) + subsurf(obj, 2) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness) + subsurf(obj, 1) + obj.scale = [self.scale] * 3 + butil.apply_transform(obj) + + with butil.SelectObjects(obj): + bpy.ops.object.shade_smooth() + + return obj diff --git a/infinigen/assets/trees/branch.py b/infinigen/assets/trees/branch.py index f4d98d2af..b68eb8b9c 100644 --- a/infinigen/assets/trees/branch.py +++ b/infinigen/assets/trees/branch.py @@ -15,7 +15,7 @@ from infinigen.core.util.math import FixedSeed from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_surface_bump', singleton=False, type='GeometryNodeTree') def nodegroup_surface_bump(nw: NodeWrangler): diff --git a/infinigen/assets/trees/generate.py b/infinigen/assets/trees/generate.py index 0553d9c1f..7102914f0 100644 --- a/infinigen/assets/trees/generate.py +++ b/infinigen/assets/trees/generate.py @@ -1,7 +1,7 @@ # 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, Yiming Zuo, Alejandro Newell +# Authors: Alexander Raistrick, Yiming Zuo, Alejandro Newell, Lingjie Mei import pdb @@ -32,9 +32,10 @@ from infinigen.core import surface from infinigen.assets.weather.cloud.generate import CloudFactory -from ..utils.decorate import write_attribute +from infinigen.assets.utils.decorate import write_attribute -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup +from ..utils.misc import toggle_show, toggle_hide logger = logging.getLogger(__name__) @@ -172,6 +173,8 @@ def create_asset(self, placeholder, face_size, distance, **kwargs) -> bpy.types. butil.parent_to(skeleton_obj, skin_obj, no_inverse=True) tag_object(skin_obj, 'tree') + butil.apply_modifiers(skin_obj) + return skin_obj @@ -272,11 +275,13 @@ def make_leaf_collection(seed, col = make_asset_collection(child_factories, n_leaf, verbose=True, weights=weights) # if leaf_surface is not None: # leaf_surface.apply(list(col.objects)) + toggle_show(col) for obj in col.objects: if decimate_rate > 0: butil.modify_mesh(obj, 'DECIMATE', ratio=1.0-decimate_rate, apply=True) butil.apply_transform(obj, rot=True, scale=True) butil.apply_modifiers(obj) + toggle_hide(col) return col def random_leaf_collection(season, n=5): diff --git a/infinigen/assets/trees/tree.py b/infinigen/assets/trees/tree.py index 9de5e3e53..13f033528 100644 --- a/infinigen/assets/trees/tree.py +++ b/infinigen/assets/trees/tree.py @@ -18,7 +18,7 @@ from infinigen.core.nodes.node_wrangler import Nodes from infinigen.core.util import blender as butil -from ..utils.object import data2mesh, mesh2obj +from infinigen.assets.utils.object import data2mesh, mesh2obj C = bpy.context D = bpy.data diff --git a/infinigen/assets/trees/tree_flower.py b/infinigen/assets/trees/tree_flower.py index a785b6b04..aee0973ee 100644 --- a/infinigen/assets/trees/tree_flower.py +++ b/infinigen/assets/trees/tree_flower.py @@ -19,7 +19,7 @@ from infinigen.core.placement.factory import AssetFactory from infinigen.core.util import blender as butil, color from infinigen.core.util.math import FixedSeed, dict_lerp -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_polar_to_cart_old', singleton=True) def nodegroup_polar_to_cart_old(nw): diff --git a/infinigen/assets/trees/utils/geometrynodes.py b/infinigen/assets/trees/utils/geometrynodes.py index ebf505947..34f67732e 100644 --- a/infinigen/assets/trees/utils/geometrynodes.py +++ b/infinigen/assets/trees/utils/geometrynodes.py @@ -3,8 +3,6 @@ # Authors: Alejandro Newell - -import imp import bpy import numpy as np diff --git a/infinigen/assets/trees/utils/materials.py b/infinigen/assets/trees/utils/materials.py index 87d78b4e4..3ada36d39 100644 --- a/infinigen/assets/trees/utils/materials.py +++ b/infinigen/assets/trees/utils/materials.py @@ -1,7 +1,7 @@ # 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: Alejandro Newell +# Authors: Alejandro Newell, Lingjie Mei import numpy as np @@ -11,6 +11,7 @@ import bpy +from infinigen.core.util.color import hsv2rgba from . import helper C = bpy.context @@ -36,7 +37,7 @@ def init_color_material(color, prefix='', hsv_variance=[0,0,0], nt = m.node_tree color = np.array(color) + np.random.randn(3) * np.array(hsv_variance) color = list(color.clip(0,1)) - color = (*colorsys.hsv_to_rgb(*color), 1) + color = (hsv2rgba(*color)) if is_emission: out_node = nt.nodes.get('Material Output') diff --git a/infinigen/assets/tropic_plants/leaf_banana_tree.py b/infinigen/assets/tropic_plants/leaf_banana_tree.py index a682cbec6..4ad93aae5 100644 --- a/infinigen/assets/tropic_plants/leaf_banana_tree.py +++ b/infinigen/assets/tropic_plants/leaf_banana_tree.py @@ -22,7 +22,7 @@ shader_stem_material ) from infinigen.core.util import blender as butil -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_nodegroup_apply_wave', singleton=False, type='GeometryNodeTree') diff --git a/infinigen/assets/tropic_plants/leaf_palm_tree.py b/infinigen/assets/tropic_plants/leaf_palm_tree.py index ba9aa21bf..a9842ce66 100644 --- a/infinigen/assets/tropic_plants/leaf_palm_tree.py +++ b/infinigen/assets/tropic_plants/leaf_palm_tree.py @@ -21,7 +21,7 @@ nodegroup_nodegroup_leaf_rotate_x, shader_stem_material ) -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @node_utils.to_nodegroup('nodegroup_nodegroup_apply_wave', singleton=False, type='GeometryNodeTree') def nodegroup_nodegroup_apply_wave(nw: NodeWrangler): diff --git a/infinigen/assets/underwater/seaweed.py b/infinigen/assets/underwater/seaweed.py index 175186fda..7d0e684ad 100644 --- a/infinigen/assets/underwater/seaweed.py +++ b/infinigen/assets/underwater/seaweed.py @@ -12,7 +12,8 @@ from numpy.random import uniform from infinigen.assets.creatures.util.animation.driver_repeated import repeated_driver -from infinigen.assets.utils.decorate import assign_material, read_co, subsurface2face_size, write_co +from infinigen.assets.utils.decorate import read_co, subsurface2face_size, write_co +from infinigen.assets.utils.misc import assign_material from infinigen.assets.utils.draw import make_circular_interp import infinigen.core.util.blender as butil from infinigen.core.placement.factory import AssetFactory @@ -21,10 +22,11 @@ from infinigen.assets.utils.mesh import polygon_angles from infinigen.core.nodes.node_wrangler import NodeWrangler, Nodes from infinigen.core import surface -from infinigen.assets.utils.misc import build_color_ramp, log_uniform +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup - +from infinigen.core.tagging import tag_object, tag_nodegroup +from infinigen.core.nodes.node_utils import build_color_ramp class SeaweedFactory(AssetFactory): @@ -107,7 +109,7 @@ def shader_seaweed(nw: NodeWrangler, base_hue=.3): v_perturb = log_uniform(1., 2) def map_perturb(h, s, v): - return *colorsys.hsv_to_rgb(h + h_perturb, s + s_perturb, v / v_perturb), 1. + return hsv2rgba(h + h_perturb, s + s_perturb, v / v_perturb) subsurface_ratio = .01 roughness = .8 diff --git a/infinigen/assets/underwater/urchin.py b/infinigen/assets/underwater/urchin.py index 239bc5d18..abd58b8ea 100644 --- a/infinigen/assets/underwater/urchin.py +++ b/infinigen/assets/underwater/urchin.py @@ -10,16 +10,18 @@ import infinigen.core.util.blender as butil from infinigen.assets.creatures.util.animation.driver_repeated import repeated_driver -from infinigen.assets.utils.object import new_icosphere -from infinigen.assets.utils.decorate import assign_material, geo_extension, separate_loose -from infinigen.assets.utils.misc import log_uniform +from infinigen.assets.utils.object import new_icosphere, separate_loose +from infinigen.assets.utils.decorate import geo_extension +from infinigen.assets.utils.misc import assign_material +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core.placement.detail import adapt_mesh_resolution from infinigen.core.placement.factory import AssetFactory from infinigen.core import surface from infinigen.core.util.math import FixedSeed -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup class UrchinFactory(AssetFactory): @@ -88,7 +90,7 @@ def shader_spikes(nw: NodeWrangler, base_hue): transmission = uniform(.95, .99) subsurface = uniform(.1, .2) roughness = uniform(.5, .8) - color = *colorsys.hsv_to_rgb(base_hue, uniform(.5, 1.), log_uniform(.05, 1.)), 1 + color = hsv2rgba(base_hue, uniform(.5, 1.), log_uniform(.05, 1.)) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={ 'Base Color': color, 'Roughness': roughness, @@ -101,7 +103,7 @@ def shader_spikes(nw: NodeWrangler, base_hue): @staticmethod def shader_girdle(nw: NodeWrangler, base_hue): roughness = uniform(.5, .8) - color = *colorsys.hsv_to_rgb(base_hue, uniform(.4, .5), log_uniform(.02, .1)), 1 + color = hsv2rgba(base_hue, uniform(.4, .5), log_uniform(.02, .1)) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Base Color': color, 'Roughness': roughness}) return principled_bsdf @@ -109,7 +111,7 @@ def shader_girdle(nw: NodeWrangler, base_hue): @staticmethod def shader_base(nw: NodeWrangler, base_hue): roughness = uniform(.5, .8) - color = *colorsys.hsv_to_rgb(base_hue, uniform(.8, 1.), log_uniform(.01, .02)), 1 + color = hsv2rgba(base_hue, uniform(.8, 1.), log_uniform(.01, .02)) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={'Base Color': color, 'Roughness': roughness}) return principled_bsdf diff --git a/infinigen/assets/utils/autobevel.py b/infinigen/assets/utils/autobevel.py new file mode 100644 index 000000000..0565a9242 --- /dev/null +++ b/infinigen/assets/utils/autobevel.py @@ -0,0 +1,53 @@ +# 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 bpy + +import numpy as np +from numpy.random import uniform + +from infinigen.core.util import blender as butil +from infinigen.core.util.random import random_general as rg + +class BevelSharp: + + def __init__( + self, + mult=1, + angle_min_deg=70, + segments=None, + ): + + self.amount = uniform(0.001, 0.006) + self.mult = mult + self.angle_min_deg = angle_min_deg + + if segments is None: + segments = 4 if uniform() < 0 else 1 + self.segments = segments + + def __call__(self, obj): + butil.select_none() + butil.select(obj) + with butil.ViewportMode(obj, 'EDIT'): + + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.tris_convert_to_quads() + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + bpy.ops.mesh.select_all(action='DESELECT') + + angle = np.deg2rad(self.angle_min_deg) + + bpy.ops.mesh.edges_select_sharp( + sharpness=angle + ) + + bpy.ops.mesh.bevel( + offset=self.amount * self.mult, + segments=self.segments, + affect='EDGES', + offset_type='WIDTH' + ) \ No newline at end of file diff --git a/infinigen/assets/utils/bbox_from_mesh.py b/infinigen/assets/utils/bbox_from_mesh.py new file mode 100644 index 000000000..eec1c42b9 --- /dev/null +++ b/infinigen/assets/utils/bbox_from_mesh.py @@ -0,0 +1,87 @@ +# 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 bpy +import numpy as np + +from infinigen.core.util import blender as butil + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.placement.factory import AssetFactory + +@node_utils.to_nodegroup('nodegroup_cube_from_corners', singleton=True) +def nodegroup_cube_from_corners(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketVector', 'min_corner', (0.0000, 0.0000, 0.0000)), + ('NodeSocketVector', 'max_corner', (0.0000, 0.0000, 0.0000))]) + + subtract = nw.new_node(Nodes.VectorMath, + input_kwargs={0: group_input.outputs["max_corner"], 1: group_input.outputs["min_corner"]}, + attrs={'operation': 'SUBTRACT'}) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': subtract.outputs["Vector"]}) + + mix = nw.new_node(Nodes.Mix, + input_kwargs={4: group_input.outputs["min_corner"], 5: group_input.outputs["max_corner"]}, + attrs={'data_type': 'VECTOR'}) + + transform_geometry = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': cube.outputs["Mesh"], 'Translation': mix.outputs[1]}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_geometry}) + +def union_all_bbox(obj: bpy.types.Object): + + mins, maxs = None, None + for oc in butil.iter_object_tree(obj): + if not oc.type == 'MESH': + continue + points = butil.apply_matrix_world(oc, np.array(oc.bound_box)) + pmins, pmaxs = points.min(axis=0), points.max(axis=0) + mins = pmins if mins is None else np.minimum(pmins, mins) + maxs = pmaxs if maxs is None else np.maximum(pmins, mins) + + return mins, maxs + +def box_from_corners(min_corner, max_corner): + + bbox = butil.modify_mesh( + butil.spawn_vert(), + 'NODES', + apply=True, + node_group=nodegroup_cube_from_corners(), + ng_inputs=dict(min_corner=min_corner, max_corner=max_corner) + ) + + return bbox + +def bbox_mesh_from_hipoly(gen: AssetFactory, inst_seed: int, use_pholder=False): + + objs = [] + objs.append(gen.spawn_placeholder(inst_seed, loc=(0,0,0), rot=(0,0,0))) + if not use_pholder: + objs.append(gen.spawn_asset(inst_seed, placeholder=objs[-1])) + + min_corner, max_corner = union_all_bbox(objs[-1]) + + if ( + min_corner is None or + max_corner is None or + np.abs(min_corner - max_corner).sum() < 1e-5 + ): + raise ValueError(f'{gen} spawned {objs[-1].name=} with total bbox {min_corner, max_corner}, invalid') + + bbox = box_from_corners(min_corner, max_corner) + + cleanup = set() + for o in objs: + cleanup.update(butil.iter_object_tree(o)) + butil.delete(list(cleanup)) + + bbox.name = f'{gen.__class__.__name__}({gen.factory_seed}).bbox_placeholder({inst_seed})' + return bbox \ No newline at end of file diff --git a/infinigen/assets/utils/decorate.py b/infinigen/assets/utils/decorate.py index 7d5596086..b538cd71d 100644 --- a/infinigen/assets/utils/decorate.py +++ b/infinigen/assets/utils/decorate.py @@ -1,23 +1,26 @@ # 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. +# 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: Lingjie Mei -from statistics import mean import logging +from collections.abc import Iterable -import bmesh import bpy +import bmesh import numpy as np from numpy.random import uniform +from trimesh.points import remove_close from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface +from infinigen.core.surface import write_attr_data from infinigen.core.util import blender as butil -from infinigen.core.util.blender import select_none +from infinigen.core.util.math import normalize def multi_res(obj): @@ -33,77 +36,21 @@ def geo_extension(nw: NodeWrangler, noise_strength=.2, noise_scale=2., musgrave_ pos = nw.new_node(Nodes.InputPosition) direction = nw.scale(pos, nw.scalar_divide(1, nw.vector_math('LENGTH', pos))) direction = nw.add(direction, uniform(-1, 1, 3)) - musgrave = nw.scalar_multiply(nw.scalar_add( - nw.new_node(Nodes.MusgraveTexture, [direction], input_kwargs={'Scale': noise_scale}, - attrs={'musgrave_dimensions': musgrave_dimensions}), .25), noise_strength) - geometry = nw.new_node(Nodes.SetPosition, - input_kwargs={'Geometry': geometry, 'Offset': nw.scale(musgrave, pos)}) + musgrave = nw.scalar_multiply( + nw.scalar_add( + nw.new_node( + Nodes.MusgraveTexture, [direction], input_kwargs={'Scale': noise_scale}, + attrs={'musgrave_dimensions': musgrave_dimensions} + ), .25 + ), noise_strength + ) + geometry = nw.new_node( + Nodes.SetPosition, + input_kwargs={'Geometry': geometry, 'Offset': nw.scale(musgrave, pos)} + ) nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) -def separate_loose(obj): - select_none() - objs = butil.split_object(obj) - i = np.argmax([len(o.data.vertices) for o in objs]) - obj = objs[i] - objs.remove(obj) - butil.delete(objs) - return obj - - -def toggle_hide(obj, recursive=True): - if obj.name in bpy.data.collections: - for o in obj.objects: - toggle_hide(o, recursive) - else: - obj.hide_set(True) - obj.hide_render = True - if recursive: - for c in obj.children: - toggle_hide(c) - - -def toggle_show(obj, recursive=True): - if obj.name in bpy.data.collections: - for o in obj.objects: - toggle_show(o, recursive) - else: - obj.hide_set(False) - obj.hide_render = False - if recursive: - for c in obj.children: - toggle_hide(c) - - -def join_objects(obj): - if not isinstance(obj, list): - obj = [obj] - if len(obj) == 1: - return obj[0] - bpy.context.view_layer.objects.active = obj[0] - butil.select_none() - butil.select(obj) - bpy.ops.object.join() - obj = bpy.context.active_object - obj.location = 0, 0, 0 - obj.rotation_euler = 0, 0, 0 - obj.scale = 1, 1, 1 - return obj - - -def assign_material(obj, material): - if not isinstance(obj, list): - obj = [obj] - for o in obj: - with butil.SelectObjects(o): - while len(o.data.materials): - bpy.ops.object.material_slot_remove() - if not isinstance(material, list): - material = [material] - for m in material: - o.data.materials.append(m) - - def subsurface2face_size(obj, face_size): arr = np.zeros(len(obj.data.polygons)) obj.data.polygons.foreach_get('area', arr) @@ -119,12 +66,86 @@ def subsurface2face_size(obj, face_size): butil.modify_mesh(obj, 'SUBSURF', levels=levels, render_levels=levels) +def read_selected(obj, domain='VERT'): + match domain: + case 'VERT': + arr = np.zeros(len(obj.data.vertices), int) + obj.data.vertices.foreach_get('select', arr) + case 'EDGE': + arr = np.zeros(len(obj.data.edges), int) + obj.data.edges.foreach_get('select', arr) + case _: + arr = np.zeros(len(obj.data.faces), int) + obj.data.faces.foreach_get('select', arr) + return arr.ravel() + + def read_co(obj): arr = np.zeros(len(obj.data.vertices) * 3) obj.data.vertices.foreach_get('co', arr) return arr.reshape(-1, 3) +def read_edges(obj): + arr = np.zeros(len(obj.data.edges) * 2, dtype=int) + obj.data.edges.foreach_get('vertices', arr) + return arr.reshape(-1, 2) + + +def read_edge_center(obj): + return read_co(obj)[read_edges(obj).reshape(-1)].reshape(-1, 2, 3).mean(1) + + +def read_edge_direction(obj): + cos = read_co(obj)[read_edges(obj).reshape(-1)].reshape(-1, 2, 3) + return normalize(cos[:, 1] - cos[:, 0]) + + +def read_edge_length(obj): + cos = read_co(obj)[read_edges(obj).reshape(-1)].reshape(-1, 2, 3) + return np.linalg.norm(cos[:, 1] - cos[:, 0], axis=-1) + + +def read_center(obj): + arr = np.zeros(len(obj.data.polygons) * 3) + obj.data.polygons.foreach_get('center', arr) + return arr.reshape(-1, 3) + + +def read_normal(obj): + arr = np.zeros(len(obj.data.polygons) * 3) + obj.data.polygons.foreach_get('normal', arr) + return arr.reshape(-1, 3) + + +def read_area(obj): + arr = np.zeros(len(obj.data.polygons)) + obj.data.polygons.foreach_get('area', arr) + return arr.reshape(-1) + + +def read_loop_vertices(obj): + arr = np.zeros(len(obj.data.loops), dtype=int) + obj.data.loops.foreach_get('vertex_index', arr) + return arr.reshape(-1) + + +def read_loop_edges(obj): + arr = np.zeros(len(obj.data.loops), dtype=int) + obj.data.loops.foreach_get('edge_index', arr) + return arr.reshape(-1) + + +def read_uv(obj): + arr = np.zeros(len(obj.data.loops) * 2) + obj.data.uv_layers.active.data.foreach_get('uv', arr) + return arr.reshape(-1, 2) + + +def write_uv(obj, arr): + obj.data.uv_layers.active.data.foreach_set('uv', arr.reshape(-1)) + + def read_base_co(obj): dg = bpy.context.evaluated_depsgraph_get() obj = obj.evaluated_get(dg) @@ -135,7 +156,13 @@ def read_base_co(obj): def write_co(obj, arr): - obj.data.vertices.foreach_set('co', arr.reshape(-1)) + try: + obj.data.vertices.foreach_set('co', arr.reshape(-1)) + except RuntimeError as e: + raise RuntimeError( + f'Failed to set vertices.co on {obj.name=}. Object has {len(obj.data.vertices)} verts, ' + f'{arr.shape=}' + ) from e def read_material_index(obj): @@ -144,22 +171,43 @@ def read_material_index(obj): return arr +def read_loop_starts(obj): + arr = np.zeros(len(obj.data.polygons), dtype=int) + obj.data.polygons.foreach_get('loop_start', arr) + return arr + + +def read_loop_totals(obj): + arr = np.zeros(len(obj.data.polygons), dtype=int) + obj.data.polygons.foreach_get('loop_total', arr) + return arr + + def write_material_index(obj, arr): obj.data.polygons.foreach_set('material_index', arr.reshape(-1)) +def set_shade_smooth(obj): + write_attr_data(obj, 'use_smooth', np.ones(len(obj.data.polygons), dtype=int), 'INT', 'FACE') + + def displace_vertices(obj, fn): co = read_co(obj) - x, y, z = co.T - f = fn(x, y, z) - for i in range(3): - co[:, i] += f[i] + if not isinstance(fn, Iterable): + x, y, z = co.T + fn = fn(x, y, z) + for i in range(3): + co[:, i] += fn[i] + else: + co += fn write_co(obj, co) -def remove_vertices(obj, fn): - x, y, z = read_co(obj).T - to_delete = np.nonzero(fn(x, y, z))[0] +def remove_vertices(obj, to_delete): + if not isinstance(to_delete, Iterable): + x, y, z = read_co(obj).T + to_delete = to_delete(x, y, z) + to_delete = np.nonzero(to_delete)[0] with butil.ViewportMode(obj, 'EDIT'): bm = bmesh.from_edit_mesh(obj.data) bm.verts.ensure_lookup_table() @@ -169,66 +217,107 @@ def remove_vertices(obj, fn): return obj -def write_attribute(obj, fn, name, domain="POINT"): - def geo_attribute(nw: NodeWrangler): - geometry = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) - attr = surface.eval_argument(nw, fn, position=nw.new_node(Nodes.InputPosition)) - geometry = nw.new_node( - Nodes.StoreNamedAttribute, - input_kwargs={'Geometry': geometry, 'Name': name, 'Value': attr}, - attrs={'domain': domain}) - nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) +def remove_edges(obj, to_delete): + if not isinstance(to_delete, Iterable): + x, y, z = read_edge_center(obj).T + to_delete = to_delete(x, y, z) + to_delete = np.nonzero(to_delete)[0] + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + bm.edges.ensure_lookup_table() + geom = [bm.edges[_] for _ in to_delete] + bmesh.ops.delete(bm, geom=geom, context='EDGES_FACES') + bmesh.update_edit_mesh(obj.data) + return obj - surface.add_geomod(obj, geo_attribute, apply=True) +def remove_faces(obj, to_delete, remove_loose=True): + if not isinstance(to_delete, Iterable): + x, y, z = read_center(obj).T + to_delete = to_delete(x, y, z) + to_delete = np.nonzero(to_delete)[0] + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + bm.faces.ensure_lookup_table() + geom = [bm.faces[_] for _ in to_delete] + bmesh.ops.delete(bm, geom=geom, context='FACES_ONLY') + bmesh.update_edit_mesh(obj.data) + if remove_loose: + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_loose() + bpy.ops.mesh.delete(type='EDGE') + return obj -def treeify(obj): - if len(obj.data.vertices) == 0: - return obj - obj = separate_loose(obj) +def select_vertices(obj, to_select): + if not isinstance(to_select, Iterable): + x, y, z = read_co(obj).T + to_select = to_select(x, y, z) + to_select = np.nonzero(to_select)[0] with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='VERT') + bpy.ops.mesh.select_all(action='DESELECT') bm = bmesh.from_edit_mesh(obj.data) bm.verts.ensure_lookup_table() + for i in to_select: + bm.verts[i].select_set(True) + bm.select_flush(False) + bmesh.update_edit_mesh(obj.data) + return obj + + +def select_edges(obj, to_select): + if not isinstance(to_select, Iterable): + x, y, z = read_edge_center(obj).T + to_select = to_select(x, y, z) + to_select = np.nonzero(to_select)[0] + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='DESELECT') + bm = bmesh.from_edit_mesh(obj.data) bm.edges.ensure_lookup_table() - included = np.zeros(len(bm.verts)) - i = min((v.co[-1], i) for i, v in enumerate(bm.verts))[1] - queue = [bm.verts[i]] - included[i] = 1 - to_keep = [] - while queue: - v = queue.pop() - for e in v.link_edges: - o = e.other_vert(v) - if not included[o.index]: - included[o.index] = 1 - to_keep.append(e) - queue.append(o) - bmesh.ops.delete(bm, geom=list(set(bm.edges).difference(to_keep)), context='EDGES') + for i in to_select: + bm.edges[i].select_set(True) + bm.select_flush(False) bmesh.update_edit_mesh(obj.data) return obj -def fix_tree(obj): - with butil.ViewportMode(obj, 'EDIT'), butil.Suppress(): - bpy.ops.mesh.remove_doubles() +def select_faces(obj, to_select): + if not isinstance(to_select, Iterable): + x, y, z = read_center(obj).T + to_select = to_select(x, y, z) + to_select = np.nonzero(to_select)[0] + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type='FACE') + bpy.ops.mesh.select_all(action='DESELECT') bm = bmesh.from_edit_mesh(obj.data) - vertices_remove = [] - for v in bm.verts: - if len(v.link_edges) == 1: - o = v.link_edges[0].other_vert(v) - if len(o.link_edges) > 2: - vertices_remove.append(v) - bmesh.ops.delete(bm, geom=vertices_remove) + bm.faces.ensure_lookup_table() + for i in to_select: + bm.faces[i].select_set(True) + bm.select_flush(False) bmesh.update_edit_mesh(obj.data) return obj -def add_distance_to_boundary(obj): +def write_attribute(obj, fn, name, domain="POINT", data_type='FLOAT'): + def geo_attribute(nw: NodeWrangler): + geometry = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + attr = surface.eval_argument(nw, fn, position=nw.new_node(Nodes.InputPosition)) + geometry = nw.new_node( + Nodes.StoreNamedAttribute, + input_kwargs={'Geometry': geometry, 'Name': name, 'Value': attr}, + attrs={'domain': domain, 'data_type': data_type} + ) + nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) + + surface.add_geomod(obj, geo_attribute, apply=True) + + +def distance2boundary(obj): with butil.ViewportMode(obj, 'EDIT'): bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.region_to_loop() - vg = obj.vertex_groups.new(name='distance') with butil.ViewportMode(obj, 'EDIT'): bm = bmesh.from_edit_mesh(obj.data) bm.verts.ensure_lookup_table() @@ -248,6 +337,78 @@ def add_distance_to_boundary(obj): d += 1 distance[distance < 0] = 0 distance /= max(d, 1) - for i, d in enumerate(distance): - vg.add([i], d, 'REPLACE') + write_attr_data(obj, 'distance', distance) return distance + + +def mirror(obj, axis=0): + obj.scale[axis] = -1 + butil.apply_transform(obj) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.flip_normals() + return obj + + +def subsurf(obj, levels, simple=False): + if levels > 0: + butil.modify_mesh( + obj, 'SUBSURF', levels=levels, render_levels=levels, + subdivision_type='SIMPLE' if simple else "CATMULL_CLARK" + ) + + +def subdivide_edge_ring(obj, cuts=64, axis=(0, 0, 1), **kwargs): + butil.select_none() + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + bm.edges.ensure_lookup_table() + selected = np.abs((read_edge_direction(obj) * np.array(axis)[np.newaxis, :]).sum(1)) > 1 - 1e-3 + edges = [bm.edges[i] for i in np.nonzero(selected)[0]] + bmesh.ops.subdivide_edgering(bm, edges=edges, cuts=int(cuts), **kwargs) + bmesh.update_edit_mesh(obj.data) + + +def solidify(obj, axis, thickness): + axes = [0, 1, 2] + axes.remove(axis) + u = np.zeros(3) + u[axes[0]] = thickness + v = np.zeros(3) + v[axes[1]] = thickness + butil.select_none() + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move(TRANSFORM_OT_translate={'value': u}) + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_region_move(TRANSFORM_OT_translate={'value': v}) + obj.location = -(u + v) / 2 + butil.apply_transform(obj, True) + return obj + + +def decimate(points, n): + dist = .1 + ratio = 1.2 + while True: + culled = remove_close(points, dist)[0] + if len(culled) <= n or dist > 10: + dist /= ratio + break + dist *= ratio + culled = remove_close(points, dist)[0] + return np.random.permutation(culled)[:n] + + +def remove_duplicate_edges(obj): + remove_faces(obj, np.ones_like(len(obj.data.polygons)), remove_loose=False) + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + bm.verts.ensure_lookup_table() + counts = [] + for v in bm.verts: + counts.append(len(v.link_edges)) + counts = np.array(counts) + u, v = read_edges(obj).T + to_delete = (counts[u] > 2) & (counts[v] > 2) + remove_edges(obj, to_delete) diff --git a/infinigen/assets/utils/draw.py b/infinigen/assets/utils/draw.py index adc9b92ac..1f6f221cc 100644 --- a/infinigen/assets/utils/draw.py +++ b/infinigen/assets/utils/draw.py @@ -1,21 +1,22 @@ # 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. +# 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: Lingjie Mei from collections.abc import Sized -import bmesh import bpy +import bmesh import numpy as np from numpy.random import uniform from scipy.interpolate import interp1d -from infinigen.assets.utils.decorate import read_co, remove_vertices, separate_loose, write_attribute, write_co +from infinigen.assets.utils.decorate import read_co, remove_vertices, write_attribute, write_co from infinigen.assets.utils.mesh import polygon_angles from infinigen.assets.utils.misc import make_circular, make_circular_angle -from infinigen.assets.utils.object import data2mesh, mesh2obj +from infinigen.assets.utils.object import data2mesh, mesh2obj, separate_loose from infinigen.core.nodes.node_info import Nodes from infinigen.core.placement.detail import sharp_remesh_with_attrs from infinigen.core.surface import read_attr_data @@ -61,37 +62,73 @@ def surface_from_func(fn, div_x=16, div_y=16, size_x=2, size_y=2): return mesh -def bezier_curve(anchors, vector_locations=(), resolution=64): +def bezier_curve(anchors, vector_locations=(), resolution=64, to_mesh=True): n = [len(r) for r in anchors if isinstance(r, Sized)][0] anchors = np.array([np.array(r, dtype=float) if isinstance(r, Sized) else np.full(n, r) for r in anchors]) bpy.ops.curve.primitive_bezier_curve_add(location=(0, 0, 0)) obj = bpy.context.active_object - with butil.ViewportMode(obj, 'EDIT'): - bpy.ops.curve.subdivide(number_cuts=n - 2) - points = obj.data.splines[0].bezier_points - for i in range(n): - points[i].co = anchors[:, i] - for i in range(n): - points[i].handle_left_type = 'AUTO' - points[i].handle_right_type = 'AUTO' - for i in vector_locations: + if n > 2: + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.curve.subdivide(number_cuts=n - 2) + points = obj.data.splines[0].bezier_points + for i in range(n): + points[i].co = anchors[:, i] + for i in range(n): + if i in vector_locations: points[i].handle_left_type = 'VECTOR' points[i].handle_right_type = 'VECTOR' + else: + points[i].handle_left_type = 'AUTO' + points[i].handle_right_type = 'AUTO' obj.data.splines[0].resolution_u = resolution + if to_mesh: + return curve2mesh(obj) + return obj + + +def curve2mesh(obj): with butil.SelectObjects(obj): bpy.ops.object.convert(target='MESH') + obj = bpy.context.active_object butil.modify_mesh(obj, 'WELD', merge_threshold=1e-4) - return bpy.context.active_object + return obj -def remesh_fill(obj): +def align_bezier(anchors, axes=None, scale=None, vector_locations=(), resolution=64, to_mesh=True): + obj = bezier_curve(anchors, vector_locations, resolution, False) + points = obj.data.splines[0].bezier_points + if scale is None: + scale = np.ones(2 * len(points) - 2) + if axes is None: + axes = [None] * len(points) + scale = [1, *scale, 1] + for i, p in enumerate(points): + a = axes[i] + if a is None: + continue + a = np.array(a) + p.handle_left_type = 'FREE' + p.handle_right_type = 'FREE' + proj_left = np.array(p.handle_left - p.co) @ a * a + p.handle_left = np.array(p.co) + proj_left / np.linalg.norm(proj_left) * np.linalg.norm( + p.handle_left - p.co) * scale[2 * i] + proj_right = np.array(p.handle_right - p.co) @ a * a + p.handle_right = np.array(p.co) + proj_right / np.linalg.norm(proj_right) * np.linalg.norm( + p.handle_right - p.co) * scale[2 * i + 1] + if to_mesh: + return curve2mesh(obj) + return obj + + +def remesh_fill(obj, resolution=.005): n = len(obj.data.vertices) butil.modify_mesh(obj, 'SOLIDIFY', thickness=.1) write_attribute(obj, lambda nw, position: nw.compare('GREATER_EQUAL', nw.new_node(Nodes.Index), n), 'top') - sharp_remesh_with_attrs(obj, .005) - is_top = read_attr_data(obj, 'top')[:, 0] > 1e-3 + sharp_remesh_with_attrs(obj, resolution) + is_top = read_attr_data(obj, 'top') > 1e-3 remove_vertices(obj, lambda x, y, z: is_top) + obj.data.attributes.remove(obj.data.attributes['top']) return obj @@ -101,7 +138,7 @@ def spin(anchors, vector_locations=(), subdivision=64, resolution=None, axis=(0, co = read_co(obj) max_radius = np.amax(np.linalg.norm(co - (co @ np.array(axis))[:, np.newaxis] * np.array(axis), axis=-1)) if resolution is None: resolution = min(int(2 * np.pi * max_radius / .005), 128) - butil.modify_mesh(obj, 'WELD', merge_threshold=.001) + butil.modify_mesh(obj, 'WELD', merge_threshold=1e-4) if loop: with butil.ViewportMode(obj, 'EDIT'), butil.Suppress(): bpy.ops.mesh.select_all(action='SELECT') @@ -110,7 +147,8 @@ def spin(anchors, vector_locations=(), subdivision=64, resolution=None, axis=(0, with butil.ViewportMode(obj, 'EDIT'), butil.Suppress(): bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.spin(steps=resolution, angle=np.pi * 2, axis=axis, dupli=dupli) - bpy.ops.mesh.remove_doubles(threshold=.001) + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.remove_doubles(threshold=1e-4) return obj diff --git a/infinigen/assets/utils/extract_nodegroup_parts.py b/infinigen/assets/utils/extract_nodegroup_parts.py new file mode 100644 index 000000000..6a9a138e1 --- /dev/null +++ b/infinigen/assets/utils/extract_nodegroup_parts.py @@ -0,0 +1,43 @@ +# 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 bpy + +from infinigen.core.util import blender as butil + +from infinigen.core.nodes.node_wrangler import NodeWrangler, Nodes, geometry_node_group_empty_new + +def extract_nodegroup_geo(target_obj, nodegroup, k, ng_params=None): + + assert k in nodegroup.outputs + assert target_obj.type == 'MESH' + + vert = butil.spawn_vert('extract_nodegroup_geo.temp') + + butil.modify_mesh(vert, type='NODES', apply=False) + if vert.modifiers[0].node_group == None: + group = geometry_node_group_empty_new() + vert.modifiers[0].node_group = group + ng = vert.modifiers[0].node_group + nw = NodeWrangler(ng) + obj_inp = nw.new_node(Nodes.ObjectInfo, [target_obj]) + + group_input_kwargs = {**ng_params} + if 'Geometry' in nodegroup.inputs: + group_input_kwargs['Geometry'] = obj_inp.outputs['Geometry'] + group = nw.new_node(nodegroup.name, input_kwargs=group_input_kwargs) + + geo = group.outputs[k] + + if k.endswith('Curve'): + # curves dont export from geonodes well, convert it to a mesh + geo = nw.new_node(Nodes.CurveToMesh, [geo]) + + output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geo}) + + butil.apply_modifiers(vert) + bpy.data.node_groups.remove(ng) + return vert \ No newline at end of file diff --git a/infinigen/assets/utils/laplacian.py b/infinigen/assets/utils/laplacian.py index 7e3a9a455..ef748eb1f 100644 --- a/infinigen/assets/utils/laplacian.py +++ b/infinigen/assets/utils/laplacian.py @@ -4,8 +4,8 @@ # Authors: Lingjie Mei -import bmesh import bpy +import bmesh import numpy as np from numpy.random import uniform from skimage.measure import find_contours, marching_cubes diff --git a/infinigen/assets/utils/mesh.py b/infinigen/assets/utils/mesh.py index 8c2a73d35..fd7f1f319 100644 --- a/infinigen/assets/utils/mesh.py +++ b/infinigen/assets/utils/mesh.py @@ -1,18 +1,27 @@ # 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. +# 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: Lingjie Mei import bpy +import bmesh import numpy as np +import shapely +import trimesh +from mathutils import Vector from numpy.random import normal, uniform +from shapely import LineString -from infinigen.assets.utils.object import new_cube +from infinigen.assets.utils.decorate import read_co, read_edges, read_edge_length, remove_faces, read_area +from infinigen.assets.utils.object import new_cube, obj2trimesh, separate_loose +from infinigen.assets.utils.shapes import dissolve_limited from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler from infinigen.core import surface from infinigen.core.util import blender as butil +from infinigen.core.util.math import normalize def build_prism_mesh(n=6, r_min=1., r_max=1.5, height=.3, tilt=.3): @@ -24,14 +33,17 @@ def build_prism_mesh(n=6, r_min=1., r_max=1.5, height=.3, tilt=.3): r_upper = uniform(r_min, r_max, n) r_lower = uniform(r_min, r_max, n) - vertices = np.block([[r_upper * np.cos(angles + a_upper), r_lower * np.cos(angles + a_lower), 0, 0], - [r_upper * np.sin(angles + a_upper), r_lower * np.sin(angles + a_lower), 0, 0], - [z_upper, -z_lower, 1, -1]]).T + vertices = np.block( + [[r_upper * np.cos(angles + a_upper), r_lower * np.cos(angles + a_lower), 0, 0], + [r_upper * np.sin(angles + a_upper), r_lower * np.sin(angles + a_lower), 0, 0], + [z_upper, -z_lower, 1, -1]] + ).T r = np.arange(n) s = np.roll(r, -1) faces = np.block( - [[r, r, r + n, s + n], [s, r + n, s + n, r + n], [np.full(n, 2 * n), s, s, np.full(n, 2 * n + 1)]]).T + [[r, r, r + n, s + n], [s, r + n, s + n, r + n], [np.full(n, 2 * n), s, s, np.full(n, 2 * n + 1)]] + ).T mesh = bpy.data.meshes.new('prism') mesh.from_pydata(vertices, [], faces) mesh.update() @@ -45,15 +57,18 @@ def build_convex_mesh(n=6, height=.2, tilt=.2): z_upper = 1 + normal(0, height, n) + uniform(0, tilt) * np.cos(angles + uniform(-np.pi, np.pi)) z_lower = 1 + normal(0, height, n) + uniform(0, tilt) * np.cos(angles + uniform(-np.pi, np.pi)) r = 1.8 - vertices = np.block([[r * np.cos(angles + a_upper), r * np.cos(angles + a_lower), 0, 0], - [r * np.sin(angles + a_upper), r * np.sin(angles + a_lower), 0, 0], - [z_upper, -z_lower, z_upper.max() + uniform(.1, .2), - -z_lower.max() - uniform(.1, .2)]]).T + vertices = np.block( + [[r * np.cos(angles + a_upper), r * np.cos(angles + a_lower), 0, 0], + [r * np.sin(angles + a_upper), r * np.sin(angles + a_lower), 0, 0], + [z_upper, -z_lower, z_upper.max() + uniform(.1, .2), + -z_lower.max() - uniform(.1, .2)]] + ).T r = np.arange(n) s = np.roll(r, -1) faces = np.block( - [[r, r, r + n, s + n], [s, r + n, s + n, r + n], [np.full(n, 2 * n), s, s, np.full(n, 2 * n + 1)]]).T + [[r, r, r + n, s + n], [s, r + n, s + n, r + n], [np.full(n, 2 * n), s, s, np.full(n, 2 * n + 1)]] + ).T mesh = bpy.data.meshes.new('prism') mesh.from_pydata(vertices, [], faces) mesh.update() @@ -69,3 +84,240 @@ def polygon_angles(n, min_angle=np.pi / 6, max_angle=np.pi * 2 / 3): else: angles = np.sort((np.arange(n) * (2 * np.pi / n) + uniform(0, np.pi * 2)) % (np.pi * 2)) return angles + + +def face_area(obj): + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + return sum(f.calc_area() for f in bm.faces) + + +def centroid(obj): + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + s = sum((f.calc_area() * f.calc_center_median() for f in bm.faces), Vector((0, 0, 0))) + area = sum(f.calc_area() for f in bm.faces) + return np.array(s / area) + + +def longest_ray(obj, obj_, direction): + co = read_co(obj_) + directions = np.array([direction] * len(co)) + mesh = obj2trimesh(obj) + signed_distance = trimesh.proximity.longest_ray(mesh, co, directions) + return signed_distance + + +def treeify(obj): + if len(obj.data.vertices) == 0: + return obj + + obj = separate_loose(obj) + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + included = np.zeros(len(bm.verts)) + i = min((v.co[-1], i) for i, v in enumerate(bm.verts))[1] + queue = [bm.verts[i]] + included[i] = 1 + to_keep = [] + while queue: + v = queue.pop() + for e in v.link_edges: + o = e.other_vert(v) + if not included[o.index]: + included[o.index] = 1 + to_keep.append(e) + queue.append(o) + bmesh.ops.delete(bm, geom=list(set(bm.edges).difference(to_keep)), context='EDGES') + bmesh.update_edit_mesh(obj.data) + return obj + + +def convert2ls(obj): + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + verts = [next(v for v in bm.verts if len(v.link_edges) == 1)] + for i in range(len(bm.verts) - 1): + vs = [e.other_vert(verts[-1]) for e in verts[-1].link_edges] + if len(verts) > 1 and len(vs) > 1: + verts.append(next(_ for _ in vs if _ != verts[-2])) + else: + verts.append(vs[0]) + return LineString(np.array([v.co for v in verts])) + + +def convert2mls(obj): + mls = [] + for o in butil.split_object(obj): + mls.append(convert2ls(o)) + return shapely.MultiLineString(mls) + + +def fix_tree(obj): + with butil.ViewportMode(obj, 'EDIT'), butil.Suppress(): + bpy.ops.mesh.remove_doubles() + bm = bmesh.from_edit_mesh(obj.data) + vertices_remove = [] + for v in bm.verts: + if len(v.link_edges) == 1: + o = v.link_edges[0].other_vert(v) + if len(o.link_edges) > 2: + vertices_remove.append(v) + bmesh.ops.delete(bm, geom=vertices_remove) + bmesh.update_edit_mesh(obj.data) + return obj + + +def longest_path(obj): + with butil.ViewportMode(obj, 'EDIT'), butil.Suppress(): + bpy.ops.mesh.remove_doubles() + bm = bmesh.from_edit_mesh(obj.data) + + def longest_path_(u, v): + dist = 0 + rest = [v] + for e in v.link_edges: + w = e.other_vert(v) + if w != u: + l, r = longest_path_(v, w) + dist = max(dist, l) + rest.extend(r) + return dist + np.linalg.norm(u.co - v.co), rest + + while True: + for v in bm.verts: + if len(v.link_edges) > 2: + ws = [e.other_vert(v) for e in v.link_edges] + dists, rests = list(zip(*list(longest_path_(v, w) for w in ws))) + geom = sum(list(rests[i] for i in np.argsort(dists)[:-2]), []) + bmesh.ops.delete(bm, geom=geom) + break + else: + break + bmesh.update_edit_mesh(obj.data) + return obj + + +def bevel(obj, width, **kwargs): + preset = np.random.choice(['LINE', 'SUPPORTS', 'CORNICE', 'CROWN', 'STEPS']) + obj, mod = butil.modify_mesh( + obj, 'BEVEL', width=width, segments=np.random.randint(20, 30), + profile_type='CUSTOM', apply=False, return_mod=True, **kwargs + ) + reset_preset(mod.custom_profile, preset) + butil.apply_modifiers(obj, mod) + + +def reset_preset(profile, name, n=None): + if n is None: + n = np.random.randint(8, 15) + match name: + case 'LINE': + configs = [(1.0, 0.0, 0, 'AUTO', 'AUTO'), (0.0, 1.0, 0, 'AUTO', 'AUTO')] + case 'CORNICE': + configs = [(1.0, 0.0, 0, 'VECTOR', 'VECTOR'), (1.0, 0.125, 0, 'VECTOR', 'VECTOR'), + (0.92, 0.16, 0, 'AUTO', 'AUTO'), (0.875, 0.25, 0, 'VECTOR', 'VECTOR'), + (0.8, 0.25, 0, 'VECTOR', 'VECTOR'), (0.733, 0.433, 0, 'AUTO', 'AUTO'), + (0.582, 0.522, 0, 'AUTO', 'AUTO'), (0.4, 0.6, 0, 'AUTO', 'AUTO'), + (0.289, 0.727, 0, 'AUTO', 'AUTO'), (0.25, 0.925, 0, 'VECTOR', 'VECTOR'), + (0.175, 0.925, 0, 'VECTOR', 'VECTOR'), (0.175, 1.0, 0, 'VECTOR', 'VECTOR'), + (0.0, 1.0, 0, 'VECTOR', 'VECTOR')] + case 'CROWN': + configs = [(1.0, 0.0, 0, 'VECTOR', 'VECTOR'), (1.0, 0.25, 0, 'VECTOR', 'VECTOR'), + (0.75, 0.25, 0, 'VECTOR', 'VECTOR'), (0.75, 0.325, 0, 'VECTOR', 'VECTOR'), + (0.925, 0.4, 0, 'AUTO', 'AUTO'), (0.975, 0.5, 0, 'AUTO', 'AUTO'), + (0.94, 0.65, 0, 'AUTO', 'AUTO'), (0.85, 0.75, 0, 'AUTO', 'AUTO'), + (0.75, 0.875, 0, 'AUTO', 'AUTO'), (0.7, 1.0, 0, 'VECTOR', 'VECTOR'), + (0.0, 1.0, 0, 'VECTOR', 'VECTOR')] + case 'SUPPORTS': + configs = [(1.0, 0.0, 0, 'VECTOR', 'VECTOR'), (1.0, 0.5, 0, 'VECTOR', 'VECTOR')] + list( + (1 - .5 * ( + 1 - np.cos(i / (n - 3) * np.pi / 2)), .5 + .5 * np.sin(i / (n - 3) * np.pi / 2), 0, 'AUTO', + 'AUTO') for i in range(1, n - 2) + ) + [(0.5, 1.0, 0, 'VECTOR', 'VECTOR'), + (0.0, 1.0, 0, 'VECTOR', 'VECTOR')] + case _: + n_steps_x = n if n % 2 == 0 else n - 1 + n_steps_y = n - 2 if n % 2 == 0 else n - 1 + configs = list( + (1 - (i + 1) // 2 * 2 / n_steps_x, i // 2 * 2 / n_steps_y, 0, 'VECTOR', 'VECTOR') for i in + range(n) + ) + k = len(configs) - len(profile.points) + for i in range(k): + profile.points.add((i + 1) / (k + 1), 0) + for p, c in zip(profile.points, configs): + p.location = c[0], c[1] + p.select = True + p.handle_type_1 = c[3] + p.handle_type_2 = c[4] + p.select = False + profile.points.update() + + +def canonicalize_ls(line): + line = shapely.simplify(line, .02) + while True: + coords = np.array(line.coords) + diff = coords[1:] - coords[:-1] + diff = diff / (np.linalg.norm(diff, axis=-1, keepdims=True) + 1e-6) + product = (diff[:-1] * diff[1:]).sum(-1) + valid_indices = (np.nonzero((1 - 1e-6 > product) & (product > -.8))[0] + 1).tolist() + ls = LineString(coords[[0] + valid_indices + [-1]]) + if ls.length < line.length: + line = ls + else: + break + return ls + + +def canonicalize_mls(mls): + return shapely.MultiLineString([canonicalize_ls(ls) for ls in mls.geoms]) + + +def separate_selected(obj, face=False): + butil.select_none() + with butil.ViewportMode(obj, 'EDIT'): + if face: + bpy.ops.mesh.duplicate_move() + bpy.ops.mesh.separate(type='SELECTED') + o = next(o for o in bpy.context.selected_objects if o != obj) + butil.select_none() + return o + + +def snap_mesh(obj, eps=1e-3): + while True: + dissolve_limited(obj) + co = read_co(obj) + u, w = read_edges(obj).T + d = co[:, np.newaxis] - co[np.newaxis, u] + l = co[np.newaxis, w] - co[np.newaxis, u] + n = normalize(l, in_place=False) + prod = (d * n).sum(-1) + diff = np.linalg.norm(d - prod[:, :, np.newaxis] * n, axis=-1) + diff[u, np.arange(len(u))] = 1 + diff[w, np.arange(len(w))] = 1 + diff[prod < 0] = 1 + diff[prod > np.linalg.norm(l, axis=-1)] = 1 + es, vs = np.nonzero((diff < eps).T) + if len(vs) == 0: + return obj + indices = np.concatenate([[0], np.nonzero(es[1:] != es[:-1])[0] + 1]) + vs = vs[indices] + es = es[indices] + with butil.ViewportMode(obj, 'EDIT'): + bm = bmesh.from_edit_mesh(obj.data) + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + dis = co[w[es]] - co[u[es]] + norms = np.linalg.norm(dis, axis=-1) + percents = ((co[vs] - co[u[es]]) * dis).sum(-1) / (norms ** 2) + edges = [bm.edges[e] for e in es] + for e, p in zip(edges, percents): + bmesh.ops.subdivide_edges(bm, edges=[e], cuts=1, edge_percents={e: p}) + bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=eps * 1.5) + bmesh.update_edit_mesh(obj.data) + diff --git a/infinigen/assets/utils/misc.py b/infinigen/assets/utils/misc.py index da5753db3..006a54043 100644 --- a/infinigen/assets/utils/misc.py +++ b/infinigen/assets/utils/misc.py @@ -1,5 +1,8 @@ # 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. +# This source code is licensed under the BSD 3-Clause license found in the LICENSE file in the root directory +# of this source tree. +import string +from functools import update_wrapper, wraps # Authors: Lingjie Mei @@ -8,8 +11,11 @@ import numpy as np from numpy.random import normal, uniform -from infinigen.core.nodes.node_info import Nodes -from infinigen.core.nodes.node_wrangler import NodeWrangler +from infinigen.assets.utils.object import origin2lowest +from infinigen.core.util import blender as butil +from infinigen.core.util.math import clip_gaussian +from infinigen.core.util.random import log_uniform # imported by other files +from infinigen.core.nodes import NodeWrangler, Nodes class CountInstance: @@ -30,10 +36,6 @@ def __exit__(self, *args): print(f"{count - self.count} {self.name} instances created.") -def log_uniform(low, high, size=None): - return np.exp(uniform(np.log(low), np.log(high), size)) - - def sample_direction(min_z): for _ in range(100): x = normal(size=3) @@ -43,6 +45,27 @@ def sample_direction(min_z): return 0, 0, 1 +def subclasses(cls): + return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in subclasses(c)]) + + +def make_normalized_factory(cls): + @wraps(cls, updated=()) + class CLS(cls): + def __init__(self, *args, **kwargs): + super(CLS, self).__init__(*args, **kwargs) + update_wrapper(self, *args, **kwargs) + + def create_asset(self, **params): + obj = super(CLS, self).create_asset(**params) + obj.rotation_euler = uniform(-np.pi, np.pi, 3) + butil.apply_transform(obj) + origin2lowest(obj) + return obj + + return CLS + + def build_color_ramp(nw: NodeWrangler, x, positions, colors, mode='HSV'): cr = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': x}) cr.color_ramp.color_mode = mode @@ -64,3 +87,56 @@ def make_circular_angle(xs): def make_circular(xs): return np.array([xs[-1], *xs, xs[0]]) + + +def toggle_hide(obj, recursive=True): + if obj.name in bpy.data.collections: + obj.hide_viewport = True + obj.hide_render = True + for o in obj.objects: + toggle_hide(o, recursive) + else: + obj.hide_set(True) + obj.hide_render = True + if recursive: + for c in obj.children: + toggle_hide(c) + + +def toggle_show(obj, recursive=True): + if obj.name in bpy.data.collections: + obj.hide_viewport = False + obj.hide_render = False + for o in obj.objects: + toggle_show(o, recursive) + else: + obj.hide_set(False) + obj.hide_render = False + if recursive: + for c in obj.children: + toggle_hide(c) + + +def assign_material(obj, material): + if not isinstance(obj, list): + obj = [obj] + for o in obj: + with butil.SelectObjects(o): + while len(o.data.materials): + bpy.ops.object.material_slot_remove() + if not isinstance(material, list): + material = [material] + for m in material: + o.data.materials.append(m) + + +character_set = list(string.ascii_lowercase + string.ascii_uppercase + string.digits) +character_set_weights = np.concatenate( + [1.5 * np.ones(len(string.ascii_lowercase)), 0.5 * np.ones(len(string.ascii_uppercase)), + 0.5 * np.ones(len(string.digits))]) +character_set_weights /= character_set_weights.sum() + + +def generate_text(): + return "".join(np.random.choice(character_set, size=int(clip_gaussian(3, 7, 2, 15)), replace=True, + p=character_set_weights)) diff --git a/infinigen/assets/utils/nodegroup.py b/infinigen/assets/utils/nodegroup.py index c130ed24f..5f559825d 100644 --- a/infinigen/assets/utils/nodegroup.py +++ b/infinigen/assets/utils/nodegroup.py @@ -1,5 +1,6 @@ # 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. +# 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: Lingjie Mei @@ -9,7 +10,7 @@ import bpy import numpy as np -from infinigen.assets.utils.decorate import toggle_hide +from infinigen.assets.utils.misc import toggle_hide from infinigen.core.nodes import node_utils from infinigen.core.nodes.node_info import Nodes from infinigen.core.nodes.node_wrangler import NodeWrangler @@ -42,12 +43,17 @@ def build_curve(nw: NodeWrangler, positions, circular=False, handle='VECTOR'): return curve -def geo_radius(nw: NodeWrangler, radius, resolution=6, merge_distance=.004): +def geo_radius(nw: NodeWrangler, radius, resolution=6, merge_distance=.004, rotation=0, to_align_tilt=True, + align_tilt_axis=(0, 0, 1)): skeleton = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) radius = surface.eval_argument(nw, radius) - curve = align_tilt(nw, nw.new_node(Nodes.MeshToCurve, [skeleton])) + curve = nw.new_node(Nodes.MeshToCurve, [skeleton]) + if to_align_tilt: + curve = align_tilt(nw, curve, align_tilt_axis) skeleton = nw.new_node(Nodes.SetCurveRadius, input_kwargs={'Curve': curve, 'Radius': radius}) - geometry = nw.curve2mesh(skeleton, nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': resolution})) + geometry = nw.curve2mesh(skeleton, nw.new_node(Nodes.Transform, [ + nw.new_node(Nodes.CurveCircle, input_kwargs={'Resolution': resolution})], + input_kwargs={'Rotation': [0, 0, rotation]})) if merge_distance > 0: geometry = nw.new_node(Nodes.MergeByDistance, [geometry, None, merge_distance]) nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) @@ -60,6 +66,14 @@ def geo_selection(nw: NodeWrangler, selection): nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) +def geo_selection_attribute(nw: NodeWrangler, selection, name, domain='POINT'): + geometry = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + selection = surface.eval_argument(nw, selection) + geometry = nw.new_node(Nodes.StoreNamedAttribute, [geometry, None, name, None, selection], + attrs={'domain': domain}) + nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': geometry}) + + def geo_base_selection(nw: NodeWrangler, base_obj, selection, merge_threshold=0): geometry = nw.new_node(Nodes.ObjectInfo, [base_obj], attrs={'transform_space': 'RELATIVE'}).outputs[ 'Geometry'] diff --git a/infinigen/assets/utils/object.py b/infinigen/assets/utils/object.py index a60363093..f8ebe7e9a 100644 --- a/infinigen/assets/utils/object.py +++ b/infinigen/assets/utils/object.py @@ -1,5 +1,6 @@ # 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. +# 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: Lingjie Mei @@ -11,18 +12,27 @@ import infinigen.core.util.blender as butil from infinigen.assets.utils.decorate import read_co +from infinigen.core.util import blender as butil +from infinigen.core.util.blender import select_none def center(obj): return (Vector(obj.bound_box[0]) + Vector(obj.bound_box[-2])) * obj.scale / 2. -def origin2lowest(obj, vertical=False): +def origin2lowest(obj, vertical=False, centered=False, approximate=False): co = read_co(obj) if not len(co): return i = np.argmin(co[:, -1]) - if vertical: + if approximate: + indices = np.argsort(co[:, -1]) + obj.location = -np.mean(co[indices[:len(co) // 10]], 0) + obj.location[-1] = -co[i, -1] + elif centered: + obj.location = -center(obj) + obj.location[-1] = -co[i, -1] + elif vertical: obj.location[-1] = -co[i, -1] else: obj.location = -co[i] @@ -65,9 +75,7 @@ def trimesh2obj(trimesh): def obj2trimesh(obj): - with butil.ViewportMode(obj, 'EDIT'): - bpy.ops.mesh.select_all(action='SELECT') - bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + butil.modify_mesh(obj, 'TRIANGULATE', min_vertices=3) vertices = read_co(obj) arr = np.zeros(len(obj.data.polygons) * 3) obj.data.polygons.foreach_get('vertices', arr) @@ -81,6 +89,22 @@ def new_cube(**kwargs): return bpy.context.active_object +def new_bbox(x, x_, y, y_, z, z_): + obj = new_cube() + obj.location = (x + x_) / 2, (y + y_) / 2, (z + z_) / 2 + obj.scale = (x_ - x) / 2, (y_ - y) / 2, (z_ - z) / 2 + butil.apply_transform(obj, True) + return obj + + +def new_bbox_2d(x, x_, y, y_, z=0): + obj = new_plane() + obj.location = (x + x_) / 2, (y + y_) / 2, z + obj.scale = (x_ - x) / 2, (y_ - y) / 2, 1 + butil.apply_transform(obj, True) + return obj + + def new_icosphere(**kwargs): kwargs['location'] = kwargs.get('location', (0, 0, 0)) bpy.ops.mesh.primitive_ico_sphere_add(**kwargs) @@ -95,6 +119,13 @@ def new_circle(**kwargs): return obj +def new_base_circle(**kwargs): + kwargs['location'] = kwargs.get('location', (0, 0, 0)) + bpy.ops.mesh.primitive_circle_add(**kwargs) + obj = bpy.context.active_object + return obj + + def new_empty(**kwargs): kwargs['location'] = kwargs.get('location', (0, 0, 0)) bpy.ops.object.empty_add(**kwargs) @@ -103,8 +134,81 @@ def new_empty(**kwargs): return obj -def new_line(scale=1., subdivisions=7): - obj = mesh2obj(data2mesh([[0, 0, 0], [scale, 0, 0]], [[0, 1]])) - butil.modify_mesh(obj, 'SUBSURF', levels=subdivisions, render_levels=subdivisions) +def new_plane(**kwargs): + kwargs['location'] = kwargs.get('location', (0, 0, 0)) + bpy.ops.mesh.primitive_plane_add(**kwargs) + obj = bpy.context.active_object + butil.apply_transform(obj, loc=True) + return obj + + +def new_cylinder(**kwargs): + kwargs['location'] = kwargs.get('location', (0, 0, .5)) + kwargs['depth'] = kwargs.get('depth', 1) + bpy.ops.mesh.primitive_cylinder_add(**kwargs) + obj = bpy.context.active_object + butil.apply_transform(obj, loc=True) + return obj + + +def new_base_cylinder(**kwargs): + bpy.ops.mesh.primitive_cylinder_add(**kwargs) + obj = bpy.context.active_object + butil.apply_transform(obj, loc=True) + return obj + + +def new_grid(**kwargs): + kwargs['location'] = kwargs.get('location', (0, 0, 0)) + bpy.ops.mesh.primitive_grid_add(**kwargs) + obj = bpy.context.active_object + butil.apply_transform(obj, loc=True) + return obj + + +def new_line(subdivisions=1, scale=1.): + vertices = np.stack( + [np.linspace(0, scale, subdivisions + 1), np.zeros(subdivisions + 1), np.zeros(subdivisions + 1)], -1) + edges = np.stack([np.arange(subdivisions), np.arange(1, subdivisions + 1)], -1) + obj = mesh2obj(data2mesh(vertices, edges)) + return obj + + +def join_objects(obj): + butil.select_none() + if not isinstance(obj, list): + obj = [obj] + if len(obj) == 1: + return obj[0] + bpy.context.view_layer.objects.active = obj[0] + butil.select_none() + butil.select(obj) + bpy.ops.object.join() + obj = bpy.context.active_object obj.location = 0, 0, 0 + obj.rotation_euler = 0, 0, 0 + obj.scale = 1, 1, 1 + butil.select_none() + return obj + + +def separate_loose(obj): + select_none() + objs = butil.split_object(obj) + i = np.argmax([len(o.data.vertices) for o in objs]) + obj = objs[i] + objs.remove(obj) + butil.delete(objs) return obj + + +def print3d_clean_up(obj): + bpy.ops.preferences.addon_enable(module='object_print3d_utils') + with butil.ViewportMode(obj, 'EDIT'), butil.Suppress(): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + bpy.ops.mesh.fill_holes() + bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + bpy.ops.mesh.normals_make_consistent() + bpy.ops.mesh.print3d_clean_distorted() + bpy.ops.mesh.print3d_clean_non_manifold() diff --git a/infinigen/assets/utils/reaction_diffusion.py b/infinigen/assets/utils/reaction_diffusion.py index f1165ccb2..46d7eb516 100644 --- a/infinigen/assets/utils/reaction_diffusion.py +++ b/infinigen/assets/utils/reaction_diffusion.py @@ -5,7 +5,7 @@ import math - +import bpy import bmesh import numpy as np import tqdm diff --git a/infinigen/assets/utils/shapes.py b/infinigen/assets/utils/shapes.py new file mode 100644 index 000000000..82676b1eb --- /dev/null +++ b/infinigen/assets/utils/shapes.py @@ -0,0 +1,142 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +import shapely +from shapely import Polygon, remove_repeated_points, simplify + +from shapely.ops import linemerge, orient, polygonize, unary_union, shared_paths +from trimesh.creation import triangulate_polygon + +from infinigen.assets.utils.decorate import write_co, read_co, select_faces, read_normal +from infinigen.assets.utils.object import new_circle, data2mesh, mesh2obj, join_objects +from infinigen.core.util import blender as butil + + +def is_valid_polygon(p): + if isinstance(p, Polygon) and p.area > 0 and p.is_valid: + if len(p.interiors) == 0: + return True + return False + + +def simplify_polygon(p): + with np.errstate(invalid="ignore"): + p = remove_repeated_points(simplify(p, 1e-6).normalize(), .01) + return p + + +def cut_polygon_by_line(polygon, *args): + merged = linemerge([polygon.boundary, *args]) + borders = unary_union(merged) + polygons = polygonize(borders) + return list(polygons) + + +def safe_polygon2obj(p, reversed=False, z=0): + ps = [p] if p.geom_type == 'Polygon' else p.geoms + objs_ = [] + for p in ps: + p = orient(p).segmentize(.005) + try: + obj = triangulate_polygon2obj(p) + objs_.append(obj) + except: + try: + obj = polygon2obj(p) + objs_.append(obj) + except: + pass + if len(objs_) == 0: + return None + obj = join_objects(objs_) + obj.location[-1] = z + butil.apply_transform(obj, True) + point_normal_up(obj, reversed) + return obj + + +def polygon2obj(p, reversed=False, z=0): + p = orient(p) + coords = np.array(p.exterior.coords)[:-1, :2] + obj = new_circle(vertices=len(coords)) + write_co(obj, np.concatenate([coords, np.zeros((len(coords), 1))], -1)) + objs = [obj] + for i in p.interiors: + coords = np.array(i.coords)[:-1, :2] + o = new_circle(vertices=len(coords)) + write_co(o, np.concatenate([coords, np.zeros((len(coords), 1))], -1)) + objs.append(o) + obj = join_objects(objs) + butil.modify_mesh(obj, 'WELD', merge_threshold=1e-6) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.fill() + dissolve_limited(obj) + obj.location[-1] = z + butil.apply_transform(obj, True) + point_normal_up(obj, reversed) + return obj + + +def point_normal_up(obj, reversed=False): + with butil.ViewportMode(obj, 'EDIT'): + no_z = read_normal(obj)[:, -1] + select_faces(obj, (no_z > 0) if reversed else (no_z < 0)) + bpy.ops.mesh.flip_normals() + + +def triangulate_polygon2obj(p): + vertices, faces = triangulate_polygon(orient(p)) + vertices = np.concatenate([vertices, np.zeros((len(vertices), 1))], -1) + obj = mesh2obj(data2mesh(vertices=vertices, faces=faces)) + co = read_co(obj) + co[:, -1] = 0 + write_co(obj, co) + butil.modify_mesh(obj, 'WELD', merge_threshold=1e-6) + dissolve_limited(obj) + return obj + + +def dissolve_limited(obj): + with butil.ViewportMode(obj, 'EDIT'), butil.Suppress(): + for angle_limit in reversed(.05 * .1 ** np.arange(5)): + bpy.ops.mesh.select_mode(type='FACE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.dissolve_limited(angle_limit=angle_limit) + + +def obj2polygon(obj): + co = read_co(obj)[:, :2] + p = shapely.union_all( + [shapely.make_valid(orient(shapely.Polygon(co[p.vertices]))) for p in obj.data.polygons] + ) + return shapely.make_valid(shapely.simplify(p, 1e-6)) + + +def buffer(p, distance): + with np.errstate(invalid="ignore"): + return remove_repeated_points(simplify(p.buffer(distance, join_style='mitre', cap_style='flat'), 1e-6)) + + +def segment_filter(mls, margin): + for ls in mls.geoms if mls.geom_type == 'MultiLineString' else [mls]: + coords = np.array(ls.coords) + if len(coords) < 2: + continue + elif np.any(np.linalg.norm(coords[1:] - coords[:-1], axis=-1) > margin): + return True + return False + + +def shared(s, t): + with np.errstate(invalid="ignore"): + forward, backward = shared_paths(s.boundary, t.boundary).geoms + if forward.length > 0: + return forward + elif backward.length > 0: + return backward + else: + return shapely.MultiLineString() diff --git a/infinigen/assets/utils/tag.py b/infinigen/assets/utils/tag.py deleted file mode 100644 index 03901d8f1..000000000 --- a/infinigen/assets/utils/tag.py +++ /dev/null @@ -1,149 +0,0 @@ -# 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: Yihan Wang - - -import os -import bpy -import json -import numpy as np -import infinigen.core.util.blender as butil -from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler - -class AutoTag(): - tag_dict = {} - def __init__(self): - self.tag_dict = {} - - def rephrase(self, TagName): - assert len(TagName) > 0, 'TagName is empty' - tags = TagName.split('.') - tags.sort() - name = tags[0] - for i in range(1, len(tags)): - name = name + '.' + tags[i] - return name - - def trigger_update(self): - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.object.mode_set(mode='OBJECT') - - def get_all_objs(self, obj): - objs = [obj] - for obj_child in obj.children: - objs += self.get_all_objs(obj_child) - return objs - - def add_attribute(self, obj_name, attr_name, type='FLOAT', domain='POINT', value=1.0, recursive=True): - root_obj = bpy.data.objects[obj_name] - # print(domain, attr_name, obj_name, type) - if recursive == False: - obj = root_obj - if obj.type != 'MESH': - attr = None - else: - attr = obj.data.attributes.new(attr_name, type, domain) - val = [value] * len(attr.data) - attr.data.foreach_set('value', val) - else: - objs = self.get_all_objs(root_obj) - for obj in objs: - if obj.type != 'MESH': - attr = None - else: - attr = obj.data.attributes.new(attr_name, type, domain) - val = [value] * len(attr.data) - attr.data.foreach_set('value', val) - return attr - - - # This function now only supports APPLIED OBJECTS - # PLEASE KEEP ALL THE GEOMETRY APPLIED BEFORE SCATTERING THEM ON THE TERRAIN - # PLEASE DO NOT USE BOOLEAN TAGS FOR OTHER USE - def save_tag(self, path='./MaskTag.json'): - with open(path, 'w') as f: - json.dump(self.tag_dict, f) - - def load_tag(self, path='./MaskTag.json'): - with open(path, 'r') as f: - self.tag_dict = json.load(f) - - def relabel_obj(self, root_obj): - - tag_dict = self.tag_dict - tag_name = [0] * len(tag_dict) - for name, tag_id in tag_dict.items(): - tag_name[int(tag_id) - 1] = name - - objs = self.get_all_objs(root_obj) - for obj in objs: - if obj.type != 'MESH': - continue - - attr_dict = {} - n = 0 - tag = None - for name, attr in obj.data.attributes.items(): - if 'TAG_' in name: - n = len(attr.data) - val = n * [0] - attr.data.foreach_get('value', val) - attr_dict[name[4:]] = val - - if name == 'MaskTag': - n = len(attr.data) - tag = n * [0] - attr.data.foreach_get('value', tag) - - for name in attr_dict.keys(): - obj.data.attributes.remove(obj.data.attributes['TAG_' + name]) - - assert (len(attr_dict) > 0) or (tag is not None), 'No tag for object {}'.format(obj.name) - - MaskTag = [0] * n - for i in range(n): - TagName = None - if (tag is not None) and (tag[i] > 0): - TagName = tag_name[tag[i] - 1] - for name, val in attr_dict.items(): - if val[i] == True: - if TagName is None: - TagName = name - else: - TagName = TagName + '.' + name - TagName = self.rephrase(TagName) - TagValue = tag_dict.get(TagName, -1) - if TagValue == -1: - TagValue = len(tag_dict) + 1 - tag_dict[TagName] = TagValue - tag_name.append(TagName) - MaskTag[i] = TagValue - - if tag is None: - MaskTag_attr = self.add_attribute(obj.name, 'MaskTag', type='INT', value=1, recursive=False) - else: - MaskTag_attr = obj.data.attributes['MaskTag'] - - MaskTag_attr.data.foreach_set('value', MaskTag) - self.tag_dict = tag_dict - - return root_obj - - -tag_system = AutoTag() - -def tag_object(obj, name=""): - if name != "": - name = 'TAG_' + name - tag_system.add_attribute(obj.name, name, type='BOOLEAN', value=True) - tag_system.relabel_obj(obj) - - -def tag_nodegroup(nw, input_node, name): - name = 'TAG_' + name - store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, - input_kwargs={'Geometry': input_node, 'Name': name, 'Value': True}, - attrs={'domain': 'POINT', 'data_type': 'BOOLEAN'}) - return store_named_attribute - \ No newline at end of file diff --git a/infinigen/assets/utils/uv.py b/infinigen/assets/utils/uv.py new file mode 100644 index 000000000..72e8090a6 --- /dev/null +++ b/infinigen/assets/utils/uv.py @@ -0,0 +1,201 @@ +# 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: Lingjie Mei +from collections.abc import Iterable + +import bpy +import bmesh +import numpy as np +from sklearn.linear_model import LinearRegression + +from infinigen.assets.materials import common +from infinigen.assets.utils.decorate import ( + read_co, read_edges, read_loop_edges, read_loop_starts, + read_loop_totals, read_loop_vertices, read_normal, read_uv, select_faces, write_uv, +) +from infinigen.core.util import blender as butil + +import logging + +logger = logging.getLogger(__name__) + + +def face_corner2faces(obj): + loop_starts = read_loop_starts(obj) + faces = np.zeros(len(obj.data.loops), dtype=int) + faces[loop_starts] = 1 + faces = np.cumsum(faces) - 1 + return faces + + +def unwrap_faces(obj, selection=None): + if isinstance(obj, Iterable): + for o in obj: + unwrap_faces(o, selection) + return + butil.select_none() + selection = common.get_selection(obj, selection) + if len(obj.data.uv_layers) == 0: + smart = True + else: + uv = read_uv(obj)[selection.astype(bool)[face_corner2faces(obj)]] + smart = (np.isnan(uv) | (np.abs(uv) < .1)).sum() / uv.size > .5 + butil.select_none() + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(type="FACE") + select_faces(obj, selection) + if smart: + bpy.ops.uv.smart_project() + else: + bpy.ops.uv.unwrap() + + +def str2vec(axis): + if not isinstance(axis, str): + return axis + match axis[-1].lower(): + case 'x': + vec = 1, 0, 0 + case 'y': + vec = 0, 1, 0 + case 'z': + vec = 0, 0, 1 + case 'u': + vec = -1, 0, 0 + case 'v': + vec = 0, -1, 0 + case 'w': + vec = 0, 0, -1 + case _: + raise NotImplementedError + vec = np.array(vec) + if axis[0] == '-': + vec = -vec + return vec + + +def compute_uv_direction(obj, x='x', y='y', selection=None): + ensure_uv(obj, selection) + x, y = str2vec(x), str2vec(y) + co = read_co(obj) + edges = read_edges(obj) + loop_vertices = read_loop_vertices(obj) + loop_edges = read_loop_edges(obj) + uv = read_uv(obj) + if selection is None: + selection = np.full(len(uv), True) + selection = selection.astype(bool) + loop_starts = read_loop_starts(obj) + loop_totals = read_loop_totals(obj) + next_vertices = edges[loop_edges].sum(1) - loop_vertices + next_loops = np.arange(len(uv)) + 1 + next_loops[loop_starts + loop_totals - 1] -= loop_totals + uv_diff = uv[next_loops] - uv + co_diff = co[next_vertices] - co[loop_vertices] + lr = LinearRegression() + lr.fit(co_diff[selection], uv_diff[selection]) + lr.coef_[lr.coef_ > 1e3] = 0 + axes = lr.predict(np.stack([x, y])) + axes = axes / (np.linalg.norm(axes, axis=-1) + 1e-6) + pred = uv @ axes.T + pred_sel = pred[selection] + x_min, x_max = np.min(pred_sel[:, 0]), np.max(pred_sel[:, 0]) + y_min, y_max = np.min(pred_sel[:, 1]), np.max(pred_sel[:, 1]) + if x_max - x_min > y_max - y_min: + scale = 1 / (x_max - x_min + 1e-4) + mid = (y_max + y_min) / 2 + pred = np.stack([(pred[:, 0] - x_min) * scale, (pred[:, 1] - mid) * scale + .5], -1) + bbox = 0, 1, .5 - .5 * (y_max - y_min) * scale, .5 + .5 * (y_max - y_min) * scale + else: + scale = 1 / (y_max - y_min + 1e-4) + mid = (x_max + x_min) / 2 + pred = np.stack([(pred[:, 0] - mid) * scale + .5, (pred[:, 1] - y_min) * scale], -1) + bbox = .5 - .5 * (x_max - x_min) * scale, .5 + .5 * (x_max - x_min) * scale, 0, 1 + new_uv = np.where(selection[:, np.newaxis], pred, uv) + write_uv(obj, new_uv) + return bbox + + +def max_bbox(bboxes): + return min(b[0] for b in bboxes), max(b[1] for b in bboxes), min(b[2] for b in bboxes), max( + b[3] for b in bboxes + ) + + +def wrap_sides(obj, surface, axes, xs, ys, groupings=None, selection=None, **kwargs): + fc2f = face_corner2faces(obj) + axes = np.array([str2vec(axis) for axis in axes]) + faces = np.argmax(read_normal(obj) @ axes.T, -1) + selection = common.get_selection(obj, selection) + faces = np.where(selection, faces, -1) + bboxes, selections = [], [] + for i in range(len(axes)): + selected = faces == i + selections.append(selected) + unwrap_faces(obj, selected) + bboxes.append(compute_uv_direction(obj, str2vec(xs[i]), str2vec(ys[i]), selected[fc2f])) + if groupings is None: + groupings = [[i] for i in range(len(axes))] + for indices in groupings: + selected = sum(selections[i] for i in indices) + try: + surface.apply(obj, selected, bbox=max_bbox([bboxes[i] for i in indices]), **kwargs) + except TypeError: + logger.debug(f'apply() for {surface=} with kwarg bbox failed, trying again without') + surface.apply(obj, selected, **kwargs) + + +def wrap_front_back(obj, surface, shared=True, **kwargs): + wrap_sides(obj, surface, 'vy', 'xu', 'zz', [[0, 1]] if shared else None, **kwargs) + +def wrap_top_bottom(obj, surface, shared=True, **kwargs): + wrap_sides(obj, surface, 'zw', 'xu', 'yy', [[0, 1]] if shared else None, **kwargs) + + +def wrap_front_back_side(obj, surface, shared=True, **kwargs): + wrap_sides(obj, surface, 'vuy', 'xyu', 'zzz', [[0, 2], [1]] if shared else None, **kwargs) + + +def wrap_four_sides(obj, surface, shared=True, **kwargs): + wrap_sides(obj, surface, 'vxyu', 'xyuv', 'zzzz', [[0, 2], [1, 3]] if shared else None, **kwargs) + + +def wrap_six_sides(obj, surface, shared=True, **kwargs): + wrap_sides( + obj, surface, 'vxyuzw', 'xyuvxx', 'zzzzyv', [[0, 2], [1, 3], [4, 5]] if shared else None, + **kwargs + ) + + +def unwrap_normal(obj, selection=None, axis=None, axis_=None): + ensure_uv(obj) + normal = read_normal(obj) + loop_vertices = read_loop_vertices(obj) + co = read_co(obj) + loop_totals = read_loop_totals(obj) + normal = normal[np.arange(len(obj.data.polygons)).repeat(loop_totals)] + selection = common.get_selection(obj, selection).repeat(loop_totals) + if axis is not None: + axis = str2vec(axis) + axis_ = np.cross(normal, axis) + axis = axis[np.newaxis, :] + elif axis_ is not None: + axis_ = str2vec(axis_) + axis = np.cross(normal, axis_) + axis_ = axis_[np.newaxis, :] + else: + axis = np.zeros(3) + i = np.argmin(np.abs(normal)[selection.astype(bool)].sum(0)) + axis[i] = 1 + axis = axis[np.newaxis, :] - np.inner(axis, normal)[:, np.newaxis] * normal + axis /= np.maximum(np.linalg.norm(axis, axis=-1, keepdims=True), 1e-4) + axis_ = np.cross(normal, axis) + uv = np.stack([(co[loop_vertices] * axis).sum(1), (co[loop_vertices] * axis_).sum(1)], -1) + uv = np.where(selection[:, np.newaxis], uv, read_uv(obj)) + write_uv(obj, uv) + + +def ensure_uv(obj, selection=None): + if len(obj.data.uv_layers) == 0: + unwrap_faces(obj, selection) diff --git a/infinigen/assets/wall_decorations/__init__.py b/infinigen/assets/wall_decorations/__init__.py new file mode 100644 index 000000000..e437b4f72 --- /dev/null +++ b/infinigen/assets/wall_decorations/__init__.py @@ -0,0 +1,8 @@ +# 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: Lingjie Mei +from .balloon import BalloonFactory +from .wall_art import WallArtFactory, MirrorFactory +from .wall_shelf import WallShelfFactory +from .range_hood import RangeHoodFactory diff --git a/infinigen/assets/wall_decorations/balloon.py b/infinigen/assets/wall_decorations/balloon.py new file mode 100644 index 000000000..bb8cc032f --- /dev/null +++ b/infinigen/assets/wall_decorations/balloon.py @@ -0,0 +1,78 @@ +# 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: Lingjie Mei +import bpy + +import numpy as np +from numpy.random import uniform + +from infinigen.assets.scatters import clothes +from infinigen.assets.utils.decorate import subdivide_edge_ring, subsurf +from infinigen.assets.utils.draw import remesh_fill +from infinigen.assets.utils.misc import generate_text +from infinigen.assets.utils.object import new_bbox +from infinigen.core.placement.factory import AssetFactory + +from infinigen.core.util import blender as butil +from infinigen.core.util.math import FixedSeed +from infinigen.assets.material_assignments import AssetList + + +class BalloonFactory(AssetFactory): + alpha = .8 + + def __init__(self, factory_seed, coarse=False): + super(BalloonFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.thickness = uniform(.06, .1) + material_assignments = AssetList['BalloonFactory']() + self.surface = material_assignments['surface'].assign_material() + self.rel_scale = uniform(.2, .3) * 4 + self.displace = uniform(.02, .04) + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + bpy.ops.object.text_add() + obj = bpy.context.active_object + + with butil.ViewportMode(obj, 'EDIT'): + for _ in 'Text': + bpy.ops.font.delete(type='PREVIOUS_OR_SELECTION') + text = generate_text().upper() + bpy.ops.font.text_insert(text=text) + with butil.SelectObjects(obj): + bpy.ops.object.convert(target='MESH') + obj = bpy.context.active_object + parent = new_bbox( + -self.thickness / 2, self.thickness / 2, 0, self.rel_scale * len(text) * self.alpha, + 0, self.rel_scale * self.alpha + ) + obj.parent = parent + return parent + + def create_asset(self, i, placeholder, **params) -> bpy.types.Object: + obj = placeholder.children[0] + obj.parent = None + remesh_fill(obj, .02) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=self.thickness, offset=.5) + subdivide_edge_ring(obj, 8, (0, 0, 1)) + + clothes.cloth_sim( + obj, + tension_stiffness=uniform(0, 5), + gravity=0, + use_pressure=True, + uniform_pressure_force=uniform(10, 20), + vertex_group_mass='pin' + ) + + subsurf(obj, 1) + obj.scale = [self.rel_scale] * 3 + obj.rotation_euler = np.pi / 2, 0, np.pi / 2 + butil.apply_transform(obj, True) + butil.modify_mesh(obj, 'DISPLACE', strength=self.displace) + butil.modify_mesh(obj, 'SMOOTH', iterations=5) + return obj + + def finalize_assets(self, assets): + self.surface.apply(assets) diff --git a/infinigen/assets/wall_decorations/range_hood.py b/infinigen/assets/wall_decorations/range_hood.py new file mode 100644 index 000000000..2cbbfd7b8 --- /dev/null +++ b/infinigen/assets/wall_decorations/range_hood.py @@ -0,0 +1,191 @@ +# 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: Yiming Zuo + +import bpy +import bpy +import mathutils +import numpy as np +from numpy.random import uniform, normal, randint +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.color import color_category +from infinigen.core import surface + +import infinigen.core.util.blender as butil +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory + +from infinigen.assets.table_decorations.utils import nodegroup_lofting_poly +from infinigen.assets.tables.table_utils import nodegroup_n_gon_profile +from infinigen.assets.material_assignments import AssetList + + +class RangeHoodFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, dimensions=None): + super(RangeHoodFactory, self).__init__(factory_seed, coarse=coarse) + + self.dimensions = dimensions + + with FixedSeed(factory_seed): + self.params = self.sample_parameters(dimensions) + self.surface, self.scratch, self.edge_wear = self.get_material_params() + + + def get_material_params(self): + material_assignments = AssetList['RangeHoodFactory']() + surface = material_assignments['surface'].assign_material() + + scratch_prob, edge_wear_prob = material_assignments['wear_tear_prob'] + scratch, edge_wear = material_assignments['wear_tear'] + + is_scratch = np.random.uniform() < scratch_prob + is_edge_wear = np.random.uniform() < edge_wear_prob + if not is_scratch: + scratch = None + + if not is_edge_wear: + edge_wear = None + + return surface, scratch, edge_wear + + @staticmethod + def sample_parameters(dimensions): + # all in meters + if dimensions is None: + x = 0.55 + y = 0.75 + z = 1.0 + dimensions = (x, y, z) + + x, y, z = dimensions + + height_1 = uniform(0.05, 0.07) + height_2 = uniform(0.1, 0.3) + scale_2 = uniform(0.25, 0.4) + + parameters = { + 'Height_total': z, + 'Width': y, + 'Depth': x, + 'Height_1': height_1, + 'Scale_2': scale_2, + 'Height_2': height_2 + } + + return parameters + + def create_asset(self, **params): + + bpy.ops.mesh.primitive_plane_add( + size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) + obj = bpy.context.active_object + + surface.add_geomod(obj, geometry_generate_hood, apply=True, input_kwargs=self.params) + butil.modify_mesh(obj, 'SOLIDIFY', apply=True, thickness=.002) + butil.modify_mesh(obj, 'SUBSURF', apply=True, levels=1, render_levels=1) + + return obj + + def finalize_assets(self, assets): + self.surface.apply(assets) + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + + + +def geometry_generate_hood(nw: NodeWrangler, **kwargs): + # Code generated using version 2.6.4 of the node_transpiler + + generatetabletop = nw.new_node(geometry_range_hood().name, + input_kwargs={'Resolution': 64, + 'Height_total': kwargs['Height_total'], + 'Width': kwargs['Width'], + 'Depth': kwargs['Depth'], + 'Height_1': kwargs['Height_1'], + 'Scale_2': kwargs['Scale_2'], + 'Height_2': kwargs['Height_2'], + }) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': generatetabletop}, attrs={'is_active_output': True}) + +@node_utils.to_nodegroup('geometry_range_hood', singleton=False, type='GeometryNodeTree') +def geometry_range_hood(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, + expose_input=[('NodeSocketInt', 'Resolution', 128), + ('NodeSocketFloat', 'Height_total', 0.0000), + ('NodeSocketFloat', 'Width', 0.0000), + ('NodeSocketFloat', 'Depth', 0.0000), + ('NodeSocketFloat', 'Profile Fillet Ratio', 0.0100), + ('NodeSocketFloat', 'Height_1', 0.0000), + ('NodeSocketFloat', 'Scale_2', 0.0000), + ('NodeSocketFloat', 'Height_2', 0.3000)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: 1.4140}, attrs={'operation': 'MULTIPLY'}) + + divide = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Depth"], 1: group_input.outputs["Width"]}, + attrs={'operation': 'DIVIDE'}) + + ngonprofile = nw.new_node(nodegroup_n_gon_profile().name, + input_kwargs={'Profile Width': multiply, 'Profile Aspect Ratio': divide, 'Profile Fillet Ratio': group_input.outputs["Profile Fillet Ratio"]}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': ngonprofile, 'Count': group_input.outputs["Resolution"]}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"]}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_1}) + + transform_geometry = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': resample_curve, 'Translation': combine_xyz}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Height_1"]}) + + transform_geometry_1 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_geometry, 'Translation': combine_xyz_1}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': group_input.outputs["Height_2"]}) + + transform_geometry_2 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_geometry, 'Translation': combine_xyz_2, 'Scale': group_input.outputs["Scale_2"]}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Height_total"], 1: group_input.outputs["Height_2"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': subtract}) + + transform_geometry_3 = nw.new_node(Nodes.Transform, input_kwargs={'Geometry': transform_geometry_2, 'Translation': combine_xyz_3}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_geometry_3, transform_geometry_2, transform_geometry_1, transform_geometry]}) + + lofting_poly = nw.new_node(nodegroup_lofting_poly().name, + input_kwargs={'Profile Curves': join_geometry, 'U Resolution': group_input.outputs["Resolution"], 'V Resolution': group_input.outputs["Resolution"]}) + + delete_geometry = nw.new_node(Nodes.DeleteGeometry, + input_kwargs={'Geometry': lofting_poly.outputs["Geometry"], 'Selection': lofting_poly.outputs["Top"]}) + + grid = nw.new_node(Nodes.MeshGrid, + input_kwargs={'Size X': group_input.outputs["Width"], 'Size Y': group_input.outputs["Depth"], 'Vertices X': group_input.outputs["Resolution"], 'Vertices Y': group_input.outputs["Resolution"]}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Depth"]}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_2}) + + transform_geometry_4 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': grid.outputs["Mesh"], 'Translation': combine_xyz_4, 'Rotation': (-0.0698, 0.0000, 0.0000), 'Scale': (0.9800, 0.9800, 1.0000)}) + + transform_geometry_5 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': transform_geometry_4, 'Rotation': (0.1047, 0.0000, 0.0000), 'Scale': (0.9500, 0.9700, 1.0000)}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [delete_geometry, transform_geometry_5]}) + + transform_geometry_6 = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': join_geometry_1, 'Rotation': (0.0, 0.0000, -np.pi/2)}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': transform_geometry_6}, attrs={'is_active_output': True}) + diff --git a/infinigen/assets/wall_decorations/skirting_board.py b/infinigen/assets/wall_decorations/skirting_board.py new file mode 100644 index 000000000..ce85f9f30 --- /dev/null +++ b/infinigen/assets/wall_decorations/skirting_board.py @@ -0,0 +1,286 @@ +# 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: Yiming Zuo, Lingjie Mei, Alexander Raistrick + +import logging + +import bmesh +import bpy +import mathutils +import numpy as np +from numpy.random import uniform, normal, randint, choice, randint +from tqdm import tqdm + +from infinigen.assets.creatures.util.geometry.curve import Curve +from infinigen.assets.utils.decorate import ( + read_co, read_edge_length, remove_edges, read_edge_direction, read_edges, + remove_duplicate_edges, +) +from infinigen.assets.utils.draw import bezier_curve +from infinigen.assets.utils.object import new_plane, join_objects +from infinigen.core.constraints.example_solver.room import constants +from infinigen.core.constraints.example_solver.room.constants import WALL_HEIGHT, DOOR_WIDTH, WALL_THICKNESS +from infinigen.core.constraints.example_solver.room.types import get_room_level +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.surface import write_attr_data +from infinigen.core.util.color import color_category +from infinigen.core import surface + +import infinigen.core.util.blender as butil + +from infinigen.core.util.math import FixedSeed +from infinigen.core.placement.factory import AssetFactory +from infinigen.assets.materials.plastics import plastic_rough + +import shapely +from shapely.geometry import Polygon, MultiPolygon +from shapely import affinity +from shapely.ops import unary_union + +from infinigen.assets.utils.shapes import polygon2obj, obj2polygon +from infinigen.core import tagging, tags as t +from shapely.plotting import plot_polygon + +logger = logging.getLogger(__name__) + + +@node_utils.to_nodegroup('nodegroup_make_skirting_board_001', singleton=False, type='GeometryNodeTree') +def nodegroup_make_skirting_board(nw: NodeWrangler, control_points): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node( + Nodes.GroupInput, + expose_input=[('NodeSocketCollection', 'Parent', None), + ('NodeSocketFloat', 'Thickness', 0.0300), + ('NodeSocketFloat', 'Height', 0.1500), + ('NodeSocketFloatDistance', 'Resolution', 0.0050), + ('NodeSocketBool', 'Is Ceiling', False)] + ) + + collection_info = nw.new_node(Nodes.CollectionInfo, input_kwargs={'Collection': group_input.outputs["Parent"]}) + + mesh = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': collection_info}) + + + quadrilateral = nw.new_node( + 'GeometryNodeCurvePrimitiveQuadrilateral', + input_kwargs={'Width': group_input.outputs["Thickness"], 'Height': group_input.outputs["Height"]} + ) + + multiply = nw.new_node( + Nodes.Math, input_kwargs={0: group_input.outputs["Thickness"]}, attrs={'operation': 'MULTIPLY'} + ) + + multiply_1 = nw.new_node( + Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: -0.5000}, attrs={'operation': 'MULTIPLY'} + ) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': multiply_1}) + + transform_geometry = nw.new_node( + Nodes.Transform, input_kwargs={'Geometry': quadrilateral, 'Translation': combine_xyz} + ) + + resample_curve_1 = nw.new_node( + Nodes.ResampleCurve, + input_kwargs={'Curve': transform_geometry, 'Count': 220, 'Length': group_input.outputs["Resolution"]}, + attrs={'mode': 'LENGTH'} + ) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + greater_than = nw.new_node(Nodes.Compare, input_kwargs={0: separate_xyz.outputs["X"]}) + + multiply_2 = nw.new_node( + Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: -1.0000}, attrs={'operation': 'MULTIPLY'} + ) + + map_range = nw.new_node(Nodes.MapRange, input_kwargs={'Value': separate_xyz.outputs["Y"], 1: multiply_2, 2: 0.0000}) + + float_curve = nw.new_node(Nodes.FloatCurve, input_kwargs={'Value': map_range.outputs["Result"]}) + node_utils.assign_curve(float_curve.mapping.curves[0], control_points) + + multiply_3 = nw.new_node( + Nodes.Math, + input_kwargs={0: float_curve, 1: group_input.outputs["Thickness"]}, + attrs={'operation': 'MULTIPLY'} + ) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_3, 'Y': separate_xyz.outputs["Y"]}) + + set_position = nw.new_node( + Nodes.SetPosition, + input_kwargs={'Geometry': resample_curve_1, 'Selection': greater_than, 'Position': combine_xyz_1} + ) + + switch = nw.new_node( + Nodes.Switch, + input_kwargs={ + 0: group_input.outputs["Is Ceiling"], 8: (-1.0000, 1.0000, 1.0000), 9: (-1.0000, -1.0000, -1.0000) + }, + attrs={'input_type': 'VECTOR'} + ) + + transform_geometry_1 = nw.new_node( + Nodes.Transform, input_kwargs={'Geometry': set_position, 'Scale': switch.outputs[3]} + ) + + curve_to_mesh_1 = nw.new_node( + Nodes.CurveToMesh, input_kwargs={'Curve': mesh, 'Profile Curve': transform_geometry_1, 'Fill Caps': True} + ) + + set_shade_smooth = nw.new_node( + Nodes.SetShadeSmooth, input_kwargs={'Geometry': curve_to_mesh_1, 'Shade Smooth': False} + ) + + group_output = nw.new_node( + Nodes.GroupOutput, input_kwargs={'Geometry': set_shade_smooth}, attrs={'is_active_output': True} + ) + + +def apply_skirtingboard(nw: NodeWrangler, contour, is_ceiling=False, seed=None, thickness=.02): + # Code generated using version 2.6.5 of the node_transpiler + + # TODO: randomize style / size / materials + if seed is None: + seed = randint(0, 10000) + with FixedSeed(seed): + thickness = uniform(.02, .05) + height = uniform(0.08, 0.15) + color = color_category('white') + roughness = uniform(0.5, 1.0) + n_peaks = randint(1, 4) + start_y = uniform(0.0, 0.5) + mid_x = uniform(0.2, 0.8) + peak_xs = np.sort(uniform(0.0, mid_x, size=n_peaks)) + peak_ys = np.sort(uniform(start_y, 1.0, size=n_peaks)) + control_points = [(0.0000, start_y)] + control_points += [(x, y) for x, y in zip(peak_xs, peak_ys)] + control_points += [(mid_x, 1.0000), + (1.0000, 1.0000)] + + makeskirtingboard = nw.new_node( + nodegroup_make_skirting_board(control_points=control_points).name, + input_kwargs={ + 'Parent': contour, + 'Resolution': 0.0010, + 'Thickness': thickness, + 'Height': height, + 'Is Ceiling': is_ceiling + } + ) + + makeskirtingboard = nw.new_node( + Nodes.SetMaterial, + input_kwargs={ + 'Geometry': makeskirtingboard, 'Material': surface.shaderfunc_to_material( + plastic_rough.shader_rough_plastic, base_color=color, roughness=roughness + ) + } + ) + + group_output = nw.new_node( + Nodes.GroupOutput, input_kwargs={'Geometry': makeskirtingboard}, attrs={'is_active_output': True} + ) + + +def make_skirtingboard_contour(objs: list[bpy.types.Object], tag: t.Subpart): + # make the outline curve + + assert len(objs) > 0 + + objs = [ + tagging.extract_tagged_faces(o, {tag, t.Subpart.Visible}, nonempty=True) + for o in list(objs) + ] + + all_polys = [] + all_zs = [] + for floor_pieces in objs: + all_polys.append(obj2polygon(floor_pieces)) + all_zs.append(read_co(floor_pieces)[:, -1] + floor_pieces.location[-1]) + + floor_z = np.mean(np.concatenate(all_zs)) + + boundary = unary_union(all_polys).buffer(.05, join_style='mitre').buffer(-.05, join_style='mitre') + + if isinstance(boundary, Polygon): + boundaries = [boundary] + else: + boundaries = boundary.geoms + + contours = [] + + for b in boundaries: + lr = b.exterior + o = linear_ring2curve(lr) + contours.append(o) + o.location[-1] += floor_z + butil.apply_transform(o, True) + for lr in b.interiors: + o = linear_ring2curve(lr, True) + contours.append(o) + o.location[-1] += floor_z + butil.apply_transform(o, True) + butil.delete(objs) + return contours + + +def make_skirting_board(objs, tag, joined=True): + if joined: + seqs = list([o for o in objs if get_room_level(o.name.split('.')[0]) == i] for i in [0]) + else: + seqs = [[o] for o in objs] + + for s in seqs: + logger.debug(f'make_skirting_board for {len(objs)=} {tag=}') + + try: + contours = make_skirtingboard_contour(s, tag) + except shapely.errors.GEOSException as e: + logger.warning(f'make_skirting_board({objs=}, {tag=}) failed with {e}, skipping') + return + + obj = new_plane() + obj.name = "skirtingboard_" + tag.value + + col = butil.put_in_collection(contours, 'contour') + kwargs = { + 'contour': col, + 'seed': np.random.randint(1e7), + 'is_ceiling': tag == t.Subpart.Ceiling + } + surface.add_geomod(obj, apply_skirtingboard, apply=True, input_kwargs=kwargs) + + portal_cutters = butil.get_collection('placeholders:portal_cutters').objects + for p in portal_cutters: + if p.name.startswith('entrance') and int(p.location[-1] / WALL_HEIGHT - 1 / 2) == 0: + p.location[-1] -= WALL_HEIGHT / 2 + butil.modify_mesh( + obj, 'BOOLEAN', object=p, operation='DIFFERENCE', use_self=True, + use_hole_tolerant=True + ) + p.location[-1] += WALL_HEIGHT / 2 + butil.delete_collection(col) + col = butil.get_collection("skirting") + butil.put_in_collection(obj, col) + + +def linear_ring2curve(ring, reversed=False): + coords = ring.coords + if shapely.is_ccw(ring) == reversed: + coords = coords[::-1] + coords = np.array(coords) + lengths = np.linalg.norm(coords[:-1] - coords[1:], axis=-1) + invalid = np.sort(np.nonzero((np.abs(lengths - WALL_THICKNESS) < .02) | (np.abs(lengths - DOOR_WIDTH) < .02))[0]) + ranges = -1, *invalid, len(coords) + curves = [] + for l, r in zip(ranges[:-1], ranges[1:]): + x, y = np.array(coords[l + 1:r + 1]).T + if len(x) > 1: + curves.append(bezier_curve((x, y, 0), list(np.arange(len(x))), 1, False)) + return join_objects(curves) diff --git a/infinigen/assets/wall_decorations/wall_art.py b/infinigen/assets/wall_decorations/wall_art.py new file mode 100644 index 000000000..8e2cc7497 --- /dev/null +++ b/infinigen/assets/wall_decorations/wall_art.py @@ -0,0 +1,90 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +from numpy.random import uniform + +from infinigen.assets.materials.art import Art +from infinigen.assets.utils.object import join_objects, new_plane, new_bbox +from infinigen.assets.utils.uv import wrap_sides +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform +from infinigen.core.util import blender as butil +from infinigen.assets.material_assignments import AssetList + + +class WallArtFactory(AssetFactory): + + def __init__(self, factory_seed, coarse=False): + super(WallArtFactory, self).__init__(factory_seed, coarse) + with FixedSeed(self.factory_seed): + self.width = log_uniform(.4, 2) + self.height = log_uniform(.4, 2) + self.thickness = uniform(.02, .05) + self.depth = uniform(.01, .02) + self.frame_bevel_segments = np.random.choice([0, 1, 4]) + self.frame_bevel_width = uniform(self.depth / 4, self.depth / 2) + self.material_assignments = AssetList['WallArtFactory']() + self.assign_materials() + + def assign_materials(self): + # self.surface = Art(self.factory_seed) + assignments = self.material_assignments + self.surface = assignments['surface'].assign_material() + if self.surface == Art: + self.surface = self.surface(self.factory_seed) + self.frame_surface = assignments['frame'].assign_material() + is_scratch = uniform() < assignments['wear_tear_prob'][0] + is_edge_wear = uniform() < assignments['wear_tear_prob'][1] + self.scratch = assignments['wear_tear'][0] if is_scratch else None + self.edge_wear = assignments['wear_tear'][1] if is_edge_wear else None + + + def create_placeholder(self, **params): + return new_bbox( + -0.01, + 0.15, + -self.width / 2 - self.thickness, + self.width / 2 + self.thickness, + -self.height / 2 - self.thickness, + self.height / 2 + self.thickness, + ) + + def create_asset(self, placeholder, **params) -> bpy.types.Object: + obj = new_plane() + obj.scale = self.width / 2, self.height / 2, 1 + obj.rotation_euler = np.pi / 2, 0, np.pi / 2 + butil.apply_transform(obj, True) + + frame = deep_clone_obj(obj) + wrap_sides(obj, self.surface, 'x', 'y', 'z') + butil.select_none() + with butil.ViewportMode(frame, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.delete(type='ONLY_FACE') + butil.modify_mesh(frame, 'SOLIDIFY', thickness=self.thickness, offset=1) + with butil.ViewportMode(frame, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bridge_edge_loops() + butil.modify_mesh(frame, 'SOLIDIFY', thickness=self.depth, offset=1) + if self.frame_bevel_segments > 0: + butil.modify_mesh(frame, 'BEVEL', width=self.frame_bevel_width, segments=self.frame_bevel_segments) + self.frame_surface.apply(frame) + obj = join_objects([obj, frame]) + return obj + + def finalize_assets(self, assets): + if self.scratch: + self.scratch.apply(assets) + if self.edge_wear: + self.edge_wear.apply(assets) + +class MirrorFactory(WallArtFactory): + def __init__(self, factory_seed, coarse=False): + super(MirrorFactory, self).__init__(factory_seed, coarse) + self.material_assignments = AssetList['MirrorFactory']() + self.assign_materials() diff --git a/infinigen/assets/wall_decorations/wall_shelf.py b/infinigen/assets/wall_decorations/wall_shelf.py new file mode 100644 index 000000000..4b05b0e3b --- /dev/null +++ b/infinigen/assets/wall_decorations/wall_shelf.py @@ -0,0 +1,126 @@ +# 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: Lingjie Mei +import bpy +import numpy as np +import shapely +from numpy.random import uniform +import shapely.affinity + +from infinigen.assets.materials import metal, plastic +from infinigen.assets.materials.woods import wood +from infinigen.assets.utils.decorate import read_edge_direction, select_edges, read_edge_center +from infinigen.assets.utils.object import new_bbox, new_bbox_2d, join_objects +from infinigen.assets.utils.shapes import polygon2obj +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.surface import write_attr_data +from infinigen.core import tagging as t +from infinigen.core.tags import Subpart + +from infinigen.core.util import blender as butil +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.random import random_general as rg, log_uniform + + +class WallShelfFactory(AssetFactory): + support_sides_ = 'weighted_choice', (.5, 'none'), (1, 'bottom'), (1, 'top'), (1.5, 'both') + support_margins = 'weighted_choice', (2, 0), (1, ('uniform', .0, .2)) + support_ratios = 'weighted_choice', (2, 1), (1, ('uniform', .5, .9)) + support_alphas = 'weighted_choice', (1, 1), ( + 1, ('weighted_choice', (1, ('log_uniform', .4, .7)), (2, ('log_uniform', 1.5, 3)), (1, 10))) + support_joins = 'mitre', 'round', 'bevel' + plate_bevels = 'weighted_choice', (1, 'none'), (1, 'front'), (1, 'side') + + plate_surfaces = 'weighted_choice', (2, wood), (1, metal) + support_surfaces = 'weighted_choice', (2, metal), (1, wood), (2, plastic) + + def __init__(self, factory_seed, coarse=False): + super(WallShelfFactory, self).__init__(factory_seed, coarse) + self.support_side = rg(self.support_sides_) + self.support_margin = rg(self.support_margins) + if self.support_margin == 0: + n_support = np.random.choice([2, 3, 4], p=[.7, .2, .1]) + else: + n_support = np.random.choice([2, 3], p=[.8, .2]) + self.support_locs = np.linspace(-.5 + self.support_margin, .5 - self.support_margin, n_support) + self.length = log_uniform(.3, .8) + self.width = log_uniform(.1, .2) + match self.support_side: + case 'none': + self.thickness = log_uniform(.03, .08) + case _: + self.thickness = log_uniform(.01, .05) + self.support_width = log_uniform(.01, .015) + self.support_thickness = self.support_width * log_uniform(.4, 1.) + self.support_length = self.width * uniform(.7, 1.1) + self.plate_bevel = rg(self.plate_bevels) + self.support_join = np.random.choice(self.support_joins) + self.plate_surface = rg(self.plate_surfaces) + self.support_surface = rg(self.support_surfaces) + + def create_placeholder(self, **kwargs) -> bpy.types.Object: + box = new_bbox(0, self.width, -self.length / 2, self.length / 2, -self.support_length, self.support_length) + plane = new_bbox_2d(0, self.width, -self.length / 2, self.length / 2, self.thickness / 2) + write_attr_data(plane, f'{t.PREFIX}{Subpart.SupportSurface.value}', np.ones(1).astype(bool), 'INT', 'FACE') + return join_objects([box, plane]) + + def create_asset(self, **params) -> bpy.types.Object: + obj = self.make_plate() + self.plate_surface.apply(obj) + if self.support_side != 'none': + support = self.make_support() + supports = [support] + [deep_clone_obj(support) for _ in range(len(self.support_locs) - 1)] + for s, l in zip(supports, self.support_locs): + s.location[1] = self.length * l + self.support_surface.apply(supports) + obj = join_objects([obj] + supports) + return obj + + def make_plate(self): + obj = new_bbox(0, self.width, -self.length / 2, self.length / 2, -self.thickness / 2, self.thickness / 2) + c = read_edge_center(obj) + d = read_edge_direction(obj) + front = (np.abs(d[:, 1]) > .5) & (c[:, 0] > .1) + side = np.abs(d[:, 0]) > .5 + match self.plate_bevel: + case 'front': + selection = front + case 'side': + selection = front + side + case _: + selection = np.zeros_like(front) + with butil.ViewportMode(obj, 'EDIT'): + select_edges(obj, selection) + bpy.ops.mesh.bevel(offset=uniform(.3, .5) * self.thickness, segments=np.random.randint(4, 9)) + return obj + + def make_support_contour(self): + l = shapely.LineString(np.array([(1, 0), (0, 0), (0, 1)]) * self.support_length) + theta = np.linspace(0, np.pi / 2, 31) + alpha = rg(self.support_alphas) + r = 1 / ((np.cos(theta) + 1e-6) ** alpha + (np.sin(theta) + 1e-6) ** alpha) ** (1 / alpha) + xy = r[:, np.newaxis] * np.stack([np.cos(theta), np.sin(theta)], -1) + d = shapely.LineString(xy * self.support_length * rg(self.support_ratios)) + return shapely.union(l, d) + + def make_support(self): + lines = [] + if self.support_side in ['top', 'both']: + lines.append(self.make_support_contour()) + if self.support_side in ['bottom', 'both']: + lines.append(shapely.affinity.scale(self.make_support_contour(), 1, -1, 1, (0, 0, 0))) + + contour = shapely.union_all(lines).buffer(self.support_thickness / 2, join_style=self.support_join) + obj = polygon2obj(contour) + obj.rotation_euler[0] = np.pi / 2 + obj.location = self.support_thickness / 2, -self.support_width / 2, 0 + butil.apply_transform(obj, True) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_region_move( + TRANSFORM_OT_translate={ + 'value': (0, self.support_width, 0) + } + ) + return obj diff --git a/infinigen/assets/weather/cloud/generate.py b/infinigen/assets/weather/cloud/generate.py index 27dfe2aea..f132f01b7 100644 --- a/infinigen/assets/weather/cloud/generate.py +++ b/infinigen/assets/weather/cloud/generate.py @@ -20,7 +20,7 @@ from infinigen.core.util.random import random_general as rg from infinigen.core.nodes.node_wrangler import Nodes -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup @gin.configurable @@ -78,13 +78,18 @@ def create_placeholder(self, **kwargs) -> bpy.types.Object: return butil.spawn_empty('placeholder', disp_type='CUBE', s=self.max_scale) def create_asset(self, distance, **kwargs): + cloud_type = np.random.choice(self.cloud_types) + resolution_min, resolution_max = self.resolutions[cloud_type] resolution = max(1 - distance / self.max_distance, 0) resolution = resolution * (resolution_max - resolution_min) + resolution_min resolution = int(resolution) + new_cloud = cloud_type("Cloud", self.ref_cloud) new_cloud = new_cloud.make_cloud(marching_cubes=False, resolution=resolution, ) + butil.apply_transform(new_cloud) + tag_object(new_cloud, 'cloud') return new_cloud diff --git a/infinigen/assets/weather/particles.py b/infinigen/assets/weather/particles.py index 9e4f89477..a57393c3c 100644 --- a/infinigen/assets/weather/particles.py +++ b/infinigen/assets/weather/particles.py @@ -18,7 +18,7 @@ from infinigen.core.nodes import node_utils from infinigen.core.util import blender as butil from infinigen.core.util.random import random_general -from infinigen.assets.utils.tag import tag_object, tag_nodegroup +from infinigen.core.tagging import tag_object, tag_nodegroup from infinigen.assets.materials import dirt from infinigen.infinigen_gpl.surfaces import snow diff --git a/infinigen/assets/windows/__init__.py b/infinigen/assets/windows/__init__.py new file mode 100644 index 000000000..4aed7852b --- /dev/null +++ b/infinigen/assets/windows/__init__.py @@ -0,0 +1,6 @@ +# 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: Hongyu Wen + +from .window import WindowFactory \ No newline at end of file diff --git a/infinigen/assets/windows/window.py b/infinigen/assets/windows/window.py new file mode 100644 index 000000000..d01d38be6 --- /dev/null +++ b/infinigen/assets/windows/window.py @@ -0,0 +1,1035 @@ +# 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: +# - Hongyu Wen: primary author +# - Alexander Raistrick: update window glass + +import bpy +import random +import mathutils +import numpy as np +from numpy.random import uniform as U, normal as N, randint as RI, uniform + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.util.blender import deep_clone_obj +from infinigen.core.util.color import color_category +from infinigen.core import surface +from infinigen.core.util import blender as butil + +from infinigen.core.util.math import FixedSeed, clip_gaussian +from infinigen.core.placement.factory import AssetFactory +from infinigen.assets.materials import metal_shader_list, wood_shader_list + +from infinigen.assets.utils.autobevel import BevelSharp + +def shader_window_glass(nw: NodeWrangler): + + """ Non-refractive glass shader, since windows consist of a one-sided mesh currently and would not properly + refract-then un-refract the light + """ + + roughness = clip_gaussian(0, 0.015, 0, 0.03, 0.03) + transmission = uniform(0.05, 0.12) + + # non-refractive glass + transparent_bsdf = nw.new_node(Nodes.TransparentBSDF) + shader = nw.new_node(Nodes.GlossyBSDF, input_kwargs={'Roughness': roughness}) + shader = nw.new_node(Nodes.MixShader, input_kwargs={'Fac': transmission, 1: transparent_bsdf, 2: shader}) + + # complete pass-through for non-camera rays, for render efficiency + light_path = nw.new_node(Nodes.LightPath) + shader = nw.new_node(Nodes.MixShader, input_kwargs={'Fac': light_path.outputs["Is Camera Ray"], 1: transparent_bsdf, 2: shader}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': shader}, attrs={'is_active_output': True}) + +class WindowFactory(AssetFactory): + def __init__(self, factory_seed, coarse=False, curtain=None, shutter=None): + super(WindowFactory, self).__init__(factory_seed, coarse=coarse) + + with FixedSeed(factory_seed): + self.params = self.sample_parameters() + self.beveler = BevelSharp() + self.curtain = curtain + self.shutter = shutter + + @staticmethod + def sample_parameters(): + frame_width = U(0.05, 0.1) + sub_frame_width = U(0.01, frame_width) + sub_frame_h_amount = RI(1, 2) + sub_frame_v_amount = RI(1, 2) + glass_thickness = U(0.01, 0.03) + + shutter_panel_radius = U(0.001, 0.003) + shutter_width = U(0.03, 0.05) + shutter_thickness = U(0.003, 0.007) + shutter_rotation = U(0, 1) + shutter_inverval = shutter_width + U(0.001, 0.003) + + curtain_frame_depth = U(0.05, 0.1) + curtain_depth = U(0.03, curtain_frame_depth) + curtain_frame_radius = U(0.01, 0.02) + + shader_frame_material_choice = random.choice(wood_shader_list) + shader_curtain_frame_material_choice = random.choice(metal_shader_list) + shader_curtain_material_choice = shader_curtain_material + + params = { + "FrameWidth": frame_width, + "SubFrameWidth": sub_frame_width, + "SubPanelHAmount": sub_frame_h_amount, + "SubPanelVAmount": sub_frame_v_amount, + "GlassThickness": glass_thickness, + "CurtainFrameDepth": curtain_frame_depth, + "CurtainDepth": curtain_depth, + "CurtainFrameRadius": curtain_frame_radius, + "ShutterPanelRadius": shutter_panel_radius, + "ShutterWidth": shutter_width, + "ShutterThickness": shutter_thickness, + "ShutterRotation": shutter_rotation, + "ShutterInterval": shutter_inverval, + "FrameMaterial": surface.shaderfunc_to_material(shader_frame_material_choice, vertical=True), + "CurtainFrameMaterial": surface.shaderfunc_to_material(shader_curtain_frame_material_choice), + "CurtainMaterial": surface.shaderfunc_to_material(shader_curtain_material_choice), + "Material": surface.shaderfunc_to_material(shader_window_glass) + } + return params + + def sample_asset_params(self, dimensions=None, open=None, curtain=None, shutter=None): + if dimensions is None: + width = U(1, 4) + height = U(1, 4) + frame_thickness = U(0.05, 0.15) + else: + width, height, frame_thickness = dimensions + + panel_h_amount = RI(1, 2) + v_ = width / height * panel_h_amount + panel_v_amount = int(uniform(v_ * 1.6, v_ * 2.5)) + + if open is None: + open = U(0, 1) < 0.5 + + if shutter is None: + shutter = U(0, 1) < 0.5 + + if curtain is None: + curtain = U(0, 1) < 0.5 + if curtain: + open = False + sub_frame_thickness = U(0.01, frame_thickness) + + open = False # keep windows closed on generation, let articulation module handle this later on + open_type = RI(0, 3) + open_offset = 0 + oe_offset = 0 + if open_type == 0: + if frame_thickness < sub_frame_thickness * 2: + open_type = RI(1, 2) + else: + oe_offset = U(sub_frame_thickness / 2, (frame_thickness - 2 * sub_frame_thickness) / 2) + if open: + open_offset = U(0, width / panel_h_amount) + else: + open_offset = 0 + open_h_angle = U(0, 0.3) if open_type == 1 and open else 0 + open_v_angle = -U(0, 0.3) if open_type == 2 and open else 0 + + curtain_interval_number = int(width / U(0.08, 0.2)) + curtain_mid_l = -U(0, width / 2) + curtain_mid_r = U(0, width / 2) + return { + **self.params, + "Width": width, + "Height": height, + "FrameThickness": frame_thickness, + "PanelHAmount": panel_h_amount, + "PanelVAmount": panel_v_amount, + "SubFrameThickness": sub_frame_thickness, + "OpenHAngle": open_h_angle, + "OpenVAngle": open_v_angle, + "OpenOffset": open_offset, + "OEOffset": oe_offset, + "Curtain": curtain, + "CurtainIntervalNumber": curtain_interval_number, + "CurtainMidL": curtain_mid_l, + "CurtainMidR": curtain_mid_r, + "Shutter": shutter, + } + + def create_asset(self, dimensions=None, open=None, realized=True, **params): + obj = butil.spawn_cube() + + butil.modify_mesh( + obj, + 'NODES', + node_group=nodegroup_window_geometry(), + ng_inputs=self.sample_asset_params(dimensions, open, self.curtain,self.shutter), + apply=realized + ) + + obj.rotation_euler[0] = np.pi / 2 + butil.apply_transform(obj, True) + obj_ =deep_clone_obj(obj) + self.beveler(obj) + if max(obj.dimensions) > 8: + butil.delete(obj) + obj = obj_ + else: + butil.delete(obj_) + + bpy.ops.object.light_add( + type='AREA', + radius=1, + align='WORLD', + location=(0,0,0), + scale=(1,1,1) + ) + portal = bpy.context.active_object + + w, _, h = obj.dimensions + portal.scale = (w, h, 1) + portal.data.cycles.is_portal = True + portal.rotation_euler = (-np.pi/2, 0, 0) + butil.parent_to(portal, obj, no_inverse=True) + portal.hide_viewport = True + + return obj + + +@node_utils.to_nodegroup('nodegroup_window_geometry', singleton=True, type='GeometryNodeTree') +def nodegroup_window_geometry(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input_1 = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloatDistance', 'Width', 2.0000), + ('NodeSocketFloatDistance', 'Height', 2.0000), ('NodeSocketFloatDistance', 'FrameWidth', 0.1000), + ('NodeSocketFloatDistance', 'FrameThickness', 0.1000), ('NodeSocketInt', 'PanelHAmount', 0), + ('NodeSocketInt', 'PanelVAmount', 0), ('NodeSocketFloatDistance', 'SubFrameWidth', 0.0500), + ('NodeSocketFloatDistance', 'SubFrameThickness', 0.0500), ('NodeSocketInt', 'SubPanelHAmount', 3), + ('NodeSocketInt', 'SubPanelVAmount', 2), ('NodeSocketFloat', 'GlassThickness', 0.0100), + ('NodeSocketFloat', 'OpenHAngle', 0.5000), ('NodeSocketFloat', 'OpenVAngle', 0.5000), + ('NodeSocketFloat', 'OpenOffset', 0.5000), ('NodeSocketFloat', 'OEOffset', 0.0500), + ('NodeSocketBool', 'Curtain', False), ('NodeSocketFloat', 'CurtainFrameDepth', 0.5000), + ('NodeSocketFloat', 'CurtainDepth', 0.0300), ('NodeSocketFloat', 'CurtainIntervalNumber', 20.0000), + ('NodeSocketFloatDistance', 'CurtainFrameRadius', 0.0100), ('NodeSocketFloat', 'CurtainMidL', -0.5000), + ('NodeSocketFloat', 'CurtainMidR', 0.5000), ('NodeSocketBool', 'Shutter', True), + ('NodeSocketFloatDistance', 'ShutterPanelRadius', 0.0050), + ('NodeSocketFloatDistance', 'ShutterWidth', 0.0500), + ('NodeSocketFloatDistance', 'ShutterThickness', 0.0050), ('NodeSocketFloat', 'ShutterRotation', 0.0000), + ('NodeSocketFloat', 'ShutterInterval', 0.0500), ('NodeSocketMaterial', 'FrameMaterial', None), + ('NodeSocketMaterial', 'CurtainFrameMaterial', None), ('NodeSocketMaterial', 'CurtainMaterial', None), + ('NodeSocketMaterial', 'Material', None)]) + + windowpanel = nw.new_node(nodegroup_window_panel().name, input_kwargs={ + 'Width': group_input_1.outputs["Width"], + 'Height': group_input_1.outputs["Height"], + 'FrameWidth': group_input_1.outputs["FrameWidth"], + 'FrameThickness': group_input_1.outputs["FrameThickness"], + 'PanelWidth': group_input_1.outputs["FrameWidth"], + 'PanelThickness': group_input_1.outputs["FrameThickness"], + 'PanelHAmount': group_input_1.outputs["PanelHAmount"], + 'PanelVAmount': group_input_1.outputs["PanelVAmount"], + 'FrameMaterial': group_input_1.outputs["FrameMaterial"], + 'Material': group_input_1.outputs["Material"] + }) + + multiply = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input_1.outputs["FrameWidth"], + 1: group_input_1.outputs["PanelVAmount"] + }, attrs={'operation': 'MULTIPLY'}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: group_input_1.outputs["Width"], 1: multiply}, + attrs={'operation': 'SUBTRACT'}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: group_input_1.outputs["PanelVAmount"]}, + attrs={'operation': 'DIVIDE'}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: divide, 1: group_input_1.outputs["SubFrameWidth"]}, + attrs={'operation': 'SUBTRACT'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input_1.outputs["FrameWidth"], + 1: group_input_1.outputs["PanelHAmount"] + }, attrs={'operation': 'MULTIPLY'}) + + subtract_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input_1.outputs["Height"], 1: multiply_1}, + attrs={'operation': 'SUBTRACT'}) + + divide_1 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_2, 1: group_input_1.outputs["PanelHAmount"]}, + attrs={'operation': 'DIVIDE'}) + + subtract_3 = nw.new_node(Nodes.Math, input_kwargs={0: divide_1, 1: group_input_1.outputs["SubFrameWidth"]}, + attrs={'operation': 'SUBTRACT'}) + + windowpanel_1 = nw.new_node(nodegroup_window_panel().name, input_kwargs={ + 'Width': subtract_1, + 'Height': subtract_3, + 'FrameWidth': group_input_1.outputs["SubFrameWidth"], + 'FrameThickness': group_input_1.outputs["SubFrameThickness"], + 'PanelWidth': group_input_1.outputs["SubFrameWidth"], + 'PanelThickness': group_input_1.outputs["SubFrameThickness"], + 'PanelHAmount': group_input_1.outputs["SubPanelHAmount"], + 'PanelVAmount': group_input_1.outputs["SubPanelVAmount"], + 'WithGlass': True, + 'GlassThickness': group_input_1.outputs["GlassThickness"], + 'FrameMaterial': group_input_1.outputs["FrameMaterial"], + 'Material': group_input_1.outputs["Material"] + }) + + windowshutter = nw.new_node(nodegroup_window_shutter().name, input_kwargs={ + 'Width': subtract_1, + 'Height': subtract_3, + 'FrameWidth': group_input_1.outputs["FrameWidth"], + 'FrameThickness': group_input_1.outputs["FrameThickness"], + 'PanelWidth': group_input_1.outputs["ShutterPanelRadius"], + 'PanelThickness': group_input_1.outputs["ShutterPanelRadius"], + 'ShutterWidth': group_input_1.outputs["ShutterWidth"], + 'ShutterThickness': group_input_1.outputs["ShutterThickness"], + 'ShutterInterval': group_input_1.outputs["ShutterInterval"], + 'ShutterRotation': group_input_1.outputs["ShutterRotation"], + 'FrameMaterial': group_input_1.outputs["FrameMaterial"] + }) + + switch = nw.new_node(Nodes.Switch, + input_kwargs={1: group_input_1.outputs["Shutter"], 14: windowpanel_1, 15: windowshutter + }) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input_1.outputs["Width"], 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + divide_2 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input_1.outputs["Width"], + 1: group_input_1.outputs["PanelVAmount"] + }, attrs={'operation': 'DIVIDE'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: divide_2}, attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: multiply_3}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input_1.outputs["Height"], 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + divide_3 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input_1.outputs["Height"], + 1: group_input_1.outputs["PanelHAmount"] + }, attrs={'operation': 'DIVIDE'}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: divide_3}, attrs={'operation': 'MULTIPLY'}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_4, 1: multiply_5}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add, 'Y': add_1}) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': switch.outputs[6], 'Translation': combine_xyz}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': transform}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input_1.outputs["PanelHAmount"], + 1: group_input_1.outputs["PanelVAmount"] + }, attrs={'operation': 'MULTIPLY'}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance, 'Amount': multiply_6}, + attrs={'domain': 'INSTANCE'}) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input_1.outputs["PanelHAmount"]}) + + divide_4 = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: reroute}, + attrs={'operation': 'DIVIDE'}) + + floor = nw.new_node(Nodes.Math, input_kwargs={0: divide_4}, attrs={'operation': 'FLOOR'}) + + add_2 = nw.new_node(Nodes.Math, input_kwargs={0: divide, 1: group_input_1.outputs["FrameWidth"]}) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: floor, 1: add_2}, attrs={'operation': 'MULTIPLY'}) + + modulo = nw.new_node(Nodes.Math, + input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: reroute}, + attrs={'operation': 'MODULO'}) + + add_3 = nw.new_node(Nodes.Math, input_kwargs={0: divide_1, 1: group_input_1.outputs["FrameWidth"]}) + + multiply_8 = nw.new_node(Nodes.Math, input_kwargs={0: modulo, 1: add_3}, attrs={'operation': 'MULTIPLY'}) + + power = nw.new_node(Nodes.Math, input_kwargs={0: -1.0000, 1: floor}, attrs={'operation': 'POWER'}) + + multiply_9 = nw.new_node(Nodes.Math, input_kwargs={0: power, 1: group_input_1.outputs["OEOffset"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': multiply_7, 'Y': multiply_8, 'Z': multiply_9}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': duplicate_elements.outputs["Geometry"], + 'Offset': combine_xyz_1 + }) + + power_1 = nw.new_node(Nodes.Math, input_kwargs={0: -1.0000, 1: floor}, attrs={'operation': 'POWER'}) + + multiply_10 = nw.new_node(Nodes.Math, input_kwargs={0: group_input_1.outputs["OpenVAngle"], 1: power_1}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_10}) + + modulo_1 = nw.new_node(Nodes.Math, input_kwargs={0: floor, 1: 2.0000}, attrs={'operation': 'MODULO'}) + + multiply_11 = nw.new_node(Nodes.Math, input_kwargs={0: divide, 1: modulo_1}, + attrs={'operation': 'MULTIPLY'}) + + add_4 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: multiply_11}) + + modulo_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_8, 1: 2.0000}, attrs={'operation': 'MODULO'}) + + multiply_12 = nw.new_node(Nodes.Math, input_kwargs={0: divide_1, 1: modulo_2}, + attrs={'operation': 'MULTIPLY'}) + + add_5 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_4, 1: multiply_12}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': add_4, 'Y': add_5}) + + rotate_instances = nw.new_node(Nodes.RotateInstances, input_kwargs={ + 'Instances': set_position, + 'Rotation': combine_xyz_3, + 'Pivot Point': combine_xyz_2 + }) + + multiply_13 = nw.new_node(Nodes.Math, input_kwargs={0: group_input_1.outputs["OpenHAngle"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_13}) + + multiply_14 = nw.new_node(Nodes.Math, input_kwargs={0: add_3, 1: -0.5000}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_14}) + + rotate_instances_1 = nw.new_node(Nodes.RotateInstances, input_kwargs={ + 'Instances': rotate_instances, + 'Rotation': combine_xyz_5, + 'Pivot Point': combine_xyz_6 + }) + + power_2 = nw.new_node(Nodes.Math, input_kwargs={0: -1.0000, 1: floor}, attrs={'operation': 'POWER'}) + + multiply_15 = nw.new_node(Nodes.Math, input_kwargs={0: power_2, 1: group_input_1.outputs["OpenOffset"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_15}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': rotate_instances_1, 'Offset': combine_xyz_4}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [windowpanel, set_position_1]}) + + multiply_16 = nw.new_node(Nodes.Math, input_kwargs={0: group_input_1.outputs["Width"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_17 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_16, 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + multiply_18 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input_1.outputs["CurtainFrameDepth"], 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + curtain = nw.new_node(nodegroup_curtain().name, input_kwargs={ + 'Width': group_input_1.outputs["Width"], + 'Depth': group_input_1.outputs["CurtainDepth"], + 'Height': group_input_1.outputs["Height"], + 'IntervalNumber': group_input_1.outputs["CurtainIntervalNumber"], + 'Radius': group_input_1.outputs["CurtainFrameRadius"], + 'L1': multiply_17, + 'R1': group_input_1.outputs["CurtainMidL"], + 'L2': group_input_1.outputs["CurtainMidR"], + 'R2': multiply_16, + 'FrameDepth': multiply_18, + 'CurtainFrameMaterial': group_input_1.outputs["CurtainFrameMaterial"], + 'CurtainMaterial': group_input_1.outputs["CurtainMaterial"] + }) + + multiply_19 = nw.new_node(Nodes.Math, input_kwargs={0: group_input_1.outputs["FrameThickness"]}, + attrs={'operation': 'MULTIPLY'}) + + add_6 = nw.new_node(Nodes.Math, + input_kwargs={0: group_input_1.outputs["CurtainFrameDepth"], 1: multiply_19}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add_6}) + + transform_geometry = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': curtain, 'Translation': combine_xyz_7}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [transform_geometry, join_geometry]}) + + switch_1 = nw.new_node(Nodes.Switch, input_kwargs={ + 1: group_input_1.outputs["Curtain"], + 14: join_geometry, + 15: join_geometry_1 + }) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': switch_1.outputs[6]}) + + bounding_box = nw.new_node(Nodes.BoundingBox, input_kwargs={'Geometry': realize_instances}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={ + 'Geometry': realize_instances, + 'Bounding Box': bounding_box.outputs["Bounding Box"] + }, attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_line_seq', singleton=False, type='GeometryNodeTree') +def nodegroup_line_seq(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'Width', -1.0000), + ('NodeSocketFloat', 'Height', 0.5000), ('NodeSocketFloat', 'Amount', 0.5000)]) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply, 'Y': multiply_1}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"], 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_2, 'Y': multiply_1}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz, 'End': combine_xyz_1}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', input_kwargs={'Geometry': curve_line}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, input_kwargs={ + 'Geometry': geometry_to_instance, + 'Amount': group_input.outputs["Amount"] + }, attrs={'domain': 'INSTANCE'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: duplicate_elements.outputs["Duplicate Index"], 1: 1.0000}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Amount"], 1: 1.0000}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: add_1}, + attrs={'operation': 'DIVIDE'}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: divide}, attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_3}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': duplicate_elements.outputs["Geometry"], + 'Offset': combine_xyz_2 + }) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Curve': set_position}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_curtain', singleton=False, type='GeometryNodeTree') +def nodegroup_curtain(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloat', 'Width', 0.5000), + ('NodeSocketFloat', 'Depth', 0.1000), ('NodeSocketFloatDistance', 'Height', 0.1000), + ('NodeSocketFloat', 'IntervalNumber', 0.5000), ('NodeSocketFloatDistance', 'Radius', 1.0000), + ('NodeSocketFloat', 'L1', 0.5000), ('NodeSocketFloat', 'R1', 0.0000), ('NodeSocketFloat', 'L2', 0.0000), + ('NodeSocketFloat', 'R2', 0.5000), ('NodeSocketFloat', 'FrameDepth', 0.0000), + ('NodeSocketMaterial', 'CurtainFrameMaterial', None), ('NodeSocketMaterial', 'CurtainMaterial', None)]) + + reroute_1 = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Radius"]}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: reroute_1, 1: 2.0000}, attrs={'operation': 'MULTIPLY'}) + + ico_sphere = nw.new_node(Nodes.MeshIcoSphere, input_kwargs={'Radius': multiply, 'Subdivisions': 4}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Width"]}, + attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: -1.0000}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_2}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': multiply_1}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz, 'End': combine_xyz_1}) + + sample_curve_1 = nw.new_node(Nodes.SampleCurve, input_kwargs={'Curves': curve_line, 'Factor': 1.0000}) + + set_position_2 = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': ico_sphere.outputs["Mesh"], + 'Offset': sample_curve_1.outputs["Position"] + }) + + combine_xyz_9 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': multiply_1, 'Z': group_input.outputs["FrameDepth"]}) + + curve_line_4 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz_1, 'End': combine_xyz_9}) + + combine_xyz_8 = nw.new_node(Nodes.CombineXYZ, + input_kwargs={'X': multiply_2, 'Z': group_input.outputs["FrameDepth"]}) + + curve_line_3 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz, 'End': combine_xyz_8}) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [curve_line, curve_line_4, curve_line_3]}) + + curve_circle = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': group_input.outputs["Radius"]}) + + curve_to_mesh_1 = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': join_geometry_3, + 'Profile Curve': curve_circle.outputs["Curve"], + 'Fill Caps': True + }) + + ico_sphere_1 = nw.new_node(Nodes.MeshIcoSphere, input_kwargs={'Radius': multiply, 'Subdivisions': 4}) + + sample_curve = nw.new_node(Nodes.SampleCurve, input_kwargs={'Curves': curve_line}) + + set_position_3 = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': ico_sphere_1.outputs["Mesh"], + 'Offset': sample_curve.outputs["Position"] + }) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [set_position_2, curve_to_mesh_1, set_position_3]}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["Height"], 1: -0.4700}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_3}) + + set_position_1 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': join_geometry_2, 'Offset': combine_xyz_3}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, input_kwargs={ + 'Geometry': set_position_1, + 'Material': group_input.outputs["CurtainFrameMaterial"] + }) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["L1"]}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["R1"]}) + + curve_line_1 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz_4, 'End': combine_xyz_5}) + + resample_curve = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': curve_line_1, 'Count': 200}) + + combine_xyz_6 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["L2"]}) + + combine_xyz_7 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': group_input.outputs["R2"]}) + + curve_line_2 = nw.new_node(Nodes.CurveLine, input_kwargs={'Start': combine_xyz_6, 'End': combine_xyz_7}) + + resample_curve_1 = nw.new_node(Nodes.ResampleCurve, input_kwargs={'Curve': curve_line_2, 'Count': 200}) + + join_geometry_1 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [resample_curve, resample_curve_1]}) + + spline_parameter_1 = nw.new_node(Nodes.SplineParameter) + + capture_attribute = nw.new_node(Nodes.CaptureAttribute, input_kwargs={ + 'Geometry': join_geometry_1, + 2: spline_parameter_1.outputs["Factor"] + }) + + spline_parameter = nw.new_node(Nodes.SplineParameter) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["IntervalNumber"], 1: 6.2800}, + attrs={'operation': 'MULTIPLY'}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: multiply_4, 1: group_input.outputs["Width"]}, + attrs={'operation': 'DIVIDE'}) + + multiply_5 = nw.new_node(Nodes.Math, input_kwargs={0: spline_parameter.outputs["Length"], 1: divide}, + attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_5, 1: 1.6800}) + + sine = nw.new_node(Nodes.Math, input_kwargs={0: add}, attrs={'operation': 'SINE'}) + + multiply_6 = nw.new_node(Nodes.Math, input_kwargs={0: sine, 1: group_input.outputs["Depth"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': multiply_6}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': capture_attribute.outputs["Geometry"], + 'Offset': combine_xyz_2 + }) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["Height"]}) + + quadrilateral = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', + input_kwargs={'Width': reroute, 'Height': 0.0020}) + + position = nw.new_node(Nodes.InputPosition) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': position}) + + divide_1 = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["X"], 1: reroute}, + attrs={'operation': 'DIVIDE'}) + + capture_attribute_1 = nw.new_node(Nodes.CaptureAttribute, + input_kwargs={'Geometry': quadrilateral, 2: divide_1}) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': set_position, + 'Profile Curve': capture_attribute_1.outputs["Geometry"] + }) + + combine_xyz_12 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': capture_attribute_1.outputs[2], + 'Y': capture_attribute.outputs[2] + }) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': curve_to_mesh, + 'Name': 'UVMap', + 3: combine_xyz_12 + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT2'}) + + set_material = nw.new_node(Nodes.SetMaterial, input_kwargs={ + 'Geometry': store_named_attribute, + 'Material': group_input.outputs["CurtainMaterial"] + }) + + multiply_7 = nw.new_node(Nodes.Math, input_kwargs={0: reroute_1, 1: 1.3000}, + attrs={'operation': 'MULTIPLY'}) + + curve_circle_1 = nw.new_node(Nodes.CurveCircle, input_kwargs={'Radius': multiply_7}) + + curve_to_mesh_2 = nw.new_node(Nodes.CurveToMesh, input_kwargs={ + 'Curve': curve_line, + 'Profile Curve': curve_circle_1.outputs["Curve"] + }) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_3, 1: group_input.outputs["Radius"]}) + + combine_xyz_10 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_1}) + + set_position_4 = nw.new_node(Nodes.SetPosition, + input_kwargs={'Geometry': curve_to_mesh_2, 'Offset': combine_xyz_10}) + + difference = nw.new_node(Nodes.MeshBoolean, input_kwargs={'Mesh 1': set_material, 'Mesh 2': set_position_4}) + + join_geometry = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [set_material_1, difference.outputs["Mesh"]]}) + + set_shade_smooth = nw.new_node(Nodes.SetShadeSmooth, + input_kwargs={'Geometry': join_geometry, 'Shade Smooth': False}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_shade_smooth}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_window_shutter', singleton=False, type='GeometryNodeTree') +def nodegroup_window_shutter(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloatDistance', 'Width', 2.0000), + ('NodeSocketFloatDistance', 'Height', 2.0000), ('NodeSocketFloatDistance', 'FrameWidth', 0.1000), + ('NodeSocketFloatDistance', 'FrameThickness', 0.1000), + ('NodeSocketFloatDistance', 'PanelWidth', 0.1000), + ('NodeSocketFloatDistance', 'PanelThickness', 0.1000), + ('NodeSocketFloatDistance', 'ShutterWidth', 0.1000), + ('NodeSocketFloatDistance', 'ShutterThickness', 0.1000), ('NodeSocketFloat', 'ShutterInterval', 0.5000), + ('NodeSocketFloat', 'ShutterRotation', 0.0000), ('NodeSocketMaterial', 'FrameMaterial', None)]) + + quadrilateral = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', input_kwargs={ + 'Width': group_input.outputs["Width"], + 'Height': group_input.outputs["Height"] + }) + + sqrt = nw.new_node(Nodes.Math, input_kwargs={0: 2.0000}, attrs={'operation': 'SQRT'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["FrameWidth"], 1: sqrt}, + attrs={'operation': 'MULTIPLY'}) + + quadrilateral_1 = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', input_kwargs={ + 'Width': multiply, + 'Height': group_input.outputs["FrameThickness"] + }) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': quadrilateral, 'Profile Curve': quadrilateral_1}) + + subtract = nw.new_node(Nodes.Math, + input_kwargs={0: group_input.outputs["Width"], 1: group_input.outputs["FrameWidth"]}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': subtract, + 'Y': group_input.outputs["ShutterWidth"], + 'Z': group_input.outputs["ShutterThickness"] + }) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz}) + + geometry_to_instance = nw.new_node('GeometryNodeGeometryToInstance', + input_kwargs={'Geometry': cube.outputs["Mesh"]}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={ + 0: group_input.outputs["Height"], + 1: group_input.outputs["FrameWidth"] + }, attrs={'operation': 'SUBTRACT'}) + + divide = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: group_input.outputs["ShutterInterval"]}, + attrs={'operation': 'DIVIDE'}) + + floor = nw.new_node(Nodes.Math, input_kwargs={0: divide}, attrs={'operation': 'FLOOR'}) + + shutter_number = nw.new_node(Nodes.Math, input_kwargs={0: floor, 1: 1.0000}, label='ShutterNumber', + attrs={'operation': 'SUBTRACT'}) + + duplicate_elements = nw.new_node(Nodes.DuplicateElements, + input_kwargs={'Geometry': geometry_to_instance, 'Amount': shutter_number}, + attrs={'domain': 'INSTANCE'}) + + shutter_true_interval = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: floor}, + label='ShutterTrueInterval', attrs={'operation': 'DIVIDE'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={ + 0: duplicate_elements.outputs["Duplicate Index"], + 1: shutter_true_interval + }, attrs={'operation': 'MULTIPLY'}) + + multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: -0.5000}, + attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2, 1: shutter_true_interval}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_1, 1: add}) + + combine_xyz_1 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': add_1}) + + set_position = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': duplicate_elements.outputs["Geometry"], + 'Offset': combine_xyz_1 + }) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["ShutterRotation"]}) + + combine_xyz_5 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': reroute}) + + rotate_instances = nw.new_node(Nodes.RotateInstances, + input_kwargs={'Instances': set_position, 'Rotation': combine_xyz_5}) + + multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: shutter_true_interval, 1: 2.0000}, + attrs={'operation': 'MULTIPLY'}) + + subtract_2 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: multiply_3}, + attrs={'operation': 'SUBTRACT'}) + + combine_xyz_2 = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': group_input.outputs["PanelWidth"], + 'Y': subtract_2, + 'Z': group_input.outputs["PanelThickness"] + }) + + cube_1 = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz_2}) + + multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["ShutterWidth"]}, + attrs={'operation': 'MULTIPLY'}) + + combine_xyz_3 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Y': multiply_4}) + + curve_line = nw.new_node(Nodes.CurveLine, input_kwargs={'End': combine_xyz_3}) + + geometry_to_instance_1 = nw.new_node('GeometryNodeGeometryToInstance', + input_kwargs={'Geometry': curve_line}) + + combine_xyz_4 = nw.new_node(Nodes.CombineXYZ, input_kwargs={'X': reroute}) + + rotate_instances_1 = nw.new_node(Nodes.RotateInstances, input_kwargs={ + 'Instances': geometry_to_instance_1, + 'Rotation': combine_xyz_4 + }) + + realize_instances = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': rotate_instances_1}) + + sample_curve = nw.new_node(Nodes.SampleCurve, input_kwargs={'Curves': realize_instances, 'Factor': 1.0000}) + + set_position_1 = nw.new_node(Nodes.SetPosition, input_kwargs={ + 'Geometry': cube_1.outputs["Mesh"], + 'Offset': sample_curve.outputs["Position"] + }) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [curve_to_mesh, rotate_instances, set_position_1]}) + + set_material = nw.new_node(Nodes.SetMaterial, input_kwargs={ + 'Geometry': join_geometry_2, + 'Material': group_input.outputs["FrameMaterial"] + }) + + set_shade_smooth = nw.new_node(Nodes.SetShadeSmooth, + input_kwargs={'Geometry': set_material, 'Shade Smooth': False}) + + realize_instances_1 = nw.new_node(Nodes.RealizeInstances, input_kwargs={'Geometry': set_shade_smooth}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': realize_instances_1}, + attrs={'is_active_output': True}) + + +@node_utils.to_nodegroup('nodegroup_window_panel', singleton=False, type='GeometryNodeTree') +def nodegroup_window_panel(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketFloatDistance', 'Width', 2.0000), + ('NodeSocketFloatDistance', 'Height', 2.0000), ('NodeSocketFloatDistance', 'FrameWidth', 0.1000), + ('NodeSocketFloatDistance', 'FrameThickness', 0.1000), + ('NodeSocketFloatDistance', 'PanelWidth', 0.1000), + ('NodeSocketFloatDistance', 'PanelThickness', 0.1000), ('NodeSocketInt', 'PanelHAmount', 0), + ('NodeSocketInt', 'PanelVAmount', 0), ('NodeSocketBool', 'WithGlass', False), + ('NodeSocketFloat', 'GlassThickness', 0.0000), ('NodeSocketMaterial', 'FrameMaterial', None), + ('NodeSocketMaterial', 'Material', None)]) + + quadrilateral = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', input_kwargs={ + 'Width': group_input.outputs["Width"], + 'Height': group_input.outputs["Height"] + }) + + sqrt = nw.new_node(Nodes.Math, input_kwargs={0: 2.0000}, attrs={'operation': 'SQRT'}) + + multiply = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["FrameWidth"], 1: sqrt}, + attrs={'operation': 'MULTIPLY'}) + + quadrilateral_1 = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', input_kwargs={ + 'Width': multiply, + 'Height': group_input.outputs["FrameThickness"] + }) + + curve_to_mesh = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': quadrilateral, 'Profile Curve': quadrilateral_1}) + + add = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["PanelHAmount"], 1: -1.0000}) + + lineseq = nw.new_node(nodegroup_line_seq().name, input_kwargs={ + 'Width': group_input.outputs["Width"], + 'Height': group_input.outputs["Height"], + 'Amount': add + }) + + reroute = nw.new_node(Nodes.Reroute, input_kwargs={'Input': group_input.outputs["PanelWidth"]}) + + subtract = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["PanelThickness"], 1: 0.0010}, + attrs={'operation': 'SUBTRACT'}) + + quadrilateral_2 = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', + input_kwargs={'Width': reroute, 'Height': subtract}) + + curve_to_mesh_1 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': lineseq, 'Profile Curve': quadrilateral_2}) + + add_1 = nw.new_node(Nodes.Math, input_kwargs={0: group_input.outputs["PanelVAmount"], 1: -1.0000}) + + lineseq_1 = nw.new_node(nodegroup_line_seq().name, input_kwargs={ + 'Width': group_input.outputs["Height"], + 'Height': group_input.outputs["Width"], + 'Amount': add_1 + }) + + transform = nw.new_node(Nodes.Transform, + input_kwargs={'Geometry': lineseq_1, 'Rotation': (0.0000, 0.0000, 1.5708)}) + + subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: 0.0010}, attrs={'operation': 'SUBTRACT'}) + + quadrilateral_3 = nw.new_node('GeometryNodeCurvePrimitiveQuadrilateral', + input_kwargs={'Width': reroute, 'Height': subtract_1}) + + curve_to_mesh_2 = nw.new_node(Nodes.CurveToMesh, + input_kwargs={'Curve': transform, 'Profile Curve': quadrilateral_3}) + + join_geometry_3 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [curve_to_mesh_1, curve_to_mesh_2]}) + + join_geometry_2 = nw.new_node(Nodes.JoinGeometry, + input_kwargs={'Geometry': [curve_to_mesh, join_geometry_3]}) + + set_material_1 = nw.new_node(Nodes.SetMaterial, input_kwargs={ + 'Geometry': join_geometry_2, + 'Material': group_input.outputs["FrameMaterial"] + }) + + combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={ + 'X': group_input.outputs["Width"], + 'Y': group_input.outputs["Height"], + 'Z': group_input.outputs["GlassThickness"] + }) + + cube = nw.new_node(Nodes.MeshCube, input_kwargs={'Size': combine_xyz}) + + store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, input_kwargs={ + 'Geometry': cube.outputs["Mesh"], + 'Name': 'uv_map', + 3: cube.outputs["UV Map"] + }, attrs={'domain': 'CORNER', 'data_type': 'FLOAT_VECTOR'}) + + set_material = nw.new_node(Nodes.SetMaterial, input_kwargs={ + 'Geometry': store_named_attribute, + 'Material': group_input.outputs["Material"] + }) + + join_geometry = nw.new_node(Nodes.JoinGeometry, input_kwargs={'Geometry': [set_material, set_material_1]}) + + switch = nw.new_node(Nodes.Switch, input_kwargs={ + 1: group_input.outputs["WithGlass"], + 14: set_material_1, + 15: join_geometry + }) + + set_shade_smooth = nw.new_node(Nodes.SetShadeSmooth, + input_kwargs={'Geometry': switch.outputs[6], 'Shade Smooth': False}) + + group_output = nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': set_shade_smooth}, + attrs={'is_active_output': True}) + + +def shader_curtain_material(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={ + 'Base Color': color_category('textile'), + 'Transmission': np.random.uniform(0, 1), + 'Transmission Roughness': 1.0 + }) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, + attrs={'is_active_output': True}) + + +def shader_curtain_frame_material(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': (0.1840, 0.0000, 0.8000, 1.0000)}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, + attrs={'is_active_output': True}) + + +def shader_frame_material(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Base Color': (0.8000, 0.5033, 0.0057, 1.0000)}) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, + attrs={'is_active_output': True}) + + +def shader_glass_material(nw: NodeWrangler): + # Code generated using version 2.6.5 of the node_transpiler + + principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, input_kwargs={ + 'Base Color': (0.0094, 0.0055, 0.8000, 1.0000), + 'Roughness': 0.0000 + }) + + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}, + attrs={'is_active_output': True}) diff --git a/infinigen/core/constraints/checks.py b/infinigen/core/constraints/checks.py new file mode 100644 index 000000000..863dfb50c --- /dev/null +++ b/infinigen/core/constraints/checks.py @@ -0,0 +1,126 @@ +# 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 typing +import itertools +import logging + +from tqdm import tqdm + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r +) +from infinigen.core.constraints.example_solver import propose_discrete, propose_relations +from infinigen_examples import indoor_constraint_examples as ex + +logger = logging.getLogger(__name__) + +def iter_domains(node: cl.Node) -> typing.Iterator[r.Domain]: + + match node: + case cl.ObjectSetExpression(): + yield node, r.constraint_domain(node) + case cl.Expression() | cl.Problem(): + for k, c in node.children(): + yield from iter_domains(c) + case _: + raise ValueError(f'iter_domains found unmatched {type(node)=} {node=}') + +def bound_coverage(b: r.Bound, stages: dict[str, r.Domain]) -> list[str]: + return [ + k + for k, f in stages.items() + if propose_discrete.active_for_stage(b.domain, f) + ] + + +def check_coverage_errors(b: r.Bound, coverage: list, stages: dict[str, r.Domain]): + + if len(coverage) == 0: + raise ValueError(f'Greedy stages did not cover all object classes! User specified bound {b} had {coverage=}') + + if len(coverage) != 1: + raise ValueError( + f'Object class {b} was covered in more than one greedy stage! Got {coverage=}. Greedy stages must be non-overlapping' + ) + + gen_options = propose_discrete.lookup_generator(b.domain.tags) + if len(gen_options) < 1: + raise ValueError(f'Object class {b=} had {gen_options=}') + + for k in coverage: + logger.debug(f'Checking coverage {k=} {b.domain=} {stages[k]=}') + + if not b.domain.intersects(stages[k]): + continue + prop = b.domain.intersection(stages[k]) + + if prop.is_recursive(): + raise ValueError(f'Found recursive prop domain {prop.tags=} {len(prop.relations)=}') + assert not prop.is_recursive(), prop.tags + + if not len(prop.relations): + continue + first, remaining, implied = propose_relations.minimize_redundant_relations(prop.relations) + if implied: + continue + if isinstance(first[0], cl.AnyRelation): + raise ValueError(f'{b=} in {stages[k]=} had underspecified {first=}') + +def check_problem_greedy_coverage(prob: cl.Problem, stages: dict[str, r.Domain]): + + bounds = r.constraint_bounds(prob) + + for b in tqdm(bounds, desc="Checking greedy stages coverage"): + coverage = bound_coverage(b, stages) + check_coverage_errors(b, coverage, stages) + +def check_unfinalized_constraints(prob: cl.Problem): + # TODO + return [] + +def check_contradictory_domains(prob: cl.Problem): + + for node, dom in iter_domains(prob): + contradictory = not dom.satisfies(dom) + if contradictory: + raise ValueError(f'Constraint node had self-contradicting domain. \n{node=} \n{dom=}') + +def validate_stages(stages: dict[str, r.Domain]): + + for k, d in stages.items(): + if d.is_recursive(): + raise ValueError(f'{k=} had recursive domain') + + for (k1, d1), (k2, d2) in itertools.product(stages.items(), stages.items()): + inter = d1.intersects(d2) + if inter != (k1 == k2): + raise ValueError( + f"User provided greedy stages with keys {k1=} {k2=} which had non-empty intersection! " + " please define greedy stages which are mutually exclusive." + ) + +def check_all( + prob: cl.Problem, + greedy_stages: dict[str, r.Domain], + all_vars: list[str] +): + + for k, v in greedy_stages.items(): + + if not isinstance(v, r.Domain): + raise TypeError(f'Greedy stage {k=} had non-domain value {v=}') + + extras = v.all_vartags() - set(all_vars) + if len(extras): + raise ValueError(f'{k=} had extra vars {extras=}. Greedy domains may only contain vars from {all_vars}') + + validate_stages(greedy_stages) + + check_problem_greedy_coverage(prob, greedy_stages) + check_unfinalized_constraints(prob) + check_contradictory_domains(prob) diff --git a/infinigen/core/constraints/constraint_language/__init__.py b/infinigen/core/constraints/constraint_language/__init__.py new file mode 100644 index 000000000..5b386e1cc --- /dev/null +++ b/infinigen/core/constraints/constraint_language/__init__.py @@ -0,0 +1,67 @@ +# 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, Karhan Kayan + +from infinigen.core.tags import Semantics, Negated +from .types import Node + +from .expression import ( + Expression, + ArithmethicExpression, + constant, + ScalarOperatorExpression, + BoolOperatorExpression, + ScalarExpression, + BoolExpression, + hinge, + max_expr, + min_expr, +) + +from .set_reasoning import ( + scene, + tagged, + excludes, + count, + in_range, + related_to, +) +from .geometry import ( + ObjectSetExpression, + distance, + min_distance_internal, + focus_score, + angle_alignment_cost, + freespace_2d, + min_dist_2d, + rotational_asymmetry, + center_stable_surface_dist, + accessibility_cost, + reflectional_asymmetry, + volume, + coplanarity_cost +) +from .result import Problem +from .relations import ( + Relation, + NegatedRelation, + AnyRelation, + ConnectorType, + RoomNeighbour, + CutFrom, + GeometryRelation, + Touching, + SupportedBy, + StableAgainst +) +from .gather import ( + sum, + mean, + all, + item, + ForAll, + SumOver, + MeanOver +) \ No newline at end of file diff --git a/infinigen/core/constraints/constraint_language/expression.py b/infinigen/core/constraints/constraint_language/expression.py new file mode 100644 index 000000000..9dc8d2c1d --- /dev/null +++ b/infinigen/core/constraints/constraint_language/expression.py @@ -0,0 +1,182 @@ +# 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 typing +import operator +from dataclasses import dataclass +import functools +import math + +from .types import Node, nodedataclass + +OPERATOR_ASSOCIATIVE = [ + operator.add, + operator.mul, + operator.and_, + max, + min +] + +@nodedataclass() +class Expression(Node): + + @classmethod + def register_postfix_func(cls, expr_cls): + @functools.wraps(expr_cls) + def postfix_instantiator(self, *args, **kwargs): + return expr_cls(self, *args, **kwargs) + setattr(cls, expr_cls.__name__, postfix_instantiator) + return expr_cls + +@nodedataclass() +class ArithmethicExpression(Expression): + pass + +@nodedataclass() +class ScalarExpression(ArithmethicExpression): + + def minimize(self, *, weight: float): + return self * constant(weight) + + def maximize(self, *, weight: float): + return self * constant(-weight) + + def multiply(self, other): + return ScalarOperatorExpression(operator.mul, [self, other]) + __mul__ = multiply + + def abs(self): + return ScalarOperatorExpression(operator.abs, [self]) + __abs__ = abs + + def add(self, other): + return ScalarOperatorExpression(operator.add, [self, other]) + __add__ = add + + def sub(self, other): + return ScalarOperatorExpression(operator.sub, [self, other]) + __sub__ = sub + + def div(self, other): + return ScalarOperatorExpression(operator.truediv, [self, other]) + __truediv__ = div + + def pow(self, other): + return ScalarOperatorExpression(operator.pow, [self, other]) + __pow__ = pow + + def equals(self, other): + return BoolOperatorExpression(operator.eq, [self, other]) + __eq__ = equals + + def __ge__(self, other): + return BoolOperatorExpression(operator.ge, [self, other]) + def __gt__(self, other): + return BoolOperatorExpression(operator.gt, [self, other]) + def __le__(self, other): + return BoolOperatorExpression(operator.le, [self, other]) + def __lt__(self, other): + return BoolOperatorExpression(operator.lt, [self, other]) + def __ne__(self, other): + return BoolOperatorExpression(operator.ne, [self, other]) + + def __neg__(self): + return self * constant(-1) + + def clamp_min(self, other): + return max_expr(self, other) + def clamp_max(self, other): + return min_expr(self, other) + +def max_expr(*args): + return ScalarOperatorExpression(max, args) + +def min_expr(*args): + return ScalarOperatorExpression(min, args) + +@nodedataclass() +class BoolExpression(ArithmethicExpression): + + def __mul__(self, other): + return BoolOperatorExpression(operator.and_, [self, other]) + + +@nodedataclass() +class constant(ScalarExpression): + value: int + + def __post_init__(self): + assert isinstance(self.value, (bool | float | int)) + + def __call__(self): + return self.value + +def _preprocess_operands(operands): + def cast_to_node(x): + match x: + case Node(): + return x + case x if isinstance(x, (bool | float | int)): + return constant(x) + case _: + raise ValueError(f'Unsupported operand type {type(x)=} {x=}') + return [cast_to_node(x) for x in operands] + +def _collapse_associative(self, operands): + + if self.func not in OPERATOR_ASSOCIATIVE: + return operands + + new_operands = [] + for op in operands: + if isinstance(op, self.__class__) and op.func == self.func: + new_operands.extend(op.operands) + else: + new_operands.append(op) + return new_operands + +@nodedataclass() +class BoolOperatorExpression(BoolExpression): + + func: typing.Callable + operands: list[Expression] + + def __post_init__(self): + self.operands = _preprocess_operands(self.operands) + self.operands = _collapse_associative(self, self.operands) + + def children(self): + for i, v in enumerate(self.operands): + yield f'operands[{i}]', v + + def __call__(self) -> typing.Any: + return self.func(*[x() for x in self.operands]) + + +@nodedataclass() +class ScalarOperatorExpression(ScalarExpression): + + func: typing.Callable + operands: list[Expression] + + def __post_init__(self): + self.operands = _preprocess_operands(self.operands) + self.operands = _collapse_associative(self, self.operands) + assert self.func not in [operator.and_, operator.or_] + + def children(self): + for i, v in enumerate(self.operands): + yield f'operands[{i}]', v + + def __call__(self) -> typing.Any: + return self.func(*[x() for x in self.operands]) + +@ScalarExpression.register_postfix_func +@nodedataclass() +class hinge(ScalarExpression): + val: ScalarExpression + low: float + high: float \ No newline at end of file diff --git a/infinigen/core/constraints/constraint_language/gather.py b/infinigen/core/constraints/constraint_language/gather.py new file mode 100644 index 000000000..071516d19 --- /dev/null +++ b/infinigen/core/constraints/constraint_language/gather.py @@ -0,0 +1,67 @@ +# 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 typing +from dataclasses import dataclass, field + +from infinigen.core import tags as t +from .relations import Relation +from .expression import BoolExpression, ScalarExpression, nodedataclass +from .geometry import ObjectSetExpression + +@nodedataclass() +class item(ObjectSetExpression): + name: str + member_of: ObjectSetExpression + + def __repr__(self): + return f'item({self.name})' + + def children(self): + # member_of is metadata, should not be treated as a child + return [] + +@nodedataclass() +class ForAll(BoolExpression): + objs: ObjectSetExpression + var: str + pred: BoolExpression + +@ObjectSetExpression.register_postfix_func +def all( + objs: ObjectSetExpression, + pred: typing.Callable[[item], BoolExpression] +) -> BoolExpression: + var = 'var_all_' + str(id(pred)) + return ForAll(objs, var, pred(item(var, objs))) + +@nodedataclass() +class SumOver(ScalarExpression): + objs: ObjectSetExpression + var: str + pred: ScalarExpression + +@ObjectSetExpression.register_postfix_func +def sum( + objs: ObjectSetExpression, + pred: typing.Callable[[item], ScalarExpression] +): + var = 'var_sum_' + str(id(pred)) + return SumOver(objs, var, pred(item(var, objs))) + +@nodedataclass() +class MeanOver(ScalarExpression): + objs: ObjectSetExpression + var: str + pred: ScalarExpression + +@ObjectSetExpression.register_postfix_func +def mean( + objs: ObjectSetExpression, + pred: typing.Callable[[item], ScalarExpression] +): + var = 'var_mean_' + str(id(pred)) + return MeanOver(objs, var, pred(item(var, objs))) \ No newline at end of file diff --git a/infinigen/core/constraints/constraint_language/geometry.py b/infinigen/core/constraints/constraint_language/geometry.py new file mode 100644 index 000000000..2e4fd8851 --- /dev/null +++ b/infinigen/core/constraints/constraint_language/geometry.py @@ -0,0 +1,92 @@ +# 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: Karhan Kayan + +import typing +from dataclasses import dataclass, field + +import numpy as np + +from infinigen.core import tags as t +from .relations import Relation +from .expression import Expression, BoolExpression, ScalarExpression, nodedataclass +from .set_reasoning import ObjectSetExpression + +@nodedataclass() +class center_stable_surface_dist(ScalarExpression): + objs: ObjectSetExpression + +@nodedataclass() +class accessibility_cost(ScalarExpression): + objs: ObjectSetExpression + others: ObjectSetExpression + normal: np.array = field(default=np.array([1, 0, 0])) + dist: float = 1.0 + + def __post_init__(self): + if isinstance(self.normal, (list, tuple)): + self.normal = np.array(self.normal) + assert isinstance(self.normal, np.ndarray) + +@ObjectSetExpression.register_postfix_func +@nodedataclass() +class distance(ScalarExpression): + objs: ObjectSetExpression + others: ObjectSetExpression + others_tags: set = field(default_factory=set) + + def __post_init__(self): + assert isinstance(self.objs, ObjectSetExpression) + assert isinstance(self.others, ObjectSetExpression) + assert isinstance(self.others_tags, set) + +@nodedataclass() +class min_distance_internal(ScalarExpression): + objs: ObjectSetExpression + +@nodedataclass() +class focus_score(ScalarExpression): + objs: ObjectSetExpression + others: ObjectSetExpression + +@nodedataclass() +class angle_alignment_cost(ScalarExpression): + objs: ObjectSetExpression + others: ObjectSetExpression + others_tags: set = None + + def __post_init__(self): + if self.others_tags is None: + self.others_tags = set() + assert isinstance(self.others_tags, set), type(self.others_tags) + +@nodedataclass() +class freespace_2d(ScalarExpression): + objs: ObjectSetExpression + others: ObjectSetExpression + +@nodedataclass() +class min_dist_2d(ScalarExpression): + objs: ObjectSetExpression + others: ObjectSetExpression + +@nodedataclass() +class rotational_asymmetry(ScalarExpression): + objs: ObjectSetExpression + +@nodedataclass() +class reflectional_asymmetry(ScalarExpression): + objs: ObjectSetExpression + others: ObjectSetExpression + use_long_plane: bool = True + +@ObjectSetExpression.register_postfix_func +@nodedataclass() +class volume(ScalarExpression): + objs: ObjectSetExpression + dims: int | tuple = 3 + +@nodedataclass() +class coplanarity_cost(ScalarExpression): + objs: ObjectSetExpression diff --git a/infinigen/core/constraints/constraint_language/relations.py b/infinigen/core/constraints/constraint_language/relations.py new file mode 100644 index 000000000..74483d0b0 --- /dev/null +++ b/infinigen/core/constraints/constraint_language/relations.py @@ -0,0 +1,414 @@ +# 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 + +from __future__ import annotations + +from abc import ABC, abstractmethod +from dataclasses import dataclass, field, fields +from enum import Enum +from typing import Optional , Union +from copy import deepcopy +import logging + +from infinigen.core import tags as t +from infinigen.core.constraints import constraint_language as cl + +logger = logging.getLogger(__name__) + +@dataclass(frozen=True) +class Relation(ABC): + + @abstractmethod + def implies(self, other) -> bool: + """ + self must imply all parts of other, both positive and negative. + """ + pass + + @abstractmethod + def satisfies(self, other: Relation) -> bool: + """ + self must imply all positive parts of other, and not contradict any negative parts + """ + pass + + @abstractmethod + def intersects(self, other, strict=False) -> bool: + pass + + @abstractmethod + def intersection(self, other: Relation) -> Relation: + pass + + @abstractmethod + def difference(self, other: Relation) -> Relation: + pass + + def __neg__(self) -> Relation: + return NegatedRelation(self) + +@dataclass(frozen=True) +class AnyRelation(Relation): + + def implies(self, other) -> bool: + return other.__class__ is AnyRelation + + def satisfies(self, other: cl.Relation) -> bool: + return other.__class__ is AnyRelation + + def intersects(self, _other: Relation, strict=False) -> bool: + return True + + def intersection(self, other: Relation) -> Relation: + return deepcopy(other) + + def difference(self, other: Relation): + return -other + +@dataclass(frozen=True) +class NegatedRelation(Relation): + + rel: Relation + + def __repr__(self): + return f'-{self.rel}' + + def __str__(self): + return f'{self.__class__.__name__}({self.rel})' + + def __neg__(self) -> Relation: + return self.rel + + def implies(self, other: Relation) -> bool: + match other: + case AnyRelation(): + return False + case NegatedRelation(rel): + return self.rel.implies(rel) + case _: + return ( + not self.rel.implies(other) + and not self.intersects(other, strict=True) + ) + + def satisfies(self, other: cl.Relation) -> bool: + match other: + case AnyRelation(): + return False + case NegatedRelation(rel): + return self.rel.satisfies(rel) + case _: + return ( + not self.rel.satisfies(other) + and not self.intersects(other, strict=True) + ) + + def intersects(self, other: Relation, strict=False) -> bool: + + match other: + case NegatedRelation(rel): + if isinstance(self.rel, AnyRelation) or isinstance(rel, AnyRelation): + return False + # TODO hacky, allows false positives for GeometryRelation. very unlikely to come up however + return True + case _: + # implementationn depends on other's type, let them handle it + return other.intersects(self, strict=strict) + + def intersection(self, other: Relation) -> Relation: + return self.rel.difference(other) + + def difference(self, other: Relation) -> Relation: + return self.rel.intersection(other) + +class ConnectorType(Enum): + Door = "door" + Open = "open" + Wall = "wall" + +@dataclass(frozen=True) +class RoomNeighbour(Relation): + connector_types: frozenset[ConnectorType] = field(default_factory=frozenset) + + def __post_init__(self): + if self.connector_types is not None: + object.__setattr__(self, 'connector_types', frozenset(self.connector_types)) + + + def implies(self, other: Relation) -> bool: + + if isinstance(other, AnyRelation): + return True + + return ( + isinstance(other, RoomNeighbour) + and self.connector_types.issuperset(other.connector_types) + ) + + def satisfies(self, other: Relation) -> bool: + return self.implies(other) + + def intersects(self, other: Relation, strict=False) -> bool: + + if isinstance(other, AnyRelation): + return True + + return ( + isinstance(other, RoomNeighbour) + and not self.connector_types.isdisjoint(other.connector_types) + ) + + def intersection(self, other: Relation) -> Relation: + + if isinstance(other, AnyRelation): + return deepcopy(self) + + return self.__class__( + connector_types=self.connector_types.intersection(other.connector_types) + ) + + def difference(self, other: Relation) -> Relation: + + if isinstance(other, AnyRelation): + return -AnyRelation() + + return self.__class__( + connector_types=self.connector_types.difference(other.connector_types) + ) + + +def no_frozenset_repr(self: GeometryRelation): + is_neg = lambda x: isinstance(x, t.Negated) + setrepr = lambda s: f'{{{", ".join(repr(x) for x in sorted(list(s), key=is_neg))}}}' + return f'{self.__class__.__name__}({setrepr(self.child_tags)}, {setrepr(self.parent_tags)})' + +@dataclass(frozen=True) +class GeometryRelation(Relation): + child_tags: frozenset[t.Subpart] = field(default_factory=frozenset) + parent_tags: frozenset[t.Subpart] = field(default_factory=frozenset) + + __repr__ = no_frozenset_repr + + def __post_init__(self): + # allow the user to init with sets that subsequently get frozen + # use object.__setattr__ to bypass dataclass's frozen since it is guaranteed safe here + object.__setattr__(self, 'child_tags', frozenset(self.child_tags)) + object.__setattr__(self, 'parent_tags', frozenset(self.parent_tags)) + + def _extra_fields(self) -> list[str]: + """Return any fields added by subclasses. Useful for implementing implies/intersects + which must check these fields regardless of inheritance. TODO, Hacky. + """ + return [ + f.name + for f in fields(self) + if f.name not in ['child_tags', 'parent_tags'] + ] + + def _compatibility_checks(self, other: GeometryRelation, strict_on_fields=False) -> bool: + + if not issubclass(other.__class__, self.__class__): + return False + + if strict_on_fields: + for k in self._extra_fields(): + if not getattr(self, k) == getattr(other, k): + #logger.warning(f'{self._compatibility_checks} ignoring mismatch {k=} for {other=}') + return False + + return True + + def implies(self, other: Relation) -> bool: + + match other: + case AnyRelation(): + return True + case NegatedRelation(AnyRelation()): + return False + case GeometryRelation(ochild, oparent): + if not self._compatibility_checks(other): + logger.debug(f'{self.implies} failed compatibility for %s', other) + return False + if not t.implies(self.child_tags, ochild): + logger.debug(f'{self.implies} failed child tags for %s', other) + return False + if not t.implies(self.parent_tags, oparent): + logger.debug(f'{self.implies} failed parent tags for %s', other) + return False + return True + case NegatedRelation(GeometryRelation(ochild, oparent)): + if not self._compatibility_checks(other.rel): + logger.debug(f'{self.implies} failed compatibility for %s', other) + return False + if ( + t.implies(self.child_tags, {-t for t in ochild}) + and t.implies(self.parent_tags, {-t for t in oparent}) + ): + return True + return False + case _: + raise ValueError(f'{self.implies} encountered unhandled {other=}') + + def satisfies(self, other: Relation) -> bool: + match other: + case AnyRelation(): + return True + case NegatedRelation(AnyRelation()): + return False + case GeometryRelation(ochild, oparent): + if not self._compatibility_checks(other): + logger.debug(f'{self.satisfies} failed compatibility for %s', other) + return False + if not t.satisfies(self.child_tags, ochild): + logger.debug(f'{self.satisfies} failed child tags for %s', other) + return False + if not t.satisfies(self.parent_tags, oparent): + logger.debug(f'{self.satisfies} failed parent tags for %s', other) + return False + return True + case NegatedRelation(GeometryRelation(ochild, oparent)): + if not self._compatibility_checks(other.rel): + logger.debug(f'{self.implies} failed compatibility for %s', other) + return False + if ( + t.satisfies(self.child_tags, {-t for t in ochild}) + and t.satisfies(self.parent_tags, {-t for t in oparent}) + ): + return True + return False + case _: + raise ValueError(f'{self.satisfies} encountered unhandled {other=}') + + def intersects(self, other: Relation, strict=False) -> bool: + + def tags_compatible(a, b): + if strict: + return t.implies(a, b) or t.implies(b, a) + else: + return not t.contradiction(a.union(b)) + + logger.debug(f'{self.intersects} other=%s', other) + + match other: + case AnyRelation(): + return True + case NegatedRelation(AnyRelation()): + return False + case GeometryRelation(ochild, oparent): + if not self._compatibility_checks(other): + logger.debug(f'{self.intersects} failed compatibility for other=%s', other) + return False + if not tags_compatible(self.child_tags, ochild): + logger.debug(f'{self.intersects} failed child tags for other=%s', other) + return False + if not tags_compatible(self.parent_tags, oparent): + logger.debug('{self.intersects} failed parent tags for other=%s', other) + return False + return True + case NegatedRelation(GeometryRelation()): + # is self compatible with NOT other.rel? + # true unless other.rel->self + return not other.rel.implies(self) + case _: + logger.warning(f'{self.intersects} encountered unhandled %s, returning False', other) + return False + + def intersection(self: Relation, other: Relation) -> Relation: + + """ TODO: There are potentially many intersections of relations with negations. + """ + + match other: + case AnyRelation(): + return deepcopy(self) + case NegatedRelation(rel): + return self.difference(rel) + case GeometryRelation(ochild, oparent): + if not self._compatibility_checks(other): + logger.warning(f'{self.intersection} failed compatibility for {other=}') + return -AnyRelation() + return self.__class__( + child_tags=self.child_tags.union(ochild), + parent_tags=self.parent_tags.union(oparent), + **{k: getattr(self, k) for k in self._extra_fields()} + ) + case _: + logger.warning(f'Encountered unhandled {other=} for {self.intersection}') + return -AnyRelation() + + def difference(self: Relation, other: Relation) -> Relation: + + match other: + case AnyRelation(): + return -AnyRelation() + case NegatedRelation(rel): + return self.intersection(rel) + case GeometryRelation(ochild, oparent): + if not self.intersects(other): + return deepcopy(self) + if ( + t.implies(self.child_tags, ochild) + and t.implies(self.parent_tags, oparent) + ): + return -AnyRelation() + + return self.__class__( + child_tags=t.difference(self.child_tags, ochild), + parent_tags=t.difference(self.parent_tags, oparent), + **{k: getattr(self, k) for k in self._extra_fields()} + ) + case _: + logger.warning(f'Encountered unhandled {other=} for {self.intersection}') + return -AnyRelation() + +@dataclass(frozen=True) +class Touching(GeometryRelation): + __repr__ = no_frozenset_repr + + +@dataclass(frozen=True) +class SupportedBy(Touching): + __repr__ = no_frozenset_repr + + +@dataclass(frozen=True) +class StableAgainst(GeometryRelation): + margin: float = 0 + + # check_ if False, only check x/z stability, z is allowed to overhand. + # typical use is chair-against-table relation + check_z: bool = True + + # rev_normal: if True, align the normals so they face the SAME direction, rather than two planes facing eachother. + # typical use is for sink embedded in countertop + rev_normal: bool = False + + __repr__ = no_frozenset_repr + + +@dataclass(frozen=True) +class CutFrom(Relation): + + def implies(self, other: Relation) -> bool: + return ( + isinstance(other, AnyRelation) + or isinstance(other, CutFrom) + ) + + def satisfies(self, other: Relation) -> bool: + return self.implies(other) + + def intersects(self, other: Relation, strict=False) -> bool: + return ( + isinstance(other, AnyRelation) + or isinstance(other, CutFrom) + ) + + def intersection(self, other: Relation) -> Relation: + return deepcopy(self) + + def difference(self, other: Relation) -> Relation: + return -AnyRelation() diff --git a/infinigen/core/constraints/constraint_language/result.py b/infinigen/core/constraints/constraint_language/result.py new file mode 100644 index 000000000..f58c59c4d --- /dev/null +++ b/infinigen/core/constraints/constraint_language/result.py @@ -0,0 +1,32 @@ +# 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 typing +from dataclasses import dataclass, field + +import numpy as np + +from .types import Node +from .expression import BoolExpression, ScalarExpression, nodedataclass + +@nodedataclass() +class Problem(Node): + + constraints: dict[str, BoolExpression] + score_terms: dict[str, ScalarExpression] + + def __post_init__(self): + + if isinstance(self.constraints, list): + self.constraints = {i: c for i, c in enumerate(self.constraints)} + if isinstance(self.score_terms, list): + self.score_terms = {i: s for i, s in enumerate(self.score_terms)} + + def children(self): + for i, v in enumerate(self.constraints.values()): + yield f'constraints[{i}]', v + for i, v in enumerate(self.score_terms.values()): + yield f'score_terms[{i}]', v \ No newline at end of file diff --git a/infinigen/core/constraints/constraint_language/set_reasoning.py b/infinigen/core/constraints/constraint_language/set_reasoning.py new file mode 100644 index 000000000..b87df0e36 --- /dev/null +++ b/infinigen/core/constraints/constraint_language/set_reasoning.py @@ -0,0 +1,83 @@ +# 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 typing +from dataclasses import dataclass, field + +from infinigen.core import tags as t +from infinigen.core.constraints import usage_lookup +from .relations import Relation, AnyRelation +from .expression import Expression, BoolExpression, ScalarExpression, nodedataclass + +@nodedataclass() +class ObjectSetExpression(Expression): + + def __getitem__(self, key): + return tagged(self, key) + +@nodedataclass() +class scene(ObjectSetExpression): + pass + +@ObjectSetExpression.register_postfix_func +@nodedataclass() +class tagged(ObjectSetExpression): + objs: ObjectSetExpression + tags: set[t.Tag] = field(default_factory=set) + + def __post_init__(self): + self.tags = t.to_tag_set(self.tags, fac_context=usage_lookup._factory_lookup) + + +@ObjectSetExpression.register_postfix_func +def excludes(objs, tags): + + # syntactic helper - assume people wont construct obvious contradictions + if isinstance(objs, tagged): + tags = tags.difference(objs.tags) + + return tagged(objs, {t.Negated(x) for x in tags}) + +@ObjectSetExpression.register_postfix_func +@nodedataclass() +class related_to(ObjectSetExpression): + child: ObjectSetExpression + parent: ObjectSetExpression + relation: Relation = field(default_factory=AnyRelation) + + def __post_init__(self): + if not isinstance(self.child, ObjectSetExpression): + raise TypeError(f'related_to got {self.child=}, must be an ObjectSetExpression') + if not isinstance(self.parent, ObjectSetExpression): + raise TypeError(f'related_to got {self.parent=}, must be an ObjectSetExpression') + if not isinstance(self.relation, Relation): + raise TypeError(f'related_to got {self.relation=}, must be a Relation') + +@ObjectSetExpression.register_postfix_func +@nodedataclass() +class count(ScalarExpression): + objs: ObjectSetExpression + + def __post_init__(self): + if not isinstance(self.objs, ObjectSetExpression): + raise TypeError(f'count got {self.objs=}, must be an ObjectSetExpression') + + +@ScalarExpression.register_postfix_func +@nodedataclass() +class in_range(BoolExpression): + val: ScalarExpression + low: float + high: float + + def __post_init__(self): + if not isinstance(self.val, ScalarExpression): + raise TypeError(f'in_range got {self.val=}, must be a ScalarExpression') + if not isinstance(self.low, (int, float)): + raise TypeError(f'in_range got {self.low=}, must be a number') + if not isinstance(self.high, (int, float)): + raise TypeError(f'in_range got {self.high=}, must be a number') + diff --git a/infinigen/core/constraints/constraint_language/types.py b/infinigen/core/constraints/constraint_language/types.py new file mode 100644 index 000000000..d6e07686d --- /dev/null +++ b/infinigen/core/constraints/constraint_language/types.py @@ -0,0 +1,45 @@ +# 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 + +from enum import Enum +from typing import Any +from dataclasses import dataclass +import functools + +nodedataclass_kwargs = dict(eq=False, order=False) + +def _nodeclass_bool_throw(self): + raise RuntimeError( + f'Attempted to convert {self.__class__} to bool, ' + f"truth value of {self} is ambiguous. Constraint language must use * instead of `and`, etc since python bool ops are not overridable" + ) + +def nodedataclass(frozen=False): + def decorator(cls): + ddec = dataclass(eq=False, order=False, frozen=frozen) + cls = ddec(cls) + cls.__bool__ = _nodeclass_bool_throw + return cls + return decorator + +@nodedataclass() +class Node: + + def children(self): + for k, v in self.__dict__.items(): + if isinstance(v, Node): + yield k, v + + def traverse(self, inorder=True): + if inorder: + yield self + for _, c in self.children(): + yield from c.traverse(inorder=inorder) + if not inorder: + yield self + + def size(self): + return len(list(self.traverse())) diff --git a/infinigen/core/constraints/constraint_language/util.py b/infinigen/core/constraints/constraint_language/util.py new file mode 100644 index 000000000..8d5d0bffc --- /dev/null +++ b/infinigen/core/constraints/constraint_language/util.py @@ -0,0 +1,404 @@ +# 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: Karhan Kayan +from typing import Union +import random +import math +import functools +import logging + +import bpy +import mathutils +from trimesh import Trimesh, Scene +import trimesh +from shapely import LineString, Point, Polygon, MultiPolygon +import numpy as np +from sklearn.decomposition import PCA +import bpy +import fcl + +from mathutils import Matrix, Vector +import gin + +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t + +logger = logging.getLogger(__name__) + +@gin.configurable +def bvh_caching_config(enabled=True): + return enabled + +@functools.cache +def group(scene, x): + if isinstance(x, (list, set)): + x = tuple(x) + return subset(scene, x) + +def meshes_from_names(scene, names): + if isinstance(names, str): + names = [names] + return [scene.geometry[g] for _, g in (scene.graph[n] for n in names)] + +def blender_objs_from_names(names): + if isinstance(names, str): + names = [names] + return [bpy.data.objects[n] for n in names] + +def name_from_mesh(scene, mesh): + mesh_name = None + for name, mesh in scene.geometry.items(): + if mesh == mesh: + mesh_name = name + break + return mesh_name + +def project_to_xy_path2d(mesh: trimesh.Trimesh) -> trimesh.path.Path2D: + poly = trimesh.path.polygons.projected(mesh, (0,0,1), (0,0,0)) + d = trimesh.path.exchange.misc.polygon_to_path(poly) + return trimesh.path.Path2D(entities = d['entities'], vertices = d['vertices']) + +def project_to_xy_poly(mesh: trimesh.Trimesh): + poly = trimesh.path.polygons.projected(mesh, (0,0,1), (0,0,0)) + return poly + + +def closest_edge_to_point_poly(polygon, point): + closest_distance = float('inf') + closest_edge = None + + for i, coord in enumerate(polygon.exterior.coords[:-1]): + start, end = coord, polygon.exterior.coords[i + 1] + line = LineString([start, end]) + distance = line.distance(point) + + if distance < closest_distance: + closest_distance = distance + closest_edge = line + + return closest_edge + +def closest_edge_to_point_edge_list(edge_list: list[LineString], point): + closest_distance = float('inf') + closest_edge = None + + for line in edge_list: + distance = line.distance(point) + + if distance < closest_distance: + closest_distance = distance + closest_edge = line + + return closest_edge + +def compute_outward_normal(line, polygon): + dx = line.xy[0][1] - line.xy[0][0] # x1 - x0 + dy = line.xy[1][1] - line.xy[1][0] # y1 - y0 + + # Candidate normal vectors (perpendicular to edge) + normal_vector_1 = np.array([dy, -dx]) + normal_vector_2 = -normal_vector_1 + + # Normalize the vectors (optional but recommended for consistency) + normal_vector_1 = normal_vector_1 / np.linalg.norm(normal_vector_1) + normal_vector_2 = normal_vector_2 / np.linalg.norm(normal_vector_2) + + # Midpoint of the line segment + mid_point = line.interpolate(0.5, normalized=True) + + # Move a tiny bit in the direction of the normals to check which points outside + test_point_1 = mid_point.coords[0] + 0.01 * normal_vector_1 + test_point_2 = mid_point.coords[0] + 0.01 * normal_vector_2 + + # Return the normal for which the test point lies outside the polygon + if polygon.contains(Point(test_point_1)): + return normal_vector_2 + else: + return normal_vector_1 + + +def get_transformed_axis(scene, obj_name): + obj = bpy.data.objects[obj_name] + trimesh_mesh = meshes_from_names(scene, obj_name)[0] + axis = trimesh_mesh.axis + rot_mat = np.array(obj.matrix_world.to_3x3()) + return rot_mat @ np.array(axis) + + +def set_axis(scene, objs: Union[str, list[str]], canonical_axis): + if isinstance(objs, str): + objs = [objs] + obj_meshes = meshes_from_names(scene, objs) + for obj_name, obj in zip(objs, obj_meshes): + obj.axis = canonical_axis + obj.axis = get_transformed_axis(scene, obj_name) + + + + +def get_plane_from_3dmatrix(matrix): + """Extract the plane_normal and plane_origin from a transformation matrix.""" + # The normal of the plane can be extracted from the 3x3 rotation part of the matrix + plane_normal = matrix[:3, 2] + plane_origin = matrix[:3, 3] + return plane_normal, plane_origin + + +def project_points_onto_plane(points, plane_origin, plane_normal): + """Project 3D points onto a plane.""" + d = np.dot(points - plane_origin, plane_normal)[:, None] + return points - d * plane_normal + +def to_2d_coordinates(points, plane_normal): + """Convert 3D points to 2D using the plane defined by its normal.""" + # Compute two perpendicular vectors on the plane + u = np.cross(plane_normal, [1, 0, 0]) + if np.linalg.norm(u) < 1e-10: + u = np.cross(plane_normal, [0, 1, 0]) + u /= np.linalg.norm(u) + v = np.cross(plane_normal, u) + v /= np.linalg.norm(v) + + # Convert 3D points to 2D using dot products + return np.column_stack([points.dot(u), points.dot(v)]) + + +def ensure_correct_order(points): + """ + Ensures the points are in counter-clockwise order. + If not, it reverses them. + """ + # Calculate signed area + n = len(points) + area = sum((points[i][0] * points[(i+1)%n][1]) - (points[(i+1)%n][0] * points[i][1]) for i in range(n)) / 2.0 + # Return the points in reverse order if area is negative + return points[::-1] if area < 0 else points + + + +def sample_random_point(polygon): + """ + Sample a random point from inside the given Shapely polygon. + """ + minx, miny, maxx, maxy = polygon.bounds + while True: + p = Point(random.uniform(minx, maxx), random.uniform(miny, maxy)) + if polygon.contains(p): + return p + +def delete_obj(scene, a, delete_blender=True): + if isinstance(a, str): + a = [a] + if delete_blender: + obj_list = [bpy.data.objects[obj_name] for obj_name in a] + butil.delete(obj_list) + for obj_name in a: + # bpy.data.objects.remove(bpy.data.objects[obj_name], do_unlink=True) + if scene: + scene.graph.transforms.remove_node(obj_name) + scene.delete_geometry(obj_name + '_mesh') + + +def global_vertex_coordinates(obj, local_vertex) -> Vector: + return obj.matrix_world @ local_vertex.co + +def global_polygon_normal(obj, polygon): + loc, rot, scale = obj.matrix_world.decompose() + rot = rot.to_matrix() + normal = rot @ polygon.normal + return normal / np.linalg.norm(normal) + +def is_planar(obj, tolerance=1e-6): + if len(obj.data.polygons) != 1: + return False + + polygon = obj.data.polygons[0] + global_normal = global_polygon_normal(obj, polygon) + + # Take the first vertex as a reference point on the plane + ref_vertex = global_vertex_coordinates(obj, obj.data.vertices[polygon.vertices[0]]) + + # Check if all vertices lie on the plane defined by the reference vertex and the global normal + for vertex in obj.data.vertices: + distance = (global_vertex_coordinates(obj, vertex) - ref_vertex).dot(global_normal) + if not math.isclose(distance, 0, abs_tol=tolerance): + return False + + return True + +def planes_parallel(plane_obj_a, plane_obj_b, tolerance=1e-6): + if plane_obj_a.type != 'MESH' or plane_obj_b.type != 'MESH': + raise ValueError("Both objects should be of type 'MESH'") + + # # Check if the objects are planar + # if not is_planar(plane_obj_a) or not is_planar(plane_obj_b): + # raise ValueError("One or both objects are not planar") + + global_normal_a = global_polygon_normal(plane_obj_a, plane_obj_a.data.polygons[0]) + global_normal_b = global_polygon_normal(plane_obj_b, plane_obj_b.data.polygons[0]) + + dot_product = global_normal_a.dot(global_normal_b) + + return math.isclose(dot_product, 1, abs_tol=tolerance) or math.isclose(dot_product, -1, abs_tol=tolerance) + + +def distance_to_plane(point, plane_point, plane_normal): + """Compute the distance from a point to a plane defined by a point and a normal.""" + return abs((point - plane_point).dot(plane_normal)) + + +def subset(scene: Scene, incl): + + if isinstance(incl, str): + incl = [incl] + + objs = [] + for n in scene.graph.nodes: + T, g = scene.graph[n] + if g is None: + continue + otags = scene.geometry[g].metadata['tags'] + if any(t in incl for t in otags): + objs.append(n) + + # assert len(objs) > 0, incl + + return objs + + +def add_object_cached(col, + name, + col_obj, + fcl_obj): + geom = fcl_obj + o = col_obj + # # Add collision object to set + if name in col._objs: + col._manager.unregisterObject(col._objs[name]) + col._objs[name] = {'obj': o, + 'geom': geom} + # # store the name of the geometry + col._names[id(geom)] = name + + col._manager.registerObject(o) + col._manager.update() + return o + +def col_from_subset(scene, names, tags=None, bvh_cache=None): + + if isinstance(names, str): + names = [names] + + + if bvh_cache is not None and bvh_caching_config(): + tag_key = frozenset(tags) if tags is not None else None + key = (frozenset(names), tag_key) + res = bvh_cache.get(key) + if res is not None: + return res + + col = trimesh.collision.CollisionManager() + + for name in names: + T, g = scene.graph[name] + geom = scene.geometry[g] + if tags is not None and len(tags) > 0: + obj = blender_objs_from_names(name)[0] + mask = tagging.tagged_face_mask(obj, tags) + if not mask.any(): + logger.warning(f'{name=} had {mask.sum()=} for {tags=}') + continue + geom = geom.submesh(np.where(mask), append=True) + T = trimesh.transformations.identity_matrix() + t = fcl.Transform(T[:3, :3], T[:3, 3]) + geom.fcl_obj = col._get_fcl_obj(geom) + geom.col_obj = fcl.CollisionObject(geom.fcl_obj, t) + assert len(geom.faces) == mask.sum() + # col.add_object(name, geom, T) + add_object_cached(col, name, geom.col_obj, geom.fcl_obj) + + if len(col._objs) == 0: + logger.debug(f'{names=} got no objs, returning None') + col = None + + if bvh_cache is not None and bvh_caching_config(): + bvh_cache[key] = col + + return col + +def plot_geometry(ax, geom, color = 'blue'): + if isinstance(geom, Polygon): + x, y = geom.exterior.xy + ax.fill(x, y, alpha=0.5, fc=color, ec='black') + elif isinstance(geom, MultiPolygon): + for sub_geom in geom: + x, y = sub_geom.exterior.xy + ax.fill(x, y, alpha=0.5, fc=color, ec='black') + elif isinstance(geom, LineString): + x, y = geom.xy + ax.plot(x, y, color=color) + elif isinstance(geom, Point): + ax.plot(geom.x, geom.y, 'o', color=color) + + +def sync_trimesh(scene: trimesh.Scene, obj_name: str): + bpy.context.view_layer.update() + blender_obj = bpy.data.objects[obj_name] + mesh = meshes_from_names(scene, obj_name)[0] + T_old = mesh.current_transform + T = np.array(blender_obj.matrix_world) + mesh.apply_transform(T @ np.linalg.inv(T_old)) + mesh.current_transform = np.array(blender_obj.matrix_world) + t = fcl.Transform(T[:3, :3], T[:3, 3]) + mesh.col_obj.setTransform(t) + +def translate(scene: trimesh.Scene, a: str, translation): + blender_obj = bpy.data.objects[a] + blender_obj.location += Vector(translation) + if scene: + sync_trimesh(scene, a) + + +def rotate(scene: trimesh.Scene, a: str, axis, angle): + blender_obj = bpy.data.objects[a] + + rotation_matrix = trimesh.transformations.rotation_matrix(angle, axis) + transform_matrix = Matrix(rotation_matrix).to_4x4() + loc, rot, scale = blender_obj.matrix_world.decompose() + rot = rot.to_matrix().to_4x4() + rot = transform_matrix @ rot + rot = rot.to_quaternion() + blender_obj.matrix_world = Matrix.LocRotScale(loc, rot, scale) + + if scene: + sync_trimesh(scene, a) + + +def set_location(scene: trimesh.Scene, obj_name: str, location): + blender_mesh = bpy.data.objects[obj_name] + blender_mesh.location = location + sync_trimesh(scene, obj_name) + + + +def set_rotation(scene: trimesh.Scene, obj_name: str, rotation): + blender_mesh = blender_objs_from_names(obj_name)[0] + blender_mesh.rotation_euler = rotation + sync_trimesh(scene, obj_name) + + +# for debugging. does not actually find centroid +def blender_centroid(a): + return np.mean([a.matrix_world @ v.co for v in a.data.vertices], axis = 0) + + +def order_objects_by_principal_axis(objects: list[bpy.types.Object]): + locations = [obj.location for obj in objects] + location_matrix = np.array(locations) + pca = PCA(n_components=1) + pca.fit(location_matrix) + locations_projected = pca.transform(location_matrix) + sorted_indices = np.argsort(locations_projected.ravel()) + return [objects[i] for i in sorted_indices] + diff --git a/infinigen/core/constraints/evaluator/__init__.py b/infinigen/core/constraints/evaluator/__init__.py new file mode 100644 index 000000000..6300f519b --- /dev/null +++ b/infinigen/core/constraints/evaluator/__init__.py @@ -0,0 +1,10 @@ +from .evaluate import ( + evaluate_problem, + evaluate_node, + EvalResult +) +from .eval_memo import ( + evict_memo_for_move, + evict_memo_for_obj, + memo_key, +) \ No newline at end of file diff --git a/infinigen/core/constraints/evaluator/domain_contains.py b/infinigen/core/constraints/evaluator/domain_contains.py new file mode 100644 index 000000000..f4a8b167a --- /dev/null +++ b/infinigen/core/constraints/evaluator/domain_contains.py @@ -0,0 +1,60 @@ +# 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 bpy + +import logging +from tqdm import tqdm + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r, +) +from infinigen.core.constraints.example_solver import state_def +from infinigen.core import tags as t + +logger = logging.getLogger(__name__) + +def domain_contains( + dom: r.Domain, + state: state_def.State, + obj: state_def.ObjectState +): + + assert isinstance(dom, r.Domain), dom + assert isinstance(obj, state_def.ObjectState), obj + + if not t.satisfies(obj.tags, dom.tags): + #logger.debug(f"domain_contains failed, {obj} does not satisfy {obj.tags}") + return False + + for rel, dom in dom.relations: + + if isinstance(rel, cl.NegatedRelation): + if any( + relstate.relation.intersects(rel.rel) and + domain_contains(dom, state, state.objs[relstate.target_name]) + for relstate in obj.relations + ): + #logger.debug(f"domain_contains failed, {obj} satisfies negative {rel} {dom}") + return False + else: + if not any( + relstate.relation.intersects(rel) and + domain_contains(dom, state, state.objs[relstate.target_name]) + for relstate in obj.relations + ): + #logger.debug(f"domain_contains failed, {obj} does not satisfy {rel} {dom}") + return False + + return True + +def objkeys_in_dom(dom: r.Domain, curr: state_def.State): + return [ + k for k, o in curr.objs.items() + if domain_contains(dom, curr, o) and o.active + ] + \ No newline at end of file diff --git a/infinigen/core/constraints/evaluator/eval_memo.py b/infinigen/core/constraints/evaluator/eval_memo.py new file mode 100644 index 000000000..91d0ea0b5 --- /dev/null +++ b/infinigen/core/constraints/evaluator/eval_memo.py @@ -0,0 +1,115 @@ +# 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 logging + +from infinigen.core.constraints.example_solver.state_def import State, ObjectState +from infinigen.core.constraints.example_solver import moves + +from infinigen.core.constraints import ( + constraint_language as cl +) +from infinigen.core import tags as t + +logger = logging.getLogger(__name__) + +def memo_key(n: cl.Node): + match n: + case cl.item(var): + return var + case cl.scene(): + return cl.scene + case _: + return id(n) + +def evict_memo_for_obj( + node: cl.Problem, + memo: dict, + obj: ObjectState +): + + recvals = [ + evict_memo_for_obj(child, memo, obj) + for _, child in node.children() + ] + res = any(recvals) + + match node: + case cl.tagged(_, tags): + if not t.implies(obj.tags, tags): + res = False + case cl.scene(): + res = True + case _: + pass + + key = memo_key(node) + if res and key in memo: + del memo[key] + + return res + +def reset_bvh_cache(state, filter_name=None): + + ''' + filter_name: if specified, only get rid of things containing this + ''' + + static_tags = {t.Semantics.Room, t.Semantics.Cutter} + + def keep_key(k): + + names, tags = k + + if filter_name is not None: + obj = state.objs[filter_name].obj + return not (obj.name in names) + + for n in names: + if n not in state.objs: + return False + ostate = state.objs[n] + if not ostate.tags.intersection(static_tags): + return False + + return True + + prev_keys = list(state.bvh_cache.keys()) + for k in prev_keys: + res = keep_key(k) + if res: + continue + del state.bvh_cache[k] + + logger.debug(f'reset_bvh_cache evicted {len(prev_keys) - len(state.bvh_cache)} out of {len(prev_keys)} orig') + +def evict_memo_for_move( + problem: cl.Problem, + state: State, + memo: dict, + move: moves.Move +): + match move: + case ( + moves.TranslateMove(names) | + moves.RotateMove(names) | + moves.Addition(names=names) | + moves.ReinitPoseMove(names=names) | + moves.RelationPlaneChange(names=names) | + moves.Resample(names=names) + ): + for name in names: + assert name is not None, move + evict_memo_for_obj(problem, memo, state.objs[name]) + reset_bvh_cache(state, filter_name=name) + case moves.Deletion(name): + # TODO hack - delete everything since we cant evict for specific obj after it has ben deleted + # easily fixable with more work / refactoring + for k in list(memo.keys()): + del memo[k] + reset_bvh_cache(state) + case _: + raise NotImplementedError(f'Unsure what to evict for {move=}') \ No newline at end of file diff --git a/infinigen/core/constraints/evaluator/evaluate.py b/infinigen/core/constraints/evaluator/evaluate.py new file mode 100644 index 000000000..6c57343ba --- /dev/null +++ b/infinigen/core/constraints/evaluator/evaluate.py @@ -0,0 +1,255 @@ +# 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 + +from typing import Type, Callable +from dataclasses import dataclass +import copy +import logging +import operator +import pandas as pd + +from infinigen.core.constraints.example_solver.state_def import State +from infinigen.core.constraints.evaluator import node_impl, eval_memo + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r +) + +logger = logging.getLogger(__name__) + +SPECIAL_CASE_NODES = [ + cl.ForAll, + cl.SumOver, + cl.MeanOver, + cl.item, + cl.Problem, + cl.scene +] + +gather_funcs = { + cl.ForAll: all, + cl.SumOver: sum, + cl.MeanOver: lambda vs: (sum(vs) / len(vs)) if len(vs) else 0 +} + +def _compute_node_val(node: cl.Node, state: State, memo: dict): + + match node: + case cl.scene(): + return set(k for k, v in state.objs.items() if v.active) + case cl.ForAll(objs, var, pred) | cl.SumOver(objs, var, pred) | cl.MeanOver(objs, var, pred): + assert isinstance(var, str) + + loop_over_objs = evaluate_node(objs, state, memo) + + results = [] + for o in loop_over_objs: + memo_sub = copy.copy(memo) + memo_sub[var] = {o} + results.append(evaluate_node(pred, state, memo=memo_sub)) + + logger.debug(f'{node.__class__.__name__} had {len(results)=}') + + return gather_funcs[node.__class__](results) + case cl.item(): + raise ValueError( + f'_compute_node_val encountered undefined variable {node}. {memo.keys()}' + ) + case cl.Node() if node.__class__ in node_impl.node_impls: + impl_func = node_impl.node_impls.get(node.__class__) + child_vals = { + name: evaluate_node(c, state, memo) + for name, c in node.children() + } + kwargs = {} + if hasattr(node, 'others_tags'): + kwargs['others_tags'] = getattr(node, 'others_tags') + return impl_func(node, state, child_vals, **kwargs) + case cl.Problem(): + raise TypeError(f'evaluate_node is invalid for {node}, please use evaluate_problem') + case _: + raise NotImplementedError( + f'Couldnt compute value for {type(node)}, please add it to ' + f'{node_impl.node_impls.keys()=} or add a specialcase' + ) + +def relevant( + node: cl.Node, + filter: r.Domain | None +) -> bool: + + if filter is None: + raise ValueError() + return True + + if not isinstance(node, cl.Node): + raise ValueError(f'{node=}') + + match node: + + case cl.ObjectSetExpression(): + d = r.constraint_domain(node, finalize_variables=True) + assert r.domain_finalized(d), f'{relevant.__name__} encountered unfinalized {d=}' + res = d.intersects(filter, require_satisfies_right=True) + logger.debug(f'{relevant.__name__} got {res=} for {d=}\n {filter=}') + return res + case _: + return any(relevant(c, filter) for _, c in node.children()) + +def _viol_count_binop( + node: cl.BoolOperatorExpression, + lhs, + rhs +) -> int: + + if not isinstance(lhs, int) or not isinstance(rhs, int): + satisfied = node.func(lhs, rhs) + return 1 if not satisfied else 0 + + match node.func: + case operator.ge: + return max(0, rhs - lhs) + case operator.le: + return max(0, lhs - rhs) + case operator.gt: + return max(0, rhs - lhs + 1) + case operator.lt: + return max(0, lhs - rhs + 1) + case _: + raise ValueError(f'Unhandled {node.func=}') + + +def viol_count( + node: cl.Node, + state: State, + memo: dict, + filter: r.Domain=None +): + + match node: + + case cl.BoolOperatorExpression(operator.and_, cons) | cl.Problem(cons): + res = sum(viol_count(o, state, memo, filter) for o in cons) + case cl.in_range(val, low, high): + + val_res = evaluate_node(val, state, memo) + + if val_res < low: + res = low - val_res + elif val_res > high: + res = val_res - high + else: + res = 0 + + if not relevant(val, filter): + res = 0 + + case cl.BoolOperatorExpression(operator.eq, [lhs, rhs]): + res = abs(evaluate_node(lhs, state, memo) - evaluate_node(rhs, state, memo)) + if not relevant(lhs, filter) and not relevant(rhs, filter): + res = 0 + case cl.ForAll(objs, var, pred): + assert isinstance(var, str) + viol = 0 + for o in evaluate_node(objs, state, memo): + memo_sub = copy.copy(memo) + memo_sub[var] = {o} + viol += viol_count(pred, state, memo_sub, filter) + res = viol + case ( + cl.BoolOperatorExpression(operator.ge, [lhs, rhs]) | + cl.BoolOperatorExpression(operator.le, [rhs, lhs]) | + cl.BoolOperatorExpression(operator.gt, [rhs, lhs]) | + cl.BoolOperatorExpression(operator.lt, [rhs, lhs]) + ): + if relevant(lhs, filter) or relevant(rhs, filter): + l_res = evaluate_node(lhs, state, memo) + r_res = evaluate_node(rhs, state, memo) + res = _viol_count_binop(node, l_res, r_res) + else: + res = 0 + + case cl.constant(val) if isinstance(val, bool): + res = 0 if val else 1 + case _: + raise NotImplementedError(f'{node.__class__.__name__}(...) is not supported for hard constraints. Please use an alternative. Full node was {node}') + + return res + +@dataclass +class ConstraintsViolated: + constraints: list[cl.Node] + + def __bool__(self): + return False + +def evaluate_node(node: cl.Node, state: State, memo=None): + + k = eval_memo.memo_key(node) + + if memo is None: + memo = {} + elif k in memo: + return memo[k] + val = _compute_node_val(node, state, memo) + + memo[k] = val + logger.debug(f'Evaluated {node.__class__} to {val}') + + return val + +@dataclass +class EvalResult: + + loss_vals: dict[str, float] + violations: dict[str, bool] + + def loss(self): + return sum(v for v in self.loss_vals.values()) + + def viol_count(self): + return sum(x for x in self.violations.values()) + + def to_df(self) -> pd.DataFrame: + keys = set(self.loss_vals.keys()).union(self.violations.keys()) + return pd.DataFrame.from_dict({ + k: dict( + loss=self.loss_vals.get(k), + viol_count=self.violations.get(k) + ) + for k in keys + }) + + +def evaluate_problem( + problem: cl.Problem, + state: State, + filter: r.Domain = None, + memo=None +): + + logger.debug( + f'Evaluating problem {len(problem.constraints)=} {len(problem.score_terms)=}' + ) + + if memo is None: + memo = {} + + scores = {} + for name, score_node in problem.score_terms.items(): + logger.debug(f'Evaluating score for {name=}') + scores[name] = evaluate_node(score_node, state, memo) + + violated = {} + for name, node in problem.constraints.items(): + violated[name] = viol_count(node, state, memo, filter=filter) + logger.debug(f'Evaluator found {violated[name]} violations for {name=}') + + return EvalResult( + loss_vals=scores, + violations=violated + ) \ No newline at end of file diff --git a/infinigen/core/constraints/evaluator/indoor_util.py b/infinigen/core/constraints/evaluator/indoor_util.py new file mode 100644 index 000000000..aa61cdd3f --- /dev/null +++ b/infinigen/core/constraints/evaluator/indoor_util.py @@ -0,0 +1,231 @@ +# 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: Karhan Kayan + +import bpy +import trimesh +from trimesh import Scene +from shapely import LineString, Point +import numpy as np +from typing import Union +import random +import math +import functools + + +def meshes_from_names(scene, names): + if isinstance(names, str): + names = [names] + return [scene.geometry[g] for _, g in (scene.graph[n] for n in names)] + +def blender_objs_from_names(names): + if isinstance(names, str): + names = [names] + return [bpy.data.objects[n] for n in names] + +def name_from_mesh(scene, mesh): + mesh_name = None + for name, mesh in scene.geometry.items(): + if mesh == mesh: + mesh_name = name + break + return mesh_name + +def project_to_xy_path2d(mesh: trimesh.Trimesh) -> trimesh.path.Path2D: + poly = trimesh.path.polygons.projected(mesh, (0,0,1), (0,0,0)) + d = trimesh.path.exchange.misc.polygon_to_path(poly) + return trimesh.path.Path2D(entities = d['entities'], vertices = d['vertices']) + +def project_to_xy_poly(mesh: trimesh.Trimesh): + poly = trimesh.path.polygons.projected(mesh, (0,0,1), (0,0,0)) + return poly + + +def closest_edge_to_point(polygon, point): + closest_distance = float('inf') + closest_edge = None + + for i, coord in enumerate(polygon.exterior.coords[:-1]): + start, end = coord, polygon.exterior.coords[i + 1] + line = LineString([start, end]) + distance = line.distance(point) + + if distance < closest_distance: + closest_distance = distance + closest_edge = line + + return closest_edge + +def compute_outward_normal(line, polygon): + dx = line.xy[0][1] - line.xy[0][0] # x1 - x0 + dy = line.xy[1][1] - line.xy[1][0] # y1 - y0 + + # Candidate normal vectors (perpendicular to edge) + normal_vector_1 = np.array([dy, -dx]) + normal_vector_2 = -normal_vector_1 + + # Normalize the vectors (optional but recommended for consistency) + normal_vector_1 = normal_vector_1 / np.linalg.norm(normal_vector_1) + normal_vector_2 = normal_vector_2 / np.linalg.norm(normal_vector_2) + + # Midpoint of the line segment + mid_point = line.interpolate(0.5, normalized=True) + + # Move a tiny bit in the direction of the normals to check which points outside + test_point_1 = mid_point.coords[0] + 0.01 * normal_vector_1 + test_point_2 = mid_point.coords[0] + 0.01 * normal_vector_2 + + # Return the normal for which the test point lies outside the polygon + if polygon.contains(Point(test_point_1)): + return normal_vector_2 + else: + return normal_vector_1 + + +def get_transformed_axis(scene, obj_name): + obj = bpy.data.objects[obj_name] + trimesh_mesh = meshes_from_names(scene, obj_name)[0] + axis = trimesh_mesh.axis + rot_mat = np.array(obj.matrix_world.to_3x3()) + return rot_mat @ np.array(axis) + + +def set_axis(scene, objs: Union[str, list[str]], canonical_axis): + if isinstance(objs, str): + objs = [objs] + obj_meshes = meshes_from_names(scene, objs) + for obj_name, obj in zip(objs, obj_meshes): + obj.axis = canonical_axis + obj.axis = get_transformed_axis(scene, obj_name) + + + + +def get_plane_from_3dmatrix(matrix): + """Extract the plane_normal and plane_origin from a transformation matrix.""" + # The normal of the plane can be extracted from the 3x3 rotation part of the matrix + plane_normal = matrix[:3, 2] + plane_origin = matrix[:3, 3] + return plane_normal, plane_origin + + +def project_points_onto_plane(points, plane_origin, plane_normal): + """Project 3D points onto a plane.""" + d = np.dot(points - plane_origin, plane_normal)[:, None] + return points - d * plane_normal + +def to_2d_coordinates(points, plane_normal): + """Convert 3D points to 2D using the plane defined by its normal.""" + # Compute two perpendicular vectors on the plane + u = np.cross(plane_normal, [1, 0, 0]) + if np.linalg.norm(u) < 1e-10: + u = np.cross(plane_normal, [0, 1, 0]) + u /= np.linalg.norm(u) + v = np.cross(plane_normal, u) + v /= np.linalg.norm(v) + + # Convert 3D points to 2D using dot products + return np.column_stack([points.dot(u), points.dot(v)]) + + +def ensure_correct_order(points): + """ + Ensures the points are in counter-clockwise order. + If not, it reverses them. + """ + # Calculate signed area + n = len(points) + area = sum((points[i][0] * points[(i+1)%n][1]) - (points[(i+1)%n][0] * points[i][1]) for i in range(n)) / 2.0 + # Return the points in reverse order if area is negative + return points[::-1] if area < 0 else points + + + +def sample_random_point(polygon): + """ + Sample a random point from inside the given Shapely polygon. + """ + minx, miny, maxx, maxy = polygon.bounds + while True: + p = Point(random.uniform(minx, maxx), random.uniform(miny, maxy)) + if polygon.contains(p): + return p + + +def delete_obj(a, scene = None): + if isinstance(a, str): + a = [a] + for obj_name in a: + bpy.data.objects.remove(bpy.data.objects[obj_name], do_unlink=True) + if scene: + scene.graph.transforms.remove_node(obj_name) + scene.delete_geometry(obj_name + '_mesh') + + +def global_vertex_coordinates(obj, local_vertex): + return obj.matrix_world @ local_vertex.co + +def global_polygon_normal(obj, polygon): + loc, rot, scale = obj.matrix_world.decompose() + rot = rot.to_matrix() + normal = rot @ polygon.normal + return normal / np.linalg.norm(normal) + +def is_planar(obj, tolerance=1e-6): + if len(obj.data.polygons) != 1: + return False + + polygon = obj.data.polygons[0] + global_normal = global_polygon_normal(obj, polygon) + + # Take the first vertex as a reference point on the plane + ref_vertex = global_vertex_coordinates(obj, obj.data.vertices[polygon.vertices[0]]) + + # Check if all vertices lie on the plane defined by the reference vertex and the global normal + for vertex in obj.data.vertices: + distance = (global_vertex_coordinates(obj, vertex) - ref_vertex).dot(global_normal) + if not math.isclose(distance, 0, abs_tol=tolerance): + return False + + return True + +def planes_parallel(plane_obj_a, plane_obj_b, tolerance=1e-6): + if plane_obj_a.type != 'MESH' or plane_obj_b.type != 'MESH': + raise ValueError("Both objects should be of type 'MESH'") + + # # Check if the objects are planar + # if not is_planar(plane_obj_a) or not is_planar(plane_obj_b): + # raise ValueError("One or both objects are not planar") + + global_normal_a = global_polygon_normal(plane_obj_a, plane_obj_a.data.polygons[0]) + global_normal_b = global_polygon_normal(plane_obj_b, plane_obj_b.data.polygons[0]) + + dot_product = global_normal_a.dot(global_normal_b) + + return math.isclose(dot_product, 1, abs_tol=tolerance) or math.isclose(dot_product, -1, abs_tol=tolerance) + + +def distance_to_plane(point, plane_point, plane_normal): + """Compute the distance from a point to a plane defined by a point and a normal.""" + return abs((point - plane_point).dot(plane_normal)) + +def is_within_margin_from_plane(obj, obj_b, margin, tol = 1e-6): + """Check if all vertices of an object are within a given margin from a plane.""" + polygon_b = obj_b.data.polygons[0] + plane_point_b = global_vertex_coordinates(obj_b, obj_b.data.vertices[polygon_b.vertices[0]]) + plane_normal_b = global_polygon_normal(obj_b, polygon_b) + for vertex in obj.data.vertices: + global_vertex = global_vertex_coordinates(obj, vertex) + distance = distance_to_plane(global_vertex, plane_point_b, plane_normal_b) + if not math.isclose(distance, margin, abs_tol=tol): + return False + return True + +# def update_blender_representation(scene, trimesh_obj): + +# transform_matrix = + + +# def update_trimesh_representation(scnene, blender_obj): +# pass \ No newline at end of file diff --git a/infinigen/core/constraints/evaluator/node_impl/__init__.py b/infinigen/core/constraints/evaluator/node_impl/__init__.py new file mode 100644 index 000000000..138875f46 --- /dev/null +++ b/infinigen/core/constraints/evaluator/node_impl/__init__.py @@ -0,0 +1 @@ +from .impl_bindings import node_impls \ No newline at end of file diff --git a/infinigen/core/constraints/evaluator/node_impl/impl_bindings.py b/infinigen/core/constraints/evaluator/node_impl/impl_bindings.py new file mode 100644 index 000000000..3ee9e0f04 --- /dev/null +++ b/infinigen/core/constraints/evaluator/node_impl/impl_bindings.py @@ -0,0 +1,374 @@ +# 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: +# - Karhan Kayan: geometry impl bindings +# - Alexander Raistrick: impl interface, set_reasoning / operator impls +# - Lingjie Mei: bugfix + +import math +import logging +import functools + +import gin +import numpy as np + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r +) +from infinigen.core.constraints.example_solver import state_def +from infinigen.core import tags as t +from infinigen.core.constraints.evaluator import domain_contains + +from . import trimesh_geometry, symmetry +import inspect + +logger = logging.getLogger(__name__) + +node_impls = {} + +def statenames_to_blnames(state, names): + return [state.objs[n].obj.name for n in names] + +def register_node_impl(node_cls): + def decorator(func): + node_impls[node_cls] = func + return func + return decorator + +def generic_impl_interface( + cons: cl.Node, + state: state_def.State, + child_vals: dict +): + pass + +@register_node_impl(cl.constant) +def constant_impl( + cons: cl.Node, + state: state_def.State, + child_vals: dict +): + return cons.value + +@register_node_impl(cl.ScalarOperatorExpression) +@register_node_impl(cl.BoolOperatorExpression) +def operator_impl( + cons: cl.ScalarOperatorExpression | cl.BoolOperatorExpression, + state: state_def.State, + child_vals: dict +): + + operands = [ + child_vals[f'operands[{i}]'] + for i in range(len(cons.operands)) + ] + + try: + if ( + isinstance(cons.func, np.ufunc) + or len(operands) == 1 + ): + return cons.func(*operands) + else: + return functools.reduce(cons.func, operands) + except ZeroDivisionError as e: + raise ZeroDivisionError(f'{e} in {cons=}, {operands=}') + +@register_node_impl(cl.center_stable_surface_dist) +def center_stable_surface_impl( + cons: cl.center_stable_surface_dist, + state: state_def.State, + child_vals: dict +): + objs = child_vals['objs'] + return trimesh_geometry.center_stable_surface(state.trimesh_scene, objs, state) + +@register_node_impl(cl.accessibility_cost) +def accessibility_impl( + cons: cl.accessibility_cost, + state: state_def.State, + child_vals: dict, + use_collision_impl: bool = True +): + + objs = statenames_to_blnames(state, child_vals['objs']) + others = statenames_to_blnames(state, child_vals['others']) + if len(objs) == 0: + return 0 + + if use_collision_impl: + res = trimesh_geometry.accessibility_cost_cuboid_penetration( + state.trimesh_scene, + objs, + others, + cons.normal, + cons.dist, + bvh_cache=state.bvh_cache + ) + else: + res = trimesh_geometry.accessibility_cost( + state.trimesh_scene, objs, others, cons.normal + ) + return res + +@register_node_impl(cl.distance) +def min_distance_impl( + cons: cl.Node, + state: state_def.State, + child_vals: dict, + others_tags: set = None +): + + objs = statenames_to_blnames(state, child_vals['objs']) + others = statenames_to_blnames(state, child_vals['others']) + + if len(objs) == 0 or len(others) == 0: + logger.debug('min_distance had no targets') + return 0 + + res = trimesh_geometry.min_dist( + state.trimesh_scene, + a=objs, + b=others, + b_tags=others_tags, + bvh_cache=state.bvh_cache + ) + + if res.dist < 0: + return 0 + + return res.dist + +@register_node_impl(cl.min_distance_internal) +def min_distance_internal_impl( + cons: cl.min_distance_internal, + state: state_def.State, + child_vals: dict +): + objs = statenames_to_blnames(state, child_vals['objs']) + if len(objs) <= 1: + return 0 + return trimesh_geometry.min_dist( + state.trimesh_scene, a=objs + ).dist + +@register_node_impl(cl.min_dist_2d) +def min_dist_2d_impl( + cons: cl.min_dist_2d, + state: state_def.State, + child_vals: dict +): + a = statenames_to_blnames(state, child_vals['objs']) + b = statenames_to_blnames(state, child_vals['others']) + if len(a) == 0 or len(b) == 0: + return 0 + return trimesh_geometry.min_dist_2d( + state.trimesh_scene, a, b + ) + +@register_node_impl(cl.focus_score) +def focus_score_impl( + cons: cl.focus_score, + state: state_def.State, + child_vals: dict, +): + + a = statenames_to_blnames(state, child_vals['objs']) + b = statenames_to_blnames(state, child_vals['others']) + + if len(a) == 0 or len(b) == 0: + return 0 + + return trimesh_geometry.focus_score( + state, + a=a, + b=b + ) + +@register_node_impl(cl.angle_alignment_cost) +def angle_alignment_impl( + cons: cl.angle_alignment_cost, + state: state_def.State, + child_vals: dict, + others_tags: set = None +): + a = statenames_to_blnames(state, child_vals['objs']) + b = statenames_to_blnames(state, child_vals['others']) + if len(a) == 0 or len(b) == 0: + return 0 + return trimesh_geometry.angle_alignment_cost( + state, a, b, others_tags + ) + +@register_node_impl(cl.freespace_2d) +def freespace_2d_impl( + cons: cl.freespace_2d, + state: state_def.State, + child_vals: dict +): + return trimesh_geometry.freespace_2d() + +@register_node_impl(cl.rotational_asymmetry) +def rotational_asymmetry_impl( + cons: cl.rotational_asymmetry, + state: state_def.State, + child_vals: dict +): + objs = statenames_to_blnames(state, child_vals['objs']) + if len(objs) <= 1: + return 0 + return symmetry.compute_total_rotation_asymmetry(objs) + +@register_node_impl(cl.reflectional_asymmetry) +def reflectional_asymmetry_impl( + cons: cl.reflectional_asymmetry, + state: state_def.State, + child_vals: dict, + use_long_plane: bool = True, +): + + objs = statenames_to_blnames(state, child_vals['objs']) + others = statenames_to_blnames(state, child_vals['others']) + if len(objs) <= 1: + return 0 + return trimesh_geometry.reflectional_asymmetry_score( + state.trimesh_scene, objs, others, use_long_plane + ) + +@register_node_impl(cl.coplanarity_cost) +def coplanarity_cost_impl( + cons: cl.coplanarity_cost, + state: state_def.State, + child_vals: dict +): + objs = child_vals['objs'] + if len(objs) <= 1: + return 0 + return trimesh_geometry.coplanarity_cost(state.trimesh_scene, objs) + + +@register_node_impl(cl.tagged) +def tagged_impl( + cons: cl.tagged, + state: state_def.State, + child_vals: dict +): + res = { + o for o in child_vals['objs'] + if t.satisfies(state.objs[o].tags, cons.tags) + } + + #logger.debug('tagged(%s) produced %s from %i candidates', cons.tags, res, len(child_vals['objs'])) + + return res + +@register_node_impl(cl.count) +def count_impl( + cons: cl.count, + state: state_def.State, + child_vals: dict +): + return len(child_vals['objs']) + +@register_node_impl(cl.in_range) +def in_range_impl( + cons: cl.in_range, + state: state_def.State, + child_vals: dict +): + x = child_vals['val'] + return ( + x <= cons.high and + x >= cons.low + ) + +@register_node_impl(cl.related_to) +def related_children_impl( + cons: cl.related_to, + state: state_def.State, + child_vals: dict +): + + r = cons.relation + children: set[str] = child_vals['child'] + parents: set[str] = child_vals['parent'] + + res = set() + for o in children: + if any( + rs.relation.implies(r) and rs.target_name in parents + for rs in state.objs[o].relations + ): + res.add(o) + + #logger.debug('related_to %s produced %s from %i candidates', cons.relation, res, len(children)) + + return res + +@register_node_impl(cl.excludes) +def excludes_impl( + cons: cl.excludes, + state: state_def.State, + child_vals: dict +): + + return { + o for o in child_vals['objs'] + if state.objs[o].tags.isdisjoint(cons.tags) + } + +@register_node_impl(cl.volume) +def volume_impl( + cons: cl.volume, + state: state_def.State, + child_vals: dict +): + objs = child_vals['objs'] + + res = 0 + for o in objs: + + s = state.objs[o] + dims = sorted(list(s.obj.dimensions), reverse=True) + + if isinstance(cons.dims, int): + dims = dims[:cons.dims] + elif isinstance(cons.dims, tuple): + dims = np.array(dims)[np.array(cons.dims)] + else: + raise TypeError(f'Unexpected {type(cons.dims)=}') + + res += math.prod(dims) + + return res + +@register_node_impl(cl.hinge) +def hinge_impl( + cons: cl.hinge, + state: state_def.State, + child_vals: dict +): + x = child_vals['val'] + + if x < cons.low: + return cons.low - x + elif x > cons.high: + return x - cons.high + else: + return 0 + +@register_node_impl(r.FilterByDomain) +def filter_by_domain_impl( + cons: r.FilterByDomain, + state: state_def.State, + child_vals: dict +) -> set[str]: + + return { + o + for o in child_vals['objs'] + if domain_contains.domain_contains(cons.filter, state, state.objs[o]) + } diff --git a/infinigen/core/constraints/evaluator/node_impl/symmetry.py b/infinigen/core/constraints/evaluator/node_impl/symmetry.py new file mode 100644 index 000000000..ec12f6017 --- /dev/null +++ b/infinigen/core/constraints/evaluator/node_impl/symmetry.py @@ -0,0 +1,263 @@ +# 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: Karhan Kayan +# Acknowledgement: Rotational symmetry code draws inspiration from https://pubs.acs.org/doi/abs/10.1021/ja00046a033 by Zabrodsky et al. + + +import numpy as np +from typing import Union, Any +from infinigen.core.constraints.evaluator.indoor_util import blender_objs_from_names +from mathutils import Vector, Quaternion, Matrix +from scipy.optimize import linear_sum_assignment +import matplotlib.pyplot as plt + + +def rotate_vector(vector, angle): + """Rotate a 2D vector by a given angle.""" + rotation_matrix = np.array([ + [np.cos(angle), -np.sin(angle)], + [np.sin(angle), np.cos(angle)] + ]) + return np.dot(vector, rotation_matrix.T) + +def compute_centroid(objects): + """Compute the centroid of the provided objects.""" + total_coords = np.zeros(2) + for obj in objects: + total_coords += np.array([obj.location.x, obj.location.y]) + return total_coords / len(objects) + +def compute_location_asymmetry(objects, centroid): + """Compute location asymmetry based on the described method.""" + num_objects = len(objects) + + P = np.zeros((num_objects, 2)) + for i, obj in enumerate(objects): + P[i] = np.array([obj.location.x, obj.location.y]) - centroid + # print(P) + + # 2. Rotate all P_i so that P_1 is aligned with the x axis + angle_p1 = np.arctan2(P[0][1], P[0][0]) + for i in range(num_objects): + P[i] = rotate_vector(P[i], -angle_p1) + + + # 3. Normalize P_i by dividing by max norm + max_norm = max(np.linalg.norm(P, axis=1)) + P /= max_norm + # print(P) + + + # 4. Compute Q as the average of the rotated P_i vectors + Q = np.zeros(2) + for i in range(num_objects): + rotated_p = rotate_vector(P[i], -i * 2 * np.pi / num_objects) +# print("rot", P[i], rotated_p) + Q += rotated_p + Q /= num_objects + + # 5. and 6. Compute Q_i and find the MSD between Q_i and P_i + total_msd = 0 + for i in range(num_objects): + Q_i = rotate_vector(Q, i * 2 * np.pi / num_objects) +# print("rot2", Q_i, P[i]) + msd = np.linalg.norm(Q_i - P[i])**2 + total_msd += msd + + return total_msd / num_objects + + +def compute_orientation_asymmetry(objects, centroid): + """Compute orientation asymmetry of objects.""" + num_objects = len(objects) + + # 1. Get the orientation vectors + V = np.zeros((num_objects, 2)) + for i, obj in enumerate(objects): + # Extract orientation from object's rotation attribute + V[i] = np.array([np.cos(obj.rotation_euler.z), np.sin(obj.rotation_euler.z)]) + + + # Rotate all V_i so that V_1 is aligned with the x axis + angle_v1 = np.arctan2(V[0][1], V[0][0]) + for i in range(num_objects): + V[i] = rotate_vector(V[i], -angle_v1) + + + # Normalize V_i by dividing by max norm + max_norm = max(np.linalg.norm(V, axis=1)) + V /= max_norm + + # 4. Compute Q as the average of the rotated V_i vectors + Q = np.zeros(2) + for i in range(num_objects): + rotated_v = rotate_vector(V[i], -i * 2 * np.pi / num_objects) +# print("rot", P[i], rotated_p) + Q += rotated_v + Q /= num_objects + + + # 5. and 6. Compute Q_i and find the MSD between Q_i and V_i + total_msd = 0 + for i in range(num_objects): + Q_i = rotate_vector(Q, i * 2 * np.pi / num_objects) +# print("rot2", Q_i, P[i]) + msd = np.linalg.norm(Q_i - V[i])**2 + total_msd += msd + + return total_msd / num_objects + +def sort_objects_clockwise(objects, centroid): + angles = [] + for obj in objects: + # Calculate the angle from the centroid to the object + dx = obj.location.x - centroid[0] + dy = obj.location.y - centroid[1] + angle = np.arctan2(dy, dx) + angles.append((obj, angle)) + + # Sort objects based on the angles in descending order for clockwise sorting + angles.sort(key=lambda x: x[1]) + + # Extract the sorted objects + sorted_objects = [obj for obj, angle in angles] + return sorted_objects + + +def compute_total_rotation_asymmetry(a: Union[str, list[str]]) -> float: + """Compute the total asymmetry.""" + if isinstance(a, str): + a = [a] + objects = blender_objs_from_names(a) + centroid = compute_centroid(objects) + objects = sort_objects_clockwise(objects, centroid) + location_asymmetry = compute_location_asymmetry(objects, centroid) + orientation_asymmetry = compute_orientation_asymmetry(objects, centroid) + + # print("location asym", location_asymmetry, "orient asym", orientation_asymmetry) + + return (location_asymmetry + orientation_asymmetry) / 2 + + + +def reflect_point(point, plane_point, plane_normal): + # Reflect a point across an arbitrary plane defined by a point and a normal. + to_point = point - plane_point + distance_to_plane = to_point.dot(plane_normal) + reflected_point = point - 2 * distance_to_plane * plane_normal + return reflected_point + +# prob doesnt work +def reflect_quaternion(q, n): + # Decompose the quaternion into scalar and vector parts + w = q.w + v = Vector((q.x, q.y, q.z)) + + # Reflect the vector part + v_reflected = v - 2 * v.dot(n) * n + + # Construct the reflected quaternion + q_reflected = Quaternion((w, v_reflected.x, v_reflected.y, v_reflected.z)) + + return q_reflected + + +def reflect_axis_angle(axis_angle, n): + axis = Vector((axis_angle[1], axis_angle[2], axis_angle[3])) + angle = axis_angle[0] + # Reflect the vector part + v_reflected = axis - 2 * axis.dot(n) * n + angle_reflected = -angle + + # Construct the reflected axis angle + axis_angle_reflected = Vector((angle_reflected, v_reflected.x, v_reflected.y, v_reflected.z)) + + return axis_angle_reflected + +def reflect(obj, plane_point, plane_normal): + obj.rotation_mode = 'AXIS_ANGLE' + reflected_position = reflect_point(obj.location, plane_point, plane_normal) + reflected_axis_angle = reflect_axis_angle(obj.rotation_axis_angle, plane_normal) + reflected_quaternion = Matrix.Rotation(reflected_axis_angle[0], 4, reflected_axis_angle[1:]).to_quaternion() + return reflected_position, reflected_quaternion + + +def distance(pos1, pos2): + # Calculate Euclidean distance between two positions + return (pos1 - pos2).length + +def angle_difference(orient1, orient2): + # Calculate the angular difference between two orientations represented as quaternions. + orient1.normalize() + orient2.normalize() + dot_product = orient1.dot(orient2) + # lose directionality information + dot_product = abs(dot_product) + dot_product = max(min(dot_product, 1.0), -1.0) + angle = 2 * np.arccos(dot_product) + return angle + +def weight(obj): + # Assign a weight based on obj size or other criteria + bbox = obj.bound_box + dims = [bbox[i][0] for i in range(8)] + volume = (max(dims) - min(dims)) ** 3 + return volume + +def normalization_factor(objs): + avg_distance = np.mean([distance(obj1.location, obj2.location) for obj1 in objs for obj2 in objs if obj1 != obj2]) + return avg_distance + +def bipartite_matching(objs, reflected_objs_data): + # Use the Hungarian algorithm to find the optimal pairing between objs and reflected_objs + for obj in objs: + obj.rotation_mode = 'QUATERNION' + cost_matrix = np.array([[distance(obj.location, ref[0]) + angle_difference(obj.rotation_quaternion, ref[1]) for ref in reflected_objs_data] for obj in objs]) + row_ind, col_ind = linear_sum_assignment(cost_matrix) + return [(objs[i], reflected_objs_data[j]) for i, j in zip(row_ind, col_ind)] + + +def calculate_reflectional_asymmetry(objs, plane_point, plane_normal, visualize = False): + if visualize: + fig, ax = plt.subplots() + # plot plane point and plane normal + ax.scatter(plane_point.x, plane_point.y, c='g', label='plane point') + ax.quiver(plane_point.x, plane_point.y, plane_normal.x, plane_normal.y, color='g', label='plane normal') + + + reflected_objs_data = [reflect(obj, plane_point, plane_normal) for obj in objs] + + # Use bipartite matching to find optimal pairings + pairings = bipartite_matching(objs, reflected_objs_data) + + total_deviation = 0 + for original, reflected_data in pairings: + positional_deviation = distance(original.location, reflected_data[0]) + original.rotation_mode = 'QUATERNION' + angular_deviation = angle_difference(original.rotation_quaternion, reflected_data[1]) + + weighted_deviation = weight(original) * (positional_deviation + angular_deviation) + total_deviation += weighted_deviation + if visualize: + # plot the point and the reflected point with different colors + ax.scatter(original.location.x, original.location.y, c='b', label='original point') + ax.scatter(reflected_data[0].x, reflected_data[0].y, c='r', label='reflected point') + + + # Normalize based on scene scale or other criteria + normalized_deviation = total_deviation / normalization_factor(objs) + + symmetry_score = 1 / (1 + normalized_deviation) + asymmetry_score = 1 - symmetry_score + + for obj in objs: + obj.rotation_mode = 'XYZ' + + if visualize: + ax.legend() + plt.show() + + + return asymmetry_score + diff --git a/infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py b/infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py new file mode 100644 index 000000000..abdf935c0 --- /dev/null +++ b/infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py @@ -0,0 +1,1172 @@ +# 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: +# - Karhan Kayan: primary author +# - Alexander Raistrick: initial version of collision/distance +# Acknowledgement: Some metrics draw inspiration from https://dl.acm.org/doi/10.1145/1964921.1964981 by Yu et al. + +from __future__ import annotations +from typing import Union, Any +import logging +from dataclasses import dataclass +from copy import copy + +import numpy as np +import gin + +import bpy +import trimesh + +from trimesh import Trimesh, Scene +import networkx as nx +from shapely.geometry import Point, LineString +from shapely.ops import unary_union, nearest_points +from shapely import Polygon +from shapely import MultiPolygon + +import matplotlib.pyplot as plt +# import fcl + +# from infinigen.core.util import blender as butil +from infinigen.core.util import blender as butil +from mathutils import Vector, Quaternion +from scipy.optimize import linear_sum_assignment + + + +import infinigen.core.constraints.constraint_language.util as iu +from infinigen.core.constraints.example_solver import state_def +from infinigen.core.constraints.example_solver.geometry.parse_scene import add_to_scene + +from infinigen.core import tags as t, tagging +import infinigen.core.constraints.evaluator.node_impl.symmetry as symmetry + + +# from infinigen.core.tagging import tag_object,tag_system +# from scipy.optimize import dual_annealing +# from tqdm import tqdm + +logger = logging.getLogger(__name__) + +def get_cardinal_planes_bbox(vertices: np.ndarray): + """ + Get the mid dividing planes. Assumes vertices form a box + """ + centroid = np.mean(vertices, axis=0) + + # Calculate the covariance matrix and principal components + centered_vertices = vertices - centroid + cov_matrix = np.cov(centered_vertices[:,:2].T) # Covariance on XY plane + eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix) + + # Sort eigenvectors based on eigenvalues + order = eigenvalues.argsort()[::-1] + principal_axes = eigenvectors[:, order] + + # Determine the longer and shorter plane normals and normalize them + if eigenvalues[order[0]] > eigenvalues[order[1]]: + longer_plane_normal = np.array([principal_axes[0, 1], principal_axes[1, 1], 0]) + shorter_plane_normal = np.array([principal_axes[0, 0], principal_axes[1, 0], 0]) + else: + longer_plane_normal = np.array([principal_axes[0, 0], principal_axes[1, 0], 0]) + shorter_plane_normal = np.array([principal_axes[0, 1], principal_axes[1, 1], 0]) + + longer_plane_normal /= np.linalg.norm(longer_plane_normal) + shorter_plane_normal /= np.linalg.norm(shorter_plane_normal) + return [[Vector(centroid), Vector(longer_plane_normal)], [Vector(centroid), Vector(shorter_plane_normal)]] + +def get_axis(state: state_def.State, obj: bpy.types.Object, tag = t.Subpart.Front): + + a_front_planes = state.planes.get_tagged_planes(obj, tag) + if len(a_front_planes) > 1: + logging.warning(f'{obj.name=} had too many front planes ({len(a_front_planes)})') + a_front_plane = a_front_planes[0] + a_front_plane_ind = a_front_plane[1] + a_poly = obj.data.polygons[a_front_plane_ind] + front_plane_pt = iu.global_vertex_coordinates(obj, obj.data.vertices[a_poly.vertices[0]]) + front_plane_normal = iu.global_polygon_normal(obj, a_poly) + return front_plane_pt, front_plane_normal + +def preprocess_collision_query_cases(a, b, a_tags, b_tags): + + if isinstance(a, list): + a = set(a) + if isinstance(b, list): + b = set(b) + + if len(a) == 1: + a = a.pop() + if len(b) == 1: + b = b.pop() + + # eliminate symmetrical cases + if ( + a is None or + (isinstance(b, set) and not isinstance(a, set)) + ): + a, b = b, a + a_tags, b_tags = b_tags, a_tags + + # nobody wants to be told a 0 distance if they query how far a chair is from the set of all chairs + if isinstance(b, str) and isinstance(a, set) and b in a: + a.remove(b) + + if isinstance(a, set) and len(a) == 0: + raise ValueError(f'query recieved empty input {a=}') + if isinstance(a, set) and len(a) == 0: + raise ValueError(f'query recieved empty input {b=}') + + # single-to-single is treated as many-to-single + if isinstance(a, str): + a = [a] + + assert a is not None + + if a_tags is None: + a_tags = set() + if b_tags is None: + b_tags = set() + + return a, b, a_tags, b_tags + +@dataclass +class ContactResult: + hit: bool + names: list[str] + contacts: list + +def any_touching( + scene: Scene, + a: Union[str, list[str]], + b: Union[str, list[str]] = None, + a_tags=None, + b_tags=None, + bvh_cache=None +): + + ''' + Computes one-to-one, many-to-one, one-to-many or many-to-many collisions + + In all cases, returns True if any one object from a and b touch + ''' + a, b, a_tags, b_tags = preprocess_collision_query_cases(a, b, a_tags, b_tags) + + col = iu.col_from_subset(scene, a, a_tags, bvh_cache) + + if b is None and len(a) == 1: + # query makes no sense, asking for intra-set collision on one element + hit, names, contacts = None, (a, b), [] + elif b is None: + hit, names, contacts = col.in_collision_internal(return_data=True, return_names=True) + elif isinstance(b, str): + T, g = scene.graph[b] + hit, names, contacts = col.in_collision_single(scene.geometry[g], transform=T, return_data=True, return_names=True) + elif isinstance(b, list): + col2 = iu.col_from_subset(scene, b, b_tags, bvh_cache) + hit, names, contacts = col.in_collision_other(col2, return_names=True, return_data=True) + else: + raise ValueError(f'Unhandled case {a=} {b=}') + + names = list(names) + if len(names) == 1: + assert isinstance(b, str) + names.append(b) + logging.debug(f'added name {b} to make {names}') + + if len(names) == 0: + names = [a, b] + + return ContactResult( + hit=hit, + names=names, + contacts=contacts + ) + +@dataclass +class DistanceResult: + dist: float + names: list[str] + data: trimesh.collision.DistanceData + +def min_dist( + scene: Scene, + a: Union[str, list[str]], + b: Union[str, list[str]] = None, + a_tags: set = None, + b_tags: set = None, + bvh_cache: dict = None +): + + ''' + Computes one-to-one, many-to-one, one-to-many or many-to-many distance + + In all cases, returns the minimum distance between any object in a and b + ''' + # we get fcl error otherwise + if len(a) == 1 and len(b) == 1 and a[0] == b[0]: + return DistanceResult(dist=0, names=[a[0], b[0]], data=None) + a, b, a_tags, b_tags = preprocess_collision_query_cases(a, b, a_tags, b_tags) + col = iu.col_from_subset(scene, a, a_tags, bvh_cache) + + + + if b is None and len(a) == 1: + dist, data = 1e9, None + elif b is None: + dist, data = col.min_distance_internal(return_data=True) + elif isinstance(b, str): + T, g = scene.graph[b] + geom = scene.geometry[g] + if b_tags is not None and len(b_tags) > 0: + obj = iu.blender_objs_from_names(b)[0] + mask = tagging.tagged_face_mask(obj, b_tags) + if not mask.any(): + logger.warning(f'{b=} had {mask.sum()=} for {b_tags=}') + geom = geom.submesh(np.where(mask), append=True) + assert len(geom.faces) == mask.sum() + dist, data = col.min_distance_single(geom, transform=T, return_data=True) + if '__external' in data.names: + data.names.remove('__external') + data.names.add(b) + data._points[b] = data._points['__external'] + data._points.pop('__external') + logging.debug(f"WARNING: swapped __external for {b} to make {data.names}") + elif isinstance(b, (list, set)): + col2 = iu.col_from_subset(scene, b, b_tags, bvh_cache) + dist, data = col.min_distance_other(col2, return_data=True) + else: + raise ValueError(f'Unhandled case {a=} {b=}') + + if data is not None: + assert '__external' not in data.names + + return DistanceResult( + dist=dist, + names=list(data.names) if data is not None else None, + data=data + ) + +def contains( + scene: Scene, + a: str, + b: str, + tol = 1e-6 +) -> bool: + """ + Check if a contains b + """ + mesh_a = scene.geometry[a] + mesh_b = scene.geometry[b] + + + difference = mesh_a.difference(mesh_b) + + return abs(difference.volume - mesh_a.volume) < tol + +def contains_all(scene: trimesh.Scene, a: Union[str, list[str]], b: Union[str, list[str]]) -> bool: + """ + Check if all objects in list 'a' contain all objects in list 'b' within the given scene. + + Parameters: + - scene: The trimesh.Scene instance. + - a: Name or list of names of objects to check for containment. + - b: Name or list of names of objects that might be contained. + + Returns: + - True if all objects in list 'a' contain all objects in list 'b', False otherwise. + """ + + if isinstance(a, str): + a = [a] + if isinstance(b, str): + b = [b] + + for obj_a in a: + if not all(contains(scene, obj_a, obj_b) for obj_b in b): + return False + + return True + + +def contains_any(scene: trimesh.Scene, a: Union[str, list[str]], b: Union[str, list[str]]) -> bool: + """ + Check if any object in list 'a' contains any object in list 'b' within the given scene. + + Parameters: + - scene: The trimesh.Scene instance. + - a: Name or list of names of objects to check for containment. + - b: Name or list of names of objects that might be contained. + + Returns: + - True if any object in list 'a' contains any object in list 'b', False otherwise. + """ + + if isinstance(a, str): + a = [a] + if isinstance(b, str): + b = [b] + + for obj_a in a: + if any(contains(scene, obj_a, obj_b) for obj_b in b): + return True + + return False + + +def has_line_of_sight(scene: trimesh.Scene, a: Union[str, list[str]], b: Union[str, list[str]], num_samples: int = 100) -> bool: + """ + Check if any object in list 'a' in the scene has a line of sight to any object in list 'b'. + + Parameters: + - scene: The trimesh.Scene instance. + - a: Name or list of names of objects from which line of sight is checked. + - b: Name or list of names of objects to which line of sight is checked. + - num_samples: Number of points to sample from each object for ray casting. + + Returns: + - True if any object in list 'a' has a line of sight to any object in list 'b', False otherwise. + """ + + # Ensure 'a' and 'b' are lists + if isinstance(a, str): + a = [a] + if isinstance(b, str): + b = [b] + + a = iu.meshes_from_names(scene, a) + b = iu.meshes_from_names(scene, b) + + # Check line of sight for each object in 'a' against any object in 'b' + for obj_a in a: + # Sample points from the surface of object 'a' + points_a = obj_a.sample(num_samples) + + combined_mesh = trimesh.util.concatenate([mesh for name, mesh in scene.geometry.items() if mesh != obj_a]) + + for obj_b in b: + # Sample points from the surface of object 'b' + points_b = obj_b.sample(num_samples) + + # Create rays from points on 'a' to points on 'b' + ray_origins = np.tile(points_a, (num_samples, 1)) + ray_directions = np.repeat(points_b, num_samples, axis=0) - ray_origins + ray_directions /= np.linalg.norm(ray_directions, axis=1)[:, None] + + # Check for intersections with the combined mesh + locations, index_ray, index_tri = combined_mesh.ray_pyembree.intersects_location(ray_origins, ray_directions, multiple_hits=False) + + # Check if point is reached + for i in range(index_ray.shape[0]): + index = index_ray[i] + hit_location = locations[i] + + # Check if any intersection is close to the point + if np.linalg.norm(points_b[index // num_samples] - hit_location) < 1e-6: + return True + + return False + +def freespace_2d(scene: trimesh.Scene, a: Union[str, list[str]], b: Union[str, list[str]]) -> float: + if isinstance(a, str): + a = [a] + if isinstance(b, str): + b = [b] + + a_meshes = iu.meshes_from_names(scene, a) + + b_meshes = iu.meshes_from_names(scene, b) + + total_projected_area = sum(iu.project_to_xy_path2d(mesh).area for mesh in b_meshes) + + + available_area = sum(iu.project_to_xy_path2d(mesh).area for mesh in a_meshes) + + + percent_available = ((available_area - total_projected_area) / available_area) * 100 + + return percent_available + +def rasterize_space_with_obstacles(scene, a: Union[str, list[str]], b: Union[str, list[str]], start_location, end_location, cell_size=1.0, visualize=False): + """ + Rasterize the union of multiple space polygons while considering obstacle polygons, + then find and visualize the shortest path from start to end. + + Parameters: + - space_polygons: list of shapely.geometry.polygon.Polygon objects representing the main spaces + - obstacle_polygons: list of shapely.geometry.polygon.Polygon objects representing obstacles + - start_location: tuple (x, y) representing the start location + - end_location: tuple (x, y) representing the end location + - cell_size: size of each cell in the grid + - visualize: boolean, if True, visualize the union of spaces, obstacles, and the shortest path + + Returns: + - graph: A networkx.Graph object representing the rasterized union of spaces minus the obstacles + - path: list of nodes representing the shortest path from start to end + """ + def is_close_to_any_node(neighbor, graph, threshold=1e-6): + for node in graph.nodes(): + distance = np.linalg.norm(np.array(neighbor) - np.array(node)) + if distance < threshold: + return node + return None + + + if isinstance(a, str): + a = [a] + if isinstance(b, str): + b = [b] + + a_meshes = iu.meshes_from_names(scene, a) + b_meshes = iu.meshes_from_names(scene, b) + + space_polygons = [iu.project_to_xy_poly(mesh) for mesh in a_meshes] + obstacle_polygons = [iu.project_to_xy_poly(mesh) for mesh in b_meshes] + + # Get the union of all space polygons + union_space = unary_union(space_polygons) + + # Get bounding box of the union space + minx, miny, maxx, maxy = union_space.bounds + + # Create a grid over the bounding box + x_coords = np.arange(minx, maxx, cell_size) + y_coords = np.arange(miny, maxy, cell_size) + + graph = nx.Graph() + + # For visualization + if visualize: + fig, ax = plt.subplots() + for space in space_polygons: + if isinstance(space, Polygon): + x, y = space.exterior.xy + ax.fill(x, y, alpha=0.5) # Fill the space + ax.plot(x, y, color='black') # Plot the space boundary + elif isinstance(space, MultiPolygon): + for sub_space in space.geoms: + x, y = sub_space.exterior.xy + ax.fill(x, y, alpha=0.5) + ax.plot(x, y, color='black') + + for obstacle in obstacle_polygons: + if isinstance(obstacle, Polygon): + x, y = obstacle.exterior.xy + ax.fill(x, y, color='grey') # Fill the obstacles + ax.plot(x, y, color='black') # Plot the obstacle boundary + elif isinstance(obstacle, MultiPolygon): + for sub_obstacle in obstacle.geoms: + x, y = sub_obstacle.exterior.xy + ax.fill(x, y, color='grey') + ax.plot(x, y, color='black') + + # For each cell in the grid, check if its center is inside the union space and outside all obstacle polygons + for x in x_coords: + for y in y_coords: + cell_center = Point(x + cell_size / 2, y + cell_size / 2) + if cell_center.within(union_space) and all(not cell_center.within(obstacle) for obstacle in obstacle_polygons): + graph.add_node((x + cell_size / 2, y + cell_size / 2)) + + # For visualization + if visualize: + ax.plot(cell_center.x, cell_center.y, 'bo', markersize=3) # Plot the point inside the union space and outside obstacles + + # Connect each node to its neighboring nodes + for node in graph.nodes(): + x, y = node + neighbors = [ + (x + cell_size, y), + (x - cell_size, y), + (x, y + cell_size), + (x, y - cell_size) + ] + for neighbor in neighbors: + closest_node = is_close_to_any_node(neighbor, graph) + if closest_node is not None: + graph.add_edge(node, closest_node) + + + # Find the closest nodes to the start and end locations + start_node = min(graph.nodes(), key=lambda node: np.linalg.norm(np.array(node) - np.array(start_location))) + end_node = min(graph.nodes(), key=lambda node: np.linalg.norm(np.array(node) - np.array(end_location))) + + + # Calculate the shortest path using Dijkstra's algorithm + path = nx.shortest_path(graph, source=start_node, target=end_node, weight='weight') + + # Visualize the path + if visualize: + path_x = [x for x, y in path] + path_y = [y for x, y in path] + ax.plot(path_x, path_y, c='red', linewidth=2, label='Shortest Path') + ax.scatter([start_node[0], end_node[0]], [start_node[1], end_node[1]], c='green', s=100, label='Start & End') + plt.legend() + plt.title('Shortest Path from Start to End') + plt.show() + + return graph, path + +def angle_alignment_cost_tagged(state: state_def.State, a: Union[str, list[str]], b: Union[str, list[str]], b_tags=None, visualize=False): + """ + Return the dot product between the axes of a and the normal of the closest edge of b + """ + if isinstance(a, str): + a = [a] + + b_objs = iu.blender_objs_from_names(b) + b_surfs = [] + for b_obj in b_objs: + b_surf = tagging.extract_tagged_faces(b_obj, b_tags) + b_surfs.append(b_surf) + + b_surf_names = [] + for i, b_surf in enumerate(b_surfs): + add_to_scene(state.trimesh_scene, b_surf) + b_surf_names.append(b_surf.name) + + res = angle_alignment_cost_base(state, a, b_surf_names, visualize) + + for b_surf_name in b_surf_names: + iu.delete_obj(state.trimesh_scene, b_surf_name) + + return res + +def angle_alignment_cost_base(state: state_def.State, a: Union[str, list[str]], b: Union[str, list[str]], visualize=False): + """ + Return the dot product between the axes of a and the normal of the closest edge of b + """ + # print(f'{a=}, {b=}') + scene = state.trimesh_scene + a_meshes = iu.meshes_from_names(scene, a) + b_meshes = iu.meshes_from_names(scene, b) + 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 isinstance(b_poly, Polygon): + for i, coord in enumerate(b_poly.exterior.coords[:-1]): + start, end = coord, b_poly.exterior.coords[i + 1] + if np.isclose(start, end).all(): + continue + b_edges.append((LineString([start, end]), b_name)) + elif isinstance(b_poly, MultiPolygon): + for sub_poly in b_poly.geoms: + for i, coord in enumerate(sub_poly.exterior.coords[:-1]): + start, end = coord, sub_poly.exterior.coords[i + 1] + if np.isclose(start, end).all(): + continue + b_edges.append((LineString([start, end]), b_name)) + else: + for edge3d in b_mesh.edges: + start = b_mesh.vertices[edge3d[0]][:2] + end = b_mesh.vertices[edge3d[1]][:2] + if np.isclose(start, end).all(): + continue + b_edges.append((LineString([start, end]), b_name)) + + a_blender_objs = iu.blender_objs_from_names(a) + + if visualize: + fig, ax = plt.subplots() + for edge, _ in b_edges: + x, y = edge.xy + ax.plot(x, y, color='red', linewidth=1, label='B Edges') + + score = 0 + + for a_name, a_obj, a_mesh in zip(a, a_blender_objs, a_meshes): + _, axis = get_axis(state, a_obj) + axis = axis[:2] + a_poly = iu.project_to_xy_poly(a_mesh) + + if a_poly is not None: + if isinstance(a_poly, Polygon): + a_centroid = a_poly.centroid + elif isinstance(a_poly, MultiPolygon): + a_centroid = a_poly.centroid + else: + a_centroid = Point(a_mesh.vertices[:, :2].mean(axis=0)) + + filtered_b_edges = [edge for edge, b_name in b_edges if b_name != a_name] + if len(filtered_b_edges) == 0: + continue + closest_line = iu.closest_edge_to_point_edge_list(filtered_b_edges, a_centroid) + + dx = closest_line.xy[0][1] - closest_line.xy[0][0] # x1 - x0 + dy = closest_line.xy[1][1] - closest_line.xy[1][0] # y1 - y0 + + # Candidate normal vectors (perpendicular to edge) + normal_vector_1 = np.array([dy, -dx]) + normal_vector_2 = -normal_vector_1 + + # Normalize the vectors + normal_vector_1 /= np.linalg.norm(normal_vector_1) + normal_vector_2 /= np.linalg.norm(normal_vector_2) + + dot1 = np.dot(axis, normal_vector_1) + dot2 = np.dot(axis, normal_vector_2) + + score1 = -dot1 / 2 + 0.5 + score2 = -dot2 / 2 + 0.5 + + score += min(score1, score2) + + if visualize: + if a_poly is not None: + if isinstance(a_poly, Polygon): + x, y = a_poly.exterior.xy + ax.fill(x, y, alpha=0.5, fc='blue', ec='black', label='Polygon a') + elif isinstance(a_poly, MultiPolygon): + for sub_poly in a_poly.geoms: + x, y = sub_poly.exterior.xy + ax.fill(x, y, alpha=0.5, fc='blue', ec='black', label='Polygon a') + else: + x, y = a_mesh.vertices[:, 0], a_mesh.vertices[:, 1] + ax.scatter(x, y, color='blue', label='Vertices a') + + ax.arrow(a_centroid.x, a_centroid.y, axis[0], axis[1], head_width=0.15, head_length=0.25, fc='green', ec='green', label='Axis of a') + x, y = closest_line.xy + ax.plot(x, y, color="green", linewidth=2.5, label='Closest Edge') + ax.plot(a_centroid.x, a_centroid.y, 'o', color='black', label='Centroid of a') + mid_point = closest_line.interpolate(0.5, normalized=True) + ax.arrow(mid_point.x, mid_point.y, normal_vector_1[0], normal_vector_1[1], head_width=0.15, head_length=0.25, fc='yellow', ec='yellow', label='Normal Vector 1') + ax.arrow(mid_point.x, mid_point.y, normal_vector_2[0], normal_vector_2[1], head_width=0.15, head_length=0.25, fc='orange', ec='orange', label='Normal Vector 2') + + if visualize: + ax.set_title('Polygons, Closest Edge and Normal') + ax.set_aspect('equal') + ax.grid(True) + plt.show() + + return score + +def angle_alignment_cost( + state: state_def.State, + a: Union[str, list[str]], + b: Union[str, list[str]], + b_tags=None, + visualize=False +): + if b_tags is not None: + return angle_alignment_cost_tagged(state, a, b, b_tags, visualize) + return angle_alignment_cost_base(state, a, b, visualize) + +def focus_score(state: state_def.State, a: Union[str, list[str]], b: str, visualize = False): + """ + The how much objects in a focus on b + """ + scene = state.trimesh_scene + if isinstance(a, str): + a = [a] + + + a_meshes = iu.meshes_from_names(scene, a) + a_blender_objs = iu.blender_objs_from_names(a) + b_mesh = iu.meshes_from_names(scene, b)[0] + + a_polys = [iu.project_to_xy_poly(mesh) for mesh in a_meshes] + b_poly = iu.project_to_xy_poly(b_mesh) + + if visualize: + # Plotting the polygons and normals + fig, ax = plt.subplots() + if isinstance(b_poly, Polygon): + x, y = b_poly.exterior.xy + ax.fill(x, y, alpha=0.5, fc='red', ec='black', label='Polygon b') + elif isinstance(b_poly, MultiPolygon): + for sub_poly in b_poly.geoms: + x, y = sub_poly.exterior.xy + ax.fill(x, y, alpha=0.5, fc='red', ec='black', label='Polygon b') + + score = 0 + for a_poly, a_mesh, a_obj in zip(a_polys, a_meshes, a_blender_objs): + axis = get_axis(state, a_obj)[1][:2] + a_centroid = a_poly.centroid + b_centroid = b_poly.centroid + + # turn centroids to np array + a_centroid = np.array([a_centroid.x, a_centroid.y]) + b_centroid = np.array([b_centroid.x, b_centroid.y]) + + focus_vec = b_centroid - a_centroid + focus_vec /= np.linalg.norm(focus_vec) + + if visualize: + # Plotting the polygons + if isinstance(a_poly, Polygon): + x, y = a_poly.exterior.xy + ax.fill(x, y, alpha=0.5, fc='blue', ec='black', label='Polygon a') + elif isinstance(a_poly, MultiPolygon): + for sub_poly in a_poly.geoms: + x, y = sub_poly.exterior.xy + ax.fill(x, y, alpha=0.5, fc='blue', ec='black', label='Polygon a') + + # plot axis + ax.arrow(a_centroid[0], a_centroid[1], axis[0], axis[1], head_width=0.15, head_length=0.25, fc='green', ec='green', label='Axis of a') + + # Highlight centroid of a + ax.plot(a_centroid[0], a_centroid[1], 'o', color='black', label='Centroid of a') + + # Plot the outward normal vector + ax.arrow(a_centroid[0], a_centroid[1], focus_vec[0], focus_vec[1], head_width=0.15, head_length=0.25, fc='yellow', ec='yellow', label='Focus vector') + + score += -np.dot(axis, focus_vec)/2 + 0.5 + + if visualize: + # Set axis properties + ax.set_title('Polygons, Focus Vector') + ax.set_aspect('equal') + ax.grid(True) + # ax.legend(loc="upper left") + plt.show() + + return score #/ len(a) + +def edge(scene, surface_name: str): + surface = iu.meshes_from_names(scene, surface_name)[0] + outline_3d = surface.outline() + return outline_3d + +def min_dist_2d(scene, a: Union[str, list[str]], b, visualize=False): + """ + projects onto b and finds the min distance between a and b + """ + if isinstance(a, str): + a = [a] + + if visualize: + fig, ax = plt.subplots() + min_dist = np.inf + + b_path2d, to_3D = b.to_planar() + plane_normal, plane_origin = iu.get_plane_from_3dmatrix(to_3D) + + a_meshes = iu.meshes_from_names(scene, a) + + a_projections = [trimesh.path.polygons.projected(mesh, plane_normal, plane_origin) for mesh in a_meshes] + # Measure the distance + for a_proj in a_projections: + source_geom = a_proj + target_geom = b_path2d.polygons_closed[0].exterior + dist = source_geom.distance(target_geom) + if dist < min_dist: + if visualize: + pt_a, pt_b = nearest_points(source_geom, target_geom) + ax.plot([pt_a.x, pt_b.x], [pt_a.y, pt_b.y], color='red') + #plot source and target geoms + iu.plot_geometry(ax, source_geom, 'blue') + iu.plot_geometry(ax, target_geom, 'green') + min_dist = dist + + if visualize: + plt.show() + return min_dist + +def min_dist_boundary(scene: Scene, a: Union[str, list[str]], boundary): + if isinstance(a, str): + a = [a] + if isinstance(boundary, trimesh.path.path.Path3D): + pass + elif isinstance(boundary, trimesh.path.path.Path2D): + pass + else: + raise TypeError(f'Unhandled type {boundary=}') + +class ConstraintViolated(Exception): + pass + +FATAL = True +def constraint_violated(message): + if FATAL: + raise ConstraintViolated(message) + else: + print(f'{ConstraintViolated.__name__}: {message}') + +def constrain_contact( + res: ContactResult, + should_touch=True, + max_depth=1e-2, + #normal_dir=None, + #normal_dot_min=None, + #normal_dot_max=None +): + + if res.hit is None: + return False # arises from an internal-contact query on a set of one element + + if should_touch is not None and should_touch != res.hit: + if should_touch: + return False #constraint_violated(f'At least one of {res.names} must touch eachother') + else: + return False #constraint_violated(f'{res.names} must not touch') + + if res.hit and max_depth is not None: + observed_depth = max(c.depth for c in res.contacts) + if observed_depth > max_depth: + return False #constraint_violated(f'Contact between {res.names} penetrates by depth {observed_depth} > {max_depth}') + return True + +def constrain_dist(res: dict, min=None, max=None): + + if res.data is None: # results from internal distance check on 1 object + print("res data error") + return + + if not ( + min is None or + min < res.dist + ): + return False + + if not ( + max is None or + max > res.dist + ): + return False + return True + +def constrain_dist_soft(res: dict, min=None, max=None): + + if res.data is None: # results from internal distance check on 1 object + print("res data error") + return + + if res.dist < min: + return min - res.dist + + if res.dist > max: + return res.dist - max + return 0 + +def touching_soft(scene, a, b): + + res = any_touching(scene, a, b) + + + if res.hit is None: + print("res hit error") + return np.inf # arises from an internal-contact query on a set of one element + + if res.hit: + observed_depth = max(c.depth for c in res.contacts) + return observed_depth + else: + res = min_dist(scene, a, b) + if res.data is None: + return np.inf + else: + return res.dist + +def dist_soft_score(res: dict, min, max): + if res.data is None: # results from internal distance check on 1 object + return 0 + + if res.dist > max: + return res.dist - max + elif res.dist < min: + return min - res.dist + else: + return 0 + +_accessibility_vis_seen_objs = set() # used to make vis=True below less spammy + +def accessibility_cost_cuboid_penetration( + scene: trimesh.Scene, + a: Union[str, list[str]], + b: Union[str, list[str]], + normal_dir: np.ndarray, + dist: float, + bvh_cache: dict = None, + vis=False +): + + """ + Extrude the bbox of a by dist in the direction of normal_dir, and check for collisions with b + Return the maximum distance that any part of b penetrates this extrusion + """ + + if isinstance(a, str): + a = [a] + if isinstance(b, str): + b = [b] + + if len(a) == 0 or len(b) == 0: + return 0 + + a_free_col = trimesh.collision.CollisionManager() + + # find which of +X, -X +Y, -Y, +Z, -Z is the normal_dir. Only these values are supported + if ( + not np.isclose(np.linalg.norm(normal_dir), 1) or + np.isclose(normal_dir, 0).sum() != 2 + ): + raise ValueError(f'Invalid normal_dir {normal_dir=}, expected +X, -X, +Y, -Y, +Z, -Z') + normal_axis = np.argmax(np.abs(normal_dir)) + normal_sign = np.sign(normal_dir[normal_axis]) + + visobjs = [] + for name in a: + + T, g = scene.graph[name] + geom = scene.geometry[g] + + # create an extrusion of the bbox by dist in the direction of normal_dir + bpy_obj = bpy.data.objects[name] + + freespace_exts = np.copy(np.array(bpy_obj.dimensions)) + freespace_exts[normal_axis] = dist + freespace_box = trimesh.creation.box(freespace_exts) + + bbox = np.array(bpy_obj.bound_box) + origin_to_bbox_center = bbox.mean(axis=0) + extent_from_real_origin = bbox[0 if normal_sign < 0 else -1][normal_axis] + + offset_vec = normal_dir * (dist/2 + extent_from_real_origin - origin_to_bbox_center[normal_axis]) + total_offset_vec = origin_to_bbox_center + offset_vec + + freespace_box_transform = np.array(bpy_obj.matrix_world) @ trimesh.transformations.translation_matrix(total_offset_vec) + + a_free_col.add_object(name, freespace_box, freespace_box_transform) + + visobjs.append(geom.apply_transform(T)) + + visobjs.append(freespace_box.apply_transform(freespace_box_transform)) + + b_col = iu.col_from_subset(scene, b, bvh_cache=bvh_cache) + hit, contacts = b_col.in_collision_other(a_free_col, return_data=True) + + if vis: + bobjs = iu.meshes_from_names(scene, b) + print(f"{np.round(origin_to_bbox_center, 3)=} {extent_from_real_origin} {bpy_obj.dimensions}") + if not all(name in _accessibility_vis_seen_objs for name in a + b): + trimesh.Scene(visobjs + bobjs).show() + _accessibility_vis_seen_objs.update(a + b) + + if hit: + return max(c.depth for c in contacts) + else: + return 0 + +@gin.configurable +def accessibility_cost(scene, a, b, normal, visualize=False, fast = True): + """ + Computes how much objs b block front access to a. b obj blockages are not summed. + the closest b obj to a is taken as the representative blockage + """ + + if isinstance(a, str): + a = [a] + if isinstance(b, str): + b = [b] + + b = [b_name for b_name in b if b_name not in a] + if len(b) == 0: + return 0 + + if visualize: + fig, ax = plt.subplots() + a_trimeshes = iu.meshes_from_names(scene, a) + b_trimeshes = iu.meshes_from_names(scene, b) + + a_objs = iu.blender_objs_from_names(a) + b_objs = iu.blender_objs_from_names(b) + + score = 0 + for a_name, a_obj, a_trimesh in zip(a, a_objs, a_trimeshes): + + a_centroid = a_trimesh.centroid + + front_plane_pt = a_centroid + front_plane_normal = np.array(a_obj.matrix_world.to_3x3() @ Vector(normal)) + + a_centroid_proj = a_centroid - np.dot(a_centroid - front_plane_pt, front_plane_normal) * front_plane_normal + + + if fast: + # get the closest centroid in b and the mesh that it belongs to + b_centroids = [b_trimesh.centroid for b_trimesh in b_trimeshes] + distances = [np.linalg.norm(pt - a_centroid_proj) for pt in b_centroids] + min_index = np.argmin(distances) + b_closest_pt = b_centroids[min_index] + b_chosen = b[min_index] + else: + # might need to change this to closest pt on the frontal plane + res = min_dist(scene, a_name, b) + b_chosen = res.names[1] if res.names[0] == a_name else res.names[0] + b_closest_pt = res.data.point(b_chosen) + + centroid_to_b = b_closest_pt - a_centroid_proj + + dist = np.linalg.norm(centroid_to_b) + bounds = iu.meshes_from_names(scene, b_chosen)[0].bounds + diag_length = np.linalg.norm(bounds[1] - bounds[0]) + if np.dot(centroid_to_b, front_plane_normal) < 0: + continue + # cos theta/dist + score += (np.dot(centroid_to_b, front_plane_normal) / dist**2) * diag_length + if visualize: + ax.plot([a_centroid_proj[0], b_closest_pt[0]], [a_centroid_proj[1], b_closest_pt[1]], color='red') + #plot source and target geoms + iu.plot_geometry(ax, a_trimesh, 'blue') + iu.plot_geometry(ax, iu.meshes_from_names(scene, b_chosen)[0], 'green') + # plot front plane + # plot_geometry(ax, planes.extract_tagged_plane(a_obj, a_tag, a_front_plane), 'black') + # plot centroid + ax.plot(a_centroid_proj[0], a_centroid_proj[1], 'o', color='black', label='Centroid of a') + + if visualize: + plt.show() + return score + + +def center_stable_surface(scene, a, state): + """ + center a objects on their assigned surfaces. + """ + if isinstance(a, str): + a = [a] + + score = 0 + a_trimeshes = iu.meshes_from_names( + scene, + [state.objs[ai].obj.name for ai in a] + ) + + for name, mesh in zip(a, a_trimeshes): + obj_state = state.objs[name] + obj = obj_state.obj + for i, relation_state in enumerate(obj_state.relations): + relation = relation_state.relation + parent_obj = state.objs[relation_state.target_name].obj + obj_tags = relation.child_tags + parent_tags = relation.parent_tags + parent_all_planes = state.planes.get_tagged_planes(parent_obj, parent_tags) + obj_all_planes = state.planes.get_tagged_planes(obj, obj_tags) + parent_plane = parent_all_planes[relation_state.parent_plane_idx] + obj_plane = obj_all_planes[relation_state.child_plane_idx] + + if relation_state.parent_plane_idx >= len(parent_all_planes): + logging.warning(f'{parent_obj.name=} had too few planes ({len(parent_all_planes)}) for {relation_state}') + return False + if relation_state.child_plane_idx >= len(obj_all_planes): + logging.warning(f'{obj.name=} had too few planes ({len(obj_all_planes)}) for {relation_state}') + return False + + splitted_parent = state.planes.extract_tagged_plane(parent_obj, parent_tags, parent_plane) + parent_trimesh = add_to_scene(state.trimesh_scene, splitted_parent, preprocess=True) + # splitted_obj = planes.extract_tagged_plane(obj, obj_tags, obj_plane) + # add_to_scene(state.trimesh_scene, splitted_obj, preprocess=True) + obj_centroid = mesh.centroid + parent_centroid = parent_trimesh.centroid + score += np.linalg.norm(obj_centroid - parent_centroid) + + iu.delete_obj(scene, splitted_parent.name) + + return score + + +def reflectional_asymmetry_score(scene, a: Union[str, list[str]], b: str, use_long_plane=True): + """ + Computes the reflectional asymmetry score between a and b + """ + if isinstance(a, str): + a = [a] + if b is None or len(b) == 0: + return 0 + + a_trimeshes = iu.meshes_from_names(scene, a) + b_trimesh = iu.meshes_from_names(scene, b)[0] + + a_objs = iu.blender_objs_from_names(a) + b_obj = iu.blender_objs_from_names(b)[0] + + bbox = b_trimesh.bounding_box_oriented + vertices = bbox.vertices + + mid_planes = get_cardinal_planes_bbox(vertices) + if use_long_plane: + plane_pt, plane_normal = mid_planes[0] + else: + plane_pt, plane_normal = mid_planes[1] + + + + return symmetry.calculate_reflectional_asymmetry(a_objs, plane_pt, plane_normal) + + +def coplanarity_cost_pair(scene, a: str, b: str): + """ + Computes the coplanarity cost between a and b + """ + a_trimesh = iu.meshes_from_names(scene, a)[0] + b_trimesh = iu.meshes_from_names(scene, b)[0] + + a_obj = iu.blender_objs_from_names(a)[0] + b_obj = iu.blender_objs_from_names(b)[0] + + a_trimesh_bbox = a_trimesh.bounding_box_oriented + b_trimesh_bbox = b_trimesh.bounding_box_oriented + + object1_planes = [] + object2_planes = [] + + # Helper function to check if a normal is close to any in the list + def is_normal_new(normal, normals_list): + normals_np = np.array(normals_list) + if len(normals_list) > 0: + return not np.any(np.all(np.isclose(normals_np, normal, atol=1e-3), axis=1)) + return True + + for i in range(len(a_trimesh_bbox.faces)): + normal = a_trimesh_bbox.face_normals[i] + if is_normal_new(normal, [n for _, n in object1_planes]): + object1_planes.append((a_trimesh_bbox.vertices[a_trimesh_bbox.faces[i]][0], normal)) + + for i in range(len(b_trimesh_bbox.faces)): + normal = b_trimesh_bbox.face_normals[i] + if is_normal_new(normal, [n for _, n in object2_planes]): + object2_planes.append((b_trimesh_bbox.vertices[b_trimesh_bbox.faces[i]][0], normal)) + + # Calculate angle cost matrix for bipartite matching + angle_cost_matrix = np.zeros((len(object1_planes), len(object2_planes))) + for j, plane1 in enumerate(object1_planes): + for k, plane2 in enumerate(object2_planes): + angle_cost = 1 - np.dot(plane1[1], plane2[1]) + angle_cost_matrix[j, k] = angle_cost + + # Perform linear sum assignment based on angle alignment + row_ind, col_ind = linear_sum_assignment(angle_cost_matrix) + + # Calculate total costs (angle + distance) for the optimal matching + total_costs = [] + for r, c in zip(row_ind, col_ind): + distance_cost = iu.distance_to_plane(object1_planes[r][0], object2_planes[c][0], object2_planes[c][1]) + total_cost = angle_cost_matrix[r, c] + distance_cost # Sum angle and distance costs + total_costs.append(total_cost) + total_costs = sorted(total_costs) + + return sum(total_costs[:-2]) + +def coplanarity_cost(scene, a: Union[str, list[str]]): + """ + Computes the coplanarity cost between a and b + """ + if isinstance(a, str): + a = [a] + + a_trimeshes = iu.meshes_from_names(scene, a) + a_objs = iu.blender_objs_from_names(a) + + # Order objects by principal axis + ordered_objects = iu.order_objects_by_principal_axis(a_objs) + + all_total_costs = [] # To store the sum of angle and distance costs for each optimal matching + + # Iterate over pairs of consecutive objects + for i in range(len(ordered_objects) - 1): + all_total_costs.append(coplanarity_cost_pair(scene, ordered_objects[i].name, ordered_objects[i + 1].name)) + + # Calculate the final cost as the sum of the remaining costs + final_cost = sum(all_total_costs) / len(a_objs) + + return final_cost \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/__init__.py b/infinigen/core/constraints/example_solver/__init__.py new file mode 100644 index 000000000..7a3d280f8 --- /dev/null +++ b/infinigen/core/constraints/example_solver/__init__.py @@ -0,0 +1,3 @@ +from . import room +from .state_def import State +from .solve import Solver \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/annealing.py b/infinigen/core/constraints/example_solver/annealing.py new file mode 100644 index 000000000..2db976694 --- /dev/null +++ b/infinigen/core/constraints/example_solver/annealing.py @@ -0,0 +1,372 @@ +# 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, Karhan Kayan + +from collections import defaultdict +import logging +import os +import time +import copy +import typing +from pprint import pprint + +import matplotlib.pyplot as plt +import bpy +import numpy as np +import tqdm +import pandas as pd +import gin + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r, + evaluator +) +from .moves import Move +from .state_def import State +from infinigen.core.util import blender as butil + +from infinigen.core.constraints.constraint_language import util as impl_util + +logger = logging.getLogger(__name__) + +BPY_GARBAGE_COLLECT_FREQUENCY = 20 # every X optim steps + +@gin.configurable +class SimulatedAnnealingSolver: + + def __init__( + self, + max_invalid_candidates, + initial_temp, + final_temp, + finetune_pct, + checkpoint_best=False, + output_folder=None, + visualize=False, + print_report_freq=10, + print_breakdown_freq=0 + ) -> None: + + self.initial_temp = initial_temp + self.final_temp = final_temp + self.max_invalid_candidates = max_invalid_candidates + self.finetune_pct = finetune_pct + + self.print_report_freq = print_report_freq + self.print_breakdown_freq = print_breakdown_freq + + self.checkpoint_best = checkpoint_best + if checkpoint_best: + raise NotImplementedError(f'{checkpoint_best=}') + + self.output_folder = output_folder + self.visualize = visualize + + self.cooling_rate = None + self.last_eval_result = None + + self.eval_memo = {} + + def save_stats(self, path): + + if len(self.stats) == 0: + return + + df = pd.DataFrame.from_records(self.stats) + + logger.info(f'Saving stats {path}') + df.to_csv(path) + + fig, ax1 = plt.subplots() + ax1.set_xlabel('Iteration') + ax1.set_ylabel('Score', color='C0') + ax1.plot(df['curr_iteration'], df['loss'], color='C0') + + #ax2 = ax1.twinx() + #ax2.set_ylabel('Move Time', color='C1') + #ax2.plot(df['curr_iteration'], df['move_dur'], color='C1') + + figpath = path.parent/(path.stem+'.png') + logger.info(f'Saving plot {figpath}') + plt.savefig(figpath) + plt.close() + + logger.info(f"Total elapsed {path.stem} {self.stats[-1]['elapsed']:.2f}") + + def reset(self, max_iters): + + self.curr_iteration = 0 + self.stats = [] + self.curr_result = None + self.best_loss = None + self.eval_memo = {} + + self.optim_start_time = time.perf_counter() + self.max_iterations = max_iters + + if max_iters == 0: + self.cooling_rate = 0 + else: + steps = (max_iters * (1 - self.finetune_pct)) + ratio = self.final_temp / self.initial_temp + self.cooling_rate = np.power(ratio, 1/steps) + + logger.debug(f'Reset solver with {max_iters=} cooling_rate={self.cooling_rate:.4f}') + + def checkpoint(self, state): + + filename = os.path.join(self.output_folder, f"checkpoint_state.pkl") + state.save(filename) + + if self.visualize: + #save score plot + plt.plot(self.score_history) + plt.savefig(os.path.join(self.output_folder, f"scores.png")) + plt.close() + + # render image + i = 1 + while os.path.exists(os.path.join(self.output_folder, f"{i:04}.png")): + i += 1 + bpy.context.scene.render.filepath = os.path.join(self.output_folder, f"{i:04}.png") + bpy.ops.render.render(write_still=True) + + def validate_lazy_eval( + self, + state: State, + consgraph: cl.Problem, + prop_result: evaluator.EvalResult, + filter_domain: r.Domain + ): + + test_memo = {} + impl_util.DISABLE_BVH_CACHE = True + real_result = evaluator.evaluate_problem(consgraph, state, filter_domain, memo=test_memo) + impl_util.DISABLE_BVH_CACHE = False + + if real_result.loss() == prop_result.loss(): + return + + for n in consgraph.traverse(inorder=False): + key = evaluator.memo_key(n) + if key not in self.eval_memo: + continue + lazy = self.eval_memo[key] + if test_memo[key] == lazy: + continue + + print('\n\n INVALID') + pprint(n, depth=3) + print(f'memo for node is out of sync, got {lazy=} yet {test_memo[key]=}') + raise ValueError(f'{real_result.loss()=:.4f} {prop_result.loss()=:.4f}') + + @gin.configurable + def evaluate_move( + self, + consgraph: cl.Node, + state: State, + move: Move, + filter_domain: r.Domain, + do_lazy_eval=True, + validate_lazy_eval=False + ): + + if do_lazy_eval: + evaluator.evict_memo_for_move(consgraph, state, self.eval_memo, move) + prop_result = evaluator.evaluate_problem( + consgraph, state, filter_domain, self.eval_memo + ) + else: + prop_result = evaluator.evaluate_problem( + consgraph, state, filter_domain, memo={} + ) + + if validate_lazy_eval: + self.validate_lazy_eval(state, consgraph, prop_result, filter_domain) + + return prop_result + + @gin.configurable + def retry_attempt_proposals( + self, + propose_func: typing.Callable, + consgraph: cl.Node, + state: State, + temp: float, + filter_domain: r.Domain, + ) -> typing.Tuple[Move, evaluator.EvalResult, int]: + + move_gen = propose_func(consgraph, state, filter_domain, temp) + + move = None + retry = None + for retry, move in enumerate(move_gen): + + if retry == self.max_invalid_candidates: + logger.debug(f'{move_gen=} reached {self.max_invalid_candidates=} without succeeding an apply()') + break + + succeeded = move.apply(state) + if succeeded: + evaluator.evict_memo_for_move(consgraph, state, self.eval_memo, move) + result = self.evaluate_move(consgraph, state, move, filter_domain) + return move, result, retry + + logger.debug(f'{retry=} reverting {move=}') + evaluator.evict_memo_for_move(consgraph, state, self.eval_memo, move) + move.revert(state) + + else: + logger.debug(f'{move_gen=} produced {retry} attempts and none were valid') + + return move, None, retry + + def curr_temp(self) -> float: + temp = self.initial_temp * self.cooling_rate ** self.curr_iteration + temp = np.clip(temp, self.final_temp, self.initial_temp) + return temp + + def metrop_hastings_with_viol(self, prop_result: evaluator.EvalResult, temp: float): + + prop_viol = prop_result.viol_count() + curr_viol = self.curr_result.viol_count() + + diff = prop_result.loss() - self.curr_result.loss() + log_prob = -diff/temp + + viol_diff = prop_viol - curr_viol + + result = {'diff': diff, 'log_prob': log_prob, 'viol_diff': viol_diff} + + if viol_diff < 0: + result['accept'] = True + return result + elif viol_diff > 0: + result['accept'] = False + return result + + # standard metropolis-hastings + rv = np.log(np.random.uniform()) + result['accept'] = rv < log_prob + return result + + def step(self, consgraph, state, move_gen_func, filter_domain): + + if self.curr_result is None: + self.curr_result = evaluator.evaluate_problem(consgraph, state, filter_domain) + + move_start_time = time.perf_counter() + + is_log_step = ( + self.print_report_freq != 0 + and self.curr_iteration % self.print_report_freq == 0 + ) + is_report_step = ( + self.print_breakdown_freq != 0 + and self.curr_iteration % self.print_breakdown_freq == 0 + ) + + temp = self.curr_temp() + move, prop_result, retry = self.retry_attempt_proposals( + move_gen_func, consgraph, state, temp, filter_domain + ) + + if prop_result is None: + # set null values for logging purposes + accept_result = {'accept': None, 'diff': 0, 'log_prob': 0, 'viol_diff': None} + else: + accept_result = self.metrop_hastings_with_viol(prop_result, temp) + if accept_result['accept']: + self.curr_result = prop_result + move.accept(state) + else: + evaluator.evict_memo_for_move(consgraph, state, self.eval_memo, move) + move.revert(state) + + dt = time.perf_counter() - move_start_time + elapsed = time.perf_counter() - self.optim_start_time + + if ( + (self.print_report_freq != 0 and accept_result['accept']) + or is_log_step + ): + + n = len(state.objs) + move_log = move_gen_func.__name__ if move is None else move + + log_prob = accept_result['log_prob'] + prob = 1 if log_prob > 7 else np.exp(accept_result['log_prob']) # avoid overflow warnings. clamp to exp = exp(7) ~= 1000 + + loss = self.curr_result.loss() + viol = self.curr_result.viol_count() + diff = accept_result['diff'] + accept = accept_result['accept'] + viol_diff = accept_result['viol_diff'] or 0 + + logger.info( + f"it={self.curr_iteration} {dt=:.3f} {n=} " + f"{loss=:.3e} {viol=:.1f} " + f"{temp=:.2e} {diff=:.2f} {viol_diff=:.1f} {prob=:.2f} {accept=} " + f"{move_log}" + ) + + + if is_log_step: + self.stats.append(dict( + curr_iteration=self.curr_iteration, + loss=self.curr_result.loss(), + viol=self.curr_result.viol_count(), + best_loss=self.best_loss, + temp=temp, + accept=accept, + move_gen=move_gen_func.__name__, + move_type=( + move.__class__.__name__ + if move is not None else None + ), + move_target=( + move.name + if move is not None and hasattr(move, 'name') + else None + ), + move_dur=dt, + elapsed=elapsed, + retry=retry + )) + + if is_report_step and prop_result is not None: + df = prop_result.to_df() + + if self.last_eval_result is not None: + last_df = self.last_eval_result.to_df() + diff_cols = [ + c for c in df.columns + if ( + not last_df[c].equals(df[c]) + or (df[c]["viol_count"] is not None and last_df[c]["viol_count"] > 0) + ) + ] + print(self.last_eval_result.viol_count(), self.curr_result.viol_count(), prop_result.viol_count()) + last_df.index = ['prev_' + x for x in last_df.index] + df = pd.concat([last_df[diff_cols], df[diff_cols]]) + + print(df) + + if self.curr_iteration % BPY_GARBAGE_COLLECT_FREQUENCY == 0: + butil.garbage_collect(butil.get_all_bpy_data_targets()) + + if self.curr_iteration != 0 and self.curr_iteration % 50 == 0: + print(f'CLUTTER REPORT {self.curr_iteration=}') + print(' State Size', len(state.objs)) + print(' Trimesh', len(state.trimesh_scene.graph.nodes)) + print(' Objects', len(bpy.data.objects)) + print(' Meshes', len(bpy.data.meshes)) + print(' Materials', len(bpy.data.materials)) + print(' Textures', len(bpy.data.materials)) + + self.curr_iteration += 1 + if prop_result is not None: + self.last_eval_result = prop_result diff --git a/infinigen/core/constraints/example_solver/geometry/dof.py b/infinigen/core/constraints/example_solver/geometry/dof.py new file mode 100644 index 000000000..5f60e1e43 --- /dev/null +++ b/infinigen/core/constraints/example_solver/geometry/dof.py @@ -0,0 +1,426 @@ +# 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: Karhan Kayan + +import logging + +import gin +import bpy +import numpy as np +from infinigen.core.constraints.example_solver.geometry import stability +from mathutils import Vector +import trimesh +from shapely.geometry import Point, Polygon, MultiPolygon +import matplotlib.pyplot as plt + +from infinigen.core import tags as t, tagging +from infinigen.core.constraints import constraint_language as cl + +from infinigen.core.constraints.example_solver import ( + state_def +) + +import infinigen.core.util.blender as butil +import infinigen.core.constraints.example_solver.geometry.validity as validity +from infinigen.core.constraints.constraint_language.util import meshes_from_names ,delete_obj +from infinigen.core.constraints.constraint_language import util as iu +from infinigen.core import tagging, tags as t +from infinigen.core.constraints.example_solver.room.constants import WALL_HEIGHT, WALL_THICKNESS + + + +logger = logging.getLogger(__name__) + +def stable_against_matrix(point, normal): + """ + Given a point and normal defining a plane, return a 3x3 matrix that + restricts movement perpendicular to the plane. + """ + # Normalize the normal vector + normal = np.array(normal) + normalized_normal = normal / np.linalg.norm(normal) + + # Create a matrix that restricts movement along the normal direction + restriction_matrix = np.identity(3) - np.outer(normalized_normal, normalized_normal) + return restriction_matrix + +def combined_stability_matrix(parent_planes): + """ + Given a list of relations (each a tuple of point and normal), + compute the combined 3x3 matrix M. + """ + + M = np.identity(3) + for name, poly in parent_planes: + obj = bpy.data.objects[name] + poly = obj.data.polygons[poly] + point = obj.data.vertices[poly.vertices[0]] + M = np.dot(M, stable_against_matrix(point, poly.normal)) + return M + + +def rotation_constraint(normal): + """ + Given a normal defining a plane, return the axis of rotation allowed by this constraint. + """ + # Normalize the normal vector + normal = np.array(normal) + normalized_normal = normal / np.linalg.norm(normal) + + return normalized_normal + +def combine_rotation_constraints(parent_planes, eps=0.01): + """ + Given a list of normals, compute the combined axis of rotation. + If there are conflicting constraints, return None. + """ + + normals = [ + bpy.data.objects[name].data.polygons[poly].normal + for name, poly in parent_planes + ] + + # Start with the first constraint + combined_axis = rotation_constraint(normals[0]) + + for normal in normals[1:]: + axis = rotation_constraint(normal) + + # If the axes are not parallel, there's a conflict + if not np.isclose(combined_axis.dot(axis), 1, atol=eps): + return None + + # Otherwise, keep the current axis (since they're parallel) + + return combined_axis + + +def rotate_object_around_axis(obj, axis, std, angle=None): + """ + Rotate an object around a given axis. + """ + # Normalize the axis + axis = np.array(axis) + normalized_axis = axis / np.linalg.norm(axis) + + # If no angle is provided, generate a random angle between 0 and 2*pi + if angle is None: + angle = np.random.normal(0,std) + + obj.rotation_mode = 'AXIS_ANGLE' + obj.rotation_axis_angle = Vector([angle]+ list(normalized_axis)) + +def check_init_valid( + state: state_def.State, + name: str, + obj_planes: list, + assigned_planes: list, + margins +): + + if len(obj_planes) == 0: + raise ValueError(f"{check_init_valid.__name__} for {name=} got {obj_planes=}") + if len(obj_planes) > 3: + raise ValueError(f'{check_init_valid.__name__} for {name=} got {len(obj_planes)=}') + + def get_rot(ind): + try: + a = obj_planes[ind][0] + b = assigned_planes[ind][0] + except IndexError: + raise ValueError(f'Invalid {ind=} {obj_planes=} {assigned_planes=}') + + a_plane = obj_planes[ind] + b_plane = assigned_planes[ind] + a_obj = bpy.data.objects[a] + b_obj = bpy.data.objects[b] + + a_poly_index = a_plane[1] + a_poly = a_obj.data.polygons[a_poly_index] + b_poly_index = b_plane[1] + b_poly = b_obj.data.polygons[b_poly_index] + plane_normal_a = iu.global_polygon_normal(a_obj, a_poly) + plane_normal_b = iu.global_polygon_normal(b_obj, b_poly) + plane_normal_b = -plane_normal_b + rotation_axis = np.cross(plane_normal_a, plane_normal_b) + + if not np.isclose(np.linalg.norm(rotation_axis),0, atol = 1e-03): + rotation_axis = rotation_axis / np.linalg.norm(rotation_axis) + else: + rotation_axis = np.array([0,0,1]) + dot = plane_normal_a.dot(plane_normal_b) + rotation_angle = np.arccos(np.clip(dot, -1, 1)) + if np.isnan(rotation_angle): + raise ValueError(f'Invalid {rotation_angle=}') + return a,b,rotation_axis, rotation_angle, plane_normal_b + + def is_rotation_allowed(rotation_axis, reference_normal): + # Check if rotation axis is the same as the reference normal (with some tolerance) + res = ( + np.allclose(rotation_axis, reference_normal, atol=1e-02) + or np.allclose(rotation_axis, -reference_normal, atol=1e-02) + ) + if not res: + dot = rotation_axis.dot(reference_normal) + logger.debug(f'{is_rotation_allowed.__name__} got {res=} with {rotation_axis=} {reference_normal=} {dot=}') + return res + + + a, b, rotation_axis, rotation_angle, plane_normal_b = get_rot(0) + iu.rotate(state.trimesh_scene, a, rotation_axis, rotation_angle) + first_plane_normal = plane_normal_b # Save the normal of the first plane + + dof_remaining = True # Degree of freedom remaining after the first alignment + + # Check and apply rotations for subsequent planes + for i in range(1, len(obj_planes)): + + a, b, rotation_axis, rotation_angle, plane_normal_b = get_rot(i) + + if np.isclose(np.linalg.norm(rotation_angle), 0, atol=1e-01): + logger.debug(f"no rotation needed for {i=} of {len(obj_planes)}") + continue + + rot_allowed = is_rotation_allowed(rotation_axis, first_plane_normal) + if dof_remaining and rot_allowed: + # Rotate around the normal of the first plane + iu.rotate(state.trimesh_scene, a, rotation_axis, rotation_angle) + dof_remaining = False # No more degrees of freedom remaining + logger.debug(f"rotated {a=} to satisfy assignment {i=}") + else: + logger.debug(f"dofs failed for {i=} of {len(obj_planes)=}, {rot_allowed=} {dof_remaining=}") + return False, None, None + + # Construct the system of linear equations for translation + A = [] + c = [] + for i in range(len(obj_planes)): + a_obj_name, a_poly_index = obj_planes[i] + b_obj_name, b_poly_index = assigned_planes[i] + margin = margins[i] + + a_obj = bpy.data.objects[a_obj_name] + b_obj = bpy.data.objects[b_obj_name] + + a_poly = a_obj.data.polygons[a_poly_index] + b_poly = b_obj.data.polygons[b_poly_index] + + # Get global coordinates and normals + plane_point_a = iu.global_vertex_coordinates(a_obj, a_obj.data.vertices[a_poly.vertices[0]]) + plane_point_b = iu.global_vertex_coordinates(b_obj, b_obj.data.vertices[b_poly.vertices[0]]) + plane_normal_b = iu.global_polygon_normal(b_obj, b_poly) + plane_point_b += plane_normal_b * margin + + # Append to the matrix A and vector b for Ax = c + A.append(plane_normal_b) + c.append(plane_normal_b.dot(plane_point_b - plane_point_a)) + + # Solve the linear system + A = np.array(A) + c = np.array(c) + + t, residuals, rank, s = np.linalg.lstsq(A, c, rcond=None) + a_obj_name, a_poly_index = obj_planes[0] + + a_obj = bpy.data.objects[a_obj_name] + + # Check if the solution is valid + # You can define a threshold to determine if the residuals are acceptable + # Manually compute residuals if m <= n + if residuals.size == 0: + computed_residuals = np.dot(A, t) - c + residuals_sum = np.sum(computed_residuals**2) + if residuals_sum < 1e-03: + return True, A.shape[1] - rank, t # Solution is valid + else: + logger.debug(f'{check_init_valid.__name__} failed with {residuals_sum=}') + return False, None, None # Solution is not valid + else: + if np.all(residuals < 1e-03): + return True, A.shape[1] - rank, t # Solution is valid + else: + logger.debug(f'{check_init_valid.__name__} failed with {residuals=}') + return False, None, None # No valid solution + + +def project(points, plane_normal): + to_2D = trimesh.geometry.plane_transform(origin=(0,0,0), normal=plane_normal) + vertices_2D = trimesh.transformations.transform_points(points, to_2D)[:, :2] + return vertices_2D + +def apply_relations_surfacesample( + state: state_def.State, + name: str, +): + obj_state = state.objs[name] + obj_name = obj_state.obj.name + + parent_objs = [] + parent_planes = [] + obj_planes = [] + margins = [] + parent_tag_list = [] + + if len(obj_state.relations) == 0: + raise ValueError(f"Object {name} has no relations") + elif len(obj_state.relations) > 3: + raise ValueError(f"Object {name} has more than 2 relations, not supported. {obj_state.relations=}") + + for i, relation_state in enumerate(obj_state.relations): + + if isinstance(relation_state.relation, cl.AnyRelation): + raise ValueError(f"Got {relation_state.relation} for {name=} {relation_state.target_name=}") + + parent_obj = state.objs[relation_state.target_name].obj + obj_plane, parent_plane = state.planes.get_rel_state_planes(state, name, relation_state) + + if obj_plane is None: + continue + if parent_plane is None: + continue + + obj_planes.append(obj_plane) + parent_planes.append(parent_plane) + parent_objs.append(parent_obj) + match relation_state.relation: + case cl.StableAgainst(child_tags, parent_tags, margin): + margins.append(margin) + parent_tag_list.append(parent_tags) + case cl.SupportedBy(child_tags, parent_tags): + margins.append(0) + parent_tag_list.append(parent_tags) + case _: + raise NotImplementedError + + valid, dof, T = check_init_valid(state, name, obj_planes, parent_planes, margins) + if not valid: + rels = [(rels.relation, rels.target_name) for rels in obj_state.relations] + logger.warning(f'Init was invalid for {name=} {rels=}') + return None + + if dof == 0: + iu.translate(state.trimesh_scene, obj_name, T) + elif dof == 1: + + assert len(parent_planes) == 2, (name, len(parent_planes)) + + parent_obj1 = parent_objs[0] + parent_obj2 = parent_objs[1] + parent_plane1 = parent_planes[0] + parent_plane2 = parent_planes[1] + parent_tags1 = parent_tag_list[0] + parent_tags2 = parent_tag_list[1] + margin1 = margins[0] + margin2 = margins[1] + obj_plane1 = obj_planes[0] + obj_plane2 = obj_planes[1] + + parent1_trimesh = state.planes.get_tagged_submesh(state.trimesh_scene, parent_obj1.name, parent_tags1, parent_plane1) + parent2_trimesh = state.planes.get_tagged_submesh(state.trimesh_scene, parent_obj2.name, parent_tags2, parent_plane2) + + parent1_poly_index = parent_plane1[1] + parent1_poly = parent_obj1.data.polygons[parent1_poly_index] + plane_normal_1 = iu.global_polygon_normal(parent_obj1, parent1_poly) + pts = parent2_trimesh.vertices + projected = project(pts,plane_normal_1) + p1_to_p1 = trimesh.path.polygons.projected(parent1_trimesh, plane_normal_1, (0,0,0)) + + if p1_to_p1 is None: + raise ValueError(f'Failed to project {parent1_trimesh=} {plane_normal_1=} for {name=}') + + + + if all([p1_to_p1.buffer(1e-1).contains(Point(pt[0], pt[1])) for pt in projected]): + face_mask = tagging.tagged_face_mask(parent_obj2, parent_tags2) + stability.move_obj_random_pt(state, obj_name, parent_obj2.name, face_mask, parent_plane2) + stability.snap_against(state.trimesh_scene, obj_name, parent_obj2.name, obj_plane2, parent_plane2, margin=margin2) + stability.snap_against(state.trimesh_scene, obj_name, parent_obj1.name, obj_plane1, parent_plane1, margin=margin1) + else: + face_mask = tagging.tagged_face_mask(parent_obj1, parent_tags1) + stability.move_obj_random_pt(state, obj_name, parent_obj1.name, face_mask, parent_plane1) + stability.snap_against(state.trimesh_scene, obj_name, parent_obj1.name, obj_plane1, parent_plane1, margin=margin1) + stability.snap_against(state.trimesh_scene, obj_name, parent_obj2.name, obj_plane2, parent_plane2, margin=margin2) + + elif dof == 2: + assert len(parent_planes) == 1, (name, len(parent_planes)) + for i, relation_state in enumerate(obj_state.relations): + parent_obj = state.objs[relation_state.target_name].obj + obj_plane, parent_plane = state.planes.get_rel_state_planes(state, name, relation_state) + if obj_plane is None: + continue + if parent_plane is None: + continue + iu.set_rotation(state.trimesh_scene, obj_name, (0, 0, 2*np.pi*np.random.randint(0, 4)/4)) + face_mask = tagging.tagged_face_mask(parent_obj, relation_state.relation.parent_tags) + stability.move_obj_random_pt(state, obj_name, parent_obj.name, face_mask, parent_plane) + match relation_state.relation: + case cl.StableAgainst(child_tags, parent_tags, margin): + stability.snap_against(state.trimesh_scene, obj_name, parent_obj.name, obj_plane, parent_plane, margin=margin) + case cl.SupportedBy(child_tags, parent_tags): + stability.snap_against(state.trimesh_scene, obj_name, parent_obj.name, obj_plane, parent_plane, margin=0) + case _: + raise NotImplementedError + return parent_planes + +def validate_relations_feasible(state: state_def.State, name: str) -> bool: + + assignments = state.objs[name].relations + targets = [rel.target_name for rel in assignments] + + rooms = {targ for targ in targets if t.Semantics.Room in state.objs[targ].tags} + if len(rooms) > 1: + raise ValueError(f"Object {name} has multiple room targets {rooms}") + +@gin.configurable +def try_apply_relation_constraints( + state: state_def.State, + name: str, + n_try_resolve=10, + visualize=False +): + + ''' + name is in objs.name + name has been recently reassigned or added or swapped + it needs snapping, and dof updates + + Result: + dof_mat and dof axis for name are updated + objstate for name has update location rotaton etc + + ''' + + validate_relations_feasible(state, name) + + for retry in range(n_try_resolve): + obj_state = state.objs[name] + if iu.blender_objs_from_names(obj_state.obj.name)[0].dimensions[2] > WALL_HEIGHT - WALL_THICKNESS: + logger.warning(f"Object {obj_state.obj.name} is too tall for the room: {obj_state.obj.dimensions[2]}, {WALL_HEIGHT=}, {WALL_THICKNESS=}") + parent_planes = apply_relations_surfacesample(state, name) + + + # assignments not valid + if parent_planes is None: + logger.debug(f'Found {parent_planes=} for {name=} {retry=}') + if visualize: + vis = butil.copy(obj_state.obj) + vis.name = obj_state.obj.name[:30] + '_noneplanes_' + str(retry) + return False + + if validity.check_post_move_validity(state, name): + obj_state.dof_matrix_translation = combined_stability_matrix(parent_planes) + obj_state.dof_rotation_axis = combine_rotation_constraints(parent_planes) + return True + + if visualize: + vis = butil.copy(obj_state.obj) + vis.name = obj_state.obj.name[:30] + '_failure_' + str(retry) + + # butil.save_blend("test.blend") + + + logger.debug(f'Exhausted {n_try_resolve=} tries for {name=}') + return False + + diff --git a/infinigen/core/constraints/example_solver/geometry/parse_scene.py b/infinigen/core/constraints/example_solver/geometry/parse_scene.py new file mode 100644 index 000000000..11cfd8cf1 --- /dev/null +++ b/infinigen/core/constraints/example_solver/geometry/parse_scene.py @@ -0,0 +1,79 @@ +# 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: Karhan Kayan + +import bpy +import trimesh +from shapely import LineString, Point +import numpy as np +from typing import Union +from trimesh import Trimesh, Scene +from mathutils import Vector, Matrix + +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t +from infinigen.core.constraints.constraint_language.util import ( + translate, + rotate, + sync_trimesh +) +import fcl + +def to_trimesh(obj: bpy.types.Object): + bpy.context.view_layer.update() + verts = np.array([obj.matrix_world @ v.co for v in obj.data.vertices]) + faces = np.array([p.vertices for p in obj.data.polygons]) + mesh = trimesh.Trimesh(vertices=verts, faces=faces, process=False) + mesh.current_transform = trimesh.transformations.identity_matrix() + return mesh + + +def preprocess_obj(obj): + with butil.ViewportMode(obj, mode='EDIT'): + butil.select(obj) + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + + bpy.context.view_layer.update() + + butil.apply_transform(obj, loc=False, rot=False, scale=True) + +def preprocess_scene(objects): + for o in objects: + preprocess_obj(o) + + + +def parse_scene(objects): + # convert all bpy.objects into a trimesh.Scene + + preprocess_scene(objects) + + scene = trimesh.Scene() + for obj in objects: + add_to_scene(scene, obj) + + return scene + +def add_to_scene(scene, obj, preprocess=True): + if preprocess: + preprocess_obj(obj) + obj_matrix_world = Matrix(obj.matrix_world) + obj.matrix_world = Matrix.Identity(4) + tmesh = to_trimesh(obj) + tmesh.metadata['tags'] = tagging.union_object_tags(obj) + scene.add_geometry( + geometry=tmesh, + # transform=np.array(obj.matrix_world), + geom_name=obj.name + '_mesh', + node_name=obj.name + ) + col = trimesh.collision.CollisionManager() + T = trimesh.transformations.identity_matrix() + t = fcl.Transform(T[:3, :3], T[:3, 3]) + tmesh.fcl_obj = col._get_fcl_obj(tmesh) + tmesh.col_obj = fcl.CollisionObject(tmesh.fcl_obj, t) + obj.matrix_world = obj_matrix_world + sync_trimesh(scene, obj.name) + return tmesh diff --git a/infinigen/core/constraints/example_solver/geometry/planes.py b/infinigen/core/constraints/example_solver/geometry/planes.py new file mode 100644 index 000000000..5e5cd1f9c --- /dev/null +++ b/infinigen/core/constraints/example_solver/geometry/planes.py @@ -0,0 +1,303 @@ +# 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: Karhan Kayan + +from __future__ import annotations +import logging + +import bpy +import numpy as np +import gin +import trimesh + + +import infinigen.core.util.blender as butil +from infinigen.core import tagging, tags as t +from infinigen.core.constraints.constraint_language.util import meshes_from_names, blender_objs_from_names + +logger = logging.getLogger(__name__) + +def global_vertex_coordinates(obj, local_vertex): + return obj.matrix_world @ local_vertex.co + +def global_polygon_normal(obj, polygon): + loc, rot, scale = obj.matrix_world.decompose() + rot = rot.to_matrix() + normal = rot @ polygon.normal + try: + return normal / np.linalg.norm(normal) + except ZeroDivisionError: + raise ZeroDivisionError(f"Zero division error in global_polygon_normal for {obj.name=}, {polygon.index=}, {normal=}") + +class Planes: + def __init__(self): + self._mesh_hashes = {} # Dictionary to store mesh hashes for each object + self._cached_planes = {} # Dictionary to store computed planes, keyed by object and face_mask hash + self._cached_plane_masks = {} # Dictionary to store computed plane masks, keyed by object, plane, and face_mask hash + + def calculate_mesh_hash(self, obj): + # Simple hash based on counts of vertices, edges, and polygons + mesh = obj.data + hash_str = f"{obj.name}_{len(mesh.vertices)}_{len(mesh.edges)}_{len(mesh.polygons)}" + return hash(hash_str) + + def hash_face_mask(self, face_mask): + # Hash the face_mask to use as part of the key for caching + return hash(face_mask.tostring()) + + def get_all_planes_cached(self, obj, face_mask, tolerance=1e-4): + current_mesh_hash = self.calculate_mesh_hash(obj) + current_face_mask_hash = self.hash_face_mask(face_mask) + cache_key = (obj.name, current_face_mask_hash) + + # Check if mesh has been modified or planes have not been computed before for this object and face_mask + if cache_key not in self._cached_planes or self._mesh_hashes.get(obj.name) != current_mesh_hash: + self._mesh_hashes[obj.name] = current_mesh_hash # Update the hash for this object + # Recompute planes for this object and face_mask and update cache + # logger.info(f'Cache MISS planes for {obj.name=}') + self._cached_planes[cache_key] = self.compute_all_planes_fast(obj, face_mask, tolerance) + + # logger.info(f'Cache HIT planes for {obj.name=}') + return self._cached_planes[cache_key] + + + @staticmethod + def normalize(v): + norm = np.linalg.norm(v) + return v / norm if norm > 0 else v + + @staticmethod + def hash_plane(normal, point, tolerance=1e-4): + normal_normalized = normal / np.linalg.norm(normal) + distance = np.dot(normal_normalized, point) + return (tuple(np.round(normal_normalized / tolerance).astype(int)), round(distance / tolerance)) + + def compute_all_planes_fast(self, obj, face_mask, tolerance=1e-4): + # Cache computations + + vertex_cache = {v.index: global_vertex_coordinates(obj, v) for v in obj.data.vertices} + normal_cache = {p.index: global_polygon_normal(obj, p) for p in obj.data.polygons if face_mask[p.index]} + + unique_planes = {} + + for polygon in obj.data.polygons: + if not face_mask[polygon.index]: + continue + + # Get the normal and a vertex to represent the plane + normal = normal_cache[polygon.index] + + if np.linalg.norm(normal) < 1e-6: + continue + + vertex = vertex_cache[polygon.vertices[0]] + + # Hash the plane using both normal and the point + plane_hash = self.hash_plane(normal, vertex, tolerance) + + if plane_hash not in unique_planes: + unique_planes[plane_hash] = (obj.name, polygon.index) + + return list(unique_planes.values()) + + + def get_all_planes_deprecated(self, obj, face_mask, tolerance=1e-4) -> tuple[str, int]: + "get all unique planes formed by faces in face_mask" + # ASSUMES: object is triangulated, no quads/polygons + unique_planes = [] + for polygon in obj.data.polygons: + if not face_mask[polygon.index]: + continue + vertex = global_vertex_coordinates(obj, obj.data.vertices[polygon.vertices[0]]) + normal = global_polygon_normal(obj, polygon) + belongs_to_existing_plane = False + for name, polygon2_index in unique_planes: + polygon2 = obj.data.polygons[polygon2_index] + plane_vertex = global_vertex_coordinates(obj, obj.data.vertices[polygon2.vertices[0]]) + plane_normal = global_polygon_normal(obj, polygon2) + if ( + np.allclose(np.cross(normal, plane_normal), 0, rtol=tolerance) and + np.allclose(np.dot(vertex - plane_vertex, plane_normal), 0, rtol=tolerance) + ): + belongs_to_existing_plane = True + break + if not belongs_to_existing_plane and polygon.normal and polygon.normal.length > 0: + unique_planes.append((obj.name, polygon.index)) + return unique_planes + + + @gin.configurable + def get_tagged_planes(self, obj: bpy.types.Object, tags: set, fast=True): + """ + get all unique planes formed by faces tagged with tags + """ + + tags = t.to_tag_set(tags) + + mask = tagging.tagged_face_mask(obj, tags) + if not mask.any(): + obj_tags = tagging.union_object_tags(obj) + logger.warning( + f'Attempted to get_tagged_planes {obj.name=} {tags=} but mask was empty, {obj_tags=}' + ) + return [] + + if fast: + planes = self.get_all_planes_cached(obj, mask) + else: + planes = self.compute_all_planes_fast(obj, mask) + return planes + + def get_rel_state_planes(self, state, name: str, relation_state: tuple): + + obj = state.objs[name].obj + relation = relation_state.relation + + parent_obj = state.objs[relation_state.target_name].obj + obj_tags = relation.child_tags + parent_tags = relation.parent_tags + + parent_all_planes = self.get_tagged_planes(parent_obj, parent_tags) + obj_all_planes = self.get_tagged_planes(obj, obj_tags) + + #for i, p in enumerate(parent_all_planes): + # splitted_parent = planes.extract_tagged_plane(parent_obj, parent_tags, p) + # splitted_parent.name = f'parent_plane_{i}' + #for i, p in enumerate(obj_all_planes): + # splitted_parent = planes.extract_tagged_plane(parent_obj, obj_tags, p) + # splitted_parent.name = f'obj_plane_{i}' + #return + + if relation_state.parent_plane_idx >= len(parent_all_planes): + logging.warning(f'{parent_obj.name=} had too few planes ({len(parent_all_planes)}) for {relation_state}') + parent_plane = None + else: + parent_plane = parent_all_planes[relation_state.parent_plane_idx] + + if relation_state.child_plane_idx >= len(obj_all_planes): + logging.warning(f'{obj.name=} had too few planes ({len(obj_all_planes)}) for {relation_state}') + obj_plane = None + else: + obj_plane = obj_all_planes[relation_state.child_plane_idx] + + return obj_plane, parent_plane + + @staticmethod + def planerep_to_poly(planerep): + name, idx = planerep + return bpy.data.objects[name].data.polygons[idx] + + def extract_tagged_plane(self, obj: bpy.types.Object, tags: set, plane: int): + """ + get a single plane formed by faces tagged with tags + """ + + if obj.type != 'MESH': + raise TypeError("Object is not a mesh!") + + face_mask = tagging.tagged_face_mask(obj, tags) + mask = self.tagged_plane_mask(obj, face_mask, plane) + + if not mask.any(): + obj_tags = tagging.union_object_tags(obj) + logger.warning( + f'Attempted to extract_tagged_plane {obj.name=} {tags=} but mask was empty, {obj_tags=}' + ) + + butil.select(obj) + bpy.context.view_layer.objects.active = obj + + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.mesh.select_all(action='DESELECT') + # Set initial selection for polygons to False + bpy.ops.object.mode_set(mode='OBJECT') + + for poly in obj.data.polygons: + poly.select = mask[poly.index] + + # Switch to Edit mode, duplicate the selection, and separate it + old_set = set(bpy.data.objects[:]) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.duplicate() + bpy.ops.mesh.separate(type='SELECTED') + bpy.ops.object.mode_set(mode='OBJECT') + new_set = set(bpy.data.objects[:]) - old_set + return new_set.pop() + + def get_tagged_submesh(self, scene: trimesh.Scene, name:str, tags: set, plane: int): + obj = blender_objs_from_names(name)[0] + face_mask = tagging.tagged_face_mask(obj, tags) + mask = self.tagged_plane_mask(obj, face_mask, plane) + tmesh = meshes_from_names(scene, name)[0] + geom = tmesh.submesh(np.where(mask), append=True) + return geom + + def tagged_plane_mask(self, obj: bpy.types.Object, face_mask: np.ndarray, plane: tuple[str, int], hash_tolerance=1e-4, plane_tolerance = 1e-2, fast = True) -> np.ndarray: + if not fast: + return self._compute_tagged_plane_mask(obj, face_mask, plane, plane_tolerance) + obj_id = obj.name + current_hash = self.calculate_mesh_hash(obj) # Calculate current mesh hash + face_mask_hash = self.hash_face_mask(face_mask) # Calculate hash for face_mask + ref_poly = self.planerep_to_poly(plane) + ref_vertex = global_vertex_coordinates(obj, obj.data.vertices[ref_poly.vertices[0]]) + ref_normal = global_polygon_normal(obj, ref_poly) + plane_hash = self.hash_plane(ref_normal, ref_vertex, hash_tolerance) # Calculate hash for plane + + # Composite key now includes face_mask_hash + cache_key = (obj_id, plane_hash, face_mask_hash) + + # Check if the mesh has been modified since last calculation or if the face mask has changed + mesh_or_face_mask_changed = cache_key not in self._cached_plane_masks or self._mesh_hashes.get(obj_id) != current_hash + + if not mesh_or_face_mask_changed: + # logger.info(f'Cache HIT plane mask for {obj.name=}') + return self._cached_plane_masks[cache_key]['mask'] + + # If mesh or face mask changed, update the hash and recompute + self._mesh_hashes[obj_id] = current_hash + + # Compute and cache the plane mask + # logger.info(f'Cache MISS plane mask for {obj.name=}') + plane_mask = self._compute_tagged_plane_mask(obj, face_mask, plane, plane_tolerance) + + # Update the cache with the new result + self._cached_plane_masks[cache_key] = { + 'mask': plane_mask, + } + + return plane_mask + + def _compute_tagged_plane_mask(self, obj, face_mask, plane, tolerance): + """ + Given a plane, return a mask of all polygons in obj that are coplanar with the plane. + """ + plane_mask = np.zeros(len(obj.data.polygons), dtype=bool) + ref_poly = self.planerep_to_poly(plane) + ref_vertex = global_vertex_coordinates(obj, obj.data.vertices[ref_poly.vertices[0]]) + ref_normal = global_polygon_normal(obj, ref_poly) + + for candidate_polygon in obj.data.polygons: + + if not face_mask[candidate_polygon.index]: + continue + + candidate_vertex = global_vertex_coordinates(obj, obj.data.vertices[candidate_polygon.vertices[0]]) + candidate_normal = global_polygon_normal(obj, candidate_polygon) + diff_vec = ref_vertex - candidate_vertex + if not np.isclose(np.linalg.norm(diff_vec), 0): + diff_vec /= np.linalg.norm(diff_vec) + + ndot = np.dot(ref_normal, candidate_normal) + pdot = np.dot(diff_vec, candidate_normal) + + in_plane = ( + np.allclose(ndot, 1, atol=tolerance) and + np.allclose(pdot, 0, atol=tolerance) + ) + + plane_mask[candidate_polygon.index] = in_plane + + + return plane_mask \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/geometry/stability.py b/infinigen/core/constraints/example_solver/geometry/stability.py new file mode 100644 index 000000000..c6a5c3b4a --- /dev/null +++ b/infinigen/core/constraints/example_solver/geometry/stability.py @@ -0,0 +1,358 @@ +# 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: Karhan Kayan + +from __future__ import annotations +import logging +from dataclasses import dataclass +from copy import copy + +import numpy as np +from shapely.affinity import rotate + +import bpy +import trimesh +from shapely.geometry import Point, LineString +from shapely.ops import unary_union, nearest_points +from shapely import Polygon +from shapely import MultiPolygon +import bmesh + +import matplotlib.pyplot as plt +import gin +# import fcl + +# from infinigen.core.util import blender as butil +from infinigen.core.constraints.example_solver.geometry import planes as planes +from infinigen.core.util import blender as butil +from mathutils import Vector, Quaternion + +from infinigen.core.constraints.example_solver import state_def +from infinigen.core.constraints import constraint_language as cl, reasoning as r + +from infinigen.core import tagging, tags as t +from infinigen.core.constraints.constraint_language import util as iu + +import logging +logger = logging.getLogger(__name__) + +def project_and_align_z_with_x(polygons, z_direction): + """ + Rotate polygons so that the Z-direction is aligned with the X-axis in 2D. + + Parameters: + polygons (list[Polygon]): List of Shapely Polygons representing the projected 2D polygons. + z_direction (np.array): The 2D direction vector where the Z-axis is projected. + + Returns: + list[Polygon]: Rotated polygons with the Z-direction aligned with the X-axis. + """ + # Calculate the angle between the Z-direction projection and the X-axis + angle_rad = np.arctan2(z_direction[1], z_direction[0]) + angle_deg = np.degrees(angle_rad) + + # Rotate polygons to align Z-direction with X-axis + rotated_polygons = [rotate(polygon, angle_deg, origin=(0, 0), use_radians=False) for polygon in polygons] + + return rotated_polygons + +def is_vertically_contained(poly_a, poly_b): + """ + Check if polygon A is vertically contained within polygon B, ignoring X-axis spillover. + + Parameters: + poly_a (Polygon): Polygon A. + poly_b (Polygon): Polygon B. + + Returns: + bool: True if A is vertically contained within B. + """ + y_coords_a = [point[1] for point in poly_a.exterior.coords] + y_coords_b = [point[1] for point in poly_b.exterior.coords] + + # Check vertical containment along the Y-axis + min_a, max_a = min(y_coords_a), max(y_coords_a) + min_b, max_b = min(y_coords_b), max(y_coords_b) + + return min_b <= min_a and max_a <= max_b + +def project_vector(vector, origin, normal): + transform = trimesh.geometry.plane_transform(origin, normal) + transformed = trimesh.transformations.transform_points([np.array([0,0,0]), vector], transform)[:, :2] + transformed_vector = transformed[1] - transformed[0] + return transformed_vector + +@gin.configurable +def stable_against( + state: state_def.State, + obj_name: str, + relation_state: state_def.RelationState, + visualize=False, + allow_overhangs=False +): + """ + check paralell, close to, and not overhanging. + """ + + relation = relation_state.relation + assert isinstance(relation, cl.StableAgainst) + + logger.debug(f'stable against {obj_name=} {relation_state=}') + a_blender_obj = state.objs[obj_name].obj + b_blender_obj = state.objs[relation_state.target_name].obj + sa = state.objs[obj_name] + sb = state.objs[relation_state.target_name] + + pa, pb = state.planes.get_rel_state_planes(state, obj_name, relation_state) + + poly_a = state.planes.planerep_to_poly(pa) + poly_b = state.planes.planerep_to_poly(pb) + + normal_a = iu.global_polygon_normal(a_blender_obj, poly_a) + normal_b = iu.global_polygon_normal(b_blender_obj, poly_b) + dot = np.array(normal_a).dot(normal_b) + if not (np.isclose(np.abs(dot), 1, atol=1e-2) or np.isclose(dot, -1, atol=1e-2)): + logger.debug(f'stable against failed, not parallel {dot=}') + return False + + origin_b = iu.global_vertex_coordinates(b_blender_obj, b_blender_obj.data.vertices[poly_b.vertices[0]]) + + scene = state.trimesh_scene + a_trimesh = iu.meshes_from_names(scene, sa.obj.name)[0] + b_trimesh = iu.meshes_from_names(scene, sb.obj.name)[0] + + + mask = tagging.tagged_face_mask(sb.obj, relation.parent_tags) + mask = state.planes.tagged_plane_mask(sb.obj, mask, pb) + assert mask.any() + b_trimesh_mask = b_trimesh.submesh([np.where(mask)[0]], append=True) + + # Project mesh A onto the plane of mesh B + projected_a = trimesh.path.polygons.projected(a_trimesh, normal_b, origin_b) + projected_b = trimesh.path.polygons.projected(b_trimesh_mask, normal_b, origin_b) + logger.debug(f'stable_against projecting along {normal_b} for parent_tags {relation.parent_tags}') + + + if projected_a is None or projected_b is None: + raise ValueError(f'Invalid {projected_a=} {projected_b=}') + + if allow_overhangs: + res = projected_a.overlaps(projected_b) + elif relation.check_z: + res = projected_a.within(projected_b.buffer(1e-2)) + else: + z_proj = project_vector(np.array([0, 0, 1]), origin_b, normal_b) + projected_a_rotated, projected_b_rotated = project_and_align_z_with_x([projected_a, projected_b], z_proj) + res = is_vertically_contained(projected_a_rotated, projected_b_rotated) + + if visualize: + fig, ax = plt.subplots() + iu.plot_geometry(ax, projected_a, 'blue') + iu.plot_geometry(ax, projected_b, 'green') + plt.title(f'{obj_name} stable against {relation_state.target_name}? {res=}') + plt.show() + + logger.debug(f'stable_against {res=}') + if not res: + return False + + for vertex in poly_a.vertices: + vertex_global = iu.global_vertex_coordinates(a_blender_obj, a_blender_obj.data.vertices[vertex]) + distance = iu.distance_to_plane(vertex_global, origin_b, normal_b) + if not np.isclose(distance, relation_state.relation.margin, atol=1e-2): + logger.debug(f'stable against failed, not close to {distance=}') + return False + + + return True + +def snap_against(scene, a, b, a_plane, b_plane, margin = 0): + """ + snap a against b with some margin. + """ + logging.debug("snap_against", a, b, a_plane, b_plane, margin) + + a_obj = bpy.data.objects[a] + b_obj = bpy.data.objects[b] + + a_poly_index = a_plane[1] + a_poly = a_obj.data.polygons[a_poly_index] + b_poly_index = b_plane[1] + b_poly = b_obj.data.polygons[b_poly_index] + plane_point_a = iu.global_vertex_coordinates(a_obj, a_obj.data.vertices[a_poly.vertices[0]]) + plane_normal_a = iu.global_polygon_normal(a_obj, a_poly) + plane_point_b = iu.global_vertex_coordinates(b_obj, b_obj.data.vertices[b_poly.vertices[0]]) + plane_normal_b = iu.global_polygon_normal(b_obj, b_poly) + plane_normal_b = -plane_normal_b + + + + norm_mag_a = np.linalg.norm(plane_normal_a) + norm_mag_b = np.linalg.norm(plane_normal_b) + assert np.isclose(norm_mag_a, 1), norm_mag_a + assert np.isclose(norm_mag_b, 1), norm_mag_b + + rotation_axis = np.cross(plane_normal_a, plane_normal_b) + if not np.isclose(np.linalg.norm(rotation_axis),0, atol = 1e-05): + rotation_axis = rotation_axis / np.linalg.norm(rotation_axis) + else: + rotation_axis = np.array([0,0,1]) + + dot = plane_normal_a.dot(plane_normal_b) + rotation_angle = np.arccos(np.clip(dot, -1, 1)) + if np.isnan(rotation_angle): + raise ValueError(f'Invalid {rotation_angle=}') + iu.rotate(scene, a, rotation_axis, rotation_angle) + + + + a_obj = bpy.data.objects[a] + a_poly = a_obj.data.polygons[a_poly_index] + # Recalculate vertex_a and normal_a after rotation + plane_point_a = iu.global_vertex_coordinates(a_obj, a_obj.data.vertices[a_poly.vertices[0]]) + plane_normal_a = iu.global_polygon_normal(a_obj, a_poly) + + distance = (plane_point_a - plane_point_b).dot(plane_normal_b) + + # Move object a by the average distance minus the margin in the direction of the plane normal of b + translation = -(distance + margin) * plane_normal_b.normalized() + iu.translate(scene, a, translation) + + + + +def random_sample_point(state: state_def.State, obj: bpy.types.Object, face_mask: np.ndarray, plane: tuple[str, int]) -> Vector: + """ + Given a plane, return a random point on the plane. + """ + + if obj.type != 'MESH': + raise ValueError(f'Unexpected {obj.type=}') + + plane_mask = state.planes.tagged_plane_mask(obj, face_mask, plane) + if not np.any(plane_mask): + logging.warning( + f'No faces in object {obj.name} are coplanar with plane {plane}.' + ) + + # Create a bmesh from the object mesh + bm = bmesh.new() + bm.from_mesh(obj.data) + bm.faces.ensure_lookup_table() + + faces = [bm.faces[i] for i in np.where(plane_mask)[0]] + + # Calculate the area for each face and create a cumulative distribution + areas = np.array([f.calc_area() for f in faces]) + cumulative_areas = np.cumsum(areas) + total_area = cumulative_areas[-1] + + # Generate a random number and find the corresponding face + random_area_point = np.random.rand() * total_area + face_index = np.searchsorted(cumulative_areas, random_area_point) + selected_face = faces[face_index] + + verts = [v.co for v in selected_face.verts] + + # Use barycentric coordinates to sample a random point in the triangle + # Random weights for each vertex + weights = np.random.rand(3) + weights /= np.sum(weights) + random_point_local = weights[0] * verts[0] + weights[1] * verts[1] + weights[2] * verts[2] + random_point_global = obj.matrix_world @ Vector(random_point_local) + + bm.free() + + return random_point_global + +def move_obj_random_pt(state: state_def.State, a, b, face_mask: np.ndarray, plane: tuple[str, int]): + """ + move a to a random point on b + """ + scene = state.trimesh_scene + b_obj = iu.blender_objs_from_names(b)[0] + + random_point_global = random_sample_point(state, b_obj, face_mask, plane) + iu.set_location(scene, a, random_point_global) + + +# def place_randomly(scene, a, b, visualize = False): +# """ +# place a randomly on b. +# """ +# a_blender_mesh = blender_objs_from_names(a)[0] +# a_trimesh = meshes_from_names(scene, a)[0] +# b_blender_mesh = blender_objs_from_names(b)[0] +# b_trimesh = meshes_from_names(scene, b)[0] +# b_proj = project_to_xy_poly(b_trimesh) + +# xy_loc = sample_random_point(b_proj) +# if visualize: +# fig, ax = plt.subplots() +# if isinstance(b_proj, Polygon): +# x, y = b_proj.exterior.xy +# ax.fill(x, y, alpha=0.5, fc='red', ec='black', label='Polygon b') +# elif isinstance(b_proj, MultiPolygon): +# for sub_poly in b_proj.geoms: +# x, y = sub_poly.exterior.xy +# ax.fill(x, y, alpha=0.5, fc='red', ec='black', label='Polygon b') +# ax.plot(xy_loc.x, xy_loc.y, 'o', color='black', label='Random point') +# plt.show() + +# set_location(scene, a, Vector((xy_loc.x, xy_loc.y, 0))) + +def supported_by(scene, a, b, visualize = False): + + #check for collision first + + + if isinstance(a, str): + a = [a] + + a_meshes = iu.blender_objs_from_names(a) + a_trimeshes = iu.meshes_from_names(scene, a) + b_mesh = iu.blender_objs_from_names(b)[0] + b_trimesh = iu.meshes_from_names(scene, b)[0] + + if visualize: + fig, ax = plt.subplots() + ax.set_aspect('equal', 'box') + b_poly = iu.project_to_xy_poly(b_trimesh) + if isinstance(b_poly, Polygon): + x, y = b_poly.exterior.xy + ax.fill(x, y, alpha=0.5, fc='red', ec='black', label='Polygon b') + elif isinstance(b_poly, MultiPolygon): + for sub_poly in b_poly.geoms: + x, y = sub_poly.exterior.xy + ax.fill(x, y, alpha=0.5, fc='red', ec='black', label='Polygon b') + + for a_mesh, a_trimesh in zip(a_meshes, a_trimeshes): + cloned_a = butil.deep_clone_obj( + a_mesh, keep_modifiers=True, keep_materials=False + ) + butil.modify_mesh( + cloned_a, "BOOLEAN", apply=True, operation="INTERSECT", object=b_mesh + ) + iu.preprocess_obj(cloned_a) + intersection = iu.to_trimesh(cloned_a) + intersection_poly = iu.project_to_xy_poly(intersection) + intersection_convex = intersection_poly.convex_hull + com_projected = a_trimesh.centroid[:2] + if visualize: + if isinstance(intersection_poly, Polygon): + x, y = intersection_poly.exterior.xy + ax.fill(x, y, alpha=0.5, fc='blue', ec='black', label='Polygon a') + elif isinstance(intersection_poly, MultiPolygon): + for sub_poly in intersection_poly.geoms: + x, y = sub_poly.exterior.xy + ax.fill(x, y, alpha=0.5, fc='blue', ec='black', label='Polygon a') + ax.plot(com_projected[0], com_projected[1], 'o', color='black', label='COM of a') + + if not intersection_convex.contains(Point(com_projected)): + if visualize: + plt.show() + return False + if visualize: + plt.show() + return True \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/geometry/validity.py b/infinigen/core/constraints/example_solver/geometry/validity.py new file mode 100644 index 000000000..c0761647f --- /dev/null +++ b/infinigen/core/constraints/example_solver/geometry/validity.py @@ -0,0 +1,122 @@ +# 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: Karhan Kayan + +import logging + +import bpy +from shapely.geometry import Point, Polygon, MultiPolygon + +from infinigen.core.util import blender as butil +import infinigen.core.constraints.constraint_language as cl +from infinigen.core.constraints.constraint_language.util import meshes_from_names, blender_objs_from_names, subset, project_to_xy_poly +from infinigen.core.constraints.example_solver.state_def import State, ObjectState, RelationState +from infinigen.core.constraints.evaluator.node_impl.trimesh_geometry import constrain_contact, any_touching +from infinigen.core.constraints.example_solver.geometry.stability import stable_against, supported_by + +from infinigen.core import tags as t + +import gin + +logger = logging.getLogger(__name__) + +def check_pre_move_validity(scene, a, parent_dict, dx, dy): + """ + """ + parent = parent_dict[a] + a_mesh = meshes_from_names(scene, a)[0] + parent_mesh = meshes_from_names(scene, parent)[0] + blender_mesh = blender_objs_from_names(a)[0] + + + # move a mesh by dx, dy and check if the projection of a_mesh is contained in parent_mesh + # a_mesh.apply_transform(trimesh.transformations.compose_matrix(translate=[dx,dy,0])) + a_poly = project_to_xy_poly(a_mesh) + parent_poly = project_to_xy_poly(parent_mesh) + centroid = a_poly.centroid + new_centroid = Point([centroid.x + dx, centroid.y + dy]) + # plot + # fig, ax = plt.subplots() + # if isinstance(parent_poly, Polygon): + # x, y = parent_poly.exterior.xy + # ax.fill(x, y, alpha=0.5, fc='red', ec='black', label='Polygon b') + # elif isinstance(parent_poly, MultiPolygon): + # for sub_poly in parent_poly.geoms: + # x, y = sub_poly.exterior.xy + # ax.fill(x, y, alpha=0.5, fc='red', ec='black', label='Polygon b') + # ax.plot(centroid.x, centroid.y, 'o', color='black', label='Random point') + # plt.show() + # scene.show() + + if isinstance(a_poly, Polygon): + if not parent_poly.contains(new_centroid): + # print("not contained") + return False + elif isinstance(a_poly, MultiPolygon): + for sub_poly in a_poly.geoms: + if not parent_poly.contains(new_centroid): + # print("not contained") + return False + + return True + +def all_relations_valid(state, name): + + rels = state.objs[name].relations + for i, relation_state in enumerate(rels): + match relation_state.relation: + case cl.StableAgainst(child_tags, parent_tags, margin): + res = stable_against(state, name, relation_state) + if not res: + logger.debug(f'{name} failed relation {i=}/{len(rels)} {relation_state.relation} on {relation_state.target_name}') + return False + case unmatched: + raise TypeError(f"Unhandled {relation_state.relation}") + + return True + +@gin.configurable +def check_post_move_validity( + state: State, + name: str, + disable_collision_checking=False, + visualize=False +): + + scene = state.trimesh_scene + objstate = state.objs[name] + + collision_objs = [ + os.obj.name for k, os in state.objs.items() + if k != name and t.Semantics.NoCollision not in os.tags + ] + + if len(collision_objs) == 0: + return True + + if not all_relations_valid(state, name): + + if visualize: + vis_obj = butil.copy(objstate.obj) + vis_obj.name = f'validity_relations_fail_{name}' + + return False + + if disable_collision_checking: + return True + if t.Semantics.NoCollision in objstate.tags: + return True + + touch = any_touching(scene, objstate.obj.name, collision_objs, bvh_cache=state.bvh_cache) + if not constrain_contact(touch, should_touch=None, max_depth=0.0001): + if visualize: + vis_obj = butil.copy(objstate.obj) + vis_obj.name = f'validity_contact_fail_{name}' + + contact_names = [[x for x in t.names if not x.startswith('_')] for t in touch.contacts] + logger.debug(f'validity failed - {name} touched {contact_names[0]} {len(contact_names)=}') + return False + + # supposed to go through the consgraph here + return True \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/greedy/__init__.py b/infinigen/core/constraints/example_solver/greedy/__init__.py new file mode 100644 index 000000000..f83271319 --- /dev/null +++ b/infinigen/core/constraints/example_solver/greedy/__init__.py @@ -0,0 +1,3 @@ +from .all_substitutions import substitutions, iterate_assignments +from .constraint_partition import filter_constraints +from .active_for_stage import update_active_flags, set_active \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/greedy/active_for_stage.py b/infinigen/core/constraints/example_solver/greedy/active_for_stage.py new file mode 100644 index 000000000..3db38b1e9 --- /dev/null +++ b/infinigen/core/constraints/example_solver/greedy/active_for_stage.py @@ -0,0 +1,113 @@ +# 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 logging + +from infinigen.core import tags as t +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r, +) +from infinigen.core.constraints.evaluator import domain_contains +from infinigen.core.constraints.example_solver import state_def + +from infinigen.core.util import blender as butil + +logger = logging.getLogger(__name__) + +def find_ancestors_of_type( + state: state_def.State, + objkey: str, + filter_type: r.Domain, + seen: set = None +) -> set[str]: + + """ + Find objkeys of all ancestors of `objkey` which match `filter_type` + + Object `A` is a parent of `objkey` if there exists a sequence of objects `A, B, C, ..., objkey` + where A is in B's relations, B is in C's relations, etc, AND only `A` matches the filter + + Returns + ------- + parents: set(str) + objkeys of objects of the given type which are parents in the relation graph + + """ + + if seen is None: + seen = set() + + seen.add(objkey) + + obj = state.objs[objkey] + + if domain_contains.domain_contains(filter_type, state, obj): + return {objkey} + + result = set() + for rel in obj.relations: + + if rel.target_name in seen: + continue + + result.update(find_ancestors_of_type( + state, rel.target_name, filter_type, seen + )) + + return result + +def _is_active_room_object( + state: state_def.State, + objkey: str, + var_assignments: dict[t.Variable, str] +) -> bool: + + """ + Determine if an object should be active for the given assignment + + if there is a `room` var specified, `objkey` must be a descendent of that room + if there is an `obj` var specified, `objkey must not be a descendent of any other obj + + """ + + for var, assignment in var_assignments.items(): + if assignment is None: + continue + match var.name: + case 'room': + room_ancestors = find_ancestors_of_type(state, objkey, r.Domain({t.Semantics.Room})) + if assignment not in room_ancestors: + logger.debug(f'{objkey} is inactive due to room {room_ancestors=} {assignment=}') + return False + case 'obj': + obj_ancestors = find_ancestors_of_type(state, objkey, r.Domain({t.Semantics.Object})) + if len(obj_ancestors) and objkey not in obj_ancestors: + logger.debug(f'{objkey} is inactive due to obj {assignment=} {obj_ancestors=}') + return False + case _: + raise NotImplementedError( + f"{_is_active_room_object.__name__} encountered unknown variable {var}. " + "Greedy stages with vars besides room/obj are not yet supported" + ) + + return True + +def set_active(state, objkey, active): + state.objs[objkey].active = active + for child in butil.iter_object_tree(state.objs[objkey].obj): + child.hide_viewport = not active + +def update_active_flags( + state: state_def.State, + var_assignments: dict[t.Variable, str] +): + count = 0 + for objkey, objstate in state.objs.items(): + active = _is_active_room_object(state, objkey, var_assignments) + set_active(state, objkey, active) + count += active + return count \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/greedy/all_substitutions.py b/infinigen/core/constraints/example_solver/greedy/all_substitutions.py new file mode 100644 index 000000000..c5237f390 --- /dev/null +++ b/infinigen/core/constraints/example_solver/greedy/all_substitutions.py @@ -0,0 +1,183 @@ +# 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 typing +import itertools +import copy +import logging +import functools + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r +) +from infinigen.core.constraints.example_solver import state_def +from infinigen.core import tags as t +from infinigen.core.constraints.evaluator.domain_contains import objkeys_in_dom + +logger = logging.getLogger(__name__) + +def _resolve_toplevel_var( + dom: r.Domain, + state: state_def.State, + limits: dict[t.Variable, int] = None, +) -> typing.Iterator[str]: + + """ + Find and yield all valid substitutions of a toplevel VariableTag in a given dom + + ASSUMES: there is at most one variable in the domain, and it is at the top level + """ + + if limits is None: + limits = {} + + vars = [ti for ti in dom.tags if isinstance(ti, t.Variable)] + if len(vars) == 0: + yield dom + return + elif len(vars) > 1: + raise ValueError(f"More than one variable in domain {dom}") + + # valid assignments for the var are any objs satisfying everything else in the domain + vartag = vars[0] + result = copy.deepcopy(dom) + result.tags.remove(vartag) + objkeys = objkeys_in_dom(result, state) + logger.debug(f'Found {len(objkeys)} valid assignments for {repr(vartag)} via {result} on ') + + # if the user says limit "room" to 3 and we are doing "room", apply the limit + name_limit = limits.get(vartag, None) + if name_limit is not None: + objkeys = objkeys[:name_limit] + + for objkey in objkeys: + logger.debug(f'Assigning {objkey} for {vartag}') + yield result.with_tags(state.objs[objkey].tags) + +def substitutions( + dom: r.Domain, + state: state_def.State, + limits: dict[t.Variable, int] | None = None, + nonempty: bool = False, +) -> typing.Iterator[r.Domain]: + + """Find all t.Variable in d's tags or relations, and return one Domain for each possible assignment + + limits cuts off enumeration of each varname with some integer count + """ + + child_assignment_prod = itertools.product(*( + substitutions(dchild, state, limits, nonempty) + for _, dchild in dom.relations + )) + + i = None + + for i, dsubs in enumerate(child_assignment_prod): + + assert len(dsubs) == len(dom.relations) + rels = [(rel, dsubs[j]) for j, (rel, _) in enumerate(dom.relations)] + + candidate = r.Domain( + tags=dom.tags, relations=rels + ) + + yield from _resolve_toplevel_var(candidate, state, limits=limits) + + if i is None and nonempty: + raise ValueError(f'Found no substitutions found for {dom=}') + +def iterate_assignments( + dom: r.Domain, + state: state_def.State, + vars: list[t.Variable], + limits: dict[t.Variable, int] | None = None, + nonempty: bool = False, +) -> typing.Iterator[dict[t.Variable, str]]: + + """Find all combinations of assignments for the listed vars. + + Variables will be considered IN ORDER, IE first variable can affect options for second variable, + but not the other way around. + + Parameters + ---------- + dom : r.Domain + The domain to substitute variables in + state : state_def.State + The state to substitute variables in + vars : list[str] + The names of the variables to substitute + limits : dict[str, int] + Consider only the first N objects for each variable + nonempty : bool + Raise an error if no substitutions are found + + Returns + ------- + typing.Iterator[dict[str, r.Domain]] + Iterator over dicts of variable assignments to domains + + """ + + if limits is None: + limits = {} + + if len(vars) == 0: + yield {} + return + + assert isinstance(vars, list), vars + var = vars[0] + + doms_for_var = [ + d for d in dom.traverse() if var in d.tags + ] + if len(doms_for_var) == 0: + yield {} + return + + combined, *rest = doms_for_var + for d in rest: + combined = combined.intersection(d) + combined = copy.deepcopy(combined) # prevents modification of original domain if it had the var + combined.tags.remove(var) + if not combined.intersects(combined): + raise ValueError(f'{iterate_assignments.__name__} with {var=} arrived at contradictory {combined=}') + + candidates = sorted(objkeys_in_dom(combined, state)) + + candidates = [ + c for c in candidates + if t.Semantics.NoChildren not in state.objs[c].tags + ] + + i = None + for i, objkey in enumerate(candidates): + + limit = limits.get(var, None) + if limit is not None and i >= limits[var]: + break + + dom_objkey = r.domain_tag_substitute( + copy.deepcopy(dom), var, combined.with_tags(t.SpecificObject(objkey)) + ) + rest_iter = iterate_assignments( + dom_objkey, state, vars[1:], limits, + ) + + for rest_assignments in rest_iter: + yield { + var: objkey, + **rest_assignments + } + + if i is None and nonempty: + raise ValueError(f'Found no assignments found for {dom=}') + + + diff --git a/infinigen/core/constraints/example_solver/greedy/constraint_partition.py b/infinigen/core/constraints/example_solver/greedy/constraint_partition.py new file mode 100644 index 000000000..1511f9765 --- /dev/null +++ b/infinigen/core/constraints/example_solver/greedy/constraint_partition.py @@ -0,0 +1,269 @@ +# 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 typing +import operator +import copy +from functools import partial +from dataclasses import dataclass +import logging + +from infinigen.core.constraints import ( + constraint_language as cl, + example_solver as ex, + reasoning as r, +) +from infinigen.core import tags as t + +logger = logging.getLogger(__name__) + +OPS_COMMUTATIVE = { + operator.add, + operator.and_, + operator.mul, + operator.or_ +} + +OPS_UNIT_VALUE = { + operator.add: 0, + operator.mul: 1, + operator.pow: 0, + operator.truediv: 0 +} + +def _get_op_unit_value(node: cl.BoolExpression | cl.ScalarExpression): + match node: + case cl.BoolOperatorExpression(func, operands): + return True + case cl.ScalarOperatorExpression(func, operands) if func in OPS_UNIT_VALUE: + return OPS_UNIT_VALUE[func] + case _: + raise ValueError(f'Found no unit value for {node.__class__} {node.func}') + +def _partition_dict( + terms: dict[str, cl.Node], + recurse: typing.Callable +): + new_terms = {} + for k, v in terms.items(): + part, relevant = recurse(v) + if not relevant: + continue + new_terms[k] = part + return new_terms + +def _update_item_nodes( + node: cl.Node, + from_varname: str, + to_varname: str, + to_objs: cl.ObjectSetExpression +): + for child in node.traverse(): + if not isinstance(child, cl.item): + continue + if child.name != from_varname: + continue + child.name = to_varname + child.member_of = to_objs + + return node + +def _filter_gather_constraint( + node: cl.ForAll | cl.SumOver | cl.MeanOver, + recurse: typing.Callable, + filter_dom: r.Domain, + var_assignments: dict[t.Variable, r.Domain] +) -> tuple[cl.Node, bool]: + + objs, var, pred = node.objs, node.var, node.pred + var = t.Variable(var) + + objs_part, objs_rel = recurse(objs) + + obj_dom = r.constraint_domain(objs) + obj_dom = r.substitute_all(obj_dom, var_assignments) + + var_assignments = copy.deepcopy(var_assignments) or {} + for varname, dom in var_assignments.items(): + assert isinstance(varname, t.Variable) + var_assignments[varname] = r.domain_tag_substitute(dom, var, obj_dom) + var_assignments[var] = obj_dom + + pred_part, pred_rel = recurse(pred, var_assignments=var_assignments) + + for pred_child in pred_part.traverse(): + if not isinstance(pred_child, r.FilterByDomain): + continue + subst_filter, matched = r.domain_tag_substitute( + pred_child.filter, var, obj_dom, return_match=True + ) + + pred_child.filter = subst_filter + + res = copy.copy(node) + res.objs = objs_part + res.var = var + res.pred = pred_part + + relevant = pred_rel + return res, relevant + +def _filter_object_set( + node: cl.ObjectSetExpression, + recurse: typing.Callable, + filter_dom: r.Domain, + var_assignments: dict[t.Variable, r.Domain], +) -> tuple[cl.Node, bool]: + + new_consnode = copy.deepcopy(node) + + dom = r.constraint_domain(node) + dom_subst = r.substitute_all(dom, var_assignments) + + if not r.domain_finalized(dom_subst, check_anyrel=False, check_variable=True): + raise ValueError( + "Domain not finalized, unable to check against filter. " + "Check for any undefined variables? should be impossible. " + f"{dom=}." + ) + + relevant = dom_subst.intersects(filter_dom, require_satisfies_right=True) + if ( + relevant + and not dom_subst.satisfies(filter_dom) # no need to filter something that is already strict enough + ): + finalized = r.domain_finalized(filter_dom, check_anyrel=False, check_variable=True) + assert finalized, filter_dom + new_consnode = r.FilterByDomain(new_consnode, filter_dom) + + return new_consnode, relevant + +def _filter_operator( + node: cl.BoolOperatorExpression | cl.ScalarOperatorExpression, + recurse: typing.Callable, + filter_dom: r.Domain, + var_assignments: dict[t.Variable, r.Domain] +) -> tuple[cl.Node, bool]: + + operands, func = node.operands, node.func + + op_results = [recurse(o) for o in operands] + relevant_ops = [node for node, rel in op_results if rel] + + match relevant_ops, func: + case ([], _): + return cl.constant(_get_op_unit_value(node)), False + case ([op], f) if f in OPS_COMMUTATIVE: + return op, True + case (new_operands, f) if ( + len(new_operands) == len(operands) + or f in OPS_COMMUTATIVE + ): + return node.__class__(f, new_operands), True + case _: + res = node.__class__(func, [o[0] for o in op_results]) + any_relevant = any(o[1] for o in op_results) + return res, any_relevant + +def _filter_node_cases( + node: cl.Node, + recurse: typing.Callable, + filter_dom: r.Domain, + var_assignments: dict[t.Variable, r.Domain] +) -> tuple[cl.Node, bool]: + match node: + case cl.Problem(cons, score_terms): + prob = cl.Problem( + _partition_dict(cons, recurse), + _partition_dict(score_terms, recurse) + ) + relevant = len(prob.constraints) > 0 or len(prob.score_terms) > 0 + return prob, relevant + case cl.ForAll() | cl.SumOver() | cl.MeanOver(): + return _filter_gather_constraint(node, recurse, filter_dom, var_assignments) + case cl.BoolOperatorExpression() | cl.ScalarOperatorExpression(): + return _filter_operator(node, recurse, filter_dom, var_assignments) + case cl.ObjectSetExpression(): + return _filter_object_set(node, recurse, filter_dom, var_assignments) + case _: + + result_relevant = False + result_consnode = copy.deepcopy(node) + + for name, child in node.children(): + res, relevant = recurse(child) + if not hasattr(node, name): + raise ValueError(f"Node {node.__class__} has child with {name=} but no attribute {name} to set") + setattr(result_consnode, name, res) + result_relevant = result_relevant or relevant + + return result_consnode, result_relevant + +def _check_partition_correctness( + node: cl.ObjectSetExpression, + filter_dom: r.Domain, + var_assignments: dict[t.Variable, r.Domain], +): + res_dom = r.constraint_domain(node) + res_dom = r.substitute_all(res_dom, var_assignments) + + if not r.domain_finalized(res_dom, check_anyrel=False, check_variable=True): + raise ValueError( + f"While doing {_check_partition_correctness.__name__} for {node=} {filter_dom=}, " + f"got {res_dom=} is not finalized, {var_assignments.keys()=}" + ) + + if not res_dom.satisfies(filter_dom): + raise ValueError(f"{res_dom=} does not satisfy {filter_dom=}") + +def filter_constraints( + node: cl.Node, + filter_dom: r.Domain, + var_assignments: dict[str, r.Domain] = None, + check_correctness=True +) -> tuple[cl.Node, bool]: + + """ Return a constraint graph representing the component of `node` that is relevant for + to a particular greedy filter domain. + + Parameters + ---------- + node : cl.Node + The constraint program to partition + filter_dom : Domain + The domain which determines whether a constraint is relevant + var_assignments : Domain + Domains to substitute for any t.Variable(name: str) in the constraint program, typically used for recursive calls. + + Returns + ------- + partitioned: cl.Node + The partitioned constraint program + relevant: bool + Was any part of the constraint program relevant? + + """ + + assert isinstance(node, cl.Node), node + + if var_assignments is None: + var_assignments = {} + + recurse = partial(filter_constraints, filter_dom=filter_dom, var_assignments=var_assignments) + + logger.debug(f"{filter_constraints.__name__} for {node.__class__.__name__}, {var_assignments.keys()=}") + new_node, relevant = _filter_node_cases(node, recurse, filter_dom, var_assignments) + + if ( + relevant + and check_correctness + and isinstance(new_node, cl.ObjectSetExpression) + ): + _check_partition_correctness(new_node, filter_dom, var_assignments) + + logger.debug(f"Partitioned {node.__class__.__name__} to {new_node.__class__.__name__}") + + return new_node, relevant \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/moves/__init__.py b/infinigen/core/constraints/example_solver/moves/__init__.py new file mode 100644 index 000000000..1e0feb4ed --- /dev/null +++ b/infinigen/core/constraints/example_solver/moves/__init__.py @@ -0,0 +1,6 @@ +from .moves import Move +from .addition import Addition, Resample +from .deletion import Deletion +from .swap import Swap +from .reassignment import RelationPlaneChange, RelationTargetChange +from .pose import TranslateMove, RotateMove, ReinitPoseMove \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/moves/addition.py b/infinigen/core/constraints/example_solver/moves/addition.py new file mode 100644 index 000000000..26326a017 --- /dev/null +++ b/infinigen/core/constraints/example_solver/moves/addition.py @@ -0,0 +1,188 @@ +# 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 + +from dataclasses import dataclass, field +import numpy as np +import typing +import logging +import gin + +from pprint import pprint + +import bpy +from mathutils import Vector, Matrix +import trimesh + +from infinigen.assets.utils import bbox_from_mesh + +from infinigen.core.constraints.example_solver.state_def import State, ObjectState +from infinigen.core import tagging, tags as t + +from infinigen.core.constraints import ( + constraint_language as cl, + usage_lookup +) + +from infinigen.core.util import blender as butil +from infinigen.core.constraints.constraint_language.util import ( + delete_obj, + meshes_from_names +) +from infinigen.core.constraints.example_solver.geometry import( + validity +) + +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes import node_utils +from infinigen.core.placement.factory import AssetFactory + +from infinigen.core.constraints.example_solver.geometry import dof, parse_scene +from . import moves +from .reassignment import pose_backup, restore_pose_backup +from time import time +# from line_profiler import LineProfiler + + +logger = logging.getLogger(__name__) + +GLOBAL_GENERATOR_SINGLETON_CACHE = {} + +def sample_rand_placeholder(gen_class: type[AssetFactory]): + + singleton_gen = usage_lookup.has_usage(gen_class, t.Semantics.SingleGenerator) + + if singleton_gen and gen_class in GLOBAL_GENERATOR_SINGLETON_CACHE: + gen = GLOBAL_GENERATOR_SINGLETON_CACHE[gen_class] + else: + fac_seed = np.random.randint(1e7) + gen = gen_class(fac_seed) + if singleton_gen: + GLOBAL_GENERATOR_SINGLETON_CACHE[gen_class] = gen + + inst_seed = np.random.randint(1e7) + + if usage_lookup.has_usage(gen_class, t.Semantics.RealPlaceholder): + new_obj = gen.spawn_placeholder(inst_seed, loc=(0,0,0), rot=(0,0,0)) + elif usage_lookup.has_usage(gen_class, t.Semantics.AssetAsPlaceholder): + new_obj = gen.spawn_asset(inst_seed, loc=(0,0,0), rot=(0,0,0)) + elif usage_lookup.has_usage(gen_class, t.Semantics.PlaceholderBBox): + new_obj = bbox_from_mesh.bbox_mesh_from_hipoly(gen, inst_seed, use_pholder=True) + else: + new_obj = bbox_from_mesh.bbox_mesh_from_hipoly(gen, inst_seed) + + if new_obj.type != 'MESH': + raise ValueError(f'Addition created {new_obj.name=} with type {new_obj.type}') + if len(new_obj.data.polygons) == 0: + raise ValueError(f'Addition created {new_obj.name=} with 0 faces') + + butil.put_in_collection( + list(butil.iter_object_tree(new_obj)), + butil.get_collection(f'placeholders') + ) + parse_scene.preprocess_obj(new_obj) + tagging.tag_canonical_surfaces(new_obj) + + return new_obj, gen + +@dataclass +class Addition(moves.Move): + + """ Move which generates an object and adds it to the scene with certain relations + """ + + gen_class: typing.Any + relation_assignments: list + temp_force_tags: set + + _new_obj: bpy.types.Object = None + + + def __repr__(self): + return f'{self.__class__.__name__}({self.gen_class.__name__}, {len(self.relation_assignments)} relations)' + + def apply(self, state: State): + + target_name, = self.names + assert target_name not in state.objs + + self._new_obj, gen = sample_rand_placeholder(self.gen_class) + + parse_scene.add_to_scene(state.trimesh_scene, self._new_obj, preprocess=True) + + tags = self.temp_force_tags.union(usage_lookup.usages_of_factory(gen.__class__)) + + assert isinstance(self._new_obj, bpy.types.Object) + objstate = ObjectState( + obj=self._new_obj, + generator=gen, + tags=tags, + relations=self.relation_assignments + ) + + state.objs[target_name] = objstate + success = dof.try_apply_relation_constraints(state, target_name) + logger.debug(f'{self} {success=}') + return success + + def revert(self, state: State): + to_delete = list(butil.iter_object_tree(self._new_obj)) + delete_obj(state.trimesh_scene, [a.name for a in to_delete]) + + new_name, = self.names + del state.objs[new_name] + +@dataclass +class Resample(moves.Move): + + """ Move which replaces an existing object with a new one from the same generator + """ + + align_corner: int = None + + _backup_gen = None + _backup_obj = None + _backup_poseinfo = None + + def apply(self, state: State): + + assert len(self.names) == 1 + target_name = self.names[0] + + os = state.objs[target_name] + self._backup_gen = os.generator + self._backup_obj = os.obj + self._backup_poseinfo = pose_backup(os) + + scene = state.trimesh_scene + scene.graph.transforms.remove_node(os.obj.name) + scene.delete_geometry(os.obj.name + '_mesh') + + os.obj, os.generator = sample_rand_placeholder(os.generator.__class__) + + if self.align_corner is not None: + c_old = self._backup_obj.bound_box[self.align_corner] + c_new = os.obj.bound_box[self.align_corner] + raise NotImplementedError(f'{self.align_corner=}') + + parse_scene.add_to_scene(state.trimesh_scene, os.obj, preprocess=True) + dof.apply_relations_surfacesample(state, target_name) + + return validity.check_post_move_validity(state, target_name) + + def revert(self, state: State): + + target_name, = self.names + + os = state.objs[target_name] + delete_obj(state.trimesh_scene, os.obj.name) + + os.obj = self._backup_obj + os.generator = self._backup_gen + parse_scene.add_to_scene(state.trimesh_scene, os.obj, preprocess=False) + restore_pose_backup(state, target_name, self._backup_poseinfo) + + def accept(self, state: State): + butil.delete(list(butil.iter_object_tree(self._backup_obj))) diff --git a/infinigen/core/constraints/example_solver/moves/deletion.py b/infinigen/core/constraints/example_solver/moves/deletion.py new file mode 100644 index 000000000..58097bc5c --- /dev/null +++ b/infinigen/core/constraints/example_solver/moves/deletion.py @@ -0,0 +1,52 @@ +# 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 + +from dataclasses import dataclass +import numpy as np +import typing +import logging + +import bpy +from infinigen.core.constraints.example_solver.geometry import parse_scene +from mathutils import Vector, Matrix +import trimesh + +from infinigen.core.constraints.example_solver import state_def +from infinigen.core.util import blender as butil + +from infinigen.core.constraints.example_solver.moves import Move + +logger = logging.getLogger(__name__) + + +@dataclass +class Deletion(Move): + + # remove obj from scene + _backup_state: state_def.ObjectState = None + + def __repr__(self): + return f'{self.__class__.__name__}({self.names})' + + def apply(self, state): + + target_name, = self.names + self._backup_state = state.objs[target_name] + + for obj in butil.iter_object_tree(state.objs[target_name].obj): + state.trimesh_scene.graph.transforms.remove_node(obj.name) + state.trimesh_scene.delete_geometry(obj.name + '_mesh') + + del state.objs[target_name] + return True + + def accept(self, state): + butil.delete(list(butil.iter_object_tree(self._backup_state.obj))) + + def revert(self, state): + target_name, = self.names + state.objs[target_name] = self._backup_state + parse_scene.add_to_scene(state.trimesh_scene, self._backup_state.obj, preprocess=True) \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/moves/moves.py b/infinigen/core/constraints/example_solver/moves/moves.py new file mode 100644 index 000000000..ada8cb2c0 --- /dev/null +++ b/infinigen/core/constraints/example_solver/moves/moves.py @@ -0,0 +1,37 @@ +# 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 + +from dataclasses import dataclass +import numpy as np +import typing +import logging + +import bpy +from infinigen.core.constraints.example_solver.geometry import parse_scene +from mathutils import Vector, Matrix +import trimesh + +from infinigen.core.constraints.example_solver import state_def +from infinigen.core.util import blender as butil + +logger = logging.getLogger(__name__) + +@dataclass +class Move: + + names: typing.List[str] + + def __post_init__(self): + assert isinstance(self.names, list) + + def apply(self, state: state_def.State): + raise NotImplementedError + + def revert(self, state: state_def.State): + raise NotImplementedError + + def accept(self, state: state_def.State): + pass \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/moves/pose.py b/infinigen/core/constraints/example_solver/moves/pose.py new file mode 100644 index 000000000..454353538 --- /dev/null +++ b/infinigen/core/constraints/example_solver/moves/pose.py @@ -0,0 +1,122 @@ +# 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, Karhan Kayan + +from dataclasses import dataclass +import numpy as np +import typing +import logging + +import bpy +from infinigen.core.constraints.example_solver.geometry import dof +import mathutils + +from infinigen.core.util import blender as butil +from infinigen.core.constraints.example_solver.geometry import validity, dof +from infinigen.core.constraints.constraint_language import util as iu + +from . import moves +from ..state_def import State +from .reassignment import pose_backup, restore_pose_backup + +logger = logging.getLogger(__name__) + +@dataclass +class TranslateMove(moves.Move): + # translate obj by vector + + translation: np.array + + _backup_pose: dict = None + + def __repr__(self): + norm = np.linalg.norm(self.translation) + return f"{self.__class__.__name__}({self.names}, {norm:.2e})" + + def apply(self, state: State): + + target_name, = self.names + + os = state.objs[target_name] + self._backup_pose = pose_backup(os, dof=False) + + iu.translate(state.trimesh_scene, os.obj.name, self.translation) + + if not validity.check_post_move_validity(state, target_name): + return False + + return True + + def revert(self, state: State): + target_name, = self.names + restore_pose_backup(state, target_name, self._backup_pose) + +@dataclass +class RotateMove(moves.Move): + + axis: np.array + angle: float + + _backup_pose = None + + def __repr__(self): + return f"{self.__class__.__name__}({self.names}, {self.angle:.2e})" + + def apply(self, state: State): + + target_name, = self.names + + os = state.objs[target_name] + self._backup_pose = pose_backup(os, dof=False) + + iu.rotate(state.trimesh_scene, os.obj.name, self.axis, self.angle) + + if not validity.check_post_move_validity(state, target_name): + return False + + return True + + def revert(self, state: State): + target_name, = self.names + restore_pose_backup(state, target_name, self._backup_pose) + +@dataclass +class ReinitPoseMove(moves.Move): + + _backup_pose: dict = None + + def __repr__(self): + return f"{self.__class__.__name__}({self.names})" + + def apply(self, state: State): + target_name, = self.names + ostate = state.objs[target_name] + self._backup_pose = pose_backup(ostate) + return dof.try_apply_relation_constraints(state, target_name) + + def revert(self, state: State): + target_name, = self.names + restore_pose_backup(state, target_name, self._backup_pose) + +''' +@dataclass +class ScaleMove(Move): + name: str + scale: np.array + + def apply(self, state: State): + blender_obj = self.obj.bpy_obj + trimesh_obj = state.get_trimesh_object(self.obj.name) + blender_obj.scale *= Vector(self.scale) + trimesh_obj.apply_transform(trimesh.transformations.compose_matrix(scale=list(self.scale))) + self.obj.update() + + def revert(self, state: State): + blender_obj = self.obj.bpy_obj + trimesh_obj = state.get_trimesh_object(self.obj.name) + blender_obj.scale /= Vector(self.scale) + trimesh_obj.apply_transform(trimesh.transformations.compose_matrix(scale=list(1/self.scale))) + self.obj.update() +''' \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/moves/reassignment.py b/infinigen/core/constraints/example_solver/moves/reassignment.py new file mode 100644 index 000000000..421391db9 --- /dev/null +++ b/infinigen/core/constraints/example_solver/moves/reassignment.py @@ -0,0 +1,110 @@ +# 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: primary author +# - Karhan Kayan: sync with trimesh fix + +from dataclasses import dataclass +import numpy as np +import typing +import logging +import copy + +from infinigen.core.util import blender as butil +from infinigen.core.constraints.constraint_language.util import ( + translate, + rotate, + sync_trimesh +) + +from infinigen.core.constraints.example_solver.geometry import validity +from infinigen.core.constraints.example_solver.geometry import dof +from . import moves +from ..state_def import State, ObjectState + +def pose_backup(os: ObjectState, dof=True): + + bak = dict( + loc=tuple(os.obj.location), + rot=tuple(os.obj.rotation_euler), + ) + + if dof: + bak['dof_trans'] = copy.copy(os.dof_matrix_translation) + bak['dof_rot'] = copy.copy(os.dof_rotation_axis) + + return bak + +def restore_pose_backup(state, name, bak): + os = state.objs[name] + os.obj.location = bak['loc'] + os.obj.rotation_euler = bak['rot'] + + if 'dof_trans' in bak: + os.dof_matrix_translation = bak['dof_trans'] + if 'dof_rot' in bak: + os.dof_rotation_axis = bak['dof_rot'] + + sync_trimesh(state.trimesh_scene, state.objs[name].obj.name) + +@dataclass +class RelationPlaneChange(moves.Move): + + relation_idx: int + plane_idx: int + + _backup_idx = None + _backup_poseinfo = None + + def apply(self, state: State): + + target_name, = self.names + + os = state.objs[target_name] + rels = os.relations[self.relation_idx] + + self._backup_idx = rels.parent_plane_idx + self._backup_poseinfo = pose_backup(os) + + rels.parent_plane_idx = self.plane_idx + + success = dof.try_apply_relation_constraints(state, target_name) + return success + + def revert(self, state: State): + + target_name, = self.names + + os = state.objs[target_name] + os.relations[self.relation_idx].parent_plane_idx = self._backup_idx + restore_pose_backup(state, target_name, self._backup_poseinfo) + +@dataclass +class RelationTargetChange(moves.Move): + + # reassign obj to new parent + name: str + relation_idx: int + new_target: str + + _backup_target = None + _backup_poseinfo = None + + def apply(self, state: State): + os = state.objs[self.name] + rels = os.relations[self.relation_idx] + + self._backup_target = rels.target_name + self._backup_poseinfo = pose_backup(os) + rels.target_name = self.new_target + + return dof.try_apply_relation_constraints(state, self._new_name) + + def revert(self, state: State): + os = state.objs[self.name] + rels = os.relations[self.relation_idx] + + rels.target_name = self._backup_target + restore_pose_backup(state, self.name, self._backup_poseinfo) \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/moves/swap.py b/infinigen/core/constraints/example_solver/moves/swap.py new file mode 100644 index 000000000..73b4ba846 --- /dev/null +++ b/infinigen/core/constraints/example_solver/moves/swap.py @@ -0,0 +1,62 @@ +# 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 + +from dataclasses import dataclass +import numpy as np +import typing +import logging + +import bpy +from infinigen.core.constraints.example_solver.geometry import parse_scene +from mathutils import Vector, Matrix +import trimesh + +from infinigen.core.constraints.example_solver import state_def +from infinigen.core.util import blender as butil + +from infinigen.core.constraints.example_solver.moves import Move + +from .reassignment import pose_backup, restore_pose_backup + +logger = logging.getLogger(__name__) + + +@dataclass +class Swap(Move): + # swap the poses and relations of two objects + + _obj1_backup = None + _obj2_backup = None + + def __post_init__(self): + raise NotImplementedError(f"{self.__class__.__name__} untested") + + def apply(self, state: state_def.State): + + target1, target2 = self.names + + o1 = state[target1].obj + o2 = state[target2].obj + + self._obj1_backup = pose_backup(o1, dof=False) + self._obj2_backup = pose_backup(o2, dof=False) + + o1.loc, o2.loc = o2.loc, o1.loc + o1.rotation_axis_angle, o2.rotation_axis_angle = o2.rotation_axis_angle, o1.rotation_axis_angle + o1.relation_assignments, o2.relation_assignments = o2.relation_assignments, o1.relation_assignments + + def revert(self, state: state_def.State): + + target1, target2 = self.names + restore_pose_backup(state, target1, self._obj1_backup) + restore_pose_backup(state, target2, self._obj2_backup) + + o1 = state[target1].obj + o2 = state[target2].obj + + o1.relation_assignments, o2.relation_assignments = o2.relation_assignments, o1.relation_assignments + + \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/populate.py b/infinigen/core/constraints/example_solver/populate.py new file mode 100644 index 000000000..affbc6d9d --- /dev/null +++ b/infinigen/core/constraints/example_solver/populate.py @@ -0,0 +1,124 @@ +# 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: populate_state_placeholders, apply_cutter +# - Stamatis Alexandropoulos: Initial version of window cutting + +import logging + +import bpy +from tqdm import tqdm + +from infinigen.core.constraints.example_solver.geometry import parse_scene + +from infinigen.core.constraints.example_solver.state_def import State +from infinigen.core.constraints.constraint_language.util import delete_obj + +from infinigen.core.placement.placement import parse_asset_name +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t +from infinigen.core.constraints import usage_lookup + +logger = logging.getLogger(__name__) + +def apply_cutter(state, objkey, cutter): + + os = state.objs[objkey] + + cut_objs = [] + for i, relation_state in enumerate(os.relations): + + # TODO in theory we maybe should check if they actually intersect + + parent_obj = state.objs[relation_state.target_name].obj + butil.modify_mesh( + parent_obj, + 'BOOLEAN', + object=butil.copy(cutter), + operation='DIFFERENCE', + solver='FAST' + ) + + target_obj_name = state.objs[relation_state.target_name].obj.name + cut_objs.append((relation_state.target_name, target_obj_name)) + + cutter_col = butil.get_collection('placeholders:asset_cutters') + butil.put_in_collection(cutter, cutter_col) + + return cut_objs + +def populate_state_placeholders(state: State, filter=None, final=True): + + logger.info(f'Populating placeholders {final=} {filter=}') + unique_assets = butil.get_collection('unique_assets') + + if final: + for os in state.objs.values(): + if t.Semantics.Room in os.tags: + os.obj = bpy.data.objects[os.obj.name + '.meshed'] + + targets = [] + + for objkey, os in state.objs.items(): + + if os.generator is None: + continue + + if ( + filter is not None + and not usage_lookup.has_usage(os.generator.__class__, filter) + ): + continue + + if 'spawn_asset' in os.obj.name: + butil.put_in_collection(os.obj, unique_assets) + logger.debug(f'Found already populated asset {os.obj.name=}, continuing') + continue + + targets.append(objkey) + + update_state_mesh_objs = [] + + for i, objkey in enumerate(targets): + + os = state.objs[objkey] + placeholder = os.obj + + logger.info(f'Populating {i}/{len(targets)} {placeholder.name=}') + + old_objname = placeholder.name + update_state_mesh_objs.append((objkey, old_objname)) + + *_, inst_seed = parse_asset_name(placeholder.name) + os.obj = os.generator.spawn_asset( + i=int(inst_seed), + loc=placeholder.location, # we could use placeholder=pholder here, but I worry pholder may have been modified + rot=placeholder.rotation_euler + ) + os.generator.finalize_assets([os.obj]) + butil.put_in_collection(os.obj, unique_assets) + + cutter = next((o for o in butil.iter_object_tree(os.obj) if o.name.endswith('.cutter')), None) + logger.debug(f'{populate_state_placeholders.__name__} found {cutter=} for {os.obj.name=}') + if cutter is not None: + cut_objs = apply_cutter(state, objkey, cutter) + logger.debug(f'{populate_state_placeholders.__name__} cut {cutter.name=} from {cut_objs=}') + update_state_mesh_objs += cut_objs + + if final: + return + + # objects modified in any way (via pholder update or boolean cut) must be synched with trimesh state + for objkey, old_objname in tqdm(set(update_state_mesh_objs), desc='Updating trimesh with populated objects'): + + os = state.objs[objkey] + + # delete old trimesh + delete_obj(state.trimesh_scene, old_objname, delete_blender=False) + + # put the new, populated object into the state + parse_scene.preprocess_obj(os.obj) + if not final: + tagging.tag_canonical_surfaces(os.obj) + parse_scene.add_to_scene(state.trimesh_scene, os.obj, preprocess=True) diff --git a/infinigen/core/constraints/example_solver/propose_continous.py b/infinigen/core/constraints/example_solver/propose_continous.py new file mode 100644 index 000000000..1e3aa9b56 --- /dev/null +++ b/infinigen/core/constraints/example_solver/propose_continous.py @@ -0,0 +1,148 @@ +# 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 typing +import logging + +import numpy as np +import gin + +from .geometry import dof +from mathutils import Vector + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r, + usage_lookup +) + +from infinigen.core.constraints.evaluator.domain_contains import domain_contains + +from . import ( + moves, + state_def +) + +from infinigen.core import tags as t + +logger = logging.getLogger(__name__) + +TRANS_MULT = 8 +TRANS_MIN = 0.01 +ROT_MULT = np.pi +ROT_MIN = 0 #2 * np.pi / 200 + +ANGLE_STEP_SIZE = (2 * np.pi) / 8 + +def get_pose_candidates( + consgraph: cl.Node, + state: state_def.State, + filter_domain: r.Domain, + require_rot_free: bool = False, +): + + return [ + k for k, o in state.objs.items() + if o.active + and domain_contains(filter_domain, state, o) + and not (require_rot_free and o.dof_rotation_axis is None) + ] + +def propose_translate( + consgraph: cl.Node, + state: state_def.State, + filter_domain: r.Domain, + temperature: float +) -> typing.Iterator[moves.TranslateMove]: + + candidates = get_pose_candidates(consgraph, state, filter_domain) + candidates = [c for c in candidates if state.objs[c].dof_matrix_translation is not None] + if not len(candidates): + return + + while True: + + obj_state_name = np.random.choice(candidates) + obj_state = state.objs[obj_state_name] + + var = max(TRANS_MIN, TRANS_MULT * temperature) + random_vector = np.random.normal(0, var, size=3) + projected_vector = obj_state.dof_matrix_translation @ random_vector + + yield moves.TranslateMove( + names=[obj_state_name], + translation=projected_vector, + ) + +def propose_rotate( + consgraph: cl.Node, + state: state_def.State, + filter_domain: r.Domain, + temperature: float +) -> typing.Iterator[moves.RotateMove]: + + candidates = get_pose_candidates(consgraph, state, filter_domain) + candidates = [ + c for c in candidates if ( + t.Semantics.NoRotation not in state.objs[c].tags + and state.objs[c].dof_rotation_axis is not None + and state.objs[c].dof_rotation_axis.dot(np.array((0,0,1))) > 0.95 + ) + ] + if not len(candidates): + return + + while True: + obj_state_name = np.random.choice(candidates) + obj_state = state.objs[obj_state_name] + + var = max(ROT_MIN, ROT_MULT * temperature) + random_angle = np.random.normal(0, var) + + ang = random_angle / ANGLE_STEP_SIZE + ang = np.ceil(ang) if ang > 0 else np.floor(ang) + random_angle = ang * ANGLE_STEP_SIZE + + axis = obj_state.dof_rotation_axis + yield moves.RotateMove( + names=[obj_state_name], + axis=axis, + angle=random_angle + ) + +def propose_reinit_pose( + consgraph: cl.Node, + state: state_def.State, + filter_domain: r.Domain, + temperature: float +) -> typing.Iterator[moves.ReinitPoseMove]: + + candidates = get_pose_candidates(consgraph, state, filter_domain) + candidates = [c for c in candidates if state.objs[c].dof_matrix_translation is not None] + + if len(candidates) == 0: + return + + while True: + obj_state_name = np.random.choice(candidates) + obj_state = state.objs[obj_state_name] + + yield moves.ReinitPoseMove( + names=[obj_state_name], + ) + +def propose_scale( + consgraph, + state, + temperature +): + raise NotImplementedError + obj_state = np.random.choice(state.objs) + random_scale = np.random.normal(0, temperature, size=3) + return moves.ScaleMove( + name=obj_state.name, + scale=random_scale + ) \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/propose_discrete.py b/infinigen/core/constraints/example_solver/propose_discrete.py new file mode 100644 index 000000000..8b3b6d8f2 --- /dev/null +++ b/infinigen/core/constraints/example_solver/propose_discrete.py @@ -0,0 +1,350 @@ +# 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: primary author +# - Karhan Kayan: fix bug to ensure deterministic behavior + +import logging +import pdb +import copy +from itertools import product +import typing + +import gin +import numpy as np +from pprint import pprint, pformat + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r, + usage_lookup +) +from infinigen.core.constraints.evaluator.domain_contains import ( + domain_contains, objkeys_in_dom +) +from .geometry import planes +from . import ( + moves, + state_def, + propose_relations +) +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util import blender as butil +from infinigen.core import tags as t + +logger = logging.getLogger(__name__) + +class DummyCubeGenerator(AssetFactory): + + def __init__(self, seed): + super().__init__(seed) + + def create_asset(self, *_, **__): + return butil.spawn_cube() + +def lookup_generator(preds: set[t.Semantics]): + + if t.contradiction(preds): + raise ValueError(f'Got lookup_generator for unsatisfiable {preds=}') + + preds_pos, preds_neg = t.decompose_tags(preds) + + fac_class_tags = [x.generator for x in preds if isinstance(x, t.FromGenerator)] + if len(fac_class_tags) > 1: + raise ValueError(f'{preds=} had {len(fac_class_tags)=}, only 1 is allowed') + elif len(fac_class_tags) == 1: + fac_class_tag, = fac_class_tags + usage = usage_lookup.usages_of_factory(fac_class_tag) + remainder = preds_pos - usage - {fac_class_tag} + if len(remainder): + raise ValueError( + f"Your constraint program requested {fac_class_tag} with {preds_pos}, " + f"but used_as[] tags specify {usage} so predicates {remainder=} are unsatisfied. " + f"Please add these remainder predicates to used_as for {fac_class_tag} so that it can be safely retrieved." + ) + return [fac_class_tag] + + options = usage_lookup.all_factories() + for pos_tag in preds_pos: + options &= usage_lookup.factories_for_usage(pos_tag) + for neg_tag in preds_neg: + options -= usage_lookup.factories_for_usage(neg_tag) + + options = list(options) + # sort options to ensure deterministic behavior + options.sort(key=lambda x: x.__name__) + np.random.shuffle(options) + + return options + +def propose_addition_bound_gen( + cons: cl.Node, + curr: state_def.State, + bounds: list[r.Bound], + goal_bound_idx: r.Bound, + gen_class: AssetFactory, + filter_domain: r.Domain +): + + ''' + Try to propose any addition move involving the specified bound and generator + ''' + + goal_bound = bounds[goal_bound_idx] + logger.debug(f'attempt propose_addition for {gen_class.__name__} rels={len(goal_bound.domain.relations)}') + + assert r.domain_finalized(goal_bound.domain), goal_bound + if not active_for_stage(goal_bound.domain, filter_domain): + raise ValueError(f'Attempted to propose {goal_bound} but it should not be active for {filter_domain=}') + if len(goal_bound.domain.relations) == 0: + raise ValueError(f'Attempted to propose unconstrained {gen_class.__name__} with no relations') + + found_tags = usage_lookup.usages_of_factory(gen_class) + goal_pos, *_ = t.decompose_tags(goal_bound.domain.tags) + if not t.implies(found_tags, goal_pos) and found_tags.issuperset(goal_pos): + raise ValueError(f'Got {gen_class=} for {goal_pos=}, but it had {found_tags=}') + + prop_dom = goal_bound.domain.intersection(filter_domain) + prop_dom.tags.update(found_tags) + + #logger.debug(f'GOAL {goal_bound.domain} \nFILTER {filter_domain}\nPROP {prop_dom}\n\n') + logger.debug( + 'GOAL %s\n FILTER %s\n PROP %s\n', + goal_bound.domain.repr(abbrv=True), + filter_domain.repr(abbrv=True), + prop_dom, + ) + + assert active_for_stage(prop_dom, filter_domain) + + search_rels = [ + rd for rd in prop_dom.relations + if not isinstance(rd[0], cl.NegatedRelation) + ] + + i = None + for i, assignments in enumerate(propose_relations.find_assignments(curr, search_rels)): + + logger.debug(f'Found assignments %d %s %s', i, len(assignments), assignments) + + yield moves.Addition( + names=[f'{np.random.randint(1e6):04d}_{gen_class.__name__}'], # decided later + gen_class=gen_class, + relation_assignments=assignments, + temp_force_tags=prop_dom.tags, + ) + + if i is None: + #raise ValueError(f'Found no assignments for {prop_dom}') + logger.debug(f'Found no assignments for {prop_dom.repr(abbrv=True)}') + pass + else: + logger.debug(f'Exhausted all assignments for {gen_class=}') + +def active_for_stage( + prop_dom: r.Domain, + filter_dom: r.Domain +): + return prop_dom.intersects(filter_dom, require_satisfies_right=True) + +@gin.configurable +def preproc_bounds( + bounds: list[r.Bound], + state: state_def.State, + filter: r.Domain, + reverse=False, + shuffle=True, + print_bounds=False +): + + if print_bounds: + print(f"{preproc_bounds.__name__} for {filter.get_objs_named()} (total {len(bounds)}):") + for b in bounds: + res = active_for_stage(b.domain, filter) + if res: + print("BOUND", res, b.domain.intersection(filter).repr(abbrv=True), "\n") + + for b in bounds: + if not r.domain_finalized(b.domain, check_anyrel=False, check_variable=True): + raise ValueError(f'{preproc_bounds.__name__} found non-finalized {b.domain=}') + + bounds = [ + b for b in bounds if active_for_stage(b.domain, filter) + ] + + if shuffle: + np.random.shuffle(bounds) + + bound_counts = [ + len(objkeys_in_dom(b.domain, state)) + for b in bounds + ] + + order = np.arange(len(bounds)) + + def key(i): + b = bounds[i] + bc = bound_counts[i] + if b.high is not None and b.high < bc: + res = 1 + elif b.low is not None and b.low > bc: + res = -1 + else: + res = 0 + return -res if reverse else res + + order = sorted(order, key=key) + + return [bounds[i] for i in order if key(i) != 1] + +def propose_addition( + cons: cl.Node, + curr: state_def.State, + filter_domain: r.Domain, + temperature: float, +): + + bounds = r.constraint_bounds(cons) + bounds = preproc_bounds(bounds, curr, filter_domain) + + if len(bounds) == 0: + logger.debug(f'Found no bounds for {filter_domain=}') + return + + for i, bound in enumerate(bounds): + + if bound.low is None: + # bounds with low=None are supposed to cap other bounds, not introduce new objects + continue + + fac_options = lookup_generator(preds=bound.domain.tags) + if len(fac_options) == 0: + if bound.low is None or bound.low == 0: + continue + raise ValueError(f'Found no generators for {bound}') + + for gen_class in fac_options: + yield from propose_addition_bound_gen(cons, curr, bounds, i, gen_class, filter_domain) + + logger.debug(f'propose_addition found no candidate moves for {bound}') + +def propose_deletion( + cons: cl.Node, + curr: state_def.State, + filter_domain: r.Domain, + temperature: float, +): + + bounds = r.constraint_bounds(cons) + bounds = preproc_bounds(bounds, curr, filter_domain, reverse=True, shuffle=True) + + if len(bounds) == 0: + logger.debug(f'Found no bounds for {filter_domain=}') + return + + np.random.shuffle(bounds) + + for i, bound in enumerate(bounds): + candidates = objkeys_in_dom(bound.domain, curr) + np.random.shuffle(candidates) + for cand in candidates: + yield moves.Deletion([cand]) + +def propose_relation_plane_change( + cons: cl.Node, + curr: state_def.State, + filter_domain: r.Domain, + temperature: float, +): + cand_objs = objkeys_in_dom(filter_domain, curr) + + if len(cand_objs) == 0: + logger.debug(f'Found no cand_objs for {filter_domain=}') + return + + np.random.shuffle(cand_objs) + for cand in cand_objs: + for i, rels in enumerate(curr.objs[cand].relations): + + if not isinstance(rels.relation, cl.GeometryRelation): + continue + + target_obj = curr.objs[rels.target_name].obj + n_planes = len(curr.planes.get_tagged_planes(target_obj, rels.relation.parent_tags)) + if n_planes <= 1: + continue + + order = np.arange(n_planes) + np.random.shuffle(order) + for plane_idx in order: + if plane_idx == rels.parent_plane_idx: + continue + yield moves.RelationPlaneChange( + names=[cand], + relation_idx=i, + plane_idx=plane_idx + ) + +def propose_resample( + cons: cl.Node, + curr: state_def.State, + filter_domain: r.Domain, + temperature: float, +): + + cand_objs = objkeys_in_dom(filter_domain, curr) + + if len(cand_objs) == 0: + logger.debug(f'Found no cand_objs for {filter_domain=}') + return + + np.random.shuffle(cand_objs) + + for cand in cand_objs: + + os = curr.objs[cand] + if usage_lookup.has_usage(os.generator.__class__, t.Semantics.SingleGenerator): + continue + + yield moves.Resample(names=[cand], align_corner=None) + + #corner_options = [None] + list(range(6)) + #np.random.shuffle(corner_options) + #for c in corner_options: + # yield moves.Resample(name=cand, align_corner=c) + +def is_swap_domains_unaffected( + state: state_def.State, + name1: str, + name2: str +): + raise NotImplementedError() + +def propose_swap( + cons: cl.Node, + curr: state_def.State, + filter_domain: r.Domain, + temperature: float, +): + + raise NotImplementedError() + + cand_objs = objkeys_in_dom(filter_domain, curr) + + if len(cand_objs) == 0: + logger.debug(f'Found no cand_objs for {filter_domain=}') + return + + a_objs = copy.copy(cand_objs) + b_objs = copy.copy(cand_objs) + np.random.shuffle(a_objs) + np.random.shuffle(b_objs) + + for a, b in product(a_objs, b_objs): + if a == b: + continue + if not is_swap_domains_unaffected(curr, a, b): + continue + yield moves.Swap(names=[a, b]) \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/propose_relations.py b/infinigen/core/constraints/example_solver/propose_relations.py new file mode 100644 index 000000000..841d873b8 --- /dev/null +++ b/infinigen/core/constraints/example_solver/propose_relations.py @@ -0,0 +1,151 @@ +# 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 logging +import typing + +import numpy as np +from pprint import pprint + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r, +) +from infinigen.core.constraints.evaluator.domain_contains import ( + objkeys_in_dom +) +from .geometry import planes +from . import ( + moves, + state_def +) + +from infinigen.core import tags as t, tagging + +logger = logging.getLogger(__name__) + +def minimize_redundant_relations( + relations: list[tuple[cl.Relation, r.Domain]] +): + + """ + Given a list of relations that must be true, use the first as a constraint to tighten the remaining relations + """ + + assert len(relations) > 0 + + # TODO Hacky: moves AnyRelations to the back so _hopefully_ they get implied before we get to them + relations = sorted( + relations, + key=lambda r: isinstance(r[0], cl.AnyRelation), + reverse=True + ) + + (rel, dom), *rest = relations + + # Force all remaining relations to be compatible with (rel, dom), thereby reducing their search space + remaining_relations = [] + for (r_later, d_later) in rest: + + logger.debug(f'Inspecting {r_later=} {d_later=}') + + if d_later.intersects(dom): + logger.debug(f"Intersecting {d_later} with {dom}") + d_later = d_later.intersection(dom) + + if r.reldom_implies((rel, dom), (r_later, d_later)): + # (rlater, dlater) is guaranteed true so long as we satisfied (rel, dom), we dont need to separately assign it + logger.debug(f'Discarding since rlater,dlater it is implied') + continue + else: + logger.debug(f'Keeping {r_later, d_later} since it is not implied by {rel, dom} ') + remaining_relations.append((r_later, d_later)) + + implied = any( + r.reldom_implies(reldom_later, (rel, dom)) + for reldom_later in remaining_relations + ) + + return (rel, dom), remaining_relations, implied + + +def find_assignments( + curr: state_def.State, + relations: list[tuple[cl.Relation, r.Domain]], + assignments: list[state_def.RelationState] = None, +) -> typing.Iterator[list[state_def.RelationState]]: + + """Iterate over possible assignments that satisfy the given relations. Some assignments may not be feasible geometrically - + a naive implementation of this function would just enumerate all possible objects matching the assignments, and let the solver + discover that many combinations are impossible. *This* implementation attemps to never generate guaranteed-invalid combinations in the first place. + + Complexity is pretty astronomical: + - N^M where N is number of candidates per relation, and M is number of relations + - reduced somewhat when relations intersect or imply eachother + - luckily, M is typically 1, 2 or 3, as objects arent often related to lots of other objects + + TODO: + - discover new relations constraints, which can arise from the particular choice of objects + - prune early when object choice causes bounds to be violated + + This function essentially does a complex form of SAT-solving. It *really* shouldnt be written in python + """ + + if assignments is None: + assignments = [] + #print('FIND ASSIGNMENTS TOPLEVEL') + #pprint(relations) + + if len(relations) == 0: + yield assignments + return + + logger.debug(f'Attempting to assign {relations[0]}') + + (rel, dom), remaining_relations, implied = minimize_redundant_relations(relations) + assert len(remaining_relations) < len(relations) + + if implied: + logger.debug(f'Found remaining_relations implies {(rel, dom)=}, skipping it') + yield from find_assignments( + curr, + relations=remaining_relations, + assignments=assignments + ) + return + + if isinstance(rel, cl.AnyRelation): + pprint(relations) + pprint([(rel, dom)] + remaining_relations) + raise ValueError(f'Got {rel} as first relation. Invalid! Maybe the program is underspecified?') + + candidates = objkeys_in_dom(dom, curr) + + for parent_candidate_name in candidates: + logging.debug(f'{parent_candidate_name=}') + + parent_state = curr.objs[parent_candidate_name] + n_parent_planes = len(curr.planes.get_tagged_planes(parent_state.obj, rel.parent_tags)) + + parent_order = np.arange(n_parent_planes) + np.random.shuffle(parent_order) + + for parent_plane in parent_order: + + #logger.debug(f'Considering {parent_candidate_name=} {parent_plane=} {n_parent_planes=}') + + assignment = state_def.RelationState( + relation=rel, + target_name=parent_candidate_name, + child_plane_idx=0, # TODO fill in at apply()-time + parent_plane_idx=parent_plane, + ) + + yield from find_assignments( + curr, + relations=remaining_relations, + assignments=assignments + [assignment], + ) diff --git a/infinigen/core/constraints/example_solver/room/__init__.py b/infinigen/core/constraints/example_solver/room/__init__.py new file mode 100644 index 000000000..58157b2f5 --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/__init__.py @@ -0,0 +1,6 @@ +# 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: Lingjie Mei +from .blueprint import RoomSolver, MultistoryRoomSolver +from .graph import GraphMaker diff --git a/infinigen/core/constraints/example_solver/room/blueprint.py b/infinigen/core/constraints/example_solver/room/blueprint.py new file mode 100644 index 000000000..e2e3dddc8 --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/blueprint.py @@ -0,0 +1,218 @@ +# 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: Lingjie Mei, Karhan Kayan: fix constants + +from copy import deepcopy + +import bpy +import numpy as np +from numpy.random import uniform +from shapely import Polygon +from tqdm import tqdm, trange +import gin + +from infinigen.assets.utils.misc import toggle_hide +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import random_general as rg +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t +from .constants import WALL_HEIGHT + +from .graph import GraphMaker +from .scorer import BlueprintScorer, JointBlueprintScorer +from .contour import ContourFactory +from .solidifier import BlueprintSolidifier +from .segment import SegmentMaker +from .solver import BlueprintSolver, BlueprintStaircaseSolver +from .utils import polygon2obj, unit_cast +from infinigen.core.constraints.example_solver.room import constants + +from infinigen.core.constraints.example_solver.state_def import State, ObjectState +from infinigen.core.constraints.constraint_language import Semantics + + +@gin.configurable +class RoomSolver: + + def __init__(self, factory_seed, n_divide_trials=2500, iters_mult=150, ): + self.factory_seed = factory_seed + with FixedSeed(factory_seed): + self.graph_maker = GraphMaker(factory_seed) + self.graph = self.graph_maker.make_graph(np.random.randint(1e7)) + self.width, self.height = self.graph_maker.suggest_dimensions(self.graph) + self.contour_factory = ContourFactory(self.width, self.height) + self.contour = self.contour_factory.make_contour(np.random.randint(1e7)) + + n = len(self.graph.neighbours) + + self.segment_maker = SegmentMaker(self.factory_seed, self.contour, n) + self.solver = BlueprintSolver(self.contour, self.graph) + self.scorer = BlueprintScorer(self.graph) + self.solidifier = BlueprintSolidifier(self.graph, 0) + + self.n_divide_trials = n_divide_trials + self.iterations = iters_mult * n + self.score_scale = 5 + + def simulated_anneal(self, assignment, info): + score = self.scorer.find_score(assignment, info) + with tqdm(total=self.iterations, desc='Sampling solutions') as pbar: + while pbar.n < self.iterations: + assignment_, info_ = deepcopy(assignment), deepcopy(info) + resp = self.solver.perturb_solution(assignment_, info_) + if not resp.is_success: + continue + pbar.update(1) + score_ = self.scorer.find_score(assignment_, info_) + scale = self.score_scale * pbar.n / self.iterations + if np.log(uniform()) < (score - score_) * scale: + assignment, info, score = assignment_, info_, score_ + pbar.set_description(f'loss={score:.4f}') + + return assignment, info + + def solve(self): + + assignment, info = [], {} + for i in range(self.n_divide_trials): + info = self.segment_maker.build_segments() + assignment = self.solver.find_assignment(info) + if assignment is not None: + break + + if assignment is None: + raise ValueError(f'{self.__class__.__name__} got {assignment=} after {self.n_divide_trials=}') + + assignment, info = self.simulated_anneal(assignment, info) + + state, rooms_meshed = self.solidifier.solidify(assignment, info) + + unique_roomtypes = set(Semantics(s.split('_')[0]) for s in self.graph.rooms) + dimensions = self.width, self.height, constants.WALL_HEIGHT + + return state, unique_roomtypes, dimensions + + +@gin.configurable +class MultistoryRoomSolver: + + def __init__(self, factory_seed, n_divide_trials=2500, iters_mult=150, + n_stories=('categorical', 0., .0, .5 ,.5), fixed_contour=('bool', .5)): + self.factory_seed = factory_seed + with FixedSeed(factory_seed): + self.n_stories = rg(n_stories) + self.fixed_contour = rg(fixed_contour) + self.n_contour_trials = 100 + self.graph_makers, self.graphs = [], [] + self.widths, self.heights = [], [] + self.build_graphs(factory_seed) + self.contour_factories, self.contours = [], [] + self.build_contours() + + self.segment_makers = [SegmentMaker(self.factory_seed, self.contours[i], len(self.graphs[i])) for i + in range(self.n_stories)] + self.solvers = [BlueprintSolver(self.contours[i], self.graphs[i]) for i in range(self.n_stories)] + self.staircase_solver = BlueprintStaircaseSolver(self.contours) + self.scorer = JointBlueprintScorer(self.graphs) + self.solidifiers = [BlueprintSolidifier(self.graphs[i], i) for i in range(self.n_stories)] + + self.n_divide_trials = n_divide_trials + self.iterations = iters_mult * sum(len(g) for g in self.graphs) + self.score_scale = 5 + self.staircase_solver_prob = .1 + + def build_graphs(self, factory_seed): + for i in range(self.n_stories): + kwargs = {'entrance_type': 'none'} if i > 0 else {} + graph_maker = GraphMaker(factory_seed, i, self.n_stories > 1, **kwargs) + self.graph_makers.append(graph_maker) + if self.fixed_contour and i > 0: + width, height = self.widths[-1], self.heights[-1] + graph = graph_maker.make_graph(np.random.randint(1e6)) + else: + for j in range(self.n_contour_trials): + graph = graph_maker.make_graph(np.random.randint(1e6)) + args = [self.widths[-1], self.heights[-1]] if len(self.graphs) > 0 else [None, None] + width, height = graph_maker.suggest_dimensions(graph, *args) + if width is not None and height is not None: + break + else: + raise Exception('Invalid graph') + self.widths.append(width) + self.heights.append(height) + self.graphs.append(graph) + + def build_contours(self): + for i in range(self.n_stories): + while len(self.contours) <= i: + for j in range(self.n_contour_trials): + contour_factory = ContourFactory(self.widths[i], self.heights[i]) + if self.fixed_contour and i > 0: + contour = self.contours[-1] + else: + contour = contour_factory.make_contour(np.random.randint(1e6)) + if len(self.contours) > 0: + x_offset = unit_cast((self.widths[i] - self.widths[0]) / 2) + y_offset = unit_cast((self.heights[i] - self.heights[0]) / 2) + contour = Polygon( + [(x - x_offset, y - y_offset) for x, y in contour.boundary.coords[:]]) + if not self.contours[-1].contains(contour): + continue + self.contour_factories.append(contour_factory) + self.contours.append(contour) + break + else: + self.widths[i] -= constants.UNIT + self.heights[i] -= constants.UNIT + + def solve(self): + assignments, infos = [], [] + while len(assignments) == 0: + staircase = self.contour_factories[-1].add_staircase(self.contours[-1]) + for j in range(self.n_stories): + for _ in trange(self.n_divide_trials, desc=f'Dividing segments for {j}'): + info = self.segment_makers[j].build_segments(staircase) + assignment = self.solvers[j].find_assignment(info) + if assignment is not None: + assignments.append(assignment) + infos.append(info) + break + else: + assignments, infos = [], [] + break + + assignments, infos = self.simulated_anneal(assignments, infos) + + obj_states = {} + for j in range(self.n_stories): + state, rooms_meshed = self.solidifiers[j].solidify(assignments[j], infos[j]) + obj_states.update(state.objs) + unique_roomtypes = set() + for graph in self.graphs: + for s in graph.rooms: + unique_roomtypes.add(Semantics(s.split('_')[0])) + dimensions = self.widths[0], self.heights[0], WALL_HEIGHT * self.n_stories + return State(obj_states), unique_roomtypes, dimensions + + def simulated_anneal(self, assignments, infos): + score = self.scorer.find_score(assignments, infos) + with tqdm(total=self.iterations, desc='Sampling solutions') as pbar: + while pbar.n < self.iterations: + assignments_, infos_ = deepcopy(assignments), deepcopy(infos) + if uniform() < self.staircase_solver_prob: + resp = self.staircase_solver.perturb_solution(assignments, infos) + else: + probs = np.array([len(g) for g in self.graphs]) + j = np.random.choice(np.arange(self.n_stories), p=probs / probs.sum()) + resp = self.solvers[j].perturb_solution(assignments_[j], infos_[j]) + if not resp.is_success: + continue + pbar.update(1) + score_ = self.scorer.find_score(assignments_, infos_) + scale = self.score_scale * pbar.n / self.iterations + if np.log(uniform()) < (score - score_) * scale: + assignments, infos, score = assignments_, infos_, score_ + return assignments, infos diff --git a/infinigen/core/constraints/example_solver/room/configs.py b/infinigen/core/constraints/example_solver/room/configs.py new file mode 100644 index 000000000..8c481faac --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/configs.py @@ -0,0 +1,132 @@ +# 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: Lingjie Mei +from collections import defaultdict + +from infinigen.assets.materials import brick, hardwood_floor, plaster, rug, tile +from infinigen.assets.materials.woods import tiled_wood +from infinigen.assets.materials.stone_and_concrete import concrete +from infinigen.core.constraints.example_solver.room.types import RoomType +from infinigen.core.util.color import hsv2rgba +from infinigen.core.util.random import log_uniform +from infinigen.core.util.random import random_general as rg + +EXTERIOR_CONNECTED_ROOM_TYPES = [RoomType.Bedroom, RoomType.Garage, RoomType.Balcony, RoomType.DiningRoom, + RoomType.Kitchen, RoomType.LivingRoom] +SQUARE_ROOM_TYPES = [RoomType.Kitchen, RoomType.Bedroom, RoomType.LivingRoom, RoomType.Closet, + RoomType.Bathroom, RoomType.Garage, RoomType.Balcony, RoomType.DiningRoom, RoomType.Utility] +TYPICAL_AREA_ROOM_TYPES = { + RoomType.Kitchen: 20, + RoomType.Bedroom: 25, + RoomType.LivingRoom: 30, + RoomType.DiningRoom: 20, + RoomType.Closet: 4, + RoomType.Bathroom: 8, + RoomType.Utility: 4, + RoomType.Garage: 30, + RoomType.Balcony: 8, + RoomType.Hallway: 8, + RoomType.Staircase: 20, +} +ROOM_NUMBERS = {RoomType.Bathroom: (1, 10), RoomType.LivingRoom: (1, 10)} +COMBINED_ROOM_TYPES = [[RoomType.Hallway, RoomType.LivingRoom, RoomType.DiningRoom], [RoomType.Garage]] +PANORAMIC_ROOM_TYPES = { + RoomType.Hallway: .3, + RoomType.LivingRoom: .5, + RoomType.DiningRoom: .5, + RoomType.Balcony: 1, +} +FUNCTIONAL_ROOM_TYPES = [RoomType.Kitchen, RoomType.Bedroom, RoomType.LivingRoom, RoomType.Bathroom, + RoomType.DiningRoom] +WINDOW_ROOM_TYPES = defaultdict(lambda: 1, { + RoomType.Utility: .3, + RoomType.Closet: 0., + RoomType.Bathroom: .5, + RoomType.Garage: .5, +}) + + +def make_room_colors(): + bedroom_color = hsv2rgba(0., .8, log_uniform(.02, .1)) + hallway_color = hsv2rgba(.4, .8, log_uniform(.02, .1)) + utility_color = hsv2rgba(.8, .8, log_uniform(.02, .1)) + return { + RoomType.Kitchen: hallway_color, + RoomType.Bedroom: bedroom_color, + RoomType.LivingRoom: hallway_color, + RoomType.Closet: bedroom_color, + RoomType.Hallway: hallway_color, + RoomType.Bathroom: bedroom_color, + RoomType.Garage: utility_color, + RoomType.Balcony: utility_color, + RoomType.DiningRoom: hallway_color, + RoomType.Utility: utility_color, + RoomType.Staircase: hallway_color, + } + + +ROOM_COLORS = make_room_colors() +ROOM_CHILDREN = defaultdict(dict, { + RoomType.LivingRoom: { + RoomType.LivingRoom: ('bool', .1), + RoomType.Bedroom: ('categorical', .0, .45, .4, .1, .05), + RoomType.Closet: ('bool', .1), + RoomType.Bathroom: ('bool', .2), + RoomType.Garage: ('bool', .2), + RoomType.Balcony: ('bool', .2), + RoomType.DiningRoom: ('bool', 1.0), + RoomType.Utility: ('bool', .2), + RoomType.Hallway: ('categorical', .5, .4, .1) + }, + RoomType.Kitchen: {RoomType.Garage: ('bool', .5), RoomType.Utility: ('bool', .1) + }, + RoomType.Bedroom: {RoomType.Bathroom: ('bool', .3), RoomType.Closet: ('bool', .5)}, + RoomType.Bathroom: {RoomType.Closet: ('bool', .2)}, + RoomType.DiningRoom: {RoomType.Kitchen: ('bool', 1.), RoomType.Hallway: ('bool', .2) + } +}) + +STUDIO_ROOM_CHILDREN = defaultdict(dict, { + RoomType.LivingRoom: { + RoomType.Bedroom: ('categorical', .0, 1.), + RoomType.DiningRoom: ('bool', 1.), + }, + RoomType.Bedroom: {RoomType.Bathroom: ('bool', 1.)}, + RoomType.DiningRoom: {RoomType.Kitchen: ('bool', 1.) + } +}) +UPSTAIRS_ROOM_CHILDREN = defaultdict(dict, { + RoomType.LivingRoom: { + RoomType.Bedroom: ('categorical', .0, .4, .5, .2), + RoomType.Closet: ('bool', .2), + RoomType.Bathroom: ('bool', .4), + RoomType.Balcony: ('bool', .4), + RoomType.Utility: ('bool', .2), + RoomType.Hallway: ('categorical', .0, .5, .5) + }, + RoomType.Bedroom: {RoomType.Bathroom: ('bool', .3), RoomType.Closet: ('bool', .5)}, + RoomType.Bathroom: {RoomType.Closet: ('bool', .2)}, + RoomType.Balcony: {RoomType.Utility: ('bool', .4), RoomType.Hallway: ('bool', .1)}, +}) +LOOP_ROOM_TYPES = { + RoomType.LivingRoom: {RoomType.Garage: .2, RoomType.Balcony: .2, RoomType.Kitchen: .1}, + RoomType.Bedroom: {RoomType.Balcony: .1}, +} + +ROOM_WALLS = defaultdict(lambda: plaster, { + RoomType.Kitchen: ('weighted_choice', (2, tile), (5, plaster)), + RoomType.Garage: ('weighted_choice', (5, concrete), (1, brick), (3, plaster)), + RoomType.Utility: ('weighted_choice', (1, concrete), (1, brick), (1, brick), (5, plaster)), + RoomType.Balcony: ('weighted_choice', (1, brick), (5, plaster)), + RoomType.Bathroom: tile +}) + +ROOM_FLOORS = defaultdict(lambda: ('weighted_choice', (3, tiled_wood), (1, tile), (1, rug)), { + RoomType.Garage: concrete, + RoomType.Utility: ('weighted_choice', (1, concrete), (1, plaster), (1, tile)), + RoomType.Bathroom: tile, + RoomType.Balcony: tile +}) + +PILLAR_ROOM_TYPES = [RoomType.Hallway, RoomType.LivingRoom, RoomType.Staircase, RoomType.DiningRoom] diff --git a/infinigen/core/constraints/example_solver/room/constants.py b/infinigen/core/constraints/example_solver/room/constants.py new file mode 100644 index 000000000..c8fec8bc3 --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/constants.py @@ -0,0 +1,100 @@ +# 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: +# - Lingjie Mei: primary author +# - Karhan Kayan: bug fixes + +import gin +import numpy as np + +from infinigen.core.util.random import random_general as rg + + +def make_np(xs): + return [np.array(x) for x in xs] + + +@gin.configurable +def global_params(unit=.5, segment_margin=1.2, wall_thickness=('uniform', .2, .3), + wall_height=('uniform', 2.7, 3.8)): + wall_thickness = rg(wall_thickness) + wall_height = rg(wall_height) + return { + 'unit': unit, + 'segment_margin': segment_margin, + 'wall_thickness': wall_thickness, + 'wall_height': wall_height + } + + +UNIT, SEGMENT_MARGIN, WALL_THICKNESS, WALL_HEIGHT = make_np(global_params().values()) + + +@gin.configurable +def door_params(door_width=('uniform', .85, 1), door_size=('uniform', 2., 2.4)): + door_width = rg(door_width) + assert door_width > 0 + door_margin = (door_width + WALL_THICKNESS) / 2 + door_size = rg(door_size) + return {'door_width': door_width, 'door_margin': door_margin, 'door_size': door_size, } + + +DOOR_WIDTH, DOOR_MARGIN, DOOR_SIZE = make_np(door_params().values()) + + +@gin.configurable +def window_params( + max_window_length=('uniform', 6, 8), + window_height=('uniform', .4, 1.2), + window_margin=('uniform', .2, .6) +): + max_window_length = rg(max_window_length) + window_height = rg(window_height) + window_margin = rg(window_margin) + window_size = WALL_HEIGHT - WALL_THICKNESS - window_height - window_margin + assert window_size > 0 + return { + 'max_window_length': max_window_length, + 'window_height': window_height, + 'window_margin': window_margin, + 'window_size': window_size, + } + + +MAX_WINDOW_LENGTH, WINDOW_HEIGHT, WINDOW_MARGIN, WINDOW_SIZE = make_np(window_params().values()) + + +@gin.configurable +def staircase_params(staircase_snap=('uniform', .8, 1.2)): + return {'staircase_snap': rg(staircase_snap)} + + +STAIRCASE_SNAP = make_np(staircase_params().values()) + + +def init_global_params(): + ys = make_np(global_params().values()) + xs = UNIT, SEGMENT_MARGIN, WALL_THICKNESS, WALL_HEIGHT + for x, y in zip(xs, ys): + x.fill(y) + + +def init_door_params(): + ys = make_np(door_params().values()) + xs = DOOR_WIDTH, DOOR_MARGIN, DOOR_SIZE + for x, y in zip(xs, ys): + x.fill(y) + + +def init_window_params(): + ys = make_np(window_params().values()) + xs = MAX_WINDOW_LENGTH, WINDOW_HEIGHT, WINDOW_MARGIN, WINDOW_SIZE + for x, y in zip(xs, ys): + x.fill(y) + +def initialize_constants(): + init_global_params() + init_door_params() + init_window_params() \ No newline at end of file diff --git a/infinigen/core/constraints/example_solver/room/contour.py b/infinigen/core/constraints/example_solver/room/contour.py new file mode 100644 index 000000000..9b116c1d2 --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/contour.py @@ -0,0 +1,132 @@ +# 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: Lingjie Mei +import random + +import bpy +import gin +import numpy as np +from numpy.random import uniform +from shapely import Polygon, box + +from infinigen.core.constraints.example_solver.room.utils import unit_cast +from infinigen.core.constraints.example_solver.room.types import RoomType +from infinigen.core.constraints.example_solver.room.configs import TYPICAL_AREA_ROOM_TYPES +from infinigen.assets.utils.decorate import read_co, write_co +from infinigen.assets.utils.object import new_plane +from infinigen.core.util import blender as butil +from infinigen.core.util.math import FixedSeed, int_hash +from infinigen.core.util.random import log_uniform + +LARGE = 100 + + +@gin.configurable(denylist=['width', 'height']) +class ContourFactory: + def __init__(self, width=17, height=9): + self.width = width + self.height = height + self.n_trials = 1000 + + def make_contour(self, i): + with FixedSeed(i): + obj = new_plane() + obj.location = self.width / 2, self.height / 2, 0 + obj.scale = self.width / 2, self.height / 2, 1 + butil.apply_transform(obj, loc=True) + corners = list((x, y) for x in [0, unit_cast(self.width)] for y in [0, unit_cast(self.height)]) + random.shuffle(corners) + corners = dict(enumerate(corners)) + + def nearest(t): + if len(corners) == 0: + return -1, np.inf + c = np.array(list(corners.values())) + dist = np.abs(c - np.array([[t[0], t[1]]])).sum(1) + return list(corners.keys())[np.argmin(dist)], np.min(dist) + + while len(corners) > 0: + _, (x, y) = corners.popitem() + r = uniform(0, 1) + if r < .2: + axes = [] + if nearest((self.width - x, y))[1] < .1: + axes.append(0) + elif nearest((x, self.height - y))[1] < .1: + axes.append(1) + if len(axes) > 0: + axis = np.random.choice(axes) + self.add_long_corner(obj, x, y, axis) + t = (self.width - x, y) if axis == 0 else (x, self.height - y) + corners.pop(nearest(t)[0]) + elif r < .35: + self.add_round_corner(obj, x, y) + elif r < .5: + self.add_straight_corner(obj, x, y) + elif r < .65: + self.add_sharp_corner(obj, x, y) + + vertices = obj.data.polygons[0].vertices + p = Polygon(read_co(obj)[:, :2][vertices]) + butil.delete(obj) + return p + + def add_round_corner(self, obj, x, y): + vg = obj.vertex_groups.new(name='corner') + for i, v in enumerate(obj.data.vertices): + vg.add([i], v.co[0] == x and v.co[1] == y, 'REPLACE') + width = unit_cast(uniform(.2, .3) * min(self.width, self.height)) + try: + butil.modify_mesh(obj, 'BEVEL', affect='VERTICES', limit_method='VGROUP', vertex_group='corner', + segments=np.random.randint(2, 5), width=width) + except: + pass + obj.vertex_groups.remove(obj.vertex_groups['corner']) + + def add_straight_corner(self, obj, x, y): + vg = obj.vertex_groups.new(name='corner') + for i, v in enumerate(obj.data.vertices): + vg.add([i], v.co[0] == x and v.co[1] == y, 'REPLACE') + width = unit_cast(uniform(.1, .3) * min(self.width, self.height)) + if width > 0: + butil.modify_mesh(obj, 'BEVEL', affect='VERTICES', limit_method='VGROUP', vertex_group='corner', + segments=1, width=width) + obj.vertex_groups.remove(obj.vertex_groups['corner']) + + def add_sharp_corner(self, obj, x, y): + cutter = new_plane(size=LARGE) + butil.modify_mesh(cutter, 'SOLIDIFY', offset=0, thickness=1) + x_ratio, y_ratio = uniform(.1, .3, 2) + cutter.location = x + (LARGE / 2 - unit_cast(x_ratio * self.width)) * (-1) ** (x <= 0), y + ( + LARGE / 2 - unit_cast(y_ratio * self.height)) * (-1) ** (y <= 0), 0 + butil.modify_mesh(obj, 'BOOLEAN', object=cutter, operation='DIFFERENCE') + butil.delete(cutter) + + def add_long_corner(self, obj, x, y, axis): + x_, y_, z_ = read_co(obj).T + i = np.nonzero((x_ == x) & (y_ == y))[0] + if axis == 0: + y_[i] -= self.height * uniform(.1, .3) * (-1) ** (y_[i] <= 0) + else: + x_[i] -= self.width * uniform(.1, .3) * (-1) ** (x_[i] <= 0) + write_co(obj, np.stack([x_, y_, z_], -1)) + + def add_staircase(self, contour): + x, y = contour.boundary.xy + x_, x__ = np.min(x), np.max(x) + y_, y__ = np.min(y), np.max(y) + for _ in range(self.n_trials): + area = TYPICAL_AREA_ROOM_TYPES[RoomType.Staircase] * uniform(1.4, 1.6) + skewness = log_uniform(.6, .8) + if uniform() < .5: + skewness = 1 / skewness + width, height = unit_cast(np.sqrt(area * skewness).item()), unit_cast( + np.sqrt(area / skewness).item()) + x = unit_cast(uniform(x_, x__ - width)) + y = unit_cast(uniform(y_, y__ - height)) + b = box(x, y, x + width, y + height) + if contour.contains(b): + return b + else: + raise ValueError('Invalid staircase') diff --git a/infinigen/core/constraints/example_solver/room/decorate.py b/infinigen/core/constraints/example_solver/room/decorate.py new file mode 100644 index 000000000..2d26e0db9 --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/decorate.py @@ -0,0 +1,360 @@ +# 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: +# - Lingjie Mei: primary author +# - Karhan Kayan: fix constants + +import logging + +import bmesh +import bpy +import gin +import numpy as np +import shapely +import trimesh.convex +from numpy.random import uniform +from shapely import Point +from shapely.ops import nearest_points +from tqdm import trange +from trimesh.transformations import translation_matrix +import shapely.affinity + +from infinigen.assets.elements import PillarFactory, random_staircase_factory +from infinigen.assets.materials import plaster, tile +from infinigen.assets.utils.decorate import read_area, read_co, read_edge_direction, read_edge_length, \ + read_edges, remove_edges, remove_faces, remove_vertices +from infinigen.assets.utils.object import obj2trimesh +from infinigen.assets.windows import WindowFactory +from infinigen.assets.elements.doors import random_door_factory + +from infinigen.core.constraints.example_solver.room.configs import PILLAR_ROOM_TYPES, ROOM_FLOORS, ROOM_WALLS +from infinigen.core.constraints.example_solver.room.constants import DOOR_WIDTH, WALL_HEIGHT, WALL_THICKNESS +from infinigen.core.constraints.example_solver.room.types import RoomType, get_room_level +from infinigen.core.constraints.example_solver import state_def + +from infinigen.core.util.blender import deep_clone_obj +import infinigen.core.constraints.example_solver.room.constants as constants +from infinigen.core.constraints.example_solver.room.types import get_room_type +from infinigen.core.util.random import random_general as rg + +from infinigen.core import tags as t, tagging + +from infinigen.core.constraints import constraint_language as cl +from infinigen.core.util import blender as butil + +logger = logging.getLogger(__name__) + +def split_rooms(rooms_meshed: list[bpy.types.Object]): + + extract_tags = { + 'wall': {t.Subpart.Wall, t.Subpart.Visible}, + 'floor': {t.Subpart.SupportSurface, t.Subpart.Visible}, + 'ceiling': {t.Subpart.Ceiling, t.Subpart.Visible}, + } + + meshes = { + n: [ + tagging.extract_tagged_faces(r, tags) + for r in rooms_meshed + ] + for n, tags in extract_tags.items() + } + + for k, ms in meshes.items(): + m2delete = [] + for m in ms: + if m.name.startswith('vert'): + butil.select_none() + butil.delete(m) + m2delete.append(m) + for m in m2delete: + ms.remove(m) + + meshes['exterior'] = [tagging.extract_mask(r, 1 - tagging.tagged_face_mask(r, t.Subpart.Visible)) for r in rooms_meshed] + + for n, objs in meshes.items(): + for o in objs: + o.name = o.name.split('.')[0] + f'.{n}' + butil.origin_set(objs, 'ORIGIN_GEOMETRY', center='MEDIAN') + + meshes = { + n: butil.put_in_collection(objs, 'unique_assets:room_' + n) + for n, objs in meshes.items() + } + + return meshes + + +def room_walls(wall_objs: list[bpy.types.Object]): + + wall_fns = list(rg(ROOM_WALLS[get_room_type(r.name)]) for r in wall_objs) + + logger.debug(f'{room_walls.__name__} adding materials to {len(wall_objs)=}, using {len(wall_fns)=}') + + for wall_fn in set(wall_fns): + rooms_ = [o for o, w in zip(wall_objs, wall_fns) if w == wall_fn] + shape = np.random.choice(['square', 'rectangle', 'hexagon']) + kwargs = dict(vertical=True, alternating=False, shape=shape) + if wall_fn in [tile, plaster]: + indices = np.random.randint(0, 3, len(rooms_)) + for i in range(3): + rooms__ = [r for r, j in zip(rooms_, indices) if j == i] + wall_fn.apply(rooms__, **kwargs) + else: + wall_fn.apply(rooms_, **kwargs) + + +def room_ceilings(ceilings: list[bpy.types.Object]): + logger.debug(f'{room_ceilings.__name__} adding materials to {len(ceilings)=}') + plaster.apply(ceilings, t.Subpart.Ceiling) + + +def room_floors(floors: list[bpy.types.Object]): + floor_fns = list(rg(ROOM_FLOORS[get_room_type(r.name)]) for r in floors) + logger.debug(f'{room_floors.__name__} adding materials to {len(floors)=}, using {len(floor_fns)=}') + for floor_fn in set(floor_fns): + rooms_ = [o for o, f in zip(floors, floor_fns) if f == floor_fn] + + if floor_fn in [tile, plaster]: + indices = np.random.randint(0, 3, len(rooms_)) + for i in range(3): + rooms__ = [r for r, j in zip(rooms_, indices) if j == i] + floor_fn.apply(rooms__) + else: + floor_fn.apply(rooms_) + + +@gin.configurable +def populate_doors( + placeholders: list[bpy.types.Object], + n_doors=3, + door_chance=1, + casing_chance=0.0, + all_open=False +): + + factories = [random_door_factory()(np.random.randint(1e7)) for _ in range(3)] + + logger.debug(f'{populate_doors.__name__} populating {len(placeholders)=} with {n_doors=} and {len(factories)=}') + + indices = np.random.randint(0, len(factories), len(placeholders)) + col = butil.get_collection('unique_assets:doors') + casing_col = butil.get_collection('unique_assets:door_casings') + + for i in trange(n_doors, desc='Placing doors'): + factory = factories[i] + casing_factory = factory.casing_factory + doors, casings = [], [] + for j in np.nonzero(indices == i)[0]: + + if uniform() > door_chance: + continue + if all_open: + rot_z = uniform(0.93, 1.93) + else: + rot_p = uniform() + if rot_p < 0.5: + rot_z = uniform(0, 0.1) + elif rot_p < 0.7: + rot_z = uniform(0.93, 1.03) + else: + rot_z = uniform(0, 1) + rot_z *= np.pi / 2 + + door = factory(int(j)) + door.parent = placeholders[j] + door.location = constants.DOOR_WIDTH / 2, constants.WALL_THICKNESS / 2, -constants.DOOR_SIZE / 2 + door.rotation_euler[-1] = -rot_z + doors.append(door) + + if uniform() > casing_chance: + continue + + casing = casing_factory(int(j)) + casing.parent = placeholders[j] + casing.location = 0, 0, -constants.DOOR_SIZE / 2 + casings.append(casing) + + factory.finalize_assets(doors) + butil.put_in_collection(doors, col) + + casing_factory.finalize_assets(casings) + butil.put_in_collection(casings, casing_col) + + +def populate_windows(placeholders: list[bpy.types.Object], n_windows=1): + + factories = [WindowFactory(np.random.randint(1e5)) for _ in range(n_windows)] + + logger.debug(f'{populate_windows.__name__} populating {len(placeholders)=} with {n_windows=} and {len(factories)=}') + + indices = np.random.randint(0, len(factories), len(placeholders)) + col = butil.get_collection('unique_assets:windows') + for i in range(n_windows): + factory = factories[i] + windows = [] + for j in np.nonzero(indices == i)[0]: + cutter_dims = placeholders[j].dimensions + dims = cutter_dims[0], cutter_dims[2], cutter_dims[1] * uniform(.1, .2) + window = factory(int(j), dimensions=dims) + window.parent = placeholders[j] + window.location[1] = -WALL_THICKNESS / 2 + window.rotation_euler[1] = np.pi + butil.put_in_collection(list(butil.iter_object_tree(window)), col) + factory.finalize_assets(windows) + + +def room_stairs(state, rooms_meshed): + + col = butil.get_collection('unique_assets:staircases') + states = list(s for k, s in state.objs.items() if get_room_type(k) == RoomType.Staircase) + contours, doors = [], [] + for s in states: + doors_ = [bpy.data.objects[k] for k, o in state.objs.items() if any( + r.relation == cl.CutFrom() and r.target_name == s.obj.name for r in o.relations) and k.startswith( + 'door')] + contour = shapely.simplify(s.contour.buffer(-WALL_THICKNESS / 2, join_style='mitre'), .1) + for door in doors_: + box = shapely.box(-DOOR_WIDTH / 2, -DOOR_WIDTH * 2, DOOR_WIDTH / 2, DOOR_WIDTH * 2) + box = shapely.affinity.translate(shapely.affinity.rotate(box, door.rotation_euler[-1]), + *door.location) + contour = contour.difference(box) + doors.append(doors_) + contours.append(contour) + geoms = [] + for c, c_ in zip(contours[:-1], contours[1:]): + geom = c.intersection(c_) + if not geom.geom_type == 'Polygon': + geom = sorted(list(g for g in geom.geoms if g.geom_type == 'Polygon'), key=lambda _: _.area)[-1] + geoms.append(geom) + placeholders, offsets, fns = [], [], [] + for _ in trange(100, desc='Generating staircases'): + butil.delete(placeholders) + fns = [random_staircase_factory()(np.random.randint(1e7)) for _ in geoms] + placeholders, mlss, lower, upper = [], [], [], [] + for j, fn in enumerate(fns): + ph = fn.create_placeholder(i=np.random.randint(1e7)) + placeholders.append(ph) + polygon = shapely.intersection_all(list( + shapely.affinity.translate(geoms[j], -x, -y) for x in [ph.bound_box[0][0], ph.bound_box[-1][0]] + for y in [ph.bound_box[0][1], ph.bound_box[-1][1]])) + mlss.append(polygon.boundary if polygon.geom_type == 'Polygon' else shapely.MultiLineString( + [p.boundary for p in polygon.geoms])) + x, y, z = read_co(ph).T + lower.append((x[z < WALL_HEIGHT], y[z < WALL_HEIGHT])) + upper.append((x[z >= WALL_HEIGHT], y[z >= WALL_HEIGHT])) + if any(p.is_empty for p in mlss): + continue + for _ in range(100): + offsets = [] + for j, mls in enumerate(mlss): + p = mls.bounds + x = uniform(p[0], p[2]) + y = uniform(p[1], p[3]) + p = Point(x, y) + projected = nearest_points(mls, p)[0] + if max(np.abs(p.x - projected.x), np.abs(p.y - projected.y)) < constants.STAIRCASE_SNAP: + p = projected + coords = mls.coords if mls.geom_type == 'LineString' else np.concatenate( + [ls.coords for ls in mls.geoms]) + projected = nearest_points(shapely.MultiPoint(coords), Point(x, y))[0] + if max(np.abs(p.x - projected.x), np.abs(p.y - projected.y)) < constants.STAIRCASE_SNAP: + p = projected + x, y = p.x, p.y + placeholders[j].location = x, y, j * WALL_HEIGHT + WALL_THICKNESS / 2 + contains_lower = shapely.contains_xy(contours[j], lower[j][0] + x, lower[j][1] + y).all() + contains_upper = shapely.contains_xy(contours[j + 1], upper[j][0] + x, upper[j][1] + y).all() + lower_valid = fns[j].valid_contour((x, y), contours[j], doors[j]) + upper_valid = fns[j].valid_contour((x, y), contours[j + 1], doors[j + 1], False) + if not (contains_lower and contains_upper and lower_valid and upper_valid): + break + offsets.append((x, y)) + if len(offsets) == len(geoms): + ts = list(trimesh.convex.convex_hull( + obj2trimesh(ph).apply_transform(translation_matrix([*o, WALL_HEIGHT * j]))) for j, (ph, o) + in enumerate(zip(placeholders, offsets))) + if all(t.intersection(t_).is_empty for t, t_ in zip(ts[:-1], ts[1:])): + break + if len(offsets) == len(geoms): + break + butil.delete(placeholders) + for j, fn in enumerate(fns): + s = fn(i=np.random.randint(1e7)) + butil.apply_transform(s, True) + s.location = *offsets[j], j * WALL_HEIGHT + WALL_THICKNESS / 2 + butil.put_in_collection(s, col) + cutter = fn.create_cutter(i=np.random.randint(1e7)) + cutter.location = *offsets[j], j * WALL_HEIGHT + WALL_THICKNESS / 2 + for mesh in rooms_meshed: + if get_room_type(mesh.name) == RoomType.Staircase: + level = get_room_level(mesh.name) + if level == j + 1: + butil.modify_mesh(mesh, 'BOOLEAN', object=cutter, operation='DIFFERENCE', use_self=True, + use_hole_tolerant=True) + butil.delete(cutter) + m = deep_clone_obj(mesh) + m.location = -offsets[j][0], -offsets[j][1], 0 + butil.apply_transform(m, True) + g = fns[j].make_guardrail(m) + g.location = s.location + g.location[-1] += WALL_HEIGHT + butil.put_in_collection(g, col) + return placeholders + + +def room_pillars(state: state_def.State, walls: list[bpy.types.Object]): + + col = butil.get_collection('pillars') + + pillar_rooms = [ + s for k, s in state.objs.items() + if get_room_type(k) in PILLAR_ROOM_TYPES + ] + + for s in pillar_rooms: + factory = PillarFactory(np.random.randint(1e7)) + mesh = next(m for m in walls if m.name.startswith(s.obj.name.split('.')[0])) + interior = tagging.extract_tagged_faces(mesh, {t.Subpart.Interior}) + remove_faces(interior, read_area(interior) < WALL_THICKNESS / 2 * WALL_HEIGHT) + selection = (read_edge_length(interior) > WALL_HEIGHT / 2) & ( + np.abs(read_edge_direction(interior))[:, -1] > .9) + selection_ = np.bincount(read_edges(interior)[selection].reshape(-1), + minlength=len(interior.data.vertices)) + remove_vertices(interior, selection_ == 0) + remove_vertices(interior, lambda x, y, z: z > WALL_THICKNESS) + remove_edges(interior, read_edge_length(interior) < WALL_THICKNESS) + interiors = butil.split_object(interior) + for i in interiors: + with butil.ViewportMode(i, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.dissolve_limited() + bm = bmesh.from_edit_mesh(i.data) + geom = [v for v in bm.verts if len(v.link_edges) < 2] + bmesh.ops.delete(bm, geom=geom) + interiors_ = [i for i in interiors if len(i.data.vertices) > 0] + butil.delete([i for i in interiors if len(i.data.vertices) == 0]) + + if len(interiors_) == 0: + return + + with butil.Suppress(): + interior = butil.join_objects(interiors_) + + staircases = list(butil.get_collection('staircases').objects) + if len(staircases) == 0: + return + + staircases = np.concatenate([ + read_co(o) + np.array([o.location]) for o in staircases + ]) + cos = read_co(interior) + cos[:, -1] = mesh.location[-1] + WALL_THICKNESS / 2 + cos = cos[np.min(np.linalg.norm(cos[:, np.newaxis] - staircases[np.newaxis], axis=-1), + -1) > WALL_THICKNESS] + for co in cos: + obj = factory(np.random.randint(1e7)) + obj.location = co + butil.put_in_collection(obj, col) + butil.delete(interior) diff --git a/infinigen/core/constraints/example_solver/room/graph.py b/infinigen/core/constraints/example_solver/room/graph.py new file mode 100644 index 000000000..7f21ac06f --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/graph.py @@ -0,0 +1,167 @@ +# 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: Lingjie Mei +from collections import defaultdict, deque +from collections.abc import Sequence + +import gin +import numpy as np +import networkx as nx +from numpy.random import uniform + +from infinigen.core.constraints.example_solver.room.utils import unit_cast +from infinigen.core.util.math import FixedSeed +from infinigen.core.util.random import log_uniform, random_general as rg + +from infinigen.core.constraints.example_solver.room.types import RoomGraph, RoomType, get_room_type +from infinigen.core.constraints.example_solver.room.configs import ( + LOOP_ROOM_TYPES, ROOM_CHILDREN, + ROOM_NUMBERS, + TYPICAL_AREA_ROOM_TYPES, UPSTAIRS_ROOM_CHILDREN, STUDIO_ROOM_CHILDREN, +) + + +@gin.configurable(denylist=['factory_seed', 'level']) +class GraphMaker: + def __init__(self, factory_seed, level=0, requires_staircase=False, room_children='home', typical_area_room_types=TYPICAL_AREA_ROOM_TYPES, + loop_room_types=LOOP_ROOM_TYPES, room_numbers=ROOM_NUMBERS, max_cycle_basis=1, + requires_bathroom_privacy=True, + entrance_type=('weighted_choice', (.5, 'porch'), (.5, 'hallway')), hallway_alpha=1, + no_hallway_children_prob=.4): + self.factory_seed = factory_seed + with FixedSeed(factory_seed): + self.requires_staircase = requires_staircase + match room_children: + case 'home': + self.room_children = ROOM_CHILDREN if level == 0 else UPSTAIRS_ROOM_CHILDREN + case _: + self.room_children = STUDIO_ROOM_CHILDREN + self.hallway_room_types = [r for r, m in self.room_children.items() if RoomType.Hallway in m] + self.typical_area_room_types = typical_area_room_types + self.loop_room_types = loop_room_types + self.room_numbers = room_numbers + self.max_samples = 1000 + self.slackness = log_uniform(1.5, 1.8) + self.max_cycle_basis = max_cycle_basis + self.requires_bathroom_privacy = requires_bathroom_privacy + self.entrance_type = rg(entrance_type) + self.hallway_prob = lambda x: 1 / (x + hallway_alpha) + self.no_hallway_children_prob = no_hallway_children_prob + self.skewness_min = .7 + + def make_graph(self, i): + with FixedSeed(i): + for _ in range(self.max_samples): + room_type_counts = defaultdict(int) + rooms = [] + children = defaultdict(list) + queue = deque() + + def add_room(t, p): + i = len(rooms) + name = f'{t}_{room_type_counts[t]}' + room_type_counts[t] += 1 + if p is not None: + children[p].append(i) + rooms.append(name) + queue.append(i) + + add_room(RoomType.LivingRoom, None) + while len(queue) > 0: + i = queue.popleft() + for rt, spec in self.room_children[get_room_type(rooms[i])].items(): + for _ in range(rg(spec)): + add_room(rt, i) + + if self.requires_bathroom_privacy and not self.has_bathroom_privacy: + continue + + for i, r in enumerate(rooms): + for j, s in enumerate(rooms): + if (rt := get_room_type(r)) in self.loop_room_types: + if (rt_ := get_room_type(s)) in self.loop_room_types[rt]: + if uniform() < self.loop_room_types[rt][rt_] and j not in children[i]: + children[i].append(j) + + for i, r in enumerate(rooms): + if get_room_type(r) in self.hallway_room_types: + hallways = [j for j in children[i] if get_room_type(rooms[j]) == RoomType.Hallway] + other_rooms = [j for j in children[i] if get_room_type(rooms[j]) != RoomType.Hallway] + children[i] = hallways.copy() + for k, o in enumerate(other_rooms): + if uniform() < self.no_hallway_children_prob or len(hallways) == 0: + children[i].append(o) + else: + children[hallways[np.random.randint(len(hallways))]].append(o) + + hallways = [i for i, r in enumerate(rooms) if get_room_type(r) == RoomType.Hallway] + if len(hallways) == 0: + entrance = 0 + else: + if self.requires_staircase: + prob = np.array([self.hallway_prob(len(children[h])) for h in hallways]) + add_room(RoomType.Staircase, np.random.choice(hallways, p=prob / prob.sum())) + prob = np.array([self.hallway_prob(len(children[h])) for h in hallways]) + entrance = np.random.choice(hallways, p=prob / prob.sum()) + if self.entrance_type == 'porch': + add_room(RoomType.Balcony, entrance) + entrance = queue.pop() + elif self.entrance_type == 'none': + entrance = None + + children_ = [children[i] for i in range(len(rooms))] + room_graph = RoomGraph(children_, rooms, entrance) + if self.satisfies_constraint(room_graph): + return room_graph + + __call__ = make_graph + + def satisfies_constraint(self, graph): + if not graph.is_planar or len(graph.cycle_basis) > self.max_cycle_basis: + return False + for room_type, constraint in self.room_numbers.items(): + if isinstance(constraint, Sequence): + n_min, n_max = constraint + else: + n_min, n_max = constraint, constraint + if not n_min <= len(graph[room_type]) <= n_max: + return False + return True + + def has_bathroom_privacy(self, rooms, children): + for i, r in rooms: + if get_room_type(r) == RoomType.LivingRoom: + has_public_bathroom = any(get_room_type(rooms[j]) == RoomType.Bathroom for j in children[i]) + if not has_public_bathroom: + for j in children[i]: + if get_room_type(rooms[j] == RoomType.Bedroom): + if not any(get_room_type(rooms[k]) for k in children[j]): + return False + return True + + def suggest_dimensions(self, graph, width=None, height=None): + area = sum([self.typical_area_room_types[get_room_type(r)] for r in graph.rooms]) * self.slackness + if width is None and height is None: + skewness = uniform(self.skewness_min, 1 / self.skewness_min) + width = unit_cast(np.sqrt(area * skewness).item()) + height = unit_cast(np.sqrt(area / skewness).item()) + elif uniform(0, 1) < .5: + height_ = unit_cast(area / width) + height = None if height_ > height and self.skewness_min < height_ / width < 1 / self.skewness_min\ + else height_ + else: + width_ = unit_cast(area / height) + width = None if width_ > width and self.skewness_min < width_ / height < 1 / self.skewness_min \ + else width_ + + return width, height + + def draw(self, graph): + g = nx.Graph() + shortnames = [r[:3].upper() + r.split('_')[-1] for r in graph.rooms] + g.add_nodes_from(shortnames) + for k in range(len(shortnames)): + for l in graph.neighbours[k]: + g.add_edge(shortnames[k], shortnames[l]) + nx.draw_planar(g, with_labels=True) diff --git a/infinigen/core/constraints/example_solver/room/scorer.py b/infinigen/core/constraints/example_solver/room/scorer.py new file mode 100644 index 000000000..c37eb1351 --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/scorer.py @@ -0,0 +1,281 @@ +# 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: +# - Lingjie Mei: primary author +# - Karhan Kayan: fix constants + +from collections import defaultdict + +import gin +import numpy as np +from shapely import LineString, Polygon + +import infinigen.core.constraints.example_solver.room.constants as constants +from infinigen.core.constraints.example_solver.room.types import RoomType, get_room_type +from infinigen.core.constraints.example_solver.room.configs import EXTERIOR_CONNECTED_ROOM_TYPES, FUNCTIONAL_ROOM_TYPES, SQUARE_ROOM_TYPES, \ + TYPICAL_AREA_ROOM_TYPES +from infinigen.core.constraints.example_solver.room.utils import abs_distance, buffer, unit_cast + + +@gin.configurable(denylist=['graph']) +class BlueprintScorer: + def __init__( + self, + graph, + shortest_path_weight=2., + typical_area_weight=10., + typical_area_room_types=TYPICAL_AREA_ROOM_TYPES, + aspect_ratio_weight=10., + aspect_ratio_room_types=SQUARE_ROOM_TYPES, + convexity_weight=50., + conciseness_weight=2., + exterior_connected_room_types=EXTERIOR_CONNECTED_ROOM_TYPES, + exterior_length_weight=.2, + exterior_corner_weight=.02, + collinearity_weight=.02, + functional_room_weight=.2, + functional_room_types=FUNCTIONAL_ROOM_TYPES, + narrow_passage_weight=5., + narrow_passage_thresh=1.5 + ): + self.graph = graph + self.shortest_path_weight = shortest_path_weight + + self.typical_area_weight = typical_area_weight + self.typical_area_room_types = typical_area_room_types + + self.aspect_ratio_weight = aspect_ratio_weight + self.aspect_ratio_room_types = aspect_ratio_room_types + + self.convexity_weight = convexity_weight + + self.conciseness_weight = conciseness_weight + self.conciseness_thresh = 4 + + self.exterior_length_weight = exterior_length_weight + self.exterior_connected_room_types = exterior_connected_room_types + self.exterior_corner_weight = exterior_corner_weight + + self.collinearity_weight = collinearity_weight + + self.functional_room_weight = functional_room_weight + self.functional_room_types = functional_room_types + + self.narrow_passage_weight = narrow_passage_weight + self.narrow_passage_thresh = narrow_passage_thresh + + def find_score(self, assignment, info): + return sum(self.compute_scores(assignment, info).values()) + + def compute_scores(self, assignment, info): + info['neighbours'] = {a: set(assignment[_] for _ in self.graph.neighbours[i]) for i, a in + enumerate(assignment)} + scores = {} + if self.shortest_path_weight > 0: + score = self.shortest_path_weight * self.shortest_path(assignment, info) + scores['shortest_path'] = score + if self.typical_area_weight > 0: + score = self.typical_area_weight * self.typical_area(assignment, info) + scores['typical_area'] = score + if self.aspect_ratio_weight > 0: + score = self.aspect_ratio_weight * self.aspect_ratio(assignment, info) + scores['aspect_ratio'] = score + if self.convexity_weight > 0: + score = self.convexity_weight * self.convexity(assignment, info) + scores['convexity'] = score + if self.conciseness_weight > 0: + score = self.conciseness_weight * self.conciseness(assignment, info) + scores['conciseness'] = score + if self.exterior_length_weight > 0: + score = self.exterior_length_weight * self.exterior_length(assignment, info) + scores['exterior_length'] = score + if self.exterior_corner_weight > 0: + score = self.exterior_corner_weight * self.exterior_corner(assignment, info) + scores['exterior_corner'] = score + if self.collinearity_weight > 0: + score = self.collinearity_weight * self.collinearity(assignment, info) + scores['collinearity'] = score + if self.functional_room_weight > 0: + score = self.functional_room_weight * self.functional_room(assignment, info) + scores['functional_room'] = score + if self.narrow_passage_weight > 0: + score = self.narrow_passage_weight * self.narrow_passage(assignment, info) + scores['narrow_passage'] = score + return scores + + def shortest_path(self, assignment, info): + shortest_paths = defaultdict(dict) + centroids = {k: s.centroid.coords[:][0] for k, s in info['segments'].items()} + for k, ses in info['shared_edges'].items(): + for l, se in ses.items(): + min_distance = np.full(100, 4) + for ls in se.geoms: + for c in ls.coords[:]: + dist = abs_distance(centroids[k], c) + abs_distance(c, centroids[l]) + if np.sum(dist) <= np.sum(min_distance): + min_distance = dist + shortest_paths[k][l] = min_distance + roots = self.graph[RoomType.Staircase] + if self.graph.entrance is not None: + roots.append(self.graph.entrance) + scores = {} + for root in roots: + root = assignment[root] + displacement = {a: np.array([1e3] * 4) for a in assignment} + displacement[root] = np.zeros(4) + updated = True + while updated: + updated = False + for k, ns in info['neighbours'].items(): + for n in ns: + d = displacement[k] + shortest_paths[k][n] + if np.sum(d) < np.sum(displacement[n]): + displacement[n] = d + updated = True + displacements = np.stack([d for k, d in displacement.items() if k != root]) + x, xx, y, yy = displacements.T + score = (1. / ((np.maximum(x, xx) + np.maximum(y, yy)) / displacements.sum(1)) - 1) ** 2 + scores[root] = score.sum() + return sum(s for s in scores.values()) + + def typical_area(self, assignment, info): + total_typical_areas, total_face_areas = [], [] + for i, r in enumerate(self.graph.rooms): + if get_room_type(r) in self.typical_area_room_types: + total_typical_areas.append(self.typical_area_room_types[get_room_type(r)]) + total_face_areas.append(info['segments'][assignment[i]].area) + total_typical_areas = np.array(total_typical_areas) + total_face_areas = np.array(total_face_areas) + scores = total_face_areas / np.sum(total_face_areas) / total_typical_areas * np.sum(total_typical_areas) + scores = np.where(scores > 1, scores, 1 / scores) - 1 + return scores.sum() + + def aspect_ratio(self, assignment, info): + aspect_ratios = [] + for i, r in enumerate(self.graph.rooms): + if get_room_type(r) in self.aspect_ratio_room_types: + x, y, xx, yy = info['segments'][assignment[i]].bounds + aspect_ratios.append((xx - x) / (yy - y)) + aspect_ratios = np.array(aspect_ratios) + aspect_ratios = np.where(aspect_ratios > 1, aspect_ratios, 1 / aspect_ratios) + scores = aspect_ratios - 1 + return scores.sum() + + def convexity(self, assignment, info): + sharpness = [] + for s in info['segments'].values(): + sharpness.append(s.convex_hull.area / s.area) + sharpness = np.array(sharpness) + scores = (sharpness - 1) ** 2 + return scores.sum() + + def conciseness(self, assignment, info): + conciseness = np.array([len(s.boundary.coords) - 1 for s in info['segments'].values()]) + scores = (conciseness / self.conciseness_thresh - 1) ** 2 + return scores.sum() + + def exterior_length(self, assignment, info): + exterior_edges = info['exterior_edges'] + total_length = 0 + for i, r in enumerate(self.graph.rooms): + if get_room_type(r) in self.exterior_connected_room_types: + if assignment[i] in exterior_edges: + total_length += exterior_edges[assignment[i]].length + score = total_length / sum(ee.length for ee in exterior_edges.values()) + return (score - 1) ** 2 * len(info['segments']) + + def exterior_corner(self, assignment, info): + exterior_edges = info['exterior_edges'] + total_corners, corners = 0, 0 + for i, r in enumerate(self.graph.rooms): + if assignment[i] in exterior_edges: + ee = exterior_edges[assignment[i]] + for e in [ee] if isinstance(ee, LineString) else ee.geoms: + n = len(e.coords[:]) - 2 + corners += n + if get_room_type(r) in self.exterior_connected_room_types: + total_corners += n + score = total_corners / corners + return (score - 1) ** 2 * len(info['segments']) + + def collinearity(self, assignment, info): + x_skeletons, y_skeletons = set(), set() + for s in info['segments'].values(): + x, y = s.boundary.xy + for i in range(len(x) - 1): + if np.abs(x[i] - x[i + 1]) < 1e-2: + x_skeletons.add(unit_cast(x[i])) + elif np.abs(y[i] - y[i + 1]) < 1e-2: + y_skeletons.add(unit_cast(y[i])) + score = len(x_skeletons) + len(y_skeletons) + return score * len(info['segments']) + + def functional_room(self, assignment, info): + total_area = 0 + segments = info['segments'] + for i, r in enumerate(self.graph.rooms): + if get_room_type(r) in self.functional_room_types: + total_area += segments[assignment[i]].area + score = total_area / sum(s.area for s in segments.values()) + return (1 - score) ** 2 * len(info['segments']) + + def narrow_passage(self, assignment, info): + scores = [] + for p in info['segments'].values(): + for d in np.arange(1, int(self.narrow_passage_thresh / constants.UNIT)): + with np.errstate(invalid="ignore"): + length = d * constants.UNIT / 2 + b = buffer(p, -length) + c = buffer(b, length) + scores.append(p.area - c.area + ( + self.narrow_passage_thresh ** 2 * 20 if not isinstance(b, Polygon) else 0)) + scores = np.array(scores).sum() + return scores + + +@gin.configurable(denylist=['graphs']) +class JointBlueprintScorer: + def __init__(self, graphs, *args, staircase_occupancy_weight=1., staircase_iou_weight=.5, **kwargs): + self.scorers = [] + self.graphs = graphs + for g in self.graphs: + self.scorers.append(BlueprintScorer(g, *args, **kwargs)) + self.staircase_occupancy_weight = staircase_occupancy_weight + self.staircase_iou_weight = staircase_iou_weight + + def compute_scores(self, assignments, infos): + scores = {} + for i, (assignment, info) in enumerate(zip(assignments, infos)): + floor_scores = self.scorers[i].compute_scores(assignment, info) + scores.update({f'{k}_{i:01d}': v for k, v in floor_scores.items()}) + if len(self.graphs) > 1: + if self.staircase_occupancy_weight > 0: + score = self.staircase_occupancy_weight * self.staircase_occupancy(assignments, infos) + scores['staircase_occupancy'] = score + if self.staircase_iou_weight > 0: + score = self.staircase_iou_weight * self.staircase_iou(assignments, infos) + scores['staircase_iou'] = score + return scores + + def find_score(self, assignments, infos): + return sum(self.compute_scores(assignments, infos).values()) + + def staircase_occupancy(self, assignments, infos): + scores = [] + for graph, assignment, info in zip(self.graphs, assignments, infos): + for _ in graph[RoomType.Staircase]: + scores.append(info['staircase_occupancies'][assignment[_]]) + scores = np.array(scores) + return ((scores - 1) ** 2).sum() * sum(len(info['segments']) for info in infos) + + def staircase_iou(self, assignments, infos): + scores = [] + for graph, assignment, info in zip(self.graphs, assignments, infos): + for _ in graph[RoomType.Staircase]: + segment = info['segments'][assignment[_]] + staircase = info['staircase'] + scores.append(segment.intersection(staircase).area / segment.union(staircase).area) + scores = np.array(scores) + return ((scores - 1) ** 2).sum() * sum(len(info['segments']) for info in infos) diff --git a/infinigen/core/constraints/example_solver/room/segment.py b/infinigen/core/constraints/example_solver/room/segment.py new file mode 100644 index 000000000..dc02f7962 --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/segment.py @@ -0,0 +1,144 @@ +# 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: +# - Lingjie Mei: primary author +# - Karhan Kayan: fix constants + +from collections import defaultdict + +import numpy as np +import shapely +from matplotlib import pyplot as plt +from numpy.random import uniform +from shapely import LineString, union + +from infinigen.assets.utils.shapes import shared +from infinigen.core.constraints.example_solver.room.utils import compute_neighbours, cut_polygon_by_line, canonicalize, is_valid_polygon, \ + unit_cast, update_exterior_edges, update_shared_edges, update_staircase_occupancies +import infinigen.core.constraints.example_solver.room.constants as constants +from infinigen.core.util.random import log_uniform +from infinigen.core.util.math import FixedSeed + + +class SegmentMaker: + def __init__(self, factory_seed, contour, n, merge_alpha=-1): + with FixedSeed(factory_seed): + self.contour = contour + self.n = n + self.n_boxes = int(self.n * uniform(1.4, 1.6)) + + self.box_ratio = .3 + self.min_segment_area = log_uniform(1.5, 2) + self.min_segment_size = log_uniform(.5, 1.) + + self.divide_box_fn = lambda x: x.area ** .5 + + self.n_box_trials = 200 + self.merge_fn = lambda x: x ** merge_alpha + + def build_segments(self, staircase=None): + while True: + try: + segments, shared_edges = self.filter_segments() + break + except: + pass + exterior_edges = update_exterior_edges(segments, shared_edges) + neighbours_all = {k: set(compute_neighbours(se, constants.SEGMENT_MARGIN)) for k, se in shared_edges.items()} + exterior_neighbours = set(compute_neighbours(exterior_edges, constants.SEGMENT_MARGIN)) + staircase_occupancies = update_staircase_occupancies(segments, staircase) + return { + 'segments': segments, + 'shared_edges': shared_edges, + 'exterior_edges': exterior_edges, + 'neighbours_all': neighbours_all, + 'exterior_neighbours': exterior_neighbours, + 'staircase_occupancies': staircase_occupancies, + 'staircase': staircase, + } + + def divide_segments(self): + segments = {0: self.contour} + for _ in range(self.n_boxes): + keys, values = zip(*segments.items()) + prob = np.array([self.divide_box_fn(v) for v in values]) + for _ in range(self.n_box_trials): + k = np.random.choice(list(keys), p=prob / prob.sum()) + x, y, xx, yy = segments[k].bounds + w, h = xx - x, yy - y + r = uniform(.25, .75) + line = None + if w >= h: + w_ = unit_cast(r * w) + bound = max(self.box_ratio * h, constants.SEGMENT_MARGIN) + if w_ >= bound and w - w_ >= bound: + line = LineString([(x + w_, -100), (x + w_, 100)]) + else: + h_ = unit_cast(r * h) + bound = max(self.box_ratio * w, constants.SEGMENT_MARGIN) + if h_ >= bound and h - h_ >= bound: + line = LineString([(-100, y + h_), (100, y + h_)]) + if line is not None: + i = max(segments.keys()) + s, t = cut_polygon_by_line(segments[k], line) + s_ = canonicalize(s) + t_ = canonicalize(t) + if np.abs(s.area - s_.area) < 1e-3 and np.abs(t.area - t_.area) < 1e-3: + segments[k], segments[i + 1] = s_, t_ + break + return {k: v for k, v in segments.items()} + + def merge_segment(self, segments, shared_edges, attached, i, j): + assert i != j + s = canonicalize(union(segments[i], segments[j])) + if not is_valid_polygon(s): return + segments[j] = s + segments.pop(i) + shared_edges.pop(i) + attached.pop(i) + for k, ses in shared_edges.items(): + if i in ses: + ses.pop(i) + for k, ats in attached.items(): + if i in ats: + ats.remove(i) + for k, s in segments.items(): + for l, t in segments.items(): + if k != l and (k == j or l == j): + se = shared(s, t) + shared_edges[k][l] = se + if se.length >= constants.SEGMENT_MARGIN: + attached[k].add(l) + attached[l].add(k) + return shared_edges + + def filter_segments(self): + segments = self.divide_segments() + shared_edges = defaultdict(dict) + attached = defaultdict(set) + for k, s in segments.items(): + for l, t in segments.items(): + if k < l: + se = shared(s, t) + shared_edges[k][l] = shared_edges[l][k] = se + if se.length >= constants.SEGMENT_MARGIN: + attached[k].add(l) + attached[l].add(k) + + while len(segments) > self.n: + prob = np.array([1 / (len(attached[c]) + 1) for c in shared_edges.keys()]) + k = np.random.choice(list(shared_edges.keys()), p=prob / prob.sum()) + candidates = list(k for k, se in shared_edges[k].items() if se.length>=1e-6) + prob = np.array([len(attached[c].difference(attached[k]))**2 + .5 for c in candidates]) + n = np.random.choice(candidates, p=prob / prob.sum()) + self.merge_segment(segments, shared_edges, attached, k, n) + return segments, shared_edges + + def plot(self, segments): + plt.clf() + for k, s in segments.items(): + shapely.plotting.plot_polygon(s, color=uniform(0, 1, 3)) + plt.tight_layout() + plt.show() diff --git a/infinigen/core/constraints/example_solver/room/solidifier.py b/infinigen/core/constraints/example_solver/room/solidifier.py new file mode 100644 index 000000000..d6eb63637 --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/solidifier.py @@ -0,0 +1,436 @@ +# 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: +# - Lingjie Mei: primary author +# - Karhan Kayan: fix constants + +import logging +from collections import defaultdict, deque +from collections.abc import Iterable, Mapping, Sequence + +import bmesh +import bpy +import gin +import numpy as np +from numpy.random import uniform +from shapely import LineString, line_interpolate_point, remove_repeated_points, simplify +from shapely.ops import linemerge +from numpy.random import uniform + +from infinigen.core.constraints.example_solver.room.types import RoomGraph, RoomType, get_room_type +from infinigen.core.constraints.example_solver.room.configs import ( + COMBINED_ROOM_TYPES, PANORAMIC_ROOM_TYPES, + WINDOW_ROOM_TYPES, TYPICAL_AREA_ROOM_TYPES, +) +from infinigen.core.constraints.example_solver.room.constants import DOOR_MARGIN, DOOR_SIZE, DOOR_WIDTH, \ + MAX_WINDOW_LENGTH, SEGMENT_MARGIN, WALL_HEIGHT, WALL_THICKNESS, WINDOW_HEIGHT, WINDOW_SIZE + +from infinigen.core.constraints.example_solver.room.utils import SIMPLIFY_THRESH, WELD_THRESH, buffer, \ + canonicalize, polygon2obj +from infinigen.assets.utils.decorate import ( + read_area, read_center, read_co, remove_edges, remove_faces, + select_faces, write_attribute, write_co, read_edges, read_edge_direction, read_edge_length, +) +from infinigen.assets.utils.object import data2mesh, join_objects, mesh2obj, new_cube, new_line +from infinigen.core.surface import write_attr_data +from infinigen.core.tagging import PREFIX +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t +from infinigen.core.constraints.example_solver.geometry import parse_scene +from infinigen.core.constraints.example_solver.state_def import ObjectState, RelationState, State +from infinigen.core.constraints import constraint_language as cl + +from infinigen.assets.utils.autobevel import BevelSharp +from infinigen.core.util.logging import BadSeedError + +logger = logging.getLogger(__name__) + +_eps = 0.01 + +@gin.configurable(denylist=['graph', 'level']) +class BlueprintSolidifier: + def __init__(self, graph: RoomGraph, level, has_ceiling=True, combined_room_types=COMBINED_ROOM_TYPES, + panoramic_room_types=PANORAMIC_ROOM_TYPES, enable_open=True): + self.graph = graph + self.level = level + self.has_ceiling = has_ceiling + self.combined_room_types = combined_room_types + self.panoramic_room_types = panoramic_room_types + self.beveler = BevelSharp(mult=10) + self.enable_open = enable_open + + def get_entrance(self, names): + return None if self.graph.entrance is None else {k for k, n in names.items() if + n == self.graph.rooms[self.graph.entrance]}.pop() + + def get_staircase(self, names): + return {k for k, n in names.items() if get_room_type(n) == RoomType.Staircase}.pop() + + @staticmethod + def unroll(x): + for k, cs in x.items(): + if isinstance(cs, Mapping): + for l, c in cs.items(): + if k < l: + yield (k, l), c + elif isinstance(cs, Iterable): + for c in cs: + yield (k,), c + else: + yield (k,), cs + + def solidify(self, assignment, info): + segments = info['segments'] + neighbours = info['neighbours'] + shared_edges = info['shared_edges'] + exterior_edges = info['exterior_edges'] + + names = {k: self.graph.rooms[assignment.index(k)] for k in segments} + rooms = {k: self.make_room(p, exterior_edges.get(k, None)) for k, p in segments.items()} + for k, o in rooms.items(): + o.name = f'{names[k]}-{self.level}' + # if segments[k].area > 2.5 * TYPICAL_AREA_ROOM_TYPES[get_room_type(names[k])] + 5: + # raise BadSeedError() + # + + open_cutters, door_cutters = self.make_interior_cutters(neighbours, shared_edges, segments, names) + exterior_cutters = self.make_exterior_cutters(exterior_edges, names) + + for k, r in rooms.items(): + r.location[-1] += WALL_HEIGHT * self.level + for cutters in [open_cutters, door_cutters, exterior_cutters]: + for k, c in self.unroll(cutters): + c.location[-1] += WALL_HEIGHT * self.level + + butil.put_in_collection(rooms.values(), 'placeholders:room_shells') + + state = self.convert_solver_state( + rooms, segments, shared_edges, open_cutters, door_cutters, exterior_cutters + ) + + def clone_as_meshed(o): + new = butil.copy(o) + new.name = o.name + '.meshed' + return new + rooms = {k: clone_as_meshed(r) for k, r in rooms.items()} + + # Cut windows & doors from final room meshes + cutter_col = butil.get_collection('placeholders:portal_cutters') + for cutters in [open_cutters, door_cutters, exterior_cutters]: + for k, c in self.unroll(cutters): + for k_ in k: + butil.put_in_collection(c, cutter_col) + before = len(rooms[k_].data.polygons) + butil.modify_mesh( + rooms[k_], 'BOOLEAN', object=c, operation='DIFFERENCE', use_self=True, + use_hole_tolerant=True + ) + after = len(rooms[k_].data.polygons) + logger.debug(f'Cutting {c.name} from {rooms[k_].name}, {before=} {after=}') + + for r in rooms.values(): + butil.modify_mesh(r, 'TRIANGULATE', min_vertices=3) + remove_faces(r, read_area(r) < 5e-4) + with butil.ViewportMode(r, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.dissolve_limited(angle_limit=0.001) + x, y, z = read_co(r).T + z = np.where(np.abs(z - WALL_THICKNESS / 2) < .01, WALL_THICKNESS / 2, z) + z = np.where(np.abs(z - WALL_HEIGHT + WALL_THICKNESS / 2) < .01, WALL_HEIGHT - WALL_THICKNESS / 2, + z) + write_co(r, np.stack([x, y, z], -1)) + butil.modify_mesh(r, 'WELD', merge_threshold=WALL_THICKNESS / 10) + + direction = read_edge_direction(r) + z_edges = np.abs(direction[:, -1]) + orthogonal = (z_edges < .1) | (z_edges > .9) + with butil.ViewportMode(r, 'EDIT'): + edge_faces = np.zeros(len(orthogonal)) + bm = bmesh.from_edit_mesh(r.data) + for f in bm.faces: + for e in f.edges: + edge_faces[e.index] += 1 + orthogonal = (z_edges < .1) | (z_edges > .9) | (edge_faces != 1) | (read_edge_length(r) < .5) + if not orthogonal.all(): + raise BadSeedError('No orthogonal edges') + + butil.group_in_collection(rooms.values(), 'placeholders:room_meshes') + + return state, rooms + + def convert_solver_state(self, rooms, segments, shared_edges, open_cutters, door_cutters, exterior_cutters): + obj_states = {} + for k, o in rooms.items(): + + tags = {t.Semantics.Room, t.Semantics(o.name.split('_')[0])} + + tags.add(t.SpecificObject(o.name)) + obj_states[o.name] = ObjectState( + obj=o, + tags=tags, + contour=segments[k] + ) + for k, r in rooms.items(): + relations = obj_states[r.name].relations + for other in shared_edges[k]: + if other in open_cutters[k]: + ct = cl.ConnectorType.Open + elif other in door_cutters[k]: + ct = cl.ConnectorType.Door + else: + ct = cl.ConnectorType.Wall + relations.append(RelationState( + cl.RoomNeighbour({ct}), rooms[other].name) + ) + + cut_state = lambda x: RelationState(cl.CutFrom(), rooms[x].name) + for cutters in [door_cutters, open_cutters, exterior_cutters]: + for k, c in self.unroll(cutters): + + tags = set({t.Semantics.Cutter, t.SpecificObject(c.name)}) + + # TODO Lingjie - do not store whole-object window/door semantics in per-vertex attributes + meshtags = tagging.union_object_tags(c) + for tag in [t.Semantics.Door, t.Semantics.Window, t.Semantics.Entrance]: + if tag.value in meshtags: + tags.add(tag) + + if t.Semantics.Door in meshtags: + # include full possible swing extent of door in state to prevent objects blocking + c.scale.x *= (DOOR_WIDTH + WALL_THICKNESS) / DOOR_WIDTH + + obj_states[c.name] = ObjectState( + obj=c, + tags=tags, + relations=list(cut_state(k_) for k_ in k) + ) + + return State(objs=obj_states) + + def make_room(self, obj, exterior_edges=None): + obj = polygon2obj(canonicalize(obj), True) + butil.modify_mesh(obj, "WELD", merge_threshold=.2) + butil.modify_mesh(obj, 'SOLIDIFY', thickness=WALL_HEIGHT, offset=-1) + self.tag(obj, False) + if exterior_edges is not None: + center = read_center(obj) + exterior_centers = [] + for ls in exterior_edges.geoms if exterior_edges.geom_type == 'MultiLineString' else [ + exterior_edges]: + for u, v in zip(ls.coords[:-1], ls.coords[1:]): + exterior_centers.append(((u[0] + v[0]) / 2, (u[1] + v[1]) / 2)) + exterior = (np.abs(center[:, np.newaxis, :2] - np.array(exterior_centers)[np.newaxis]).sum( + -1) < WALL_THICKNESS * 4).any(-1).astype(int) + else: + exterior = np.zeros(len(obj.data.polygons), dtype=int) + write_attr_data(obj, f'{PREFIX}{t.Subpart.Exterior.value}', exterior, 'INT', 'FACE') + write_attr_data(obj, f'{PREFIX}{t.Subpart.Interior.value}', 1 - exterior, 'INT', 'FACE') + + assert len(obj.data.vertices) > 0 + + obj.vertex_groups.new(name='visible_') + butil.modify_mesh(obj, 'SOLIDIFY', thickness=WALL_THICKNESS / 2, offset=-1, use_even_offset=True, + shell_vertex_group='visible_', use_quality_normals=True) + write_attribute(obj, 'visible_', f'{PREFIX}{t.Subpart.Visible.value}', 'FACE', 'INT') + obj.vertex_groups.remove(obj.vertex_groups['visible_']) + tagging.tag_object(obj, t.Semantics.Room) + return obj + + def make_interior_cutters(self, neighbours, shared_edges, segments, names): + name_groups = {} + for k, n in names.items(): + name_groups[k] = set(i for i, rt in enumerate(self.combined_room_types) if get_room_type(n) in rt) + dist2entrance = self.compute_dist2entrance(neighbours, names) + centroids = {k: np.array(s.centroid.coords[0]) for k, s in segments.items()} + open_cutters, door_cutters = defaultdict(dict), defaultdict(dict) + for k, ses in shared_edges.items(): + for l, se in ses.items(): + if l not in neighbours[k] or k >= l: + continue + if len(name_groups[k].intersection(name_groups[l])) > 0 and self.enable_open: + open_cutters[k][l] = open_cutters[l][k] = self.make_open_cutter(se) + else: + direction = (centroids[k] - centroids[l]) * ( + 1 if dist2entrance[k] > dist2entrance[l] else -1) + door_cutters[k][l] = door_cutters[l][k] = self.make_door_cutter(se, direction) + return open_cutters, door_cutters + + def compute_dist2entrance(self, neighbours, names): + root = self.get_entrance(names) + if root is None: + root = self.get_staircase(names) + queue = deque([root]) + dist2living_room = {root: 0} + while len(queue) > 0: + node = queue.popleft() + for n in neighbours[node]: + if n not in dist2living_room: + dist2living_room[n] = dist2living_room[node] + 1 + queue.append(n) + return dist2living_room + + def make_exterior_cutters(self, exterior_edges, names): + cutters = defaultdict(list) + entrance = self.get_entrance(names) + + for k, mls in exterior_edges.items(): + + room_type = get_room_type(names[k]) + pano_chance = self.panoramic_room_types.get(room_type, 0) + is_panoramic = uniform() < pano_chance + + lss = [] + for ls in mls.geoms: + coords = ls.coords[:] + lss.extend(list(zip(coords[:-1], coords[1:]))) + np.random.shuffle(lss) + if k == entrance: + ls = lss.pop() + cutter = self.make_entrance_cutter(ls) + cutters[k].append(cutter) + for ls in lss: + coords = LineString(ls).segmentize(MAX_WINDOW_LENGTH).coords[:] + for seg in zip(coords[:-1], coords[1:]): + length = np.linalg.norm([seg[1][1] - seg[0][1], seg[1][0] - seg[0][0]]) + if length >= DOOR_WIDTH + WALL_THICKNESS and uniform() < WINDOW_ROOM_TYPES[ + get_room_type(names[k])]: + cutter = self.make_window_cutter(seg, is_panoramic) + cutters[k].append(cutter) + return cutters + + def make_staircase_cutters(self, staircase, names): + cutters = defaultdict(list) + if self.level > 0: + for k, name in names.items(): + if get_room_type(name) == RoomType.Staircase: + with np.errstate(invalid="ignore"): + cutter = polygon2obj(buffer(staircase, -WALL_THICKNESS / 2)) + butil.modify_mesh(cutter, 'SOLIDIFY', thickness=WALL_THICKNESS * 1.2, offset=0) + self.tag(cutter) + cutter.name = 'staircase_cutter' + cutters[k].append(cutter) + return cutters + + def make_door_cutter(self, es, direction): + lengths = [ls.length for ls in es.geoms] + (x, y), (x_, y_) = es.geoms[np.argmax(lengths)].coords + + cutter = new_cube() + vertical = np.abs(x - x_) < .1 + cutter.scale = DOOR_WIDTH / 2 * (1 - _eps), DOOR_WIDTH, DOOR_SIZE / 2 + + butil.apply_transform(cutter, True) + if vertical: + y = uniform(min(y, y_) + DOOR_MARGIN, max(y, y_) - DOOR_MARGIN) + z_rot = -np.pi / 2 if direction[0] > 0 else np.pi / 2 + else: + x = uniform(min(x, x_) + DOOR_MARGIN, max(x, x_) - DOOR_MARGIN) + z_rot = 0 if direction[-1] > 0 else np.pi + cutter.location = x, y, DOOR_SIZE / 2 + WALL_THICKNESS / 2 + _eps + cutter.rotation_euler[-1] = z_rot + tagging.tag_object(cutter, t.Semantics.Door) + self.tag(cutter) + cutter.name = t.Semantics.Door.value + return cutter + + def make_entrance_cutter(self, ls): + (x, y), (x_, y_) = ls + cutter = new_cube() + length = np.linalg.norm([y_ - y, x_ - x]) + d = (DOOR_WIDTH + WALL_THICKNESS) / 2 / length + lam = uniform(d, 1 - d) + cutter.scale = DOOR_WIDTH / 2, DOOR_WIDTH / 2, DOOR_SIZE / 2 + butil.apply_transform(cutter, True) + cutter.location = lam * x + (1 - lam) * x_, lam * y + ( + 1 - lam) * y_, DOOR_SIZE / 2 + WALL_THICKNESS / 2 + _eps + cutter.rotation_euler = 0, 0, np.arctan2(y_ - y, x_ - x) + self.tag(cutter) + tagging.tag_object(cutter, t.Semantics.Entrance) + cutter.name = t.Semantics.Entrance.value + return cutter + + def make_window_cutter(self, ls, is_panoramic): + (x, y), (x_, y_) = ls + length = np.linalg.norm([y_ - y, x_ - x]) + if is_panoramic: + x_scale = length / 2 - WALL_THICKNESS / 2 + lam = 1 / 2 + z_scale = (WALL_HEIGHT - WALL_THICKNESS) / 2 + z_loc = z_scale + WALL_THICKNESS / 2 + else: + x_scale = uniform(DOOR_WIDTH / 2, length / 2 - WALL_THICKNESS / 2) + m = (x_scale + WALL_THICKNESS / 2) / length + lam = uniform(m, 1 - m) + z_scale = WINDOW_SIZE / 2 + z_loc = z_scale + WINDOW_HEIGHT + WALL_THICKNESS / 2 + cutter = new_cube() + cutter.scale = x_scale, WALL_THICKNESS, z_scale + butil.apply_transform(cutter) + cutter.location = lam * x + (1 - lam) * x_, lam * y + (1 - lam) * y_, z_loc + cutter.rotation_euler = 0, 0, np.arctan2(y - y_, x - x_) + self.tag(cutter) + tagging.tag_object(cutter, t.Semantics.Window) + cutter.name = t.Semantics.Window.value + return cutter + + def make_open_cutter(self, es): + es = remove_repeated_points(simplify(es, SIMPLIFY_THRESH).normalize(), WELD_THRESH) + es = linemerge(es) if not isinstance(es, LineString) else es + es = [es] if isinstance(es, LineString) else es.geoms + lines = [] + for ls in es: + coords = np.array(ls.coords[:]) + start, end = 0, -1 + while np.linalg.norm(coords[start] - coords[start + 1]) < SEGMENT_MARGIN: + start += 1 + while np.linalg.norm(coords[end] - coords[end - 1]) < SEGMENT_MARGIN: + end -= 1 + coords = coords[start:end + 1] if end < -1 else coords[start:] + if len(coords) < 2: + continue + coords[0] = line_interpolate_point(LineString(coords[0: 2]), WALL_THICKNESS / 2 + _eps).coords[0] + coords[-1] = line_interpolate_point(LineString(coords[-1:-3:-1]), WALL_THICKNESS / 2 + _eps).coords[ + 0] + line = new_line(len(coords) - 1) + write_co(line, np.concatenate([coords, np.zeros((len(coords), 1))], -1)) + lines.append(line) + cutter = join_objects(lines) + butil.modify_mesh(cutter, 'WELD', merge_threshold=WELD_THRESH) + butil.select_none() + + with butil.ViewportMode(cutter, 'EDIT'): + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.extrude_edges_move( + TRANSFORM_OT_translate={'value': (0, 0, WALL_HEIGHT - WALL_THICKNESS - 2 * _eps) + }) + bpy.ops.mesh.select_mode(type='FACE') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.normals_make_consistent(inside=False) + + cutter.location[-1] += WALL_THICKNESS / 2 + _eps + butil.apply_transform(cutter, True) + butil.modify_mesh(cutter, 'SOLIDIFY', thickness=WALL_THICKNESS * 3, offset=0, use_even_offset=True) + self.tag(cutter) + tagging.tag_object(cutter, t.Semantics.Open) + cutter.name = t.Semantics.Open.value + return cutter + + @staticmethod + def tag(obj, visible=True): + center = read_center(obj) + obj.location + ceiling = center[:, -1] > WALL_HEIGHT - WALL_THICKNESS / 2 - .1 + floor = center[:, -1] < WALL_THICKNESS / 2 + .1 + wall = ~(ceiling | floor) + write_attr_data(obj, f'{PREFIX}{t.Subpart.Ceiling.value}', ceiling, 'INT', 'FACE') + write_attr_data(obj, f'{PREFIX}{t.Subpart.SupportSurface.value}', floor, 'INT', 'FACE') + write_attr_data(obj, f'{PREFIX}{t.Subpart.Wall.value}', wall, 'INT', 'FACE') + write_attr_data(obj, 'segment_id', np.arange(len(center)), 'INT', 'FACE') + write_attr_data(obj, f'{PREFIX}{t.Subpart.Visible.value}', + np.ones_like(ceiling) if visible else np.zeros_like(ceiling), 'INT', 'FACE') + write_attr_data(obj, f'{PREFIX}{t.Subpart.Invisible.value}', + np.zeros_like(ceiling) if visible else np.ones_like(ceiling), 'INT', 'FACE') + parse_scene.preprocess_obj(obj) + tagging.tag_canonical_surfaces(obj) diff --git a/infinigen/core/constraints/example_solver/room/solver.py b/infinigen/core/constraints/example_solver/room/solver.py new file mode 100644 index 000000000..c431f933a --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/solver.py @@ -0,0 +1,285 @@ +# 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: +# - Lingjie Mei: primary author +# - Karhan Kayan: fix constants + +from copy import deepcopy +from dataclasses import dataclass +from typing import List, Optional + +import gin +import numpy as np +from numpy.random import uniform +from shapely import LineString, Polygon, union +from shapely.ops import shared_paths + +from infinigen.core.constraints.example_solver.room.types import RoomType, get_room_type +from infinigen.core.constraints.example_solver.room.configs import EXTERIOR_CONNECTED_ROOM_TYPES +from infinigen.core.constraints.example_solver.room.constants import SEGMENT_MARGIN +import infinigen.core.constraints.example_solver.room.constants as constants +from infinigen.core.constraints.example_solver.room.utils import compute_neighbours, cut_polygon_by_line, is_valid_polygon, linear_extend_x, \ + linear_extend_y, canonicalize, update_exterior_edges, update_shared_edges, update_staircase_occupancies + + +@dataclass +class RoomSolverMsg: + status: str + index_changed: Optional[List[int]] = None + + @property + def is_success(self): + return self.status == 'success' + + +@gin.configurable(denylist=['contour', 'graph']) +class BlueprintSolver: + def __init__(self, contour, graph, exterior_connected_room_types=EXTERIOR_CONNECTED_ROOM_TYPES, + max_stride=1, staircase_occupancy_thresh=.75): + self.contour = contour + x, y = self.contour.boundary.xy + self.x_min, self.x_max = np.min(x), np.max(x) + self.y_min, self.y_max = np.min(y), np.max(y) + self.graph = graph + self.staircase_occupancy_thresh = staircase_occupancy_thresh + self.exterior_connected_room_types = exterior_connected_room_types + self.exterior_connected_rooms = set( + i for i, r in enumerate(self.graph.rooms) if get_room_type(r) in self.exterior_connected_room_types) + if self.graph.entrance is not None: + self.exterior_connected_rooms.add(self.graph.entrance) + self.staircase_rooms = set(self.graph[RoomType.Staircase]) + self.max_stride = max_stride + + def find_assignment(self, info): + assignment = [0] * len(self.graph.rooms) + neighbours_all = info['neighbours_all'] + exterior_neighbours = info['exterior_neighbours'] + staircase_occupancies = info['staircase_occupancies'] + if info['staircase'] is not None: + staircase_candidates = list( + (k for k, v in staircase_occupancies.items() if v > self.staircase_occupancy_thresh)) + if len(staircase_candidates) == 0: + return None + else: + staircase_candidates = [] + unassigned = set(neighbours_all.keys()) + + def assign_(i): + if i == len(self.graph.rooms): + return assignment + if i in self.staircase_rooms: + candidates = unassigned.intersection(staircase_candidates) + elif i in self.exterior_connected_rooms: + candidates = unassigned.intersection(exterior_neighbours) + else: + candidates = unassigned.copy() + n_unassigned = len(list(j for j in self.graph.neighbours[i] if j > i)) + assigned_neighbours = set(assignment[j] for j in self.graph.neighbours[i] if j < i) + for n in candidates: + if assigned_neighbours.issubset(neighbours_all[n]): + if len(neighbours_all[n].intersection(unassigned)) >= n_unassigned: + assignment[i] = n + unassigned.remove(n) + r = assign_(i + 1) + if r is not None: + return r + unassigned.add(n) + + return assign_(0) + + def satisfies_constraints(self, assignment, info): + neighbours_all = info['neighbours_all'] + exterior_neighbours = info['exterior_neighbours'] + staircase_occupancies = info['staircase_occupancies'] + for k, ns in enumerate(self.graph.neighbours): + for n in ns: + if assignment[k] not in neighbours_all[assignment[n]]: + return RoomSolverMsg('neighbours unsatisfied', [k, n]) + if k in self.exterior_connected_rooms: + if assignment[k] not in exterior_neighbours: + return RoomSolverMsg('exterior neighbours unsatisfied', [k]) + if get_room_type(self.graph.rooms[k]) == RoomType.Staircase: + if staircase_occupancies[assignment[k]] < self.staircase_occupancy_thresh: + return RoomSolverMsg('staircase occupancy unsatisfied', [k]) + return RoomSolverMsg('success') + + def perturb_solution(self, assignment, info): + k = np.random.choice(list(info['segments'].keys())) + while True: + info_ = deepcopy(info) + assignment_ = deepcopy(assignment) + try: + rn = uniform() + if rn < 1 / 3: + resp = self.extrude_room_out(assignment, info, k) + elif rn < 2 / 3: + resp = self.extrude_room_in(assignment, info, k) + else: + resp = self.swap_room(assignment, info, k) + except: + info, assignment = info_, assignment_ + else: + break + if not resp.is_success: + return resp + for c in resp.index_changed: + if not is_valid_polygon(info['segments'][c]): + return RoomSolverMsg('invalid segment', [c]) + try: + for c in resp.index_changed: + update_shared_edges(info['segments'], info['shared_edges'], c) + update_exterior_edges(info['segments'], info['shared_edges'], info['exterior_edges'], c) + update_staircase_occupancies(info['segments'], info['staircase'], info['staircase_occupancies'], + c) + except: + return RoomSolverMsg('Exception') + info['neighbours_all'] = {k: set(compute_neighbours(se, SEGMENT_MARGIN)) for k, se in + info['shared_edges'].items()} + info['exterior_neighbours'] = set(compute_neighbours(info['exterior_edges'], SEGMENT_MARGIN)) + for k, s in info['segments'].items(): + x, y = np.array(s.boundary.coords).T + if np.any((x < -1.) | (y < -1.) | (x > 40.) | (y > 40.)): + return RoomSolverMsg('OOB') + satisfies = self.satisfies_constraints(assignment, info) + if not satisfies.is_success: + return satisfies + return resp + + def extrude_room(self, i, info, out=True): + segments = info['segments'] + coords = canonicalize(segments[i]).boundary.coords[:] + indices = [] + for k in range(len(coords) - 1): + (x, y), (x_, y_) = coords[k:k + 2] + if np.abs(x - x_) < 1e-2 and self.x_min < x < self.x_max: + indices.append(k) + elif np.abs(y - y_) < 1e-2 and self.y_min < y < self.y_max: + indices.append(k) + k = np.random.choice(indices) + (x, y), (x_, y_) = coords[k:k + 2] + is_vertical = np.abs(x - x_) < 1e-2 + line = LineString(coords[k:k + 2]) + mod = len(coords) - 1 + stride = constants.UNIT * (np.random.randint(self.max_stride) + 1) + if is_vertical: + new_x = x + stride if (y_ < y) ^ out else x - stride + new_first = new_x, linear_extend_x(coords[(k - 1) % mod], coords[k], new_x) + new_second = new_x, linear_extend_x(coords[(k + 2) % mod], coords[k + 1], new_x) + else: + new_y = y + stride if (x_ > x) ^ out else y - stride + new_first = linear_extend_y(coords[(k - 1) % mod], coords[k], new_y), new_y + new_second = linear_extend_y(coords[(k + 2) % mod], coords[k + 1], new_y), new_y + coords[k % mod] = new_first + coords[(k + 1) % mod] = new_second + coords[-1] = coords[0] + s = canonicalize(Polygon(LineString(coords))) + return s, line, is_vertical + + def extrude_room_out(self, assignment, info, i): + segments, shared_edges = map(info.get, ['segments', 'info']) + s, _, _ = self.extrude_room(i, info, True) + if not is_valid_polygon(s): + return RoomSolverMsg('extrude_room_out_invalid', [i]) + cutter = s.difference(segments[i]) + if not is_valid_polygon(cutter): + return RoomSolverMsg('extrude_room_out_invalid', [i]) + cutter = canonicalize(cutter) + shared = list(k for k in info['shared_edges'][i].keys() if segments[k].intersection(cutter).area > .1) + index_changed = [i, *shared] + total_pre_area = sum([segments[i].area for i in index_changed]) + for l in shared: + segments[l] = canonicalize(segments[l].difference(cutter)) + segments[i] = s + total_post_area = sum([segments[i].area for i in index_changed]) + if np.abs(total_pre_area - total_post_area) < .1: + return RoomSolverMsg('success', index_changed) + else: + return RoomSolverMsg('extrude_room_out_oob', index_changed) + + def extrude_room_in(self, assignment, info, i): + segments, shared_edges = map(info.get, ['segments', 'shared_edges']) + s, line, is_vertical = self.extrude_room(i, info, False) + if not is_valid_polygon(s): + return RoomSolverMsg('extrude_room_in_invalid', [i]) + cutter = segments[i].difference(s) + if not is_valid_polygon(cutter): + return RoomSolverMsg('extrude_room_in_invalid', [i]) + cutter = canonicalize(cutter) + shared = {} + for k in shared_edges[i].keys(): + with np.errstate(invalid="ignore"): + forward, backward = shared_paths(segments[k].boundary, line).geoms + if forward.length > 0: + shared[k] = forward.geoms[0] + elif backward.length > 0: + shared[k] = backward.geoms[0] + index_changed = [i, *shared.keys()] + ranges = [] + for k, ls in shared.items(): + if is_vertical: + y0, y1 = ls.xy[1] + ranges.append((min(y0, y1), max(y0, y1))) + else: + x0, x1 = ls.xy[0] + ranges.append((min(x0, x1), max(x0, x1))) + indices = np.argsort([(m + mm) / 2 for m, mm in ranges]) + affected = [list(shared.keys())[_] for _ in indices] + cuts = [ranges[_][0] for _ in indices[1:]] + if is_vertical: + lss = [LineString([(-1, c), (100, c)]) for c in cuts] + else: + lss = [LineString([(c, -1), (c, 100)]) for c in cuts] + polygons = cut_polygon_by_line(cutter, *lss) + polygons.sort(key=lambda p: p.centroid.coords[0][1 if is_vertical else 0]) + total_pre_area = sum([segments[i].area for i in index_changed]) + for a, p in zip(affected, polygons): + segments[a] = canonicalize(union(segments[a], p)) + segments[i] = s + total_post_area = sum([segments[i].area for i in index_changed]) + if np.abs(total_pre_area - total_post_area) < .1: + return RoomSolverMsg('success', index_changed) + else: + return RoomSolverMsg('extrude_room_in_oob', index_changed) + + def swap_room(self, assignment, info, i): + j = np.random.choice(list(info['neighbours_all'][i])) + j_ = assignment.index(j) + i_ = assignment.index(i) + assignment[i_], assignment[j_] = j, i + return RoomSolverMsg('success', [i, j]) + + +class BlueprintStaircaseSolver: + def __init__(self, contours, max_stride=1): + self.contours = contours + self.max_stride = max_stride + self.n_trials = 100 + + def perturb_solution(self, assignments, infos): + resp = self.move_staircase(infos) + if not resp.is_success: + return resp + for info in infos: + for k in info['segments']: + update_staircase_occupancies(info['segments'], info['staircase'], info['staircase_occupancies'], + k) + return resp + + def move_staircase(self, infos): + staircase = infos[0]['staircase'] + if staircase is None: + return RoomSolverMsg('success') + directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] + for i in range(self.n_trials): + stride = constants.UNIT * (np.random.randint(self.max_stride) + 1) + x, y = directions[np.random.randint(4)] + coords = list((x_ + x * stride, y_ + y * stride) for x_, y_ in staircase.boundary.coords[:]) + p = Polygon(LineString(coords)) + if self.contours[-1].contains(p): + for info in infos: + info['staircase'] = p + return RoomSolverMsg('success') + else: + return RoomSolverMsg('invalid staircase') diff --git a/infinigen/core/constraints/example_solver/room/types.py b/infinigen/core/constraints/example_solver/room/types.py new file mode 100644 index 000000000..ec9eba04c --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/types.py @@ -0,0 +1,80 @@ +# 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: Lingjie Mei +from typing import List + +import networkx as nx + + +class RoomType: + Kitchen = "kitchen" + Bedroom = 'bedroom' + LivingRoom = 'living-room' + Closet = 'closet' + Hallway = 'hallway' + Bathroom = 'bathroom' + Garage = 'garage' + Balcony = 'balcony' + DiningRoom = 'dining-room' + Utility = 'utility' + Staircase = 'staircase' + + +def get_room_type(name): + return name.split('_')[0] + + +def get_room_level(name): + return int(name.split('-')[-1]) + + +class RoomGraph: + def __init__(self, children: List[List[int]], rooms, entrance=None): + self.neighbours = [[] for _ in children] + for i, cs in enumerate(children): + for c in cs: + self.neighbours[i].append(c) + self.neighbours[c].append(i) + self.rooms = rooms + self.entrance = entrance + + @property + def is_planar(self): + try: + nx.planar_layout(self.to_nx) + return True + except nx.NetworkXException: + return False + + @property + def to_nx(self): + g = nx.Graph() + g.add_nodes_from(self.rooms) + for k in range(len(self.rooms)): + for l in self.neighbours[k]: + g.add_edge(self.rooms[k], self.rooms[l]) + return g + + @property + def cycle_basis(self): + return nx.cycle_basis(self.to_nx) + + def __getitem__(self, item): + return [i for i, r in enumerate(self.rooms) if get_room_type(r) == item] + + def __len__(self): + return len(self.rooms) + + def __str__(self): + return {'neighbours': self.neighbours, 'rooms': self.rooms, 'entrance': self.entrance} + + +def make_demo_tree(): + children = [[1, 2], [], [3, 4], [5, 6], [7], [8, 9], [10, 11], [], [], [12], [], [13], [], [14], []] + rooms = ['hallway_0', 'closet_0', 'kitchen_0', 'dining-room_0', 'utility_0', 'hallway_1', 'living-room_0', + 'utility_1', 'bathroom_0', 'bedroom_0', 'balcony_0', 'bedroom_1', 'closet_1', 'bathroom_1', 'closet_2'] + return RoomGraph(children, rooms, 0) + + +DEMO_GRAPH = make_demo_tree() diff --git a/infinigen/core/constraints/example_solver/room/utils.py b/infinigen/core/constraints/example_solver/room/utils.py new file mode 100644 index 000000000..9353c9fb5 --- /dev/null +++ b/infinigen/core/constraints/example_solver/room/utils.py @@ -0,0 +1,149 @@ +# 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: +# - Lingjie Mei: primary author +# - Karhan Kayan: fix constants + +from collections import defaultdict + +import bpy +import numpy as np +import shapely +from shapely import LineString, MultiLineString, Polygon, remove_repeated_points, simplify +from shapely.ops import linemerge, orient, polygonize, shared_paths, unary_union + +import infinigen.core.constraints.example_solver.room.constants as constants +from infinigen.assets.utils.decorate import write_co +from infinigen.assets.utils.object import new_circle +from infinigen.assets.utils.shapes import simplify_polygon +from infinigen.core.util import blender as butil + +SIMPLIFY_THRESH = 1e-6 +ANGLE_SIMPLIFY_THRESH = .2 +WELD_THRESH = .01 + + +def is_valid_polygon(p): + if isinstance(p, Polygon) and p.area > 0 and p.is_valid: + if len(p.interiors) == 0: + return True + return False + + +def canonicalize(p): + p = p.buffer(0) + try: + while True: + p_ = shapely.force_2d(simplify_polygon(p)) + l = len(p.boundary.coords) + if p.area == 0: + raise NotImplementedError('Polygon empty.') + p = orient(p_) + coords = np.array(p.boundary.coords[:]) + rounded = np.round(coords / constants.UNIT) * constants.UNIT + coords = np.where(np.all(np.abs(coords - rounded) < 1e-3, -1)[:, np.newaxis], rounded, coords) + diff = coords[1:] - coords[:-1] + diff = diff / (np.linalg.norm(diff, axis=-1, keepdims=True) + 1e-6) + product = (diff[[-1] + list(range(len(diff) - 1))] * diff).sum(-1) + valid_indices = list(range(len(coords) - 1)) + invalid_indices = np.nonzero((product < -.8) | (product > 1 - 1e-6))[0].tolist() + if len(invalid_indices) > 0: + i = invalid_indices[len(invalid_indices) // 2] + valid_indices.remove(i) + p = shapely.Polygon(coords[valid_indices + [valid_indices[0]]]) + if len(p.exterior.coords) == l: + break + if not is_valid_polygon(p): + raise NotImplementedError('Invalid polygon') + return p + except AttributeError: + raise NotImplementedError('Invalid multi polygon') + + +def unit_cast(x, unit=None): + if unit is None: + unit = constants.UNIT + return int(x / unit) * unit + + +def abs_distance(x, y): + z = [0] * 4 + z[0 if y[0] > x[0] else 1] = np.abs(y[0] - x[0]) + z[2 if y[1] > x[1] else 3] = np.abs(y[1] - x[1]) + return np.array(z) + + +def update_exterior_edges(segments, shared_edges, exterior_edges=None, i=None): + if exterior_edges is None: exterior_edges = {} + for k, s in segments.items(): + if i is None or k == i: + l = s.boundary + for ls in shared_edges[k].values(): + l = l.difference(ls) + if l.length > 0: + exterior_edges[k] = MultiLineString([l]) if isinstance(l, LineString) else l + elif k in exterior_edges: + exterior_edges.pop(k) + return exterior_edges + + +def update_shared_edges(segments, shared_edges=None, i=None): + if shared_edges is None: shared_edges = defaultdict(dict) + for k, s in segments.items(): + for l, t in segments.items(): + if k != l and (i is None or k == i or l == i): + with np.errstate(invalid="ignore"): + forward, backward = shared_paths(s.boundary, t.boundary).geoms + if forward.length > 0: + shared_edges[k][l] = forward + elif backward.length > 0: + shared_edges[k][l] = backward + elif l in shared_edges[k]: + shared_edges[k].pop(l) + return shared_edges + + +def update_staircase_occupancies(segments, staircase, staircase_occupancies=None, i=None): + if staircase is None: return None + if staircase_occupancies is None: staircase_occupancies = defaultdict(dict) + for k, s in segments.items(): + if i is None or k == i: + staircase_occupancies[k] = s.intersection(staircase).area / staircase.area + return staircase_occupancies + + +def compute_neighbours(ses, margin): + return list(l for l, se in ses.items() if any(ls.length >= margin for ls in se.geoms)) + + +def linear_extend_x(base, target, new_x): + return target[1] + (new_x - target[0]) * (base[1] - target[1]) / (base[0] - target[0]) + + +def linear_extend_y(base, target, new_y): + return target[0] + (new_y - target[1]) * (base[0] - target[0]) / (base[1] - target[1]) + + +def cut_polygon_by_line(polygon, *args): + merged = linemerge([polygon.boundary, *args]) + borders = unary_union(merged) + polygons = polygonize(borders) + return list(polygons) + + +def polygon2obj(p, reversed=False): + x, y = orient(p).exterior.xy + obj = new_circle(vertices=len(x) - 1) + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.edge_face_add() + if reversed: + bpy.ops.mesh.flip_normals() + write_co(obj, np.stack([x[:-1], y[:-1], np.zeros_like(x[:-1])], -1)) + return obj + + +def buffer(p, distance): + with np.errstate(invalid="ignore"): + return remove_repeated_points(simplify(p.buffer(distance, join_style='mitre'), SIMPLIFY_THRESH)) diff --git a/infinigen/core/constraints/example_solver/solve.py b/infinigen/core/constraints/example_solver/solve.py new file mode 100644 index 000000000..4a4c6e8ef --- /dev/null +++ b/infinigen/core/constraints/example_solver/solve.py @@ -0,0 +1,242 @@ +# 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 logging +from pathlib import Path +import copy + +import bpy +import numpy as np +from tqdm import trange, tqdm +import gin +from infinigen.core import surface +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r, + usage_lookup, + evaluator, + checks +) +from infinigen.core.constraints.example_solver.geometry import parse_scene, planes + +from .room import RoomSolver, MultistoryRoomSolver + +from infinigen.core.constraints.example_solver.state_def import State +from infinigen.core.constraints.constraint_language.util import delete_obj + +from infinigen.core.constraints.example_solver import ( + propose_continous, + propose_discrete, + greedy, +) +from infinigen.core.constraints.evaluator import domain_contains + +from infinigen.core.placement.placement import parse_asset_name +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t + +from .annealing import SimulatedAnnealingSolver + +logger = logging.getLogger(__name__) + +def map_range(x, xmin, xmax, ymin, ymax, exp=1): + + if x < xmin: + return ymin + if x > xmax: + return ymax + + t = (x - xmin) / (xmax - xmin) + return ymin + (ymax - ymin) * t ** exp + +@gin.register +class LinearDecaySchedule: + + def __init__(self, start, end, pct_duration): + self.start = start + self.end = end + self.pct_duration = pct_duration + + def __call__(self, t): + return map_range(t, 0, self.pct_duration, self.start, self.end) + +@gin.configurable +class Solver: + + def __init__( + self, + output_folder: Path, + multistory: bool = False, + restrict_moves: list = None + ): + + """ Initialize the solver + + Parameters + ---------- + output_folder : Path + The folder to save output plots to + print_report_freq : int + How often to print loss reports + multistory : bool + Whether to use the multistory room solver + constraints_greedy_unsatisfied : str | None + What do we do if relevant constraints are unsatisfied at the end of a greedy stage? + Options are 'warn` or `abort` or None + + """ + + self.output_folder = output_folder + + self.optim = SimulatedAnnealingSolver( + output_folder=output_folder, + ) + self.room_solver_fn = MultistoryRoomSolver if multistory else RoomSolver + self.state: State = None + self.all_roomtypes = None + self.dimensions = None + + self.moves = self._configure_move_weights(restrict_moves) + + + def _configure_move_weights(self, restrict_moves): + + schedules = { + 'addition': ( + propose_discrete.propose_addition, + LinearDecaySchedule(6, 0.1, 0.9), + ), + 'deletion': ( + propose_discrete.propose_deletion, + LinearDecaySchedule(2, 0.0, 0.5), + ), + 'plane_change': ( + propose_discrete.propose_relation_plane_change, + LinearDecaySchedule(2, 0.1, 1), + ), + 'resample_asset': ( + propose_discrete.propose_resample, + LinearDecaySchedule(1, 0.1, 0.7), + ), + 'reinit_pose': ( + propose_continous.propose_reinit_pose, + LinearDecaySchedule(1, 0.5, 1), + ), + 'translate': ( + propose_continous.propose_translate, + 1 + ), + 'rotate': ( + propose_continous.propose_rotate, + 0.5 + ), + } + + if restrict_moves is not None: + schedules = {k: v for k, v in schedules.items() if k in restrict_moves} + logger.info(f'Restricting {self.__class__.__name__} moves to {list(schedules.keys())}') + + return schedules + + @gin.configurable + def choose_move_type( + self, + it: int, + max_it: int, + ): + t = it / max_it + names, confs = zip(*self.moves.items()) + funcs, scheds = zip(*confs) + weights = np.array([s if isinstance(s, (float, int)) else s(t) for s in scheds]) + return np.random.choice(funcs, p=weights/weights.sum()) + + def solve_rooms(self, scene_seed, consgraph: cl.Problem, filter: r.Domain): + self.state, self.all_roomtypes, self.dimensions = self.room_solver_fn(scene_seed).solve() + return self.state + + @gin.configurable + def solve_objects( + self, + consgraph: cl.Problem, + filter_domain: r.Domain, + var_assignments: dict[str, str], + n_steps: int, + desc: str, + abort_unsatisfied: bool = False, + print_bounds: bool = False, + ): + + filter_domain = copy.deepcopy(filter_domain) + + desc_full = (desc, *var_assignments.values()) + + dom_assignments = {k: r.Domain(self.state.objs[objkey].tags) for k, objkey in var_assignments.items()} + filter_domain = r.substitute_all(filter_domain, dom_assignments) + + if not r.domain_finalized(filter_domain): + raise ValueError(f'Cannot solve {desc_full=} with non-finalized domain {filter_domain}') + + orig_bounds = r.constraint_bounds(consgraph) + bounds = propose_discrete.preproc_bounds( + orig_bounds, + self.state, + filter_domain, + print_bounds=print_bounds + ) + + if len(bounds) == 0: + logger.info(f'No objects to be added for {desc_full=}, skipping') + return self.state + + active_count = greedy.update_active_flags(self.state, var_assignments) + + n_start = len(self.state.objs) + logger.info( + f"Greedily solve {desc_full} - stage has {len(bounds)}/{len(orig_bounds)} bounds, " + f"{active_count=}/{len(self.state.objs)} objs" + ) + + self.optim.reset(max_iters=n_steps) + ra = trange(n_steps) if self.optim.print_report_freq == 0 else range(n_steps) + for j in ra: + move_gen = self.choose_move_type(j, n_steps) + self.optim.step(consgraph, self.state, move_gen, filter_domain) + self.optim.save_stats(self.output_folder/f'optim_{desc}.csv') + + logger.info( + f"Finished solving {desc_full}, added {len(self.state.objs) - n_start} " + f"objects, loss={self.optim.curr_result.loss():.4f} viol={self.optim.curr_result.viol_count()}" + ) + + logger.info(self.optim.curr_result.to_df()) + + violations = { + k: v for k, v in self.optim.curr_result.violations.items() + if v > 0 + } + + if len(violations): + msg = f'Solver has failed to satisfy constraints for stage {desc_full}. {violations=}.' + if abort_unsatisfied: + butil.save_blend(self.output_folder/f'abort_{desc}.blend') + raise ValueError(msg) + else: + msg += ' Continuing anyway, override `solve_objects.abort_unsatisfied=True` via gin to crash instead.' + logger.warning(msg) + + # re-enable everything so the blender scene populates / displays correctly etc + for k, v in self.state.objs.items(): + greedy.set_active(self.state, k, True) + + return self.state + + def get_bpy_objects(self, domain: r.Domain) -> list[bpy.types.Object]: + objkeys = domain_contains.objkeys_in_dom(domain, self.state) + return [ + self.state.objs[k].obj for k in objkeys + ] + diff --git a/infinigen/core/constraints/example_solver/state_def.py b/infinigen/core/constraints/example_solver/state_def.py new file mode 100644 index 000000000..bc325d6d8 --- /dev/null +++ b/infinigen/core/constraints/example_solver/state_def.py @@ -0,0 +1,207 @@ +# 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: state, print, to_json +# - Karhan Kayan: add dof / trimesh + +from __future__ import annotations +from dataclasses import dataclass, field +import typing +import pickle +import copy +import importlib +import logging +from collections import OrderedDict +import json +from pathlib import Path +import enum +from collections.abc import Collection + +import numpy as np +import bpy +import shapely +import trimesh +from infinigen.core.constraints.example_solver.geometry.planes import Planes + +from infinigen.core.placement.factory import AssetFactory +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r +) +from .geometry import parse_scene +import trimesh +from infinigen.core import tags as t + +logger = logging.getLogger(__name__) + +@dataclass +class RelationState: + relation: cl.Relation + target_name: str + child_plane_idx: int = None + parent_plane_idx: int = None + +@dataclass +class ObjectState: + + obj: bpy.types.Object + generator: typing.Optional[AssetFactory] = None + tags: set = field(default_factory=set) + relations: list[RelationState] = field(default_factory=list) + + dof_matrix_translation: np.array = None + dof_rotation_axis: np.array = None + contour : shapely.Geometry = None + _pose_affects_score = None + + fcl_obj = None + col_obj = None + + # store whether this object is active for the current greedy stage + # inactive objects arent returned by scene() and arent accessible through blender (for perf) + # updated by greedy.update_active_flags() + active: bool = True + + def __post_init__(self): + assert not t.contradiction(self.tags) + assert not any(isinstance(r.relation, cl.NegatedRelation) for r in self.relations), self.relations + + def __repr__(self): + obj = self.obj + tags = self.tags + relations = self.relations + + name = obj.name if obj is not None else None + return f"{self.__class__.__name__}(obj.name={name}, {tags=}, {relations=})" + +@dataclass +class State: + + objs: OrderedDict[str, ObjectState] + + trimesh_scene: trimesh.Scene = None + bvh_cache : dict = field(default_factory=dict) + planes: Planes = None + + def print(self): + + print(f"State ({len(self.objs)} objs)") + order = sorted( + self.objs.keys(), + key=lambda s: s.split('_')[-1] + ) + for k in order: + v = self.objs[k] + relations = ', '.join( + f'{r.relation.__class__.__name__}({r.target_name})' + for r in v.relations + ) + semantics = {tg for tg in t.decompose_tags(v.tags)[0] if not isinstance(tg, t.SpecificObject)} + print(f" {v.obj.name} {semantics} [{relations}]") + + def to_json(self, path: Path): + + JSON_SUPPORTED_TYPES = ( + int, float, str, bool, list, dict + ) + + def preprocess_field(x): + match x: + case np.ndarray(): + return x.tolist() + case np.int64(): + return x.item() + case t.Tag(): + return str(x) + case bpy.types.Object(): + return x.name + case enum.Enum(): + return x.name + case type(): + return x.__module__ + '.' + x.__name__ + case set() | frozenset(): + return list(x) + case val if isinstance(val, JSON_SUPPORTED_TYPES): + return x + case AssetFactory(): + return repr(x) + case ObjectState() | RelationState(): + return x.__dict__ + case cl.Relation(): + res = x.__dict__ + res['relation_type'] = x.__class__.__name__ + return res + case _: + return "" + + data = { + 'objs': self.objs, + } + + with path.open('w') as f: + json.dump( + data, + f, + default=preprocess_field, + sort_keys=True, + indent=4, + check_circular=True + ) + + def __post_init__(self): + bpy_objs = [o.obj for o in self.objs.values() if o.obj is not None] + self.trimesh_scene = parse_scene.parse_scene(bpy_objs) + self.planes = Planes() + + def save(self, filename: str): + return + # serialize objs and python modules + for os in self.objs.values(): + os.obj = os.obj.name + if os.generator is not None: + path = os.generator.__module__ + '.' + os.generator.__name__ + os.generator = path + + with open(filename, 'wb') as file: + pickle.dump(self, file) + + for os in self.objs.values(): + os.obj = bpy.data.objects[os.obj] + + if os.generator is not None: + *mod, name = os.generator.split('.') + mod = importlib.import_module('.'.join(mod)) + os.generator = getattr(mod, name) + + @classmethod + def load(cls, filename: str): + with open(filename, 'rb') as file: + state = pickle.load(file) + + # all objs were serialized as strings, unpack them + for o in state.objs: + if o.obj not in bpy.data.objects: + raise ValueError( + f"While deserializing {filename}, found name {o.obj=} which " + "isnt present in current blend scene. Did you load the " + "correct blend before loading the state?" + ) + o.obj = bpy.data.objects[o.obj] + + +def state_from_dummy_scene(col: bpy.types.Collection) -> State: + + objs = {} + for obj in col.all_objects: + obj.rotation_mode = 'AXIS_ANGLE' + tags = {t.Semantics(c.name) for c in col.children if obj.name in c.objects} + tags.add(t.SpecificObject(obj.name)) + objs[obj.name] = ObjectState( + obj=obj, + generator=None, + tags=tags + ) + return State(objs=objs) + diff --git a/infinigen/core/constraints/reasoning/__init__.py b/infinigen/core/constraints/reasoning/__init__.py new file mode 100644 index 000000000..47a90d7c9 --- /dev/null +++ b/infinigen/core/constraints/reasoning/__init__.py @@ -0,0 +1,22 @@ +from .constraint_bounding import Bound, constraint_bounds +from .constraint_constancy import is_constant + +from .domain import ( + reldom_implies, + reldom_compatible, + reldom_intersection, + reldom_intersects, + reldom_satisfies, + + domain_finalized, +) +from .domain_substitute import ( + domain_tag_substitute, + substitute_all, +) +from .constraint_domain import ( + Domain, + constraint_domain, + FilterByDomain +) +from .expr_equal import expr_equal \ No newline at end of file diff --git a/infinigen/core/constraints/reasoning/constraint_bounding.py b/infinigen/core/constraints/reasoning/constraint_bounding.py new file mode 100644 index 000000000..a283ae853 --- /dev/null +++ b/infinigen/core/constraints/reasoning/constraint_bounding.py @@ -0,0 +1,210 @@ +# 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: primary author +# - David Yan: bounding for inequalities / expressions + +import operator +import typing +import copy +import dataclasses +from functools import partial +import logging + +import numpy as np + +from infinigen.core.constraints import constraint_language as cl + +from .domain import Domain +from .constraint_domain import constraint_domain +from .domain_substitute import domain_tag_substitute + +from .constraint_constancy import is_constant +from infinigen.core import tags as t + +logger = logging.getLogger(__name__) + +@dataclasses.dataclass +class Bound: + + domain: Domain = None + low: int = None + high: int = None + + _init_ops: typing.ClassVar = { + operator.eq, + operator.le, + operator.ge, + operator.lt, + operator.gt, + } + + @classmethod + def from_comparison(cls, opfunc, lhs, rhs): + + lhs = lhs() if is_constant(lhs) else None + rhs = rhs() if is_constant(rhs) else None + + if lhs is None == rhs is None: + raise ValueError(f'Attempted to create bound with neither side constant {lhs=} {rhs=}') + right_const = rhs is not None + val = rhs if right_const else lhs + + match (opfunc, right_const): + case operator.eq, _: + return cls(low=val, high=val) + case (operator.le, False) | (operator.ge, True): + return cls(low=val) + case (operator.le, True) | (operator.ge, False): + return cls(high=val) + case (operator.lt, True): + return cls(high=val - 1) + case (operator.gt, False): + return cls(high=val - 1) + case (operator.lt, False): + return cls(low=val + 1) + case (operator.gt, True): + return cls(low=val + 1) + case _: + raise ValueError(f'Unhandled case {opfunc=}, {right_const=}') + + def map(self, func, lhs=None, rhs=None): + + if lhs is None == rhs is None: + raise ValueError(f'Expected exactly one of {lhs=} {rhs=} to be provided') + + if lhs is not None: + return Bound( + low=func(lhs, self.low), + high=func(lhs, self.high) + ) + else: + return Bound( + low=func(self.low, rhs), + high=func(self.high, rhs) + ) + +int_inverse_op = { + operator.add: operator.sub, + operator.mul: operator.floordiv, +} +int_inverse_op.update({v: k for k, v in int_inverse_op.items()}) + +def _expression_map_bound_binop( + node: cl.ScalarOperatorExpression, + bound: Bound +) -> list[Bound]: + + lhs, rhs = node.operands + inv_func = int_inverse_op.get(node.func) + if inv_func is None: + return [] + + consts = is_constant(lhs), is_constant(rhs) + match consts: + case (False, False): + return [] + case (True, False): + return expression_map_bound(rhs, bound.map(inv_func, lhs=lhs())) + case (False, True): + return expression_map_bound(lhs, bound.map(inv_func, rhs=rhs())) + case (True, True): # both const, nothing to bound + return [] + case _: + raise ValueError("Impossible") + +def evaluate_known_vars(node: cl.Node, known_vars) -> cl.constant: + if is_constant(node): + return None + match node: + case cl.ScalarOperatorExpression(f, (lhs, rhs)) if f in int_inverse_op.keys() or f in int_inverse_op: + if is_constant(lhs): + rhs_eval = evaluate_known_vars(rhs, known_vars) + if is_constant(rhs_eval): return f(lhs, rhs_eval) + else: return None + else: + lhs_eval = evaluate_known_vars(lhs, known_vars) + if is_constant(lhs_eval): return f(lhs_eval, rhs) + else: return None + case cl.count(objs): + return evaluate_known_vars(objs, known_vars) + case cl.ObjectSetExpression() as objs: + domain = constraint_domain(objs) + vals = [] + for known_domain, known_val in known_vars: + if domain == known_domain: + vals.append(known_val) + if len(vals) == 0: + return None + else: + return cl.constant(min(vals)) + case _: + raise NotImplementedError(node) + +def expression_map_bound(node: cl.Node, bound: Bound) -> list[Bound]: + + match node: + case cl.ScalarOperatorExpression(f, (lhs, rhs)) if f in int_inverse_op.keys(): + return _expression_map_bound_binop(node, bound) + case cl.count(objs): + return expression_map_bound(objs, bound) + case cl.ObjectSetExpression() as objs: + bound = Bound( + domain=constraint_domain(objs), + low=bound.low, + high=bound.high, + ) + return [bound] + case _: + # distance & other hard constraints do not produce quantity-bounds + return [] + +def update_var(var, scene_state): + if not is_constant(var) and not isinstance(var, int) and scene_state is not None: + var_eval = evaluate_known_vars(var, scene_state) + var = var_eval if is_constant(var_eval) else var + return var + +def constraint_bounds( + node: cl.Node, + state=None +) -> list[Bound]: + + recurse = partial(constraint_bounds, state=state) + + match node: + case cl.Problem(cons): + return sum((recurse(c) for c in cons.values()), []) + case cl.BoolOperatorExpression(operator.and_, cons): + return sum((recurse(c) for c in cons), []) + case cl.in_range(val, low, high): + low = update_var(low, state) + high = update_var(high, state) + if is_constant(low) and is_constant(high): + low = low() + high = high() + bound = Bound(low=low, high=high) + return expression_map_bound(val, bound) + case cl.BoolOperatorExpression(f, (lhs, rhs)) if f in Bound._init_ops: + lhs, rhs = update_var(lhs, state), update_var(rhs, state) + + if not is_constant(lhs) and not is_constant(rhs): + logger.debug(f'Encountered {cl.BoolOperatorExpression.__name__} {f} with non-constant lhs and rhs. Producing no bound.') + return [] + + bound = Bound.from_comparison(node.func, lhs, rhs) + expr = rhs if is_constant(lhs) else lhs + return expression_map_bound(expr, bound) + case cl.ForAll(objs, varname, pred): + o_domain = constraint_domain(objs) + bounds = recurse(pred) + for b in bounds: + # TODO INCORRECT. Doesnt force EVERY object in o_domain to satify the bound + b.domain = domain_tag_substitute(b.domain, t.Variable(varname), o_domain) + return bounds + case unmatched: + assert isinstance(unmatched, cl.Expression), unmatched + return [] + \ No newline at end of file diff --git a/infinigen/core/constraints/reasoning/constraint_constancy.py b/infinigen/core/constraints/reasoning/constraint_constancy.py new file mode 100644 index 000000000..54523b7ca --- /dev/null +++ b/infinigen/core/constraints/reasoning/constraint_constancy.py @@ -0,0 +1,21 @@ +# 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 operator +import typing + +import numpy as np + +from infinigen.core.constraints import constraint_language as cl + +def is_constant(node: cl.Node): + match node: + case cl.constant(): + return True + case cl.BoolOperatorExpression(_, vs) | cl.ScalarOperatorExpression(_, vs): + return all(is_constant(x) for x in vs) + case _: + return False \ No newline at end of file diff --git a/infinigen/core/constraints/reasoning/constraint_domain.py b/infinigen/core/constraints/reasoning/constraint_domain.py new file mode 100644 index 000000000..e627a3c56 --- /dev/null +++ b/infinigen/core/constraints/reasoning/constraint_domain.py @@ -0,0 +1,75 @@ +# 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 + +from __future__ import annotations +import logging +import itertools +from functools import partial + +from dataclasses import dataclass, field +import copy +import typing + +import numpy as np + +from infinigen.core.constraints import constraint_language as cl +from infinigen.core import tags as t +from .domain import Domain + +logger = logging.getLogger(__name__) + +def constraint_domain( + node: cl.ObjectSetExpression, + finalize_variables=False +) -> Domain: + + """Given an expression, find a compact representation of what types of objects it is applying to. + + User can compared the resulting Domain against their State and see what objects fit. + """ + + assert isinstance(node, cl.ObjectSetExpression), node + + recurse = partial(constraint_domain, finalize_variables=finalize_variables) + + match node: + case cl.tagged(objs, tags): + d = recurse(objs) + d.tags.update(tags) + if t.contradiction(d.tags): + raise ValueError(f'Contradictory tags {tags=} for {d=} while parsing constraint {node=}') + return d + case cl.related_to(children, parents, relation): + c_d = recurse(children) + p_d = recurse(parents) + c_d.add_relation(relation, p_d) + return c_d + case cl.scene(): + return Domain() + case cl.item(x): + if finalize_variables: + return recurse(node.member_of) # TODO - worried about infinite recursion somehow + else: + return Domain(tags={t.Variable(x)}) + case FilterByDomain(objs, filter): + return filter.intersection(recurse(objs)) + case _: + raise NotImplementedError(node) + +@dataclass +class FilterByDomain(cl.ObjectSetExpression): + + """ Constraint node which says to return all objects matching a domain. + + Used as a compacted representation of the filtering performed many cl.tagged and cl.related_to calls. + One r.Domain is sufficient to represent the effect of and combination of intersection-style filtering. + + Introduced (currently) only by greedy.filter_constraints, since that function needs to work + with domains in order to narrow the scope of some constraints. + """ + + objs: cl.ObjectSetExpression + filter: Domain \ No newline at end of file diff --git a/infinigen/core/constraints/reasoning/domain.py b/infinigen/core/constraints/reasoning/domain.py new file mode 100644 index 000000000..f36815c23 --- /dev/null +++ b/infinigen/core/constraints/reasoning/domain.py @@ -0,0 +1,416 @@ +# 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 + +from __future__ import annotations +import logging +import itertools + +from dataclasses import dataclass, field +import copy +import typing + +import numpy as np + +from infinigen.core.constraints import constraint_language as cl +from infinigen.core import tags as t + +logger = logging.getLogger(__name__) + +def reldom_implies( + a: tuple[cl.Relation, Domain], + b: tuple[cl.Relation, Domain] +): + """ If relation a is satisfied, is relation guaranteed to be satisfied? + """ + + assert isinstance(a[1], Domain) + assert isinstance(b[1], Domain) + + return ( + a[0].implies(b[0]) and + a[1].implies(b[1]) + ) + +def reldom_compatible( + a: tuple[cl.Relation, Domain], + b: tuple[cl.Relation, Domain], +): + """ If relation a is satisfied, can relation b be satisfied? + """ + + assert isinstance(a[1], Domain) + assert isinstance(b[1], Domain) + + a_neg = isinstance(a[0], cl.NegatedRelation) + b_neg = isinstance(b[0], cl.NegatedRelation) + match (a_neg, b_neg): + case True, False: + if b[0].implies(a[0].rel) and b[1].intersects(a[1]): + logger.debug('reldom_compatible found contradicting negated %s %s', a[0], b[0]) + return False + case False, True: + if a[0].implies(b[0].rel) and a[1].intersects(b[1]): + logger.debug('reldom_compatible found contradicting negated %s %s', a[0], b[0]) + return False + + return True + +def reldom_satisfies( + a: tuple[cl.Relation, Domain], + b: tuple[cl.Relation, Domain], +): + return ( + a[0].intersects(b[0], strict=True) + and a[1].satisfies(b[1]) + ) + +def reldom_intersects( + a: tuple[cl.Relation, Domain], + b: tuple[cl.Relation, Domain], + **kwargs +): + return ( + a[0].intersects(b[0]) and + a[1].intersects(b[1], **kwargs) + ) + + +def reldom_intersection( + a: tuple[cl.Relation, Domain], + b: tuple[cl.Relation, Domain], +): + return ( + a[0].intersection(b[0]), + a[1].intersection(b[1]) + ) + + +def domain_finalized(dom: Domain, check_anyrel=False, check_variable=True): + + if check_variable and any(isinstance(x, t.Variable) for x in dom.tags): + return False + + for rel, cdom in dom.relations: + if check_anyrel and isinstance(rel, cl.AnyRelation): + return False + if not domain_finalized(cdom): + return False + + return True + +@dataclass +class Domain: + + ''' + Describes a class of object in the scene + + Objects are in the domain if: + - Some part of the object is tagged with each of 'tags' + - It is related to an object matching each Domain in relations + + WARNING: Recurive datastructure, here be dragons + + Note: Default-constructed Domain contains Everything + ''' + + tags: set[t.Semantics] = field(default_factory=set) + relations: list[tuple[cl.Relation, Domain]] = field(default_factory=list) + + def repr(self, abbrv=False, onelevel=False, oneline=False): + + is_neg = lambda x: isinstance(x, t.Negated) + def setrepr(s): + inner = ", ".join( + repr(x) for x in sorted(list(s), key=is_neg) + if not (abbrv and isinstance(x, t.Negated)) + ) + return '{' + inner + '}' + + next_abbrv = abbrv or onelevel + def repr_reldom(r, d): + if abbrv: + rel = f'-{r.rel.__class__.__name__}' if isinstance(r, cl.NegatedRelation) else f'{r.__class__.__name__}' + return f'({rel}(...), Domain({setrepr(d.tags)}, [...]))' + else: + return f'({repr(r)}, {d.repr(abbrv=next_abbrv)})' + + relations = [ + repr_reldom(r, d) + for r, d in sorted( + self.relations, + key=lambda x: isinstance(x[0], cl.NegatedRelation) + ) + ] + + if not oneline and sum(len(x) for x in relations) > 20: + relations = [r.replace('\n', '\n\t') for r in relations] + relations = '\n\t' + ',\n\t'.join(relations) + '\n' + else: + relations = ', '.join(relations) + return f'{self.__class__.__name__}({setrepr(self.tags)}, [{relations}])' + + __repr__ = repr + + def __post_init__(self): + assert isinstance(self.tags, set) + assert isinstance(self.relations, list) + + def implies(self, other: Domain): + + return ( + t.implies(self.tags, other.tags) + and all( + any(reldom_implies(rel, orel) for rel in self.relations) + for orel in other.relations + ) + ) + + def add_relation( + self, + new_rel: cl.Relation, + new_dom: Domain, + optimize_check_implies=True + ): + + """ + new_rel, new_dom: the relation and domain to be added + optimize_check_implies: bool + If enabled, dont add relations (aka predicates) that are already + provably true based on existing predicates. This should not be necessary for correctness: + object addition should check if relation constraints are satisfied/implied before adding more. + But it may (?) help speed, and the unit tests assume it is enabled for the most part + """ + + assert new_dom is not self + + logger.debug('add_relation %s %s to existing %i', new_rel, new_dom, len(self.relations)) + + if not optimize_check_implies: + self.relations.append((new_rel, new_dom)) + return + + covered = False + + for i, (er, ed) in enumerate(self.relations): + if isinstance(new_rel, cl.NegatedRelation): + continue + elif isinstance(er, cl.NegatedRelation): + continue + elif reldom_implies((er, ed), (new_rel, new_dom)): + covered = True + elif ( + reldom_satisfies((er, ed), (new_rel, new_dom)) + or reldom_satisfies((new_rel, new_dom), (er, ed)) + ): + logger.debug('Tightening existing relation %s with %s', (er, ed), (new_rel, new_dom)) + self.relations[i] = reldom_intersection((new_rel, new_dom), (er, ed)) + covered = True + elif new_dom.intersects(ed, require_satisfies_right=True): + logger.debug('Tightening domain %s with %s', ed, new_dom) + self.relations[i] = (er, ed.intersection(new_dom)) + else: + logger.debug('%s is not relevant for %s', (er, ed), (new_rel, new_dom)) + + if not covered: + logger.debug('optimize_check_implies found nothing, adding relation %s %s', new_rel, new_dom) + self.relations.append((new_rel, new_dom)) + + if self.is_recursive(): + raise ValueError(f'Encountered recursive domain after add_relation {new_rel=} {new_dom=} onto {self.tags=} {len(self.relations)=}') + + def with_relation(self, rel: cl.Relation, dom: Domain): + new = copy.deepcopy(self) + new.add_relation(rel, dom) + return new + + def with_tags(self, tags: set[t.Semantics]): + if not isinstance(tags, set): + tags = {tags} + new = copy.deepcopy(self) + new.tags.update(tags) + return new + + def satisfies( + self, + other: Domain + ): + + """ + + Assumes that 'self' is fully specified: any predicates that arent listed are false. + + Different from 'implies' in that if `other` contains negative predicates, `self` need not imply these, + it just needs to not contradict them. + + Different from 'intersects' in that + """ + + logger.debug("%s for %s %s", Domain.satisfies.__name__, self, other) + + if not t.satisfies(self.tags, other.tags): + logger.debug('failed tag implication %s -> %s', self.tags, other.tags) + return False + + def bothsat(reldom1, reldom2): + return ( + reldom1[0].satisfies(reldom2[0]) + and reldom1[1].satisfies(reldom2[1]) + ) + + for orel in other.relations: + match orel: + case (cl.NegatedRelation(n), d): + + contradictor = next(( + srel for srel in self.relations if bothsat(srel, (n, d)) + ), None) + + if contradictor is not None: + logger.debug( + 'satisfies found %s in self, which contradicts %s because it satisfies %s', contradictor, orel, (n, d) + ) + return False + case _: + if not any(bothsat(srel, orel) for srel in self.relations): + logger.debug('found unsatisfied %s for %s', orel, self.relations) + return False + + return True + + def intersects( + self, + other: Domain, + require_satisfies_left=False, + require_satisfies_right=False + ): + + """Return True if self and other could have a non-empty intersection. + + Parameters + ---------- + self: Domain - the domain to check + other: Domain - the domain to check against + + require_satisfies_left: bool - + If True, assume that `self` is exhaustively specified (ie, any predicates not listed are false), + and therefore `other` must imply `self` for the intersection to be non-empty. + require_satisfies_right: bool - + If True, assume that `other` is exhaustively specified (ie, any predicates not listed are false), + and therefore `self` must imply `other` for the intersection to be non-empty. + """ + + logger.debug('Domain.intersects for \n\t%s \n\t%s', self, other) + + if t.contradiction(self.tags.union(other.tags)): + logger.debug('tag contradiction %s, %s', self.tags, other.tags) + return False + + # no relations can contradict eachother + for ard, brd in itertools.product(self.relations, other.relations): + if ard is brd: + continue + if not reldom_compatible(ard, brd): + logger.debug('found incompatible %s %s', ard, brd) + return False + + # any relations actually known to be present must intersect + a_pos = [rd for rd in self.relations if not isinstance(rd[0], cl.NegatedRelation)] + b_pos = [rd for rd in other.relations if not isinstance(rd[0], cl.NegatedRelation)] + if require_satisfies_left: + if not t.satisfies(other.tags, self.tags): + return False + for ard in a_pos: + if not any(reldom_intersects(ard, brd) for brd in b_pos): + logger.debug('require_satisfies_left found no intersecting %s %s', ard, b_pos) + return False + if require_satisfies_right: + if not t.satisfies(self.tags, other.tags): + return False + for brd in b_pos: + if not any(reldom_intersects(ard, brd) for ard in a_pos): + logger.debug('require_satisfies_right found no intersecting %s %s', brd, a_pos) + return False + + logger.debug('Domain.intersects for %s %s returning True', self, other) + + return True + + def intersection(self, other: Domain): + + ''' + Return a domain representing the intersection of self and other. + Result is at least as strict as self and other. + + contains(self, x) and contains(other, x) -> contains(intersection, x) + + TODO: + - does order relations are checked for intersection matter? + - almost certainly yes, intersection is not transitive. + - so what order is best? fewest remaining relations? does it matter? + ''' + + newtags = self.tags.union(other.tags) + if t.contradiction(newtags): + raise ValueError(f'Contradictory {newtags=} for {self.intersection} {other=}') + + newdom = Domain(newtags) + for orel, odom in *self.relations, *other.relations: + newdom.add_relation(orel, copy.deepcopy(odom)) + + return newdom + + def is_recursive(self, seen=None): + + """ Check if this domain somehow references itself via its own relations. + Domains should ideally never reach this state; this function is used to check that they dont. + """ + + if seen is None: + seen = set() + + if id(self) in seen: + return True + + seen.add(id(self)) + + return any( + d.is_recursive(seen=seen) + for _, d in self.relations + ) + + def positive_part(self): + return Domain( + tags={ti for ti in self.tags if not isinstance(ti, t.Negated)}, + relations=[ + (r, d.positive_part()) + for r, d in self.relations + if not isinstance(r, cl.NegatedRelation) + ] + ) + + def traverse(self): + yield self + for rel, dom in self.relations: + yield from dom.traverse() + + def all_vartags(self) -> set[t.Variable]: + return { + x + for d in self.traverse() + for x in d.tags + if isinstance(x, t.Variable) + } + + def get_objs_named(self): + objnames = { + x.name for x in self.tags + if isinstance(x, t.SpecificObject) + } + for rel, dom in self.relations: + if isinstance(rel, cl.NegatedRelation): + continue + objnames = objnames.union(dom.get_objs_named()) + return objnames \ No newline at end of file diff --git a/infinigen/core/constraints/reasoning/domain_substitute.py b/infinigen/core/constraints/reasoning/domain_substitute.py new file mode 100644 index 000000000..752fea0b6 --- /dev/null +++ b/infinigen/core/constraints/reasoning/domain_substitute.py @@ -0,0 +1,66 @@ +# 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 + +from __future__ import annotations +import logging +import itertools + +from dataclasses import dataclass, field +import copy +import typing + +import numpy as np + +from infinigen.core.constraints import constraint_language as cl +from infinigen.core import tags as t +from .constraint_domain import Domain + +logger = logging.getLogger(__name__) + +def domain_tag_substitute( + domain: Domain, + vartag: t.Variable, + subst_domain: Domain, + return_match=False +) -> Domain: + + """Return concrete substitution of `domain`, where `subst_domain` must be satisfied + whenever `subst_tag` was present in the original. + """ + + assert isinstance(vartag, t.Variable), vartag + domain = copy.deepcopy(domain) # prevent modification of original + + o_match = vartag in domain.tags + + rd_sub, rd_matches = [], [] + for r, d in domain.relations: + d, match = domain_tag_substitute(d, vartag, subst_domain, return_match=True) + rd_sub.append((r, d)) + rd_matches.append(match) + rd_match = any(rd_matches) + + if not (o_match or rd_match): + return (domain, False) if return_match else domain + + domain.relations = [] + for r, d in rd_sub: + domain.add_relation(r, d) + + if o_match: + if vartag in domain.tags: + domain.tags.remove(vartag) + domain = domain.intersection(subst_domain) + + return (domain, True) if return_match else domain + +def substitute_all( + dom: Domain, + assignments: dict[t.Variable, Domain], +) -> Domain: + for var, d in assignments.items(): + dom = domain_tag_substitute(dom, var, d) + return dom \ No newline at end of file diff --git a/infinigen/core/constraints/reasoning/expr_equal.py b/infinigen/core/constraints/reasoning/expr_equal.py new file mode 100644 index 000000000..9fd4a2a35 --- /dev/null +++ b/infinigen/core/constraints/reasoning/expr_equal.py @@ -0,0 +1,64 @@ +# 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 dataclasses + +from ..constraint_language.types import Node + +@dataclasses.dataclass +class FalseEqualityResult: + n1: Node + n2: Node + reason: str + + def __repr__(self) -> str: + # default dataclass repr is too long + c1 = self.n1.__class__.__name__ + c2 = self.n2.__class__.__name__ + return f'{self.__class__.__name__}({c1}, {c2}, {repr(self.reason)})' + + def __bool__(self): + return False + +def expr_equal(n1: Node, n2: Node, name: str = None) -> bool | FalseEqualityResult: + + """ An equality comparison operator for constraint Node expressions + + Using the default Node == Node is unsafe since Nodes override == + in order to return another expression + """ + + if not dataclasses.is_dataclass(n1) or not dataclasses.is_dataclass(n2): + raise ValueError( + f'expr_equal {name=} called with non-dataclass {n1.__class__=} {n2.__class__=}.' + ' Expected all Node types to be dataclasses' + ) + + if name is None: + name = n1.__class__.__name__ + + if type(n1) is not type(n2): + return FalseEqualityResult(n1, n2, f"Unequal types for {name}: {type(n1).__name__} != {type(n2).__name__}") + + n1_child_keys = [k for k, _ in n1.children()] + n2_child_keys = [k for k, _ in n1.children()] + n1_children = [v for _, v in n1.children()] + n2_children = [v for _, v in n1.children()] + + if n1_child_keys != n2_child_keys: + return FalseEqualityResult(n1, n2, f'Unequal child keys for {name}: {n1_children}!={n2_children}') + + for f in dataclasses.fields(n1): + v1 = getattr(n1, f.name) + v2 = getattr(n2, f.name) + if isinstance(v1, Node): + res = expr_equal(v1, v2, name=f'{name}.{f.name}') + if not res: + return res + elif v1 != v2: + return FalseEqualityResult(n1, n2, f'Unequal attr {repr(f.name)}, {v1} != {v2}') + + return True \ No newline at end of file diff --git a/infinigen/core/constraints/usage_lookup.py b/infinigen/core/constraints/usage_lookup.py new file mode 100644 index 000000000..f2c0a83b1 --- /dev/null +++ b/infinigen/core/constraints/usage_lookup.py @@ -0,0 +1,48 @@ +# 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 + +from collections import defaultdict +from infinigen.core import tags as t + +_factory_lookup: dict[type, set[t.Tag]] = None +_tag_lookup: dict[t.Tag, set[type]] = None + +def initialize_from_dict(d): + + global _factory_lookup, _tag_lookup + _factory_lookup = defaultdict(set) + _tag_lookup = defaultdict(set) + + for tag, fac_list in d.items(): + _tag_lookup[tag] = set() + for fac in fac_list: + _factory_lookup[fac].add(tag) + _tag_lookup[tag].add(fac) + +def usages_of_factory(fac) -> set[t.Tag]: + return _factory_lookup[fac].union({t.FromGenerator(fac)}) + +def factories_for_usage(tags: set[t.Tag]): + if not isinstance(tags, set): + tags = [tags] + else: + tags = list(tags) + + res = _tag_lookup[tags[0]] + for t in tags[1:]: + res.intersection_update(_tag_lookup[t]) + return res + +def all_usage_tags(): + return _tag_lookup.keys() + +def all_factories(): + return _factory_lookup.keys() + +def has_usage(fac, tag): + assert fac in _factory_lookup.keys(), fac + assert tag in _tag_lookup.keys(), tag + return tag in _factory_lookup[fac] \ No newline at end of file diff --git a/infinigen/core/execute_tasks.py b/infinigen/core/execute_tasks.py index dee5ebcc7..0b082aaa9 100644 --- a/infinigen/core/execute_tasks.py +++ b/infinigen/core/execute_tasks.py @@ -14,6 +14,7 @@ import pprint import time from collections import defaultdict +import pickle # ruff: noqa: F402 os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1" # This must be done BEFORE import cv2. @@ -80,12 +81,13 @@ pipeline, exporting ) - +from infinigen.tools.export import export_scene from infinigen.core.util.math import FixedSeed, int_hash from infinigen.core.util.logging import Timer, save_polycounts, create_text_file from infinigen.core.util.pipeline import RandomStageExecutor from infinigen.core.util.random import sample_registry -from infinigen.assets.utils.tag import tag_system +from infinigen.core.tagging import tag_system + logger = logging.getLogger(__name__) @@ -226,12 +228,29 @@ def render(scene_seed, output_folder, camera_id, render_image_func=render_image, with Timer('Render Frames'): render_image_func(frames_folder=Path(output_folder), camera_id=camera_id) +def triangulate_meshes(): + for obj in bpy.context.scene.objects: + if obj.type == 'MESH': + view_state = obj.hide_viewport + obj.hide_viewport = False + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + logging.info(f"Triangulating {obj}") + bpy.ops.mesh.quads_convert_to_tris() + bpy.ops.object.mode_set(mode='OBJECT') + obj.select_set(False) + obj.hide_viewport = view_state + @gin.configurable def save_meshes(scene_seed, output_folder, frame_range, resample_idx=False): - + if resample_idx is not None and resample_idx > 0: resample_scene(int_hash((scene_seed, resample_idx))) - + + triangulate_meshes() + for obj in bpy.data.objects: obj.hide_viewport = obj.hide_render @@ -285,9 +304,8 @@ def execute_tasks( generate_resolution=(1280,720), fps=24, reset_assets=True, - focal_length=None, dryrun=False, - optimize_terrain_diskusage=False, + optimize_terrain_diskusage=False ): if input_folder != output_folder: if reset_assets: @@ -327,19 +345,26 @@ def execute_tasks( if Task.Coarse in task: butil.clear_scene(targets=[bpy.data.objects]) butil.spawn_empty(f'{infinigen.__version__=}') - compose_scene_func(output_folder, scene_seed) - + info = compose_scene_func(output_folder, scene_seed) + outpath = output_folder/"assets" + outpath.mkdir(exist_ok=True) + with open(outpath/"info.pickle", 'wb') as f: + pickle.dump(info, f, protocol=pickle.HIGHEST_PROTOCOL) + camera = cam_util.set_active_camera(*camera_id) - if focal_length is not None: - camera.data.lens = focal_length group_collections() if Task.Populate in task: populate_scene(output_folder, scene_seed) - if Task.FineTerrain in task: - terrain = Terrain(scene_seed, surface.registry, task=task, on_the_fly_asset_folder=output_folder/"assets") + need_terrain_processing = 'OpaqueTerrain' in bpy.data.objects + + if Task.FineTerrain in task and need_terrain_processing: + with open(output_folder/"assets"/"info.pickle", 'rb') as f: + info = pickle.load(f) + terrain = Terrain(scene_seed, surface.registry, task=task, on_the_fly_asset_folder=output_folder/"assets", height_offset=info["height_offset"], whole_bbox=info["whole_bbox"]) + cameras = [cam_util.get_camera(i, j) for i, j in cam_util.get_cameras_ids()] terrain.fine_terrain(output_folder, cameras=cameras, optimize_terrain_diskusage=optimize_terrain_diskusage) @@ -369,7 +394,11 @@ def execute_tasks( for col in bpy.data.collections['unique_assets'].children: col.hide_viewport = False - if Task.Render in task or Task.GroundTruth in task or Task.MeshSave in task: + if need_terrain_processing and ( + Task.Render in task + or Task.GroundTruth in task + or Task.MeshSave in task + ): terrain = Terrain( scene_seed, surface.registry, @@ -386,6 +415,9 @@ def execute_tasks( camera_id=camera_id, resample_idx=resample_idx ) + + if Task.Export in task: + export_scene(input_folder / output_blend_name, output_folder) if Task.MeshSave in task: save_meshes( diff --git a/infinigen/core/init.py b/infinigen/core/init.py index 3398f94d9..c8aff5c3c 100644 --- a/infinigen/core/init.py +++ b/infinigen/core/init.py @@ -1,5 +1,7 @@ # 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. +# This source code is licensed under the BSD 3-Clause license found in the LICENSE file in the root directory +# of this source tree. + import bpy import argparse @@ -117,6 +119,7 @@ def apply_gin_configs( configs: list[str] = None, overrides: list[str] = None, skip_unknown: bool = False, + finalize_config=False, mandatory_folders: list[Path] = None, mutually_exclusive_folders: list[Path] = None ): @@ -192,11 +195,12 @@ def find_config(p): 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): + with LogLevel(logger=logging.getLogger(), level=logging.WARNING): gin.parse_config_files_and_bindings( configs, - bindings=overrides, - skip_unknown=skip_unknown + bindings=overrides, + skip_unknown=skip_unknown, + finalize_config=finalize_config ) def import_addons(names): diff --git a/infinigen/core/nodes/compatibility.py b/infinigen/core/nodes/compatibility.py index 2441e1393..ff52e73e9 100644 --- a/infinigen/core/nodes/compatibility.py +++ b/infinigen/core/nodes/compatibility.py @@ -1,3 +1,9 @@ +# 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, David Yan + import logging from collections import OrderedDict diff --git a/infinigen/core/nodes/node_info.py b/infinigen/core/nodes/node_info.py index afa52c001..4d66073ad 100644 --- a/infinigen/core/nodes/node_info.py +++ b/infinigen/core/nodes/node_info.py @@ -1,14 +1,11 @@ # 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: all infinigen authors - +# This source code is licensed under the BSD 3-Clause license found in the LICENSE file in the root directory +# of this source tree. import bpy import numpy as np - class Nodes: """ An enum for all node types. @@ -24,7 +21,7 @@ class Nodes: Attribute = "ShaderNodeAttribute" CaptureAttribute = "GeometryNodeCaptureAttribute" AttributeStatistic = 'GeometryNodeAttributeStatistic' - TransferAttribute = "GeometryNodeAttributeTransfer" # removed in b3.4, still supported via compatibility.py + TransferAttribute = "GeometryNodeAttributeTransfer" # removed in b3.4, still supported via compatibility.py DomainSize = 'GeometryNodeAttributeDomainSize' StoreNamedAttribute = "GeometryNodeStoreNamedAttribute" NamedAttribute = 'GeometryNodeInputNamedAttribute' @@ -32,7 +29,6 @@ class Nodes: SampleNearest = "GeometryNodeSampleNearest" SampleNearestSurface = "GeometryNodeSampleNearestSurface" - # Color Menu ColorRamp = "ShaderNodeValToRGB" MixRGB = "ShaderNodeMixRGB" @@ -47,7 +43,7 @@ class Nodes: CombineColor = 'ShaderNodeCombineColor' CompCombineColor = 'CompositorNodeCombineColor' - #bl3.5 additions + # bl3.5 additions SeparateComponents = 'GeometryNodeSeparateComponents' SetID = 'GeometryNodeSetID' InterpolateCurves = 'GeometryNodeInterpolateCurves' @@ -86,6 +82,7 @@ class Nodes: ReverseCurve = 'GeometryNodeReverseCurve' SplineLength = 'GeometryNodeSplineLength' FillCurve = 'GeometryNodeFillCurve' + FilletCurve = 'GeometryNodeFilletCurve' # Curve Primitves QuadraticBezier = 'GeometryNodeCurveQuadraticBezier' @@ -130,6 +127,10 @@ class Nodes: Integer = 'FunctionNodeInputInt' LightPath = 'ShaderNodeLightPath' ShortestEdgePath = 'GeometryNodeInputShortestEdgePaths' + EdgeNeighbors = 'GeometryNodeInputMeshEdgeNeighbors' + ShaderNodeNormalMap = 'ShaderNodeNormalMap' + HueSaturationValue = 'ShaderNodeHueSaturation' + BlackBody = "ShaderNodeBlackbody" # Instances RealizeInstances = "GeometryNodeRealizeInstances" @@ -158,6 +159,7 @@ class Nodes: EdgePathToCurve = 'GeometryNodeEdgePathsToCurves' DeleteGeom = 'GeometryNodeDeleteGeometry' SplitEdges = 'GeometryNodeSplitEdges' + VertexNeighbors = "GeometryNodeInputMeshVertexNeighbors" # Mesh Primitives MeshCircle = "GeometryNodeMeshCircle" @@ -176,7 +178,7 @@ class Nodes: Composite = "CompositorNodeComposite" Viewer = "CompositorNodeViewer" - # Point + # Point DistributePointsOnFaces = "GeometryNodeDistributePointsOnFaces" PointsToVertices = "GeometryNodePointsToVertices" PointsToVolume = 'GeometryNodePointsToVolume' @@ -188,6 +190,7 @@ class Nodes: VectorCurve = "ShaderNodeVectorCurve" VectorRotate = "ShaderNodeVectorRotate" AlignEulerToVector = "FunctionNodeAlignEulerToVector" + Displacement = "ShaderNodeDisplacement" # Volume VolumeToMesh = 'GeometryNodeVolumeToMesh' @@ -213,6 +216,10 @@ class Nodes: ImageTexture = "GeometryNodeImageTexture" GradientTexture = 'ShaderNodeTexGradient' ShaderImageTexture = "ShaderNodeTexImage" + MagicTexture = "ShaderNodeTexMagic" + BrickTexture = 'ShaderNodeTexBrick' + CheckerTexture = "ShaderNodeTexChecker" + EnvironmentTexture = "ShaderNodeTexEnvironment" # Shaders MixShader = "ShaderNodeMixShader" @@ -229,6 +236,8 @@ class Nodes: GlassBSDF = "ShaderNodeBsdfGlass" GlossyBSDF = "ShaderNodeBsdfGlossy" LayerWeight = "ShaderNodeLayerWeight" + UVMap = "ShaderNodeUVMap" + Bump = "ShaderNodeBump" # Layout Reroute = "NodeReroute" @@ -248,7 +257,7 @@ class Nodes: SkyTexture = "ShaderNodeTexSky" Background = "ShaderNodeBackground" - #bl3.5 additions + # bl3.5 additions SeparateComponents = 'GeometryNodeSeparateComponents' SetID = 'GeometryNodeSetID' InterpolateCurves = 'GeometryNodeInterpolateCurves' @@ -267,6 +276,7 @@ class Nodes: OffsetPointinCurve = 'GeometryNodeOffsetPointInCurve' SplineResolution = 'GeometryNodeInputSplineResolution' + ''' Blender doesnt have an automatic way of discovering what properties exist on a node that might need to be set but are NOT in .inputs. This dict @@ -310,7 +320,7 @@ class Nodes: Nodes.RandomValue: ['data_type'], Nodes.Switch: ['input_type'], - Nodes.TransferAttribute: ['data_type', 'mapping'], + Nodes.TransferAttribute: ['data_type', 'mapping'], Nodes.SeparateGeometry: ['domain'], Nodes.MergeByDistance: ['mode'], @@ -355,33 +365,36 @@ class Nodes: SINGLETON_NODES = [Nodes.GroupInput, Nodes.GroupOutput, Nodes.MaterialOutput, Nodes.WorldOutput, Nodes.Viewer, Nodes.Composite, Nodes.RenderLayers, Nodes.LightOutput] -# Map the type of a socket (ie, .outputs[0].type), to the corresponding value to put into a -# data_type attr, ie CaptureAttributes data_type. Frustratingly these are not directly related. +# Map the type of a socket (ie, .outputs[0].type), to the corresponding value to put into a +# data_type attr, ie CaptureAttributes data_type. Frustratingly these are not directly related. NODETYPE_TO_DATATYPE = { 'VALUE': 'FLOAT', 'INT': 'INT', 'VECTOR': 'FLOAT_VECTOR', 'FLOAT_COLOR': 'RGBA', - 'BOOLEAN': 'BOOLEAN'} + 'BOOLEAN': 'BOOLEAN' +} NODECLASS_TO_DATATYPE = { 'NodeSocketFloat': 'FLOAT', 'NodeSocketInt': 'INT', 'NodeSocketVector': 'FLOAT_VECTOR', 'NodeSocketColor': 'RGBA', - 'NodeSocketBool': 'BOOLEAN'} + 'NodeSocketBool': 'BOOLEAN' +} DATATYPE_TO_NODECLASS = {v: k for k, v in NODECLASS_TO_DATATYPE.items()} NODECLASSES = [k for k in dir(bpy.types) if 'NodeSocket' in k] PYTYPE_TO_DATATYPE = { - int: 'INT', - float: 'FLOAT', + int: 'INT', + float: 'FLOAT', np.float32: 'FLOAT', np.float64: 'FLOAT', - np.array: 'FLOAT_VECTOR', + np.array: 'FLOAT_VECTOR', bool: 'BOOLEAN' } +DATATYPE_TO_PYTYPE = {v: k for k, v in PYTYPE_TO_DATATYPE.items()} # Each thing containing nodes has a different output node id OUTPUT_NODE_IDS = { @@ -408,4 +421,5 @@ class Nodes: 'INT': 'value', 'FLOAT_VECTOR': 'vector', 'FLOAT_COLOR': 'color', - 'BOOLEAN': 'boolean', } + 'BOOLEAN': 'value', +} diff --git a/infinigen/core/nodes/node_utils.py b/infinigen/core/nodes/node_utils.py index 90d511c90..da1e5c35f 100644 --- a/infinigen/core/nodes/node_utils.py +++ b/infinigen/core/nodes/node_utils.py @@ -36,13 +36,15 @@ def init_fn(*args, **kwargs): return registration_fn -def to_nodegroup(name, singleton, type='GeometryNodeTree'): +def to_nodegroup(name=None, singleton=False, type='GeometryNodeTree'): """Wrapper for initializing and registering new nodegroups.""" - if singleton: - name += ' (no gc)' - def registration_fn(fn): + nonlocal name + if name is None: + name = fn.__name__ + if singleton: + name = name + ' (no gc)' def init_fn(*args, **kwargs): if singleton and name in bpy.data.node_groups: return bpy.data.node_groups[name] @@ -105,3 +107,17 @@ def resample_node_group(nw: NodeWrangler, scene_seed: int): if input_socket.name == "Seed": input_socket.default_value = np.random.randint(1000) + +def build_color_ramp(nw, x, positions, colors, mode='HSV'): + cr = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': x}) + cr.color_ramp.color_mode = mode + elements = cr.color_ramp.elements + size = len(positions) + assert len(colors) == size + if size > 2: + for _ in range(size - 2): + elements.new(0) + for i, (p, c) in enumerate(zip(positions, colors)): + elements[i].position = p + elements[i].color = c + return cr \ No newline at end of file diff --git a/infinigen/core/nodes/node_wrangler.py b/infinigen/core/nodes/node_wrangler.py index 4f0972d76..1ea2d0162 100644 --- a/infinigen/core/nodes/node_wrangler.py +++ b/infinigen/core/nodes/node_wrangler.py @@ -1,6 +1,6 @@ # 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. - +# 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: NodeWrangler class, node linking, expose_input, nodegroup support # - Zeyu Ma: initial version, fixes, arithmetic utilties @@ -13,7 +13,7 @@ import warnings import traceback import logging - +import itertools from collections.abc import Iterable import bpy @@ -22,11 +22,12 @@ from infinigen.core.util.random import random_vector3 from infinigen.core.nodes.node_info import Nodes, NODE_ATTRS_AVAILABLE from infinigen.core.nodes import node_info -from infinigen.core.nodes.compatibility import COMPATIBILITY_MAPPINGS +from .compatibility import COMPATIBILITY_MAPPINGS logger = logging.getLogger(__name__) + class NodeMisuseWarning(UserWarning): pass @@ -62,18 +63,13 @@ def warn_with_traceback(message, category, filename, lineno, file=None, line=Non log.write(warnings.formatwarning(message, category, filename, lineno, line)) -warnings.showwarning = warn_with_traceback - -warnings.simplefilter('always', NodeMisuseWarning) - - def isnode(x): return isinstance(x, (bpy.types.ShaderNode, bpy.types.NodeInternal, bpy.types.GeometryNode)) def infer_output_socket(item): """ - Figure out if `item` somehow represents a node with an output we can use. + Figure out if `item` somehow represents a node with an output we can use. If so, return that output socket """ @@ -114,14 +110,13 @@ def infer_input_socket(node, input_socket_name): input_socket = node.inputs[input_socket_name] if not input_socket.enabled: - warnings.warn( + logger.warning( f'Attempted to use ({input_socket.name=},{input_socket.type=}) of {node.name=}, but it was ' f'disabled. Either change attrs={{...}}, ' f'change the socket index, or specify the socket by name (assuming two enabled sockets don\'t ' f'share a name).' f'The input sockets are ' - f'{[(i.name, i.type, ("ENABLED" if i.enabled else "DISABLED")) for i in node.inputs]}.', - NodeMisuseWarning) + f'{[(i.name, i.type, ("ENABLED" if i.enabled else "DISABLED")) for i in node.inputs]}.', ) return input_socket @@ -156,11 +151,8 @@ def new_value(self, v, label=None): node.outputs[0].default_value = v return node - def new_node( - self, node_type, - input_args=None, attrs=None, input_kwargs=None, label=None, - expose_input=None, compat_mode=True - ): + def new_node(self, node_type, input_args=None, attrs=None, input_kwargs=None, label=None, expose_input=None, + compat_mode=True): if input_args is None: input_args = [] if input_kwargs is None: @@ -171,7 +163,7 @@ def new_node( compat_map = COMPATIBILITY_MAPPINGS.get(node_type) if compat_mode and compat_map is not None: - logger.debug(f'Using {compat_map.__name__=} for {node_type=}') + # logger.debug(f'Using {compat_map.__name__=} for {node_type=}') return compat_map(self, node_type, input_args, attrs, input_kwargs) node = self._make_node(node_type) @@ -183,7 +175,7 @@ def new_node( if attrs is not None: for key, val in attrs.items(): # if key not in NODE_ATTRS_AVAILABLE.get(node.bl_idname, []): - # warnings.warn(f'Node Wrangler is setting attr {repr(key)} on {node.bl_idname=}, + # logger.warn(f'Node Wrangler is setting attr {repr(key)} on {node.bl_idname=}, # but it is not in node_info.NODE_ATTRS_AVAILABLE. Please add it so that the transpiler is # aware') try: @@ -202,7 +194,7 @@ def new_node( else: input_kwargs["Vector"] = self.new_node(Nodes.InputPosition) else: - pass #print(f"{w}, please fix it if you found it causes inconsistency") + pass # print(f"{w}, please fix it if you found it causes inconsistency") input_keyval_list = list(enumerate(input_args)) + list(input_kwargs.items()) for input_socket_name, input_item in input_keyval_list: @@ -227,7 +219,8 @@ def new_node( names = [v[1] for v in expose_input] uniq, counts = np.unique(names, return_counts=True) if (counts > 1).any(): - raise ValueError(f'expose_input with {names} features duplicate entries. in bl3.5 this is invalid.') + raise ValueError( + f'expose_input with {names} features duplicate entries. in bl3.5 this is invalid.') for inp in expose_input: nodeclass, name, val = inp self.expose_input(name, val=val, dtype=nodeclass) @@ -238,7 +231,7 @@ def expose_input(self, name, val=None, attribute=None, dtype=None, use_namednode ''' Expose an input to the nodegroups interface, making it able to be specified externally - If this nodegroup is + If this nodegroup is ''' if attribute is not None: @@ -355,11 +348,10 @@ def _make_node(self, node_type): f'regular node' nodegroup_type = { - 'ShaderNodeTree': 'ShaderNodeGroup', + 'ShaderNodeTree': 'ShaderNodeGroup', 'GeometryNodeTree': 'GeometryNodeGroup', 'CompositorNodeTree': 'CompositorNodeGroup' - }[ - bpy.data.node_groups[node_type].bl_idname] + }[bpy.data.node_groups[node_type].bl_idname] node = self.nodes.new(nodegroup_type) node.node_tree = bpy.data.node_groups[node_type] @@ -373,6 +365,27 @@ def get_position_translation_seed(self, i): self.position_translation_seed[i] = random_vector3() return self.position_translation_seed[i] + def find(self, name): + return [n for n in self.nodes if name in type(n).__name__] + + def find_recursive(self, name): + return [(self, n) for n in self.find(name)] + sum( + (NodeWrangler(n.node_tree).find_recursive(name) for n in self.nodes if n.type == 'GROUP'), []) + + def find_from(self, to_socket): + return [l for l in self.links if l.to_socket == to_socket] + + def find_from_recursive(self, name): + return [(self, n) for n in self.find(name)] + sum( + (NodeWrangler(n.node_tree).find_from_recursive(name) for n in self.nodes if n.type == 'GROUP'), []) + + def find_to(self, from_socket): + return [l for l in self.links if l.from_socket == from_socket] + + def find_to_recursive(self, name): + return [(self, n) for n in self.find(name)] + sum( + (NodeWrangler(n.node_tree).find_to_recursive(name) for n in self.nodes if n.type == 'GROUP'), []) + @staticmethod def is_socket(node): return isinstance(node, bpy.types.NodeSocket) or isinstance(node, bpy.types.Node) diff --git a/infinigen/core/nodes/shader_utils.py b/infinigen/core/nodes/shader_utils.py new file mode 100644 index 000000000..bf4f52f21 --- /dev/null +++ b/infinigen/core/nodes/shader_utils.py @@ -0,0 +1,53 @@ +# 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: David Yan + +import bpy + +def find_displacement_node(mat): + links = mat.node_tree.links + shader_nodes = mat.node_tree.nodes + outputNode = shader_nodes['Material Output'] + displacement_node = None + for link in links: + if (link.to_node == outputNode and link.to_socket.name == 'Displacement'): + displacement_node = link.from_node + break + return displacement_node + +def convert_shader_displacement(mat : bpy.types.Material): + + mat_copy = mat.copy() + mat_copy.name = mat.name + "_copy" + + shader_nodes = mat_copy.node_tree.nodes + + displacement_node = find_displacement_node(mat_copy) + + assert displacement_node is not None + + height = displacement_node.inputs["Height"].default_value + mid_level = displacement_node.inputs["Midlevel"].default_value + scale = displacement_node.inputs["Scale"].default_value + + new_scale = (height - mid_level) * scale + shader_nodes.remove(displacement_node) + + geo_node_group = bpy.data.node_groups.new('GeometryNodes', 'GeometryNodeTree') + group_input = geo_node_group.nodes.new('NodeGroupInput') + group_output = geo_node_group.nodes.new('NodeGroupOutput') + geo_node_group.outputs.new('NodeSocketGeometry', 'Geometry') + geo_node_group.inputs.new('NodeSocketGeometry', 'Geometry') + set_pos = geo_node_group.nodes.new('GeometryNodeSetPosition') + normal = geo_node_group.nodes.new('GeometryNodeInputNormal') + scale = geo_node_group.nodes.new('ShaderNodeVectorMath') + scale.operation = 'SCALE' + scale.inputs["Scale"].default_value = new_scale + + geo_node_group.links.new(group_input.outputs[0], set_pos.inputs['Geometry']) + geo_node_group.links.new(normal.outputs['Normal'], scale.inputs['Vector']) + geo_node_group.links.new(scale.outputs['Vector'], set_pos.inputs['Offset']) + geo_node_group.links.new(set_pos.outputs['Geometry'], group_output.inputs[0]) + + return mat_copy, geo_node_group diff --git a/infinigen/core/placement/__init__.py b/infinigen/core/placement/__init__.py index e69de29bb..0b2e9209a 100644 --- a/infinigen/core/placement/__init__.py +++ b/infinigen/core/placement/__init__.py @@ -0,0 +1 @@ +from . import camera \ No newline at end of file diff --git a/infinigen/core/placement/animation_policy.py b/infinigen/core/placement/animation_policy.py index 3a6e4e883..7e12cc522 100644 --- a/infinigen/core/placement/animation_policy.py +++ b/infinigen/core/placement/animation_policy.py @@ -1,7 +1,9 @@ # 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 +# Authors: +# - Alexander Raistrick: Primary author +# - Zeyu Ma: Animation with path finding from copy import deepcopy, copy @@ -10,6 +12,7 @@ import bpy import mathutils +from mathutils.bvhtree import BVHTree import gin import numpy as np @@ -17,31 +20,34 @@ from mathutils import Matrix, Vector, Euler from tqdm import trange, tqdm +import infinigen.assets.utils.mesh from infinigen.assets.creatures.util.geometry.curve import Curve from infinigen.core.util.math import clip_gaussian, lerp from infinigen.core.util.random import random_general from infinigen.core.util import blender as butil +from infinigen.core.placement.path_finding import path_finding logger = logging.getLogger(__name__) class PolicyError(ValueError): pass -def get_altitude(loc, terrain_bvh, dir=Vector((0.,0.,-1.))): - *_, straight_down_dist = terrain_bvh.ray_cast(loc, dir) +def get_altitude(loc, scene_bvh, dir=Vector((0.,0.,-1.))): + *_, straight_down_dist = scene_bvh.ray_cast(loc, dir) return straight_down_dist @gin.configurable def walk_same_altitude( - start_loc, - sampler, - bvh, - filter_func=None, - fall_ratio=1.5, - retries=30, - step_up_height=2, - ignore_missed_rays=False + start_loc, + sampler, + bvh, + filter_func=None, + fall_ratio=1.5, + retries=30, + step_up_height=2, + ignore_missed_rays=False, + z_move_up=1, ): ''' @@ -53,7 +59,7 @@ def walk_same_altitude( pos = start_loc + Vector(sampler()) - pos.z += 1 # move it up a ways, so that it can raycast back down onto something + pos.z += z_move_up # move it up a ways, so that it can raycast back down onto something curr_alt = get_altitude(start_loc, bvh) new_alt = get_altitude(pos, bvh) @@ -62,7 +68,7 @@ def walk_same_altitude( curr_alt = start_loc.z if new_alt is None: new_alt = pos.z - + if curr_alt is None or new_alt is None: if curr_alt is None: raise PolicyError() @@ -90,10 +96,10 @@ class AnimPolicyBrownian: def __init__(self, speed=3, pos_var=15.0): self.speed = speed self.pos_var = pos_var - + def __call__(self, obj, frame_curr, bvh, retry_pct): - + speed = random_general(self.speed) sampler = lambda: N(0, [self.pos_var, self.pos_var, 0.5]) pos = walk_same_altitude(obj.location, sampler, bvh) @@ -102,7 +108,7 @@ def __call__(self, obj, frame_curr, bvh, retry_pct): rot = np.array(obj.rotation_euler) + np.deg2rad(N(0, [5, 0, 5], 3)) return Vector(pos), Vector(rot), time, "BEZIER" - + @gin.configurable class AnimPolicyPan: @@ -112,7 +118,7 @@ def __init__(self, speed=3, dist=("uniform", 5, 20), rot_var=[10, 0, 20]): self.rot_var = rot_var def __call__(self, obj, frame_curr, bvh, retry_pct): - + speed = random_general(self.speed) def sampler(): theta = U(0, 2*np.pi) @@ -125,15 +131,15 @@ def sampler(): rot = np.array(obj.rotation_euler) + np.deg2rad(N(0, self.rot_var, 3)) return Vector(pos), Vector(rot), time, "LINEAR" - + @gin.configurable class AnimPolicyRandomForwardWalk: def __init__( - self, + self, forward_vec, - speed=2, - yaw_dist=("uniform", -30, 30), + speed=2, + yaw_dist=("uniform", -30, 30), altitude_var=0, step_range=(1, 10), rot_vars=[5, 0, 5], @@ -163,7 +169,7 @@ def sampler(): rot = np.array(obj.rotation_euler) + np.deg2rad(N(0, self.rot_vars, 3)) return Vector(pos), Vector(rot), time, 'BEZIER' - + @gin.configurable class AnimPolicyRandomWalkLookaround: @@ -174,9 +180,10 @@ def __init__( yaw_sampler=('uniform',-20, 20), step_range=('clip_gaussian', 3, 5, 0.5, 10), rot_vars=(5, 0, 5), - motion_dir_zoff=('clip_gaussian', 0, 90, 0, 180) + motion_dir_zoff=('clip_gaussian', 0, 90, 0, 180), + force_single_keyframe=False ): - + self.speed = random_general(speed) self.step_speed_mult = step_speed_mult @@ -187,11 +194,13 @@ def __init__( self.motion_dir_euler = None self.motion_dir_zoff = motion_dir_zoff + self.force_single_keyframe = force_single_keyframe + def __call__(self, obj, frame_curr, bvh, retry_pct): if self.motion_dir_euler is None: self.motion_dir_euler = copy(obj.rotation_euler) - + self.motion_dir_euler[2] += np.deg2rad(random_general(self.motion_dir_zoff)) orig_motion_dir_euler = copy(self.motion_dir_euler) @@ -209,21 +218,24 @@ def sampler(): time = np.linalg.norm(pos - obj.location) / step_speed rot = np.array(obj.rotation_euler) + np.deg2rad(N(0, self.rot_vars, 3)) + if self.force_single_keyframe: + time = bpy.context.scene.frame_end - frame_curr + return Vector(pos), Vector(rot), time, 'BEZIER' - + @gin.configurable class AnimPolicyFollowObject: def __init__( - self, target_obj, pois, bvh, - zrot_vel_var=20, - follow_zrot=0, + self, target_obj, pois, bvh, + zrot_vel_var=20, + follow_zrot=0, follow_rad_mult=('uniform', 1, 6), alt_mult=('uniform', 0.25, 1) ): self.pois = pois - self.target_obj = target_obj + self.target_obj = target_obj self.follow_zrot = follow_zrot self.zrot_vel = np.deg2rad(N(0, zrot_vel_var)) self.rad_vel = N(0, 0.03) @@ -259,7 +271,7 @@ def reset(self): if alt is None: logger.warning(f'In AnimPolicyFollowObject.reset(), got {alt=}') if alt is not None and alt < 2: - self.target_obj.location *= self.target_obj.location.z / 2 + self.target_obj.location *= self.target_obj.location.z / 2 for c in self.target_obj.constraints: self.target_obj.constraints.remove(c) @@ -276,7 +288,7 @@ def __call__(self, obj, frame_curr, bvh, retry_pct): frame_next = min(t for t in ts if t > frame_curr) except (ValueError, AttributeError): # no next frame, or no animation_data.action frame_next = frame_curr + bpy.context.scene.render.fps - + time = (frame_next - frame_curr) / bpy.context.scene.render.fps bpy.context.scene.frame_set(frame_curr) @@ -298,13 +310,13 @@ def __call__(self, obj, frame_curr, bvh, retry_pct): return Vector(pos), None, time, 'BEZIER' def validate_keyframe_range( - obj, - start_frame, end_frame, + obj, + start_frame, end_frame, bvhtree, validate_pose_func=None, stride=5, # runs faster but imperfect precision check_straight_line=True # rules out proposals faster, but has imperfect precision ): - + last_pos = deepcopy(obj.location) def freespace_ray_check(a, b): @@ -324,27 +336,29 @@ def freespace_ray_check(a, b): logger.debug(f'{frame_idx=} freespace_ray_check failed') return False - if validate_pose_func is not None and not validate_pose_func(obj): + if validate_pose_func is not None and not validate_pose_func(obj): # technically we should validate against all cameras, but this would be expensive logger.debug(f'{frame_idx} validate_pose_func failed') - return False + return False last_pos = deepcopy(obj.location) return True def try_animate_trajectory( - obj, bvh, policy_func, + obj: bpy.types.Object, + bvh: BVHTree, + policy_func, keyframe, duration_frames, validate_pose_func=None, max_step_tries=50, verbose=True, ): - + frame_curr = bpy.context.scene.frame_start pbar = tqdm(total=duration_frames) if verbose else None while frame_curr < bpy.context.scene.frame_start + duration_frames: - + orig_loc = copy(obj.location) orig_rot = copy(obj.rotation_euler) for retry in range(max_step_tries): @@ -352,19 +366,19 @@ def try_animate_trajectory( bpy.context.view_layer.update() try: loc, rot, duration, interp = policy_func( - obj, - frame_curr=frame_curr, - retry_pct=retry/max_step_tries, + obj, + frame_curr=frame_curr, + retry_pct=retry/max_step_tries, bvh=bvh ) except PolicyError as e: logger.debug(f'PolicyError on {retry=} {e=}') continue - + step_frames = int(duration * bpy.context.scene.render.fps) + 1 step_end_frame = frame_curr + step_frames - keyframe(loc, rot, step_end_frame, interp='BEZIER') + keyframe(obj, loc, rot, step_end_frame, interp='BEZIER') if not validate_keyframe_range(obj, frame_curr, step_end_frame, bvh, validate_pose_func): logger.debug(f'validate_keyframe_range failed on moving {obj.location} to {loc}') @@ -374,7 +388,7 @@ def try_animate_trajectory( continue obj.keyframe_delete(data_path=fc.data_path, frame=step_end_frame) continue - + if verbose: pbar.update(min(step_frames, duration_frames - frame_curr)) # dont overshoot the pbar, it makes the formatting not nice @@ -388,44 +402,150 @@ def try_animate_trajectory( return True + +@gin.configurable +def try_animate_with_pathfinding( + obj, bvh, policy_func, + keyframe, duration_frames, + validate_pose_func, + max_step_tries, + verbose, + bounding_box, + turning_limit_degree=10, +): + + frame_curr = bpy.context.scene.frame_start + pbar = tqdm(total=duration_frames) if verbose else None + while frame_curr < bpy.context.scene.frame_start + duration_frames: + + orig_loc = copy(obj.location) + orig_rot = copy(obj.rotation_euler) + for retry in range(max_step_tries): + obj.location, obj.rotation_euler = orig_loc, orig_rot + bpy.context.view_layer.update() + try: + loc, rot, duration, interp = policy_func( + obj, + frame_curr=frame_curr, + retry_pct=retry/max_step_tries, + bvh=bvh + ) + except PolicyError as e: + logger.debug(f'PolicyError on {retry=} {e=}') + continue + + bpy.context.scene.frame_set(frame_curr) + last_pose = (deepcopy(obj.location), deepcopy(obj.rotation_euler)) + keyframe(obj, loc, rot, frame_curr+1, interp='BEZIER') + bpy.context.scene.frame_set(frame_curr+1) + + valid_target = True + if validate_pose_func is not None and not validate_pose_func(obj): + valid_target = False + + # if valid_target: + # bpy.ops.wm.save_mainfile(filepath="/u/zeyum/p/debug.blend") + # assert(0) + + for fc in obj.animation_data.action.fcurves: + if fc.data_path == "": + continue + obj.keyframe_delete(data_path=fc.data_path, frame=frame_curr+1) + + if not valid_target: + logger.debug(f'validate_pose_func at target pose failed, aborting path finding') + continue + + + + bounded = True + current_pose = (loc, rot) + for i in range(3): + if current_pose[0][i] < bounding_box[0][i] or current_pose[0][i] >= bounding_box[1][i]: + bounded = False + if not bounded: + logger.debug(f'target pose out of bound, aborting path finding') + continue + + poses = path_finding(bvh, bounding_box, last_pose, current_pose) + + if poses is None: + logger.debug(f'path not found, aborting') + continue + + base_length = (last_pose[0] - current_pose[0]).length + scaling = poses[-1][0] / base_length + + step_frames = int(duration * bpy.context.scene.render.fps * scaling) + 1 + step_end_frame = frame_curr + step_frames + + turning_too_fast = False + for i in range(len(poses) - 1): + rotation_euler0 = poses[i][2] + rotation_euler1 = poses[i+1][2] + if abs(rotation_euler0.z - rotation_euler1.z) > turning_limit_degree / 180 * np.pi * step_frames * (poses[i+1][0] - poses[i][0]) / poses[-1][0]: + turning_too_fast = True + break + if turning_too_fast: + logger.debug(f'path turns too fast, aborting') + continue + + for l, location, rotation_euler in poses: + t = l / poses[-1][0] + keyframe(obj, location, rotation_euler, round(frame_curr + (step_end_frame - frame_curr) * t), interp='LINEAR') + + if verbose: + pbar.update(min(step_frames, duration_frames - frame_curr)) # dont overshoot the pbar, it makes the formatting not nice + + break # we found a good pose + + else: # for-else block triggers when for loop terminates w/o a break statement + return False + + frame_curr = step_end_frame + bpy.context.scene.frame_current = frame_curr + + return True + +def keyframe(obj, loc, rot, t, interp='BEZIER'): + + if obj.animation_data is not None and obj.animation_data.action is not None: + for fc in obj.animation_data.action.fcurves: + for kp in fc.keyframe_points: + if kp.co > t: + raise ValueError(f'Unexpected out-of-order keyframing {kp.co=}, {t=}') + + if loc is not None: + obj.location = loc + obj.keyframe_insert(data_path="location", frame=t), + + if rot is not None: + obj.rotation_euler = rot + obj.keyframe_insert(data_path="rotation_euler", frame=t) + + for fc in obj.animation_data.action.fcurves: + for k in fc.keyframe_points: + if k.co[0] == t: + k.interpolation = interp + @gin.configurable def animate_trajectory( obj, bvh, policy_func, validate_pose_func=None, max_step_tries=25, max_full_retries=10, - default_interpolation='BEZIER', retry_rotation=False, verbose=True, fatal=False, reverse_time=False, + bounding_box=None, + path_finding_enabled=False, ): duration_frames = (bpy.context.scene.frame_end - bpy.context.scene.frame_start) duration_sec = duration_frames / bpy.context.scene.render.fps if duration_sec < 1e-3: return - def keyframe(loc, rot, t, interp=default_interpolation): - - if obj.animation_data is not None and obj.animation_data.action is not None: - for fc in obj.animation_data.action.fcurves: - for kp in fc.keyframe_points: - if kp.co > t: - raise ValueError(f'Unexpected out-of-order keyframing {kp.co=}, {t=}') - - if loc is not None: - obj.location = loc - obj.keyframe_insert(data_path="location", frame=t), - - if rot is not None: - obj.rotation_euler = rot - obj.keyframe_insert(data_path="rotation_euler", frame=t) - - for fc in obj.animation_data.action.fcurves: - for k in fc.keyframe_points: - if k.co[0] == t: - k.interpolation = interp - obj_orig_loc = copy(obj.location) obj_orig_rot = copy(obj.rotation_euler) @@ -438,10 +558,13 @@ def keyframe(loc, rot, t, interp=default_interpolation): obj.rotation_euler.z = U(0, 2 * np.pi) if hasattr(policy_func, 'reset'): - policy_func.reset() + infinigen.assets.utils.mesh.reset_preset() - keyframe(obj.location, obj.rotation_euler, 0, interp='LINEAR') - if try_animate_trajectory(obj, bvh, policy_func, keyframe, duration_frames, validate_pose_func, max_step_tries, verbose): + keyframe(obj, obj.location, obj.rotation_euler, 0, interp='LINEAR') + try_animate_trajectory_func = try_animate_trajectory if not path_finding_enabled else try_animate_with_pathfinding + args = [obj, bvh, policy_func, keyframe, duration_frames, validate_pose_func, max_step_tries, verbose] + if path_finding_enabled: args.append(bounding_box) + if try_animate_trajectory_func(*args): if reverse_time: kf_locs = [] kf_rots = [] @@ -460,7 +583,7 @@ def keyframe(loc, rot, t, interp=default_interpolation): )) obj.animation_data_clear() for i, t in enumerate(kf_ts): - keyframe(kf_locs[i], kf_rots[i], bpy.context.scene.frame_end + bpy.context.scene.frame_start - t, interp='LINEAR') + keyframe(obj, kf_locs[i], kf_rots[i], bpy.context.scene.frame_end + bpy.context.scene.frame_start - t, interp='LINEAR') # bpy.context.scene.frame_set(bpy.context.scene.frame_end) # obj.keyframe_insert(data_path="location", frame=bpy.context.scene.frame_end) # obj.keyframe_insert(data_path="rotation_euler", frame=bpy.context.scene.frame_end) @@ -474,9 +597,10 @@ def keyframe(loc, rot, t, interp=default_interpolation): else: logger.warning(err) return - + + def policy_create_bezier_path(start_pose_obj, bvh, policy_func, to_mesh=False, eval_offset=(0,0,0), **kwargs): - + eval_offset = Vector(eval_offset) # animate a dummy using the policy diff --git a/infinigen/core/placement/camera.py b/infinigen/core/placement/camera.py index aa5127e91..ea92abba7 100644 --- a/infinigen/core/placement/camera.py +++ b/infinigen/core/placement/camera.py @@ -3,18 +3,20 @@ # Authors: # - Zeyu Ma, Lahav Lipson: Stationary camera selection -# - Alex Raistrick: Refactor into proposal/validate, camera animation +# - Alexander Raistrick: Refactor into proposal/validate, camera animation # - Lingjie Mei: get_camera_trajectory from random import sample import sys import warnings +import typing from copy import deepcopy, copy from functools import partial from itertools import chain import logging from pathlib import Path +from dataclasses import dataclass from numpy.random import uniform as U @@ -36,10 +38,13 @@ from . import animation_policy from infinigen.core.util import blender as butil +from infinigen.core.util.blender import SelectObjects, delete from infinigen.core.util.logging import Timer from infinigen.core.util.math import clip_gaussian, lerp from infinigen.core.util import camera from infinigen.core.util.random import random_general +from infinigen.core.tagging import tag_system +from infinigen.core.util.organization import SelectionCriterions from infinigen.tools.suffixes import get_suffix @@ -201,7 +206,7 @@ def set_camera( if focus_dist is not None: camera.data.dof.keyframe_insert(data_path="focus_distance", frame=frame) -def terrain_camera_query(cam, terrain_bvh, terrain_tags_queries, vertexwise_min_dist, min_dist=0): +def terrain_camera_query(cam, scene_bvh, terrain_tags_queries, vertexwise_min_dist, min_dist=0): dists = [] sensor_coords, pix_it = get_sensor_coords(cam, sparse=True) @@ -209,7 +214,7 @@ def terrain_camera_query(cam, terrain_bvh, terrain_tags_queries, vertexwise_min_ for x,y in pix_it: direction = (sensor_coords[y,x] - cam.matrix_world.translation).normalized() - _, _, index, dist = terrain_bvh.ray_cast(cam.matrix_world.translation, direction) + _, _, index, dist = scene_bvh.ray_cast(cam.matrix_world.translation, direction) if dist is None: continue dists.append(dist) @@ -217,6 +222,7 @@ def terrain_camera_query(cam, terrain_bvh, terrain_tags_queries, vertexwise_min_ dist < min_dist or (vertexwise_min_dist is not None and dist < vertexwise_min_dist[index]) ): + logger.debug(f'Found {dist=} < {min_dist=}') dists = None # means dist < min break for q in terrain_tags_queries: @@ -226,59 +232,56 @@ def terrain_camera_query(cam, terrain_bvh, terrain_tags_queries, vertexwise_min_ return dists, terrain_tags_queries_counts, n_pix +@dataclass +class CameraProposal: + loc: np.array + rot: np.array + focal_length: float + + def apply(self, cam): + cam.location = self.loc + cam.rotation_euler = self.rot + cam.data.lens = self.focal_length + @gin.configurable def camera_pose_proposal( - terrain_bvh, - terrain_bbox, - altitude=2, + scene_bvh, + location_sample: typing.Callable | tuple, + altitude=('uniform', 1.5, 2.5), roll=0, yaw=('uniform', -180, 180), pitch=90, - headspace_retries=30, + focal_length=50, override_loc=None, - override_rot=None ): + + if isinstance(location_sample, tuple): + location_sample = Vector(location_sample) + location_sample = lambda: location_sample - if override_loc is None: - loc = np.random.uniform(*terrain_bbox) - - alt = animation_policy.get_altitude(loc, terrain_bvh) - if alt is None: - return None - - headspace = animation_policy.get_altitude(loc, terrain_bvh, dir=Vector((0, 0, 1))) - for headspace_retry in range(headspace_retries): - desired_alt = random_general(altitude) - if desired_alt is None: - zoff = 0 - break - zoff = desired_alt - alt - if headspace is None: - break - if desired_alt < headspace: - break - logger.debug(f'camera_pose_proposal failed {headspace_retry=} due to {headspace=} {desired_alt=} {alt=}') - else: # for-else triggers if no break, IE no acceptable voffset was found - logger.warning(f'camera_pose_proposal found no zoff for {loc=} after {headspace_retries=}') - return None - - loc[2] = loc[2] + zoff - if loc[2] > terrain_bbox[1][2] or loc[2] < terrain_bbox[0][2]: - return None - else: + if override_loc is not None: loc = Vector(random_general(override_loc)) - if override_rot: - rot = np.deg2rad(override_rot) + elif altitude is None: + loc = location_sample() else: - rot = np.deg2rad([random_general(pitch), random_general(roll), random_general(yaw)]) + loc = location_sample() + curr_alt = animation_policy.get_altitude(loc, scene_bvh) + if curr_alt is None: + logger.debug(f'camera_pose_proposal got {curr_alt=} for {loc=}') + butil.spawn_empty(f'fail') + return None + desired_alt = random_general(altitude) + loc[2] = loc[2] + desired_alt - curr_alt - return loc, rot + rot = np.deg2rad([random_general(pitch), random_general(roll), random_general(yaw)]) + focal_length = random_general(focal_length) + return CameraProposal(loc, rot, focal_length) @gin.configurable def keep_cam_pose_proposal( cam, terrain, - terrain_bvh, + scene_bvh, placeholders_kd, camera_selection_answers, vertexwise_min_dist, @@ -305,7 +308,7 @@ def keep_cam_pose_proposal( return None dists, camera_selection_answers_counts, n_pix = terrain_camera_query( - cam, terrain_bvh, camera_selection_answers, vertexwise_min_dist, min_dist=min_terrain_distance) + cam, scene_bvh, camera_selection_answers, vertexwise_min_dist, min_dist=min_terrain_distance) if dists is None: logger.debug('keep_cam_pose_proposal rejects terrain dists') @@ -313,29 +316,29 @@ def keep_cam_pose_proposal( coverage = len(dists)/n_pix if coverage < terrain_coverage_range[0] or coverage > terrain_coverage_range[1]: + logger.debug(f'keep_cam_pose_proposal rejects {coverage=} for {terrain_coverage_range=}') return None - - if terrain is None: - return 0 - - if terrain_sdf <= 0: + + if terrain is not None and terrain_sdf <= 0: logger.debug(f'keep_cam_pose_proposal rejects {terrain_sdf=}') return None if rparams := camera_selection_ratio: for q in rparams: - if type(q) is tuple and q[0] == "closeup": + if type(q) is tuple and q[0] == SelectionCriterions.CloseUp: closeup = len([d for d in dists if d < q[1]])/n_pix if closeup < rparams[q][0] or closeup > rparams[q][1]: + logger.debug(f'keep_cam_pose_proposal rejects {closeup=} for {q=}') return None else: minv, maxv = rparams[q][0], rparams[q][1] if q in camera_selection_answers_counts: ratio = camera_selection_answers_counts[q] / n_pix if ratio < minv or ratio > maxv: + logger.debug(f'keep_cam_pose_proposal rejects {ratio=} for {q=}') return None - return np.std(dists) + return np.std(dists) + 1.5 * np.min(dists) @gin.configurable class AnimPolicyGoToProposals: @@ -354,93 +357,152 @@ def __call__(self, camera_rig, frame_curr, retry_pct, bvh): res = camera_pose_proposal(bvh, bbox) if res is None: continue - pos, rot = res - pos = np.array(pos) - if np.linalg.norm(pos - np.array(camera_rig.location)) < self.min_dist: + dist = np.linalg.norm(np.array(res.loc) - np.array(camera_rig.location)) + if dist < self.min_dist: continue break else: raise animation_policy.PolicyError(f'{__name__} found no keyframe after {self.retries=}') - time = np.linalg.norm(pos - camera_rig.location) / random_general(self.speed) - return Vector(pos), Vector(rot), time, 'BEZIER' + time = dist / random_general(self.speed) + return Vector(res.loc), Vector(res.rot), time, 'BEZIER' @gin.configurable def compute_base_views( cam, n_views, terrain, - terrain_bvh, - terrain_bbox, + scene_bvh, + location_sample: typing.Callable, placeholders_kd=None, camera_selection_answers={}, vertexwise_min_dist=None, camera_selection_ratio=None, min_candidates_ratio=20, - max_tries=10000, + max_tries=30000, + visualize=False ): potential_views = [] n_min_candidates = int(min_candidates_ratio * n_views) with tqdm(total=n_min_candidates, desc='Searching for camera viewpoints') as pbar: for it in range(1, max_tries): - props = camera_pose_proposal(terrain_bvh=terrain_bvh, terrain_bbox=terrain_bbox) - if props is None: continue - loc, rot = props + props = camera_pose_proposal( + scene_bvh=scene_bvh, + location_sample=location_sample + ) + + if props is None: + logger.debug(f'{camera_pose_proposal.__name__} returned {props=} for {it=}') + continue + + props.apply(cam) - cam.location = loc - cam.rotation_euler = rot criterion = keep_cam_pose_proposal( - cam, terrain, terrain_bvh, placeholders_kd, + cam, terrain, scene_bvh, placeholders_kd, camera_selection_answers=camera_selection_answers, vertexwise_min_dist=vertexwise_min_dist, camera_selection_ratio=camera_selection_ratio, ) + + if visualize: + criterion_str = f'{criterion:.2f}' if criterion is not None else 'None' + marker = butil.spawn_empty(f'attempt_{it}_{criterion_str}') + marker.location = cam.location + marker.rotation_euler = cam.rotation_euler + if criterion is None: + logger.debug(f'{it=} {criterion=}') continue # Compute focus distance destination = cam.matrix_world @ Vector((0.,0.,-1.)) forward_dir = (destination - cam.location).normalized() - *_, straight_ahead_dist = terrain_bvh.ray_cast(cam.location, forward_dir) + *_, straight_ahead_dist = scene_bvh.ray_cast(cam.location, forward_dir) - potential_views.append((criterion, deepcopy(cam.location), deepcopy(cam.rotation_euler), straight_ahead_dist)) + potential_views.append((criterion, deepcopy(props), straight_ahead_dist)) pbar.update(1) if len(potential_views) >= n_min_candidates: break if len(potential_views) < n_views: + if visualize: + butil.save_blend('compute_base_views-failed.blend') raise ValueError(f'Could not find {n_views} camera views') - return sorted(potential_views, reverse=True)[:n_views] - -@gin.configurable -def camera_selection_keep_in_animation(**kwargs): - return kwargs - -@gin.configurable -def camera_selection_tags_ratio(**kwargs): - keep_in_animation = camera_selection_keep_in_animation() - d = {} - for k in kwargs: - d[k] = (*kwargs[k], k in keep_in_animation and keep_in_animation[k]) - return d - -@gin.configurable -def camera_selection_ranges_ratio(**kwargs): - keep_in_animation = camera_selection_keep_in_animation() - d = {} - for k in kwargs: - d[kwargs[k][:-2]] = (kwargs[k][-2], kwargs[k][-1], k in keep_in_animation and keep_in_animation[k]) - return d + views = sorted(potential_views, reverse=True) + + return views[:n_views] + + +def build_bvh_and_attrs(objs, tags_queries): + dup_objs = [] + for obj in objs: + with SelectObjects(obj): + bpy.ops.object.duplicate(linked=0,mode='TRANSLATION') + dup_objs.append(bpy.context.view_layer.objects.active) + for obj in dup_objs: + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + with SelectObjects(dup_objs[0]): + for obj in dup_objs[1:]: + obj.select_set(True) + bpy.ops.object.join() + obj = bpy.context.view_layer.objects.active + bvh = BVHTree.FromObject(obj, bpy.context.evaluated_depsgraph_get()) + from infinigen.terrain.utils import Mesh + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + mesh = Mesh(obj=obj) + delete(obj) + + camera_selection_answers = {} + for q0 in tags_queries: + if type(q0) is not tuple: + q = (q0,) + else: + q = q0 + if q[0] in [SelectionCriterions.CloseUp]: continue + if q[0] == SelectionCriterions.Altitude: + min_altitude, max_altitude = q[1:3] + altitude = mesh.vertices[:, 2] + camera_selection_answers[q0] = mesh.facewise_mean((altitude > min_altitude) & (altitude < max_altitude)) + else: + camera_selection_answers[q0] = np.zeros(len(mesh.faces), dtype=bool) + for key in tag_system.tag_dict: + if set(q).issubset(set(key.split('.'))): + camera_selection_answers[q0] |= (mesh.face_attributes["MaskTag"] == tag_system.tag_dict[key]).reshape(-1) + return bvh, camera_selection_answers def camera_selection_preprocessing( terrain, - terrain_mesh, + scene_objs, + tags_ratio: dict = None, + ranges_ratio: dict = None, + anim_criterion_keys: dict = None, ): - camera_selection_ratio = camera_selection_tags_ratio() - camera_selection_ratio.update(camera_selection_ranges_ratio()) + + if tags_ratio is None: + tags_ratio = {} + if ranges_ratio is None: + ranges_ratio = {} + if anim_criterion_keys is None: + anim_criterion_keys = {} + + # preprocessing code adapted from mazeyu's original gin-oriented solution + tags_ratio = { + k: (*v, anim_criterion_keys.get(k, False)) + for k, v in tags_ratio.items() + } + ranges_ratio = { + v[:-2]: (v[-2], v[-1], anim_criterion_keys.get(k, False)) + for k, v in ranges_ratio.items() + } + + all_selection_ratios = {**tags_ratio, **ranges_ratio} + with Timer('Building placeholders KDTree'): placeholders = list(chain.from_iterable( @@ -451,46 +513,81 @@ def camera_selection_preprocessing( placeholders_kd = butil.joined_kd(placeholders, include_origins=True) if terrain is None: - bvh = BVHTree.FromObject(terrain_mesh, bpy.context.evaluated_depsgraph_get()) - return dict( - terrain=None, - terrain_bvh=bvh, - placeholders_kd=placeholders_kd - ) - - with Timer(f'Building terrain BVHTree'): - terrain_bvh, camera_selection_answers, vertexwise_min_dist = terrain.build_terrain_bvh_and_attrs(camera_selection_ratio.keys()) + scene_bvh, camera_selection_answers = build_bvh_and_attrs(scene_objs, all_selection_ratios.keys()) + vertexwise_min_dist = None + else: + scene_bvh, camera_selection_answers, vertexwise_min_dist = terrain.build_terrain_bvh_and_attrs(all_selection_ratios.keys()) return dict( terrain=terrain, - terrain_bvh=terrain_bvh, + scene_bvh=scene_bvh, + camera_selection_ratio=all_selection_ratios, camera_selection_answers=camera_selection_answers, vertexwise_min_dist=vertexwise_min_dist, placeholders_kd=placeholders_kd, - camera_selection_ratio=camera_selection_ratio, ) +@node_utils.to_nodegroup('geo_distrib', singleton=True, type='GeometryNodeTree') +def geo_distrib_random_points(nw: NodeWrangler): + input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + distribute = nw.new_node(Nodes.DistributePointsOnFaces, input_kwargs={ + 'Mesh': input.outputs['Geometry'], + 'Density': 500 + }) + verts = nw.new_node(Nodes.PointsToVertices, [distribute]) + output = nw.new_node(Nodes.GroupOutput, input_kwargs={"Geometry": verts}) + +def sample_random_locs(surface: bpy.types.Object, eps=0.01): + # HACK implementation - uses blender geonodes' uniform surface sample, im fairly sure theres a numpy impl somewhere in the repo + surface = butil.copy(surface) + butil.apply_transform(surface, loc=True, rot=True, scale=True) + butil.modify_mesh( + surface, + "NODES", + node_group=geo_distrib_random_points(), + apply=True + ) + locs = np.array([v.co for v in surface.data.vertices]) + locs[:, -1] += eps + butil.delete(surface) + return locs + @gin.configurable def configure_cameras( cam_rigs, - bounding_box, - scene_preprocessed, + scene_preprocessed: dict, + init_bounding_box: tuple[np.array, np.array] = None, + init_surfaces: list[bpy.types.Object] = None, ): bpy.context.view_layer.update() dummy_camera = spawn_camera() + if init_bounding_box is not None: + location_sample = lambda: np.random.uniform(*init_bounding_box) + elif init_surfaces is not None: + random_locs = sample_random_locs(init_surfaces) + def location_sample(): + loc = Vector(random_locs[np.random.randint(len(random_locs)), :]) + loc.z += 1e-3 + return loc + else: + raise ValueError('Either init_bounding_box or init_surfaces must be provided') + base_views = compute_base_views( dummy_camera, n_views=len(cam_rigs), - terrain_bbox=bounding_box, + location_sample=location_sample, **scene_preprocessed ) for view, cam_rig in zip(base_views, cam_rigs): - score, loc, rot, focus_dist = view - cam_rig.location = loc - cam_rig.rotation_euler = rot + score, props, focus_dist = view + cam_rig.location = props.loc + cam_rig.rotation_euler = props.rot + + for cam in cam_rig.children: + cam.data.lens = props.focal_length if focus_dist is not None: for cam in cam_rig.children: @@ -501,7 +598,8 @@ def configure_cameras( @gin.configurable def animate_cameras( - cam_rigs, + cam_rigs, + bounding_box, scene_preprocessed, pois=None, follow_poi_chance=0.0, @@ -517,7 +615,7 @@ def animate_cameras( anim_valid_pose_func = partial( keep_cam_pose_proposal, placeholders_kd=scene_preprocessed['placeholders_kd'], - terrain_bvh=scene_preprocessed['terrain_bvh'], + scene_bvh=scene_preprocessed['scene_bvh'], terrain=scene_preprocessed['terrain'], vertexwise_min_dist=scene_preprocessed['vertexwise_min_dist'], camera_selection_answers=animation_answers, @@ -526,12 +624,12 @@ def animate_cameras( for cam_rig in cam_rigs: - if policy_registry is None: + if policy_registry is None: if U() < follow_poi_chance and pois is not None and len(pois): policy = animation_policy.AnimPolicyFollowObject( - target_obj=cam_rig, - pois=pois, - bvh=scene_preprocessed['terrain_bvh'] + target_obj=cam_rig, + pois=pois, + bvh=scene_preprocessed['scene_bvh'] ) else: policy = animation_policy.AnimPolicyRandomWalkLookaround() @@ -541,12 +639,13 @@ def animate_cameras( logger.info(f'Animating {cam_rig=} using {policy=}') animation_policy.animate_trajectory( - cam_rig, - scene_preprocessed['terrain_bvh'], + cam_rig, + scene_preprocessed['scene_bvh'], policy_func=policy, validate_pose_func=anim_valid_pose_func, verbose=True, - fatal=True + fatal=True, + bounding_box=bounding_box, ) @gin.configurable @@ -606,3 +705,4 @@ def save_camera_parameters(camera_ids, output_folder, frame, use_dof=False): color_depth = colorize_depth(depth_output) imageio.imwrite(f"color_depth.png", color_depth) + diff --git a/infinigen/core/placement/density.py b/infinigen/core/placement/density.py index 19d97616d..fb830526a 100644 --- a/infinigen/core/placement/density.py +++ b/infinigen/core/placement/density.py @@ -1,7 +1,9 @@ # 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 +# Authors: +# - Alexander Raistrick: The majority part +# - Zeyu Ma: Selection based on tag import pdb @@ -23,6 +25,30 @@ def set_tag_dict(tag_dict_): global tag_dict tag_dict = tag_dict_ +def tag_mask(nw, tag): + keys = list(tag_dict.keys()) + tag_parts = tag.split(',') + logger.debug(f'Parsing {tag=} into {len(tag_parts)=}, matching against {len(tag_dict)=}') + for part in tag_parts: + if part.startswith("-"): + keys = [k for k in keys if part[1:] not in k.split('.')] + else: + keys = [k for k in keys if part in k.split('.')] + conditions = [] + for k in keys: + comp = nw.new_node( + Nodes.Compare, + attrs={'operation': "EQUAL", "data_type": "FLOAT"}, + input_args=[eval_argument(nw, "MaskTag"), tag_dict[k]] + ) + conditions.append(comp) + if len(conditions): + return nw.scalar_add(*conditions) + else: + mask = nw.new_node(Nodes.Value) + mask.outputs["Value"].default_value = 1 + return mask + def placement_mask(scale=0.05, select_thresh=0.55, normal_thresh=0.5, normal_thresh_high=2., normal_dir=(0, 0, 1), tag=None, return_scalar=False, altitude_range=None): def selection(nw): @@ -45,22 +71,10 @@ def selection(nw): mask = nw.scalar_multiply(mask, facing_mask) if tag is not None: - keys = list(tag_dict.keys()) - tag_parts = tag.split(',') - logger.debug(f'Parsing {tag=} into {len(tag_parts)=}, matching against {len(tag_dict)=}') - for part in tag_parts: - if part.startswith("-"): - keys = [k for k in keys if part[1:] not in k.split('.')] - else: - keys = [k for k in keys if part in k.split('.')] - conditions = [] - for k in keys: - conditions.append(nw.new_node(Nodes.Compare, attrs={'operation': "EQUAL", "data_type": "FLOAT"}, input_args=[eval_argument(nw, "MaskTag"), tag_dict[k]])) - if len(conditions) > 0: - mask = nw.scalar_multiply( - mask, - nw.scalar_add(*conditions) - ) + mask = nw.scalar_multiply( + mask, + tag_mask(nw, tag) + ) if altitude_range is not None: z = (nw.new_node(Nodes.SeparateXYZ, [nw.new_node(Nodes.InputPosition)]), 2) start, end = altitude_range diff --git a/infinigen/core/placement/detail.py b/infinigen/core/placement/detail.py index 082522d6c..e4a4c37c4 100644 --- a/infinigen/core/placement/detail.py +++ b/infinigen/core/placement/detail.py @@ -78,7 +78,7 @@ def remesh_with_attrs(obj, face_size, apply=True, min_remesh_size=None, attribut logger.debug(f'remesh_with_attrs on {obj.name=} with {face_size=:.4f} {attributes=}') temp_copy = deep_clone_obj(obj) - + remesh_size = face_size if min_remesh_size is None else max(face_size, min_remesh_size) butil.modify_mesh(obj, type='REMESH', apply=True, voxel_size=remesh_size) @@ -96,7 +96,8 @@ def sharp_remesh_with_attrs(obj, face_size, apply=True, min_remesh_size=None, at remesh_size = face_size if min_remesh_size is None else max(face_size, min_remesh_size) butil.modify_mesh(obj, 'REMESH', apply=apply, mode='SHARP', - octree_depth=int(np.ceil(np.log2((max(obj.dimensions) + .01) / remesh_size)))) + octree_depth=int(np.ceil(np.log2((max(obj.dimensions) + .01) / remesh_size))), + use_remove_disconnected=False) transfer_attributes.transfer_all(source=temp_copy, target=obj, attributes=attributes, uvs=True) bpy.data.objects.remove(temp_copy, do_unlink=True) @@ -113,7 +114,7 @@ def subdivide_to_face_size(obj, from_facesize, to_facesize, apply=True, max_leve logger.warn(f'subdivide_to_facesize({obj.name=}, {from_facesize=:.6f}, {to_facesize=:.6f}) attempted {levels=}, clamping to {max_levels=}') levels = max_levels logger.debug(f'subdivide_to_face_size applying {levels=} of subsurf to {obj.name=}') - _, mod = butil.modify_mesh(obj, 'SUBSURF', apply=apply, + _, mod = butil.modify_mesh(obj, 'SUBSURF', apply=apply, levels=levels, render_levels=levels, return_mod=True) return mod # None if apply=True @@ -142,7 +143,7 @@ def min_max_edgelen(mesh): def adapt_mesh_resolution(obj, face_size, method, approx=0.2, **kwargs): - + assert obj.type == 'MESH' assert 0 <= approx and approx <= 0.5 @@ -173,4 +174,4 @@ def adapt_mesh_resolution(obj, face_size, method, approx=0.2, **kwargs): elif method == 'sharp_remesh': sharp_remesh_with_attrs(obj, face_size, **kwargs) else: - raise ValueError(f'Unrecognized adapt_mesh_resolution(..., {method=})') \ No newline at end of file + raise ValueError(f'Unrecognized adapt_mesh_resolution(..., {method=})') diff --git a/infinigen/core/placement/factory.py b/infinigen/core/placement/factory.py index d7d68c888..393ece294 100644 --- a/infinigen/core/placement/factory.py +++ b/infinigen/core/placement/factory.py @@ -1,7 +1,8 @@ # 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. +# 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: +# Authors: # - Alexander Raistrick: AssetFactory, make_asset_collection # - Lahav Lipson: quickly_resample @@ -17,13 +18,14 @@ from infinigen.core.util import blender as butil from infinigen.core.util.math import FixedSeed, int_hash from . import detail +from ...assets.utils.object import center logger = logging.getLogger(__name__) + class AssetFactory: def __init__(self, factory_seed=None, coarse=False): - self.factory_seed = factory_seed if self.factory_seed is None: self.factory_seed = np.random.randint(1e9) @@ -51,8 +53,11 @@ def finalize_placeholders(self, placeholders: typing.List[bpy.types.Object]): def asset_parameters(self, distance: float, vis_distance: float) -> dict: # Optionally, override to determine the **params input of create_asset w.r.t. camera distance - return {'face_size': detail.target_face_size(distance), 'distance': distance, - 'vis_distance': vis_distance} + return { + 'face_size': detail.target_face_size(distance), + 'distance': distance, + 'vis_distance': vis_distance + } def create_asset(self, **params) -> bpy.types.Object: # Override this function to produce a high detail asset @@ -78,16 +83,20 @@ def spawn_placeholder(self, i, loc, rot): obj.rotation_euler = rot else: logger.debug(f'Not assigning placeholder {obj.name=} location due to presence of' - 'location-sensitive constraint, typically a follow curve') + 'location-sensitive constraint, typically a follow curve') obj.name = f'{repr(self)}.spawn_placeholder({i})' if obj.parent is not None: - logger.warning(f'{obj.name=} has no-none parent {obj.parent.name=}, this may cause it not to get populated') + logger.warning( + f'{obj.name=} has no-none parent {obj.parent.name=}, this may cause it not to get populated') return obj def spawn_asset(self, i, placeholder=None, distance=None, vis_distance=0, loc=(0, 0, 0), rot=(0, 0, 0), **kwargs): + + if not isinstance(i, int): + raise TypeError(f'{i=} {type(i)=}, expected int') # Not intended to be overridden - override create_asset instead logger.debug(f'{self}.spawn_asset({i}...)') @@ -98,13 +107,14 @@ def spawn_asset(self, i, placeholder=None, distance=None, vis_distance=0, loc=(0 if self.coarse: raise ValueError('Attempted to spawn_asset() on an AssetFactory(coarse=True)') - if placeholder is None: + user_provided_placeholder = placeholder is not None + + if user_provided_placeholder: + assert loc == (0, 0, 0) and rot == (0, 0, 0) + else: placeholder = self.spawn_placeholder(i=i, loc=loc, rot=rot) self.finalize_placeholders([placeholder]) - keep_placeholder = False - else: - keep_placeholder = True - assert loc == (0, 0, 0) and rot == (0, 0, 0) + gc_targets = [bpy.data.meshes, bpy.data.textures, bpy.data.node_groups, bpy.data.materials] @@ -115,7 +125,7 @@ def spawn_asset(self, i, placeholder=None, distance=None, vis_distance=0, loc=(0 obj.name = f'{repr(self)}.spawn_asset({i})' - if keep_placeholder: + if user_provided_placeholder: if obj is not placeholder: if obj.parent is None: butil.parent_to(obj, placeholder, no_inverse=True) @@ -130,10 +140,12 @@ def spawn_asset(self, i, placeholder=None, distance=None, vis_distance=0, loc=(0 return obj __call__ = spawn_asset # for convinience - -def make_asset_collection(spawn_fns, n, name=None, weights=None, as_list=False, verbose=True, **kwargs): + def post_init(self): + pass +def make_asset_collection(spawn_fns, n, name=None, weights=None, as_list=False, verbose=True, centered=False, + **kwargs): if not isinstance(spawn_fns, list): spawn_fns = [spawn_fns] if weights is None: @@ -151,13 +163,16 @@ def make_asset_collection(spawn_fns, n, name=None, weights=None, as_list=False, for i in r: fn_idx = np.random.choice(np.arange(len(spawn_fns)), p=weights) obj = spawn_fns[fn_idx](i=i, **kwargs) + if centered: + obj.location = -center(obj) + butil.apply_transform(obj, True) objs[fn_idx].append(obj) - + for os, f in zip(objs, spawn_fns): if hasattr(f, 'finalize_assets'): f.finalize_assets(os) - objs = sum(objs, start=[]) + objs = sum(objs, start=[]) if as_list: return objs @@ -166,3 +181,4 @@ def make_asset_collection(spawn_fns, n, name=None, weights=None, as_list=False, col.hide_viewport = True col.hide_render = True return col + diff --git a/infinigen/core/placement/instance_scatter.py b/infinigen/core/placement/instance_scatter.py index 1183be515..ace0c62d9 100644 --- a/infinigen/core/placement/instance_scatter.py +++ b/infinigen/core/placement/instance_scatter.py @@ -99,7 +99,7 @@ def geo_instance_scatter( scaling=Vector((1, 1, 1)), normal=None, normal_fac=1, rotation_offset=None, selection=True, taper_scale=False, taper_density=False, ground_offset=0, instance_index=None, - transform_space='RELATIVE', reset_children=True + transform_space='RELATIVE', reset_children=True, realize=False ): base_geo = nw.new_node(Nodes.ObjectInfo, [base_obj], attrs={'transform_space':transform_space}).outputs['Geometry'] @@ -174,15 +174,16 @@ def geo_instance_scatter( if ground_offset != 0: instances = nw.new_node(Nodes.TranslateInstances, [instances], input_kwargs={ "Translation": nw.combine(0, 0, point_fields['ground_offset']), "Local Space": True}) - - instances = nw.new_node(Nodes.SetShadeSmooth, input_kwargs={'Geometry': instances, 'Shade Smooth': False}) + + if realize: + instances = nw.new_node(Nodes.RealizeInstances, [instances]) nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': instances}) def scatter_instances( collection, density=None, vol_density=None, max_density=5000, - scale=None, scale_rand=0, scale_rand_axi=0, + scale=None, scale_rand=0, scale_rand_axi=0, apply_geo=False, **kwargs ): @@ -217,6 +218,6 @@ def scaling(nw: NodeWrangler): scatter_obj = butil.spawn_vert(name) kwargs.update(dict(collection=collection, density=density)) with CountInstance(name): - surface.add_geomod(scatter_obj, geo_instance_scatter, apply=False, input_kwargs=kwargs) + surface.add_geomod(scatter_obj, geo_instance_scatter, apply=apply_geo, input_kwargs=kwargs) butil.put_in_collection(scatter_obj, butil.get_collection('scatters')) return scatter_obj \ No newline at end of file diff --git a/infinigen/core/placement/path_finding.py b/infinigen/core/placement/path_finding.py new file mode 100644 index 000000000..0e491d3ca --- /dev/null +++ b/infinigen/core/placement/path_finding.py @@ -0,0 +1,173 @@ +# 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: Zeyu Ma + +from tqdm import tqdm +import numpy as np +import mathutils +import itertools +import networkx as nx +from scipy.sparse import csr_matrix +import matplotlib.pyplot as plt +import os + + +def camera_rotation_matrix(pointing_direction, up_vector): + forward = pointing_direction / np.linalg.norm(pointing_direction) + right = np.cross(forward, up_vector) + right /= np.linalg.norm(right) + up = np.cross(forward, right) + up /= np.linalg.norm(up) + return np.column_stack((right, up, forward)) + +def path_finding(bvhtree, bounding_box, start_pose, end_pose, resolution=100000, margin=0.1): + volume = np.product(bounding_box[1] - bounding_box[0]) + N = np.floor((bounding_box[1] - bounding_box[0]) * (resolution / volume) ** (1/3)).astype(np.int32) + NN = np.product(N) + # print(f"{N=}") + start_location, start_rotation = start_pose + end_location, end_rotation = end_pose + margin_d = np.ceil((resolution / volume) ** (1/3) * margin) + row = [] + col = [] + data = [] + + def freespace_ray_check(a, b, margin=0): + v = b - a + location, *_ = bvhtree.ray_cast(a, v, v.length) + if location is not None: return False + if margin != 0: + if v[0] != 0: + perp = mathutils.Vector([v[1], -v[0], 0]) + else: + perp = mathutils.Vector([0, v[2], -v[1]]) + offset = v.cross(perp) + offset *= margin / offset.length + check_N = 10 + angle = np.pi * 2 / check_N + for i in range(check_N): + location, *_ = bvhtree.ray_cast(a + offset, v, v.length) + if location is not None: return False + tar_direction = offset.cross(v) + tar_direction *= margin / tar_direction.length + offset = offset * np.cos(angle) + tar_direction * np.sin(angle) + return True + + def index(i, j, k): + return i * N[1] * N[2] + j * N[2] + k + + x, y, z = np.meshgrid(np.arange(N[0]), np.arange(N[1]), np.arange(N[2]), indexing="ij") + x = bounding_box[0][0] + (bounding_box[1][0]-bounding_box[0][0]) * (x+0.5) / N[0] + y = bounding_box[0][1] + (bounding_box[1][1]-bounding_box[0][1]) * (y+0.5) / N[1] + z = bounding_box[0][2] + (bounding_box[1][2]-bounding_box[0][2]) * (z+0.5) / N[2] + x, y, z = x.reshape(-1), y.reshape(-1), z.reshape(-1) + + start_index = index(*np.floor((np.array(start_location) - bounding_box[0]) / (bounding_box[1] - bounding_box[0]) * N).astype(np.int32)) + end_index = index(*np.floor((np.array(end_location) - bounding_box[0]) / (bounding_box[1] - bounding_box[0]) * N).astype(np.int32)) + if end_index == start_index: return None + + x[start_index] = start_pose[0].x + y[start_index] = start_pose[0].y + z[start_index] = start_pose[0].z + x[end_index] = end_pose[0].x + y[end_index] = end_pose[0].y + z[end_index] = end_pose[0].z + + penalty = 99 + for i, j, k in list(itertools.product(range(N[0]), range(N[1]), range(N[2]))): + index_ijk = index(i, j, k) + pos_from = mathutils.Vector([x[index_ijk], y[index_ijk], z[index_ijk]]) + for di, dj, dk in [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0], [0, 1, 1], [1, 0, 1], [1, -1, 0], [0, 1, -1], [1, 0, -1]]: + ni, nj, nk = i+di, j+dj, k+dk + if ni >= 0 and nj >= 0 and nk >= 0 and ni < N[0] and nj < N[1] and nk < N[2]: + index_nijk = index(ni, nj, nk) + pos_to = mathutils.Vector([x[index_nijk], y[index_nijk], z[index_nijk]]) + connected = freespace_ray_check(pos_from, pos_to) + if connected: + row.append(index_ijk) + col.append(index_nijk) + data.append(1 if dk == 0 else penalty) + row.append(index_nijk) + col.append(index_ijk) + data.append(1 if dk == 0 else penalty) + + row = np.array(row) + col = np.array(col) + data = np.array(data) + + A = csr_matrix((data, (row, col)), shape=(NN, NN)) + G = nx.from_scipy_sparse_array(A) + + boundaries = [] + n_neighbors = np.array(A.sum(axis=0))[0] + + for i in range(NN): + if n_neighbors[i] != 8 + 10 * penalty: + boundaries.append(i) + + lengths_dict = nx.multi_source_dijkstra_path_length(G, boundaries, weight="weight") + lengths = np.zeros(NN) + np.inf + for n in lengths_dict: + lengths[n] = lengths_dict[n] + + mask1 = (lengths[row] >= margin_d) + mask2 = (lengths[col] >= margin_d) + row = row[mask1 & mask2] + col = col[mask1 & mask2] + data = data[mask1 & mask2] + + A = csr_matrix((data, (row, col)), shape=(NN, NN)) + G = nx.from_scipy_sparse_array(A) + + + try: + path = nx.shortest_path(G, start_index, end_index, weight="weight") + except: + return None + + stack = [start_index] + + for p in path[1:]: + back = 0 + while freespace_ray_check(mathutils.Vector([x[stack[-1-back]], y[stack[-1-back]], z[stack[-1-back]]]), mathutils.Vector([x[p], y[p], z[p]]), margin=margin): + back += 1 + if back == len(stack): + break + if back != 1: + stack = stack[:1-back] + stack.append(p) + + locations = [] + lengths = [] + for i, p in enumerate(stack): + if i == 0: + locations.append(start_pose[0]) + elif i == len(stack) - 1: + locations.append(end_pose[0]) + else: + locations.append(mathutils.Vector([x[p], y[p], z[p]])) + if len(locations) >= 2: lengths.append((locations[-1] - locations[-2]).length) + keyframed_poses = [] + + for i in range(len(stack)): + if i == 0: + keyframed_poses.append((0, *start_pose)) + else: + if i == len(stack) - 1: + rotation_euler = end_pose[1] + else: + rotation_matrix = mathutils.Matrix(camera_rotation_matrix(np.array(locations[i] - locations[i-1]), np.array([0, 0, 1]))) @ mathutils.Matrix([[1, 0, 0], [0, -1, 0], [0, 0, -1]]) + rotation_euler = rotation_matrix.to_euler() + if rotation_euler.y != 0: + rotation_euler.y = 0 + rotation_euler.x += np.pi + rotation_euler.z += np.pi + angle_differece = [ + abs(rotation_euler.z - 2 * np.pi - keyframed_poses[i-1][2].z), + abs(rotation_euler.z - keyframed_poses[i-1][2].z), + abs(rotation_euler.z + 2 * np.pi - keyframed_poses[i-1][2].z), + ] + rotation_euler.z += (np.argmin(angle_differece) - 1) * 2 * np.pi + keyframed_poses.append((np.sum(lengths[:i]), locations[i], rotation_euler)) + return keyframed_poses \ No newline at end of file diff --git a/infinigen/core/placement/placement.py b/infinigen/core/placement/placement.py index def1b9d12..c76e44a5d 100644 --- a/infinigen/core/placement/placement.py +++ b/infinigen/core/placement/placement.py @@ -3,7 +3,6 @@ # Authors: Alexander Raistrick - import re import logging from collections import defaultdict @@ -65,7 +64,7 @@ def placeholder_locs(terrain, overall_density, selection, distance_min=0, altitu return locations -def points_near_camera(cam, terrain_bvh, n, alt, dist_range): +def points_near_camera(cam, scene_bvh, n, alt, dist_range): points = [] while len(points) < n: @@ -75,7 +74,7 @@ def points_near_camera(cam, terrain_bvh, n, alt, dist_range): off = rad * mathutils.Vector((np.cos(angle), np.sin(angle), 0)) pos = cam.location + off - pos, *_ = terrain_bvh.ray_cast(pos, mathutils.Vector((0, 0, -1))) + pos, *_ = scene_bvh.ray_cast(pos, mathutils.Vector((0, 0, -1))) if pos is None: continue pos.z += alt @@ -122,7 +121,7 @@ def get_placeholder_points(obj): return np.array([obj.matrix_world.translation]).reshape(1, 3) def parse_asset_name(name): - match = re.fullmatch('(.*)\((\d+)\)\.spawn_(.*)\((\d+)\)', name) + match = re.fullmatch('(.*)\((\d+)\)\..*_(.*)\((\d+)\)', name) if not match: return None, None, None, None return list(match.groups()) @@ -244,7 +243,7 @@ def populate_all(factory_class, camera, dist_cull=200, vis_cull=0, cache_system return results -def make_placeholders_float(placeholder_col, terrain_bvh, water): +def make_placeholders_float(placeholder_col, scene_bvh, water): deps = bpy.context.evaluated_depsgraph_get() water_bvh = mathutils.bvhtree.BVHTree.FromObject(water, deps) @@ -254,7 +253,7 @@ def make_placeholders_float(placeholder_col, terrain_bvh, water): for p in tqdm(placeholder_col.objects, desc=f'Computing fluid-floating locations for {placeholder_col.name=}'): w_up, *_ = water_bvh.ray_cast(p.location + margin, up) if w_up is not None: - t_up, *_ = terrain_bvh.ray_cast(p.location + margin, up) + t_up, *_ = scene_bvh.ray_cast(p.location + margin, up) z = min(w_up.z, t_up.z) if t_up is not None else w_up.z z = max(p.location.z, z - 0.7) # the origin will be the creature's foot, allow some space for the rest of it p.location.z = np.random.uniform(p.location.z, z) diff --git a/infinigen/core/rendering/post_render.py b/infinigen/core/rendering/post_render.py index 31cfecf04..dfeb46c51 100644 --- a/infinigen/core/rendering/post_render.py +++ b/infinigen/core/rendering/post_render.py @@ -6,17 +6,23 @@ import argparse import os +import logging # ruff: noqa: E402 os.environ["OPENCV_IO_ENABLE_OPENEXR"]="1" # This must be done BEFORE import cv2. import cv2 import colorsys + import numpy as np from matplotlib import pyplot as plt from pathlib import Path from imageio import imwrite +import flow_vis + +logger = logging.getLogger(__name__) + def load_exr(path): assert Path(path).exists() and Path(path).suffix == ".exr", path return cv2.imread(str(path), cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH) @@ -28,7 +34,6 @@ def load_exr(path): load_uniq_inst = lambda p: load_exr(p).view(np.int32) def colorize_flow(optical_flow): - import flow_vis flow_uv = optical_flow[...,:2] flow_color = flow_vis.flow_to_color(flow_uv, convert_to_bgr=False) return flow_color diff --git a/infinigen/core/rendering/render.py b/infinigen/core/rendering/render.py index 6b27a2a9f..e248b66ac 100644 --- a/infinigen/core/rendering/render.py +++ b/infinigen/core/rendering/render.py @@ -1,7 +1,7 @@ # 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: +# Authors: # - Lahav Lipson - Render, flat shading, etc # - Alex Raistrick - Compositing # - Hei Law - Initial version @@ -12,6 +12,7 @@ import os import time import warnings +from pathlib import Path import bpy import gin @@ -104,11 +105,11 @@ def compositor_postprocessing(nw, source, show=True, autoexpose=False, autoexpos if distort > 0: source = nw.new_node(Nodes.LensDistortion, input_kwargs={'Image': source, 'Dispersion': distort}) - + if color_correct: source = nw.new_node(Nodes.BrightContrast, input_kwargs={'Image': source, 'Bright': 1.0, 'Contrast': 4.0}) - + if glare: source = nw.new_node( Nodes.Glare, @@ -127,12 +128,12 @@ def compositor_postprocessing(nw, source, show=True, autoexpose=False, autoexpos @gin.configurable def configure_compositor_output( - nw, - frames_folder, - image_denoised, - image_noisy, - passes_to_save, - saving_ground_truth, + nw, + frames_folder, + image_denoised, + image_noisy, + passes_to_save, + saving_ground_truth, ): file_output_node = nw.new_node(Nodes.OutputFile, attrs={ @@ -262,8 +263,12 @@ def postprocess_blendergt_outputs(frames_folder, output_stem): np.save(flow_dst_path.with_name(f"InstanceSegmentation{output_stem}.npy"), uniq_inst_array) imwrite(uniq_inst_path.with_name(f"InstanceSegmentation{output_stem}.png"), colorize_int_array(uniq_inst_array)) uniq_inst_path.unlink() - -def configure_compositor(frames_folder, passes_to_save, flat_shading): + +def configure_compositor( + frames_folder: Path, + passes_to_save: list, + flat_shading: bool, +): compositor_node_tree = bpy.context.scene.node_tree nw = NodeWrangler(compositor_node_tree) diff --git a/infinigen/core/rendering/resample.py b/infinigen/core/rendering/resample.py index cf2cf8d1f..c3f098516 100644 --- a/infinigen/core/rendering/resample.py +++ b/infinigen/core/rendering/resample.py @@ -1,4 +1,3 @@ - # 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. diff --git a/infinigen/core/surface.py b/infinigen/core/surface.py index 609439762..5b1302f55 100644 --- a/infinigen/core/surface.py +++ b/infinigen/core/surface.py @@ -1,10 +1,11 @@ # 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. +# 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: +# Authors: # - Alex Raistrick: primary author # - Lahav Lipson: Surface mixing - +# - Lingjie Mei: attributes and geo nodes import string from collections import defaultdict @@ -18,9 +19,12 @@ from tqdm import trange from infinigen.core.util import blender as butil -from infinigen.core.util.blender import set_geomod_inputs # got moved, left here for import compatibility -from infinigen.core.nodes.node_wrangler import NodeWrangler, Nodes, isnode, infer_output_socket, geometry_node_group_empty_new +from infinigen.core.util.blender import set_geomod_inputs # got moved, left here for import compatibility +from infinigen.core.nodes.node_wrangler import NodeWrangler, Nodes, isnode, infer_output_socket, \ + geometry_node_group_empty_new from infinigen.core.nodes import node_info +from infinigen.core import tagging, tags as t + def remove_materials(obj): with butil.SelectObjects(obj): @@ -40,23 +44,21 @@ def attr_writer(nw, **kwargs): if data_type is None: data_type = node_info.NODETYPE_TO_DATATYPE[infer_output_socket(value).type] - capture = nw.new_node(Nodes.CaptureAttribute, - attrs={'data_type': data_type}, - input_kwargs={ - 'Geometry': nw.new_node(Nodes.GroupInput), - 'Value': value + capture = nw.new_node(Nodes.CaptureAttribute, attrs={'data_type': data_type}, + input_kwargs={'Geometry': nw.new_node(Nodes.GroupInput), 'Value': value }) - output = nw.new_node(Nodes.GroupOutput, input_kwargs={ - 'Geometry': (capture, 'Geometry'), - name: (capture, 'Attribute') - }) + output = nw.new_node(Nodes.GroupOutput, + input_kwargs={'Geometry': (capture, 'Geometry'), name: (capture, 'Attribute') + }) mod = add_geomod(objs, attr_writer, name=f'write_attribute({name})', apply=apply, attributes=[name]) - return name + return name + -def read_attr_data(obj, attr, domain='POINT') -> np.array: +def read_attr_data(obj, attr, domain='POINT', result_dtype=None) -> np.array: if isinstance(attr, str): attr = obj.data.attributes[attr] + domain = attr.domain if domain == 'POINT': n = len(obj.data.vertices) @@ -66,13 +68,26 @@ def read_attr_data(obj, attr, domain='POINT') -> np.array: n = len(obj.data.polygons) else: raise NotImplementedError - dtype = attr.data_type - dim = node_info.DATATYPE_DIMS[dtype] - field = node_info.DATATYPE_FIELDS[dtype] - data = np.empty(n * dim) + dim = node_info.DATATYPE_DIMS[attr.data_type] + field = node_info.DATATYPE_FIELDS[attr.data_type] + + if result_dtype is None: + result_dtype = node_info.DATATYPE_TO_PYTYPE[attr.data_type] + + data = np.empty(n * dim, dtype=result_dtype) attr.data.foreach_get(field, data) - return data.reshape(-1, dim) + + if dim > 1: + data = data.reshape(-1, dim) + + return data + + +def set_active(obj, name): + attributes = obj.data.attributes + attributes.active_index = next((i for i, a in enumerate(attributes) if a.name == name)) + attributes.active = attributes[attributes.active_index] def write_attr_data(obj, attr, data: np.array, type='FLOAT', domain='POINT'): @@ -85,9 +100,10 @@ def write_attr_data(obj, attr, data: np.array, type='FLOAT', domain='POINT'): field = node_info.DATATYPE_FIELDS[attr.data_type] attr.data.foreach_set(field, data.reshape(-1)) + def new_attr_data(obj, attr, type, domain, data: np.array): - assert(isinstance(attr, str)) - assert(attr not in obj.data.attributes) + assert (isinstance(attr, str)) + assert (attr not in obj.data.attributes) obj.data.attributes.new(name=attr, type=type, domain=domain) attr = obj.data.attributes[attr] @@ -126,7 +142,8 @@ def attribute_to_vertex_group(obj, attr, name=None, min_thresh=0, binary=False): if attr_data.shape[-1] != 1: raise ValueError( - f'Could not convert non-scalar attribute {attr} to vertex group, expected 1 data dimension but got {attr_data.shape=}') + f'Could not convert non-scalar attribute {attr} to vertex group, expected 1 data dimension but ' + f'got {attr_data.shape=}') group = obj.vertex_groups.new(name=name) @@ -180,8 +197,9 @@ def shaderfunc_to_material(shader_func, *args, name=None, **kwargs): material.node_tree.nodes.remove(material.node_tree.nodes['Principled BSDF']) # remove the default BSDF nw = NodeWrangler(material.node_tree) - new_node_tree = shader_func(nw, *args, **kwargs) + new_node_tree = shader_func(nw, *args, **kwargs) + if new_node_tree is not None: if isinstance(new_node_tree, tuple) and isnode(new_node_tree[1]): new_node_tree, volume = new_node_tree @@ -209,15 +227,18 @@ def add_material(objs, shader_func, selection=None, input_args=None, input_kwarg if (not reuse) and (name in bpy.data.materials): name += f"_{seed_generator(8)}" material = shaderfunc_to_material(shader_func, *input_args, **input_kwargs) - elif isinstance(selection, str): + elif isinstance(selection, (str, t.Semantics)): + if isinstance(selection, t.Semantics): + selection = selection.value name = "MixedSurface" if name in objs[0].data.materials: material = objs[0].data.materials[name] else: material = bpy.data.materials.new(name=name) material.use_nodes = True - material.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = (1, 0, 1, 1) # Set Magenta + material.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = ( + 1, 0, 1, 1) # Set Magenta objs[0].active_material = material nw = NodeWrangler(material.node_tree) @@ -229,7 +250,8 @@ def add_material(objs, shader_func, selection=None, input_args=None, input_kwarg socket_index_old = 2 else: socket_index_old = 0 - new_attribute_sum_node = nw.scalar_add((old_attribute_sum_node, socket_index_old), (new_attribute_node, 2)) + new_attribute_sum_node = nw.scalar_add((old_attribute_sum_node, socket_index_old), + (new_attribute_node, 2)) old_attribute_sum_node.name = "Attribute Sum Old" new_attribute_sum_node.name = "Attribute Sum" else: @@ -243,16 +265,14 @@ def add_material(objs, shader_func, selection=None, input_args=None, input_kwarg socket_index_new = 2 else: socket_index_new = 0 - selection_weight = nw.divide2( - (new_attribute_node, 2), - (new_attribute_sum_node, socket_index_new) - ) + selection_weight = nw.divide2((new_attribute_node, 2), (new_attribute_sum_node, socket_index_new)) # spawn in the node tree to mix with it new_node_tree = shader_func(nw, **input_kwargs) if new_node_tree is None: raise ValueError( - f'{shader_func} returned None while attempting add_material(selection=...). Shaderfunc must return its output to be mixable') + f'{shader_func} returned None while attempting add_material(selection=...). Shaderfunc must ' + f'return its output to be mixable') if isinstance(new_node_tree, tuple) and isnode(new_node_tree[1]): new_node_tree, volume = new_node_tree nw.new_node(Nodes.MaterialOutput, input_kwargs={'Volume': volume}) @@ -267,11 +287,9 @@ def add_material(objs, shader_func, selection=None, input_args=None, input_kwarg obj.active_material = material return material -def add_geomod(objs, geo_func, - name=None, apply=False, reuse=False, input_args=None, - input_kwargs=None, attributes=None, show_viewport=True, selection=None, - domains=None, input_attributes=None, ): - + +def add_geomod(objs, geo_func, name=None, apply=False, reuse=False, input_args=None, input_kwargs=None, + attributes=None, show_viewport=True, selection=None, domains=None, input_attributes=None, ): if input_args is None: input_args = [] if input_kwargs is None: @@ -295,7 +313,6 @@ def add_geomod(objs, geo_func, ng = None for obj in objs: - mod = obj.modifiers.new(name=name, type='NODES') mod.show_viewport = False @@ -323,7 +340,8 @@ def add_geomod(objs, geo_func, identifiers = [outputs[i].identifier for i in range(len(outputs)) if outputs[i].type != 'GEOMETRY'] if len(identifiers) != len(attributes): raise Exception( - f"has {len(identifiers)} identifiers, but {len(attributes)} attributes. Specifically, {identifiers=} and {attributes=}") + f"has {len(identifiers)} identifiers, but {len(attributes)} attributes. Specifically, " + f"{identifiers=} and {attributes=}") for id, att_name in zip(identifiers, attributes): # attributes are a 1-indexed list, and Geometry is the first element, so we start from 2 # while f'Output_{i}_attribute_name' not in @@ -364,12 +382,9 @@ def __init__(self): def get_surface(name): if name == '': return NoApply - - prefixes = [ - 'infinigen.infinigen_gpl.surfaces', - 'infinigen.assets.materials', - 'infinigen.assets.scatters' - ] + + prefixes = ['infinigen.infinigen_gpl.surfaces', 'infinigen.assets.materials', + 'infinigen.assets.scatters'] for prefix in prefixes: try: return importlib.import_module('.' + name, prefix) @@ -385,7 +400,6 @@ def sample_registry(registry): @gin.configurable('registry') def initialize_from_gin(self, smooth_categories=0, **gin_category_info): - if smooth_categories != 0: raise NotImplementedError @@ -398,14 +412,14 @@ def __call__(self, category_key): if self._registry is None: raise ValueError( 'Surface registry has not been initialized! Have you loaded gin and called .initialize()?' - 'Note, this step cannot happen at module initialization time, as gin is not yet loaded' - ) + 'Note, this step cannot happen at module initialization time, as gin is not yet loaded') if category_key not in self._registry: raise KeyError( - f'registry recieved request with {category_key=}, but no gin_config for this key was provided. {self._registry.keys()=}') + f'registry recieved request with {category_key=}, but no gin_config for this key was ' + f'provided. {self._registry.keys()=}') - return self.sample_registry(self._registry[category_key]) + return self.sample_registry(self._registry[category_key]) registry = Registry() diff --git a/infinigen/core/tagging.py b/infinigen/core/tagging.py new file mode 100644 index 000000000..1ec08b5cd --- /dev/null +++ b/infinigen/core/tagging.py @@ -0,0 +1,428 @@ +# 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: Yihan Wang, Karhan Kayan: face based tagging, canonical surface tagging, mask extraction + + +import os +import bpy +import json +import logging + +import numpy as np +import infinigen.core.util.blender as butil +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core import surface + +from . import tags as t + +from typing import Union, Any + +logger = logging.getLogger(__name__) + +PREFIX = 'TAG_' +COMBINED_ATTR_NAME = 'MaskTag' + + +class AutoTag(): + tag_dict = {} + + def __init__(self): + self.tag_dict = {} + + def clear(self): + self.tag_dict = {} + + # This function now only supports APPLIED OBJECTS + # PLEASE KEEP ALL THE GEOMETRY APPLIED BEFORE SCATTERING THEM ON THE TERRAIN + # PLEASE DO NOT USE BOOLEAN TAGS FOR OTHER USE + def save_tag(self, path='./MaskTag.json'): + with open(path, 'w') as f: + json.dump(self.tag_dict, f) + + def load_tag(self, path='./MaskTag.json'): + with open(path, 'r') as f: + self.tag_dict = json.load(f) + + def _extract_incoming_tagmasks(self, obj): + + new_attr_names = [ + name for name in obj.data.attributes.keys() + if name.startswith(PREFIX) + ] + + n_poly = len(obj.data.polygons) + for name in new_attr_names: + attr = obj.data.attributes[name] + if attr.domain != 'FACE': + raise ValueError(f'Incoming attribute {obj.name=} {attr.name=} had invalid {attr.domain=}, expected FACE') + if len(attr.data) != n_poly: + raise ValueError(f'Incoming attribute {obj.name=} {attr.name=} had invalid {len(attr.data)=}, expected {n_poly=}') + + new_attrs = { + name[len(PREFIX):]: surface.read_attr_data(obj, name, 'FACE') + for name in new_attr_names + } + + for name, vals in new_attrs.items(): + if vals.dtype == bool: + continue + elif vals.dtype.kind == 'f': + new_attrs[name] = vals > 0.5 + elif vals.dtype.kind == 'i': + new_attrs[name] = vals > 0 + else: + raise ValueError(f'Incoming attribute {obj.name=} had invalid np dtype {vals.dtype} {vals.dtype.kind=}, expected float or ideally boolean ') + + + for name, arr in new_attrs.items(): + if arr.dtype != bool: + raise ValueError(f'Retrieved incoming tag mask {name=} had {arr.dtype=}, expected bool') + + for name in new_attr_names: + obj.data.attributes.remove(obj.data.attributes[name]) + + return new_attrs + + def _specialize_tag_name(self, vi, name, tag_name_lookup): + + if '.' in name: + raise ValueError(f'{name=} should not contain separator character "."') + + if vi == 0: + return name + + existing = tag_name_lookup[vi - 1] + parts = set(existing.split('.')) + + if name in parts: + return existing + + parts.add(name) + return '.'.join(sorted(list(parts))) + + def _relabel_obj_single(self, obj, tag_name_lookup): + + n_poly = len(obj.data.polygons) + new_attrs = self._extract_incoming_tagmasks(obj) + + if COMBINED_ATTR_NAME in obj.data.attributes.keys(): + domain = obj.data.attributes[COMBINED_ATTR_NAME].domain + if domain != 'FACE': + raise ValueError(f'{obj.name=} had {COMBINED_ATTR_NAME} on {domain=}, expected FACE') + tagint = surface.read_attr_data(obj, COMBINED_ATTR_NAME, domain='FACE') + else: + tagint = np.full(n_poly, 0, np.int64) + + assert tagint.dtype == np.int64, tagint.dtype + + for name, new_mask in new_attrs.items(): + + affected_tagints = np.unique(tagint[new_mask]) + + for vi in affected_tagints: + + affected_mask = new_mask * (tagint == vi) + if not affected_mask.any(): + continue + + new_tag_name = self._specialize_tag_name(vi, name, tag_name_lookup) + tag_value = self.tag_dict.get(new_tag_name) + + if tag_value is None: + tag_value = len(self.tag_dict) + 1 + self.tag_dict[new_tag_name] = tag_value + tag_name_lookup.append(new_tag_name) + + assert len(self.tag_dict) == len(tag_name_lookup), \ + f'{len(self.tag_dict)=} yet {len(tag_name_lookup)=}, out of sync at {vi=} {new_tag_name=}' + assert new_tag_name in tag_name_lookup + + logger.debug(f"{self._relabel_obj_single.__name__} updating {vi=} to {new_tag_name=} with {affected_mask.mean()=:.2f} for {obj.name=}") + + tagint[affected_mask] = tag_value + + if COMBINED_ATTR_NAME not in obj.data.attributes.keys(): + mask_tag_attr = obj.data.attributes.new(COMBINED_ATTR_NAME, 'INT', 'FACE') + else: + mask_tag_attr = obj.data.attributes[COMBINED_ATTR_NAME] + + mask_tag_attr.data.foreach_set('value', tagint) + + + def relabel_obj(self, root_obj): + + tag_name_lookup = [None] * len(self.tag_dict) + + for name, tag_id in self.tag_dict.items(): + key = tag_id - 1 + if key >= len(tag_name_lookup): + raise IndexError(f'{name} had {tag_id=} {key=} yet {len(self.tag_dict)=}') + if tag_name_lookup[key] is not None: + raise ValueError(f'{name=} {tag_id=} {key=} attempted to overwrite {tag_name_lookup[key]=}') + tag_name_lookup[key] = name + + for obj in butil.iter_object_tree(root_obj): + if obj.type != 'MESH': + continue + self._relabel_obj_single(obj, tag_name_lookup) + + return root_obj + +tag_system = AutoTag() + +def print_segments_summary(obj: bpy.types.Object): + + tagint = surface.read_attr_data(obj, COMBINED_ATTR_NAME, domain='FACE') + + results = [] + for vi in np.unique(tagint): + mask = (tagint == vi) + results.append((vi, mask.mean())) + + results.sort(key=lambda x: x[1], reverse=True) + + print(f'Tag Segments Summary for {obj.name=}') + for vi, mean in results: + name = _name_for_tagval(vi) + print(f' {mean*100:.1f}% {vi=} {name}') + +def tag_object(obj, name=None, mask=None): + + if name is not None: + name = t.to_string(name) + + for o in butil.iter_object_tree(obj): + + if o.type != 'MESH': + continue + + if name is not None: + + n_poly = len(o.data.polygons) + + if n_poly == 0: + logger.debug(f'{tag_object.__name__} had {n_poly=} for {o.name=} {name=} child of {obj.name=}') + continue + + mask_o = np.full(n_poly, 1, dtype=bool) if mask is None else mask + + assert isinstance(mask_o, np.ndarray) + assert len(mask_o) == n_poly + + logger.debug(f'{tag_object.__name__} applying {name=} {mask_o.mean()=:.2f} to {o.name=}') + surface.write_attr_data( + obj=o, + attr=(PREFIX + name), + data=mask_o, + type='BOOLEAN', + domain='FACE' + ) + + tag_system.relabel_obj(obj) + +def vert_mask_to_tri_mask(obj, vert_mask, require_all=True): + + arr = np.zeros(len(obj.data.polygons) * 3) + obj.data.polygons.foreach_get('vertices', arr) + face_vert_idxs = arr.reshape(-1, 3).astype(int) + + if require_all: + return ( + vert_mask[face_vert_idxs[:, 0]] * + vert_mask[face_vert_idxs[:, 1]] * + vert_mask[face_vert_idxs[:, 2]] + ) + else: + return ( + vert_mask[face_vert_idxs[:, 0]] | + vert_mask[face_vert_idxs[:, 1]] | + vert_mask[face_vert_idxs[:, 2]] + ) + +CANONICAL_TAGS = [t.Subpart.Back, t.Subpart.Front, t.Subpart.Top, t.Subpart.Bottom] +CANONICAL_TAG_MEANINGS = { + t.Subpart.Back: (np.min, 0), + t.Subpart.Front: (np.max, 0), + t.Subpart.Bottom: (np.min, 2), + t.Subpart.Top: (np.max, 2), +} + +def tag_canonical_surfaces(obj, rtol=0.01): + + obj.update_from_editmode() + + n_vert = len(obj.data.vertices) + n_poly = len(obj.data.polygons) + + verts = np.empty(n_vert * 3, dtype=float) + obj.data.vertices.foreach_get('co', verts) + verts = verts.reshape(n_vert, 3) + + for tag in CANONICAL_TAGS: + + gather_func, axis_idx = CANONICAL_TAG_MEANINGS[tag] + target_axis_val = gather_func(verts[:, axis_idx]) + + atol = rtol * obj.dimensions[axis_idx] + vert_mask = np.isclose(verts[:, axis_idx], target_axis_val, atol=atol) + + face_mask = vert_mask_to_tri_mask(obj, vert_mask, require_all=True) + + if not face_mask.any(): + logger.warning(f'{tag_canonical_surfaces.__name__} found got {face_mask.mean()=:.2f} for {tag=} on {obj.name=}') + + logger.debug(f'{tag_canonical_surfaces.__name__} applying {tag=} {face_mask.mean()=:.2f} to {obj.name=}') + surface.write_attr_data(obj, PREFIX + tag.value, face_mask, type='BOOLEAN', domain='FACE') + + tag_system.relabel_obj(obj) + +def tag_nodegroup(nw: NodeWrangler, input_node, name: t.Tag, selection=None): + + name = PREFIX + t.to_string(name) + sel = surface.eval_argument(nw, selection) + store_named_attribute = nw.new_node( + Nodes.StoreNamedAttribute, + input_kwargs={ + 'Geometry': input_node, + 'Name': name, + 'Selection': sel, + 'Value': True + }, + attrs={ + 'domain': 'FACE', + 'data_type': 'BOOLEAN' + } + ) + return store_named_attribute + +def _name_for_tagval(i: int) -> str | None: + + if i == 0: + # index 0 represents an untagged face + return None + + name = next( + (k for k, v in tag_system.tag_dict.items() if v == i), + None + ) + + if name is None: + raise ValueError(f'Found {name=} for {i=} in {tag_system.tag_dict=}') + + return name + +def union_object_tags(obj): + + if COMBINED_ATTR_NAME not in obj.data.attributes: + return set() + + masktag = surface.read_attr_data(obj, COMBINED_ATTR_NAME) + res = set() + for v in np.unique(masktag): + if v == 0: + continue + res = res.union(_name_for_tagval(v).split('.')) + + def try_convert(x): + try: + return t.to_tag(x) + except ValueError: + return x + + return {try_convert(x) for x in res} + +def tagged_face_mask(obj: bpy.types.Object, tags: Union[t.Subpart]) -> np.ndarray: + + # ASSUMES: object is triangulated, no quads/polygons + + tags = t.to_tag_set(tags) + pos_tags = [t.to_string(tagval) for tagval in tags if not isinstance(tagval, t.Negated)] + neg_tags = [t.to_string(tagval.tag) for tagval in tags if isinstance(tagval, t.Negated)] + del tags + + n_poly = len(obj.data.polygons) + if COMBINED_ATTR_NAME not in obj.data.attributes: + return np.ones(n_poly, dtype=bool) + masktag = surface.read_attr_data(obj, COMBINED_ATTR_NAME, domain='FACE') + face_mask = np.zeros(n_poly, dtype=bool) + + for v in np.unique(masktag): + + if v == 0: + name_parts = [] + else: + name_parts = _name_for_tagval(v).split('.') + + v_mask = (masktag == v) + + if len(pos_tags) > 0 and not all(tag in name_parts for tag in pos_tags): + continue + if len(neg_tags) > 0 and any(tag in name_parts for tag in neg_tags): + continue + + face_mask |= v_mask + + logger.debug(f'{obj.name=} had {face_mask.mean()=:.2f} for {pos_tags=} {neg_tags=}') + + return face_mask + +def extract_tagged_faces(obj: bpy.types.Object, tags: set, nonempty=False) -> bpy.types.Object: + "extract the surface that satisfies all tags" + + # Ensure we're dealing with a mesh object + if obj.type != 'MESH': + raise TypeError("Object is not a mesh!") + + face_mask = tagged_face_mask(obj, tags) + + if nonempty and not face_mask.any(): + raise ValueError(f'extract_tagged_faces({obj.name=}, {tags=}, {nonempty=}) got empty mask for {len(obj.data.polygons)}') + + return extract_mask(obj, face_mask, nonempty=nonempty) + +def extract_mask( + obj: bpy.types.Object, + face_mask: np.array, + nonempty=False +) -> bpy.types.Object: + + if not face_mask.any(): + if nonempty: + raise ValueError(f'extract_mask({obj.name=}) got empty mask') + return butil.spawn_vert() + + orig_hide_viewport = obj.hide_viewport + obj.hide_viewport = False + + # Switch to Edit mode, duplicate the selection, and separate it + with butil.SelectObjects(obj, active=0): + + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.mesh.select_all(action='DESELECT') + + for poly in obj.data.polygons: + poly.select = face_mask[poly.index] + if nonempty and len([p for p in obj.data.polygons if p.select]) == 0: + raise ValueError(f'extract_mask({obj.name=}, {nonempty=}) failed to select polygons') + + with butil.ViewportMode(obj, 'EDIT'): + bpy.ops.mesh.duplicate_move() + bpy.ops.mesh.separate(type='SELECTED') + + res = next((o for o in bpy.context.selected_objects if o != obj), None) + + obj.hide_viewport = orig_hide_viewport + + if nonempty: + if res is None: + raise ValueError(f'extract_mask({obj.name=}) got {res=} for {face_mask.mean()=}') + if len(res.data.polygons) == 0: + raise ValueError(f'extract_mask({obj.name=}) got {res=} with {len(res.data.polygons)=}') + elif res is None: + logger.warning(f'extract_mask({obj.name=}) failed to extract any faces') + return butil.spawn_vert() + + return res \ No newline at end of file diff --git a/infinigen/core/tags.py b/infinigen/core/tags.py new file mode 100644 index 000000000..f31d38f6d --- /dev/null +++ b/infinigen/core/tags.py @@ -0,0 +1,306 @@ +# 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 + +from __future__ import annotations + +from abc import ABCMeta +from enum import Enum, EnumMeta +from dataclasses import dataclass + +class ABCEnumMeta(EnumMeta, ABCMeta): + pass + +class Tag: + + def __neg__(self) -> Negated: + return Negated(self) + +class StringTag(Tag): + + def __init__(self, desc: str): + self.desc = desc + +class EnumTag(Tag, Enum, metaclass=ABCEnumMeta): + pass + +class Semantics(EnumTag): + + # Mesh types + Room = "room" + Object = "object" + Cutter = "cutter" + + # Room types + Kitchen = "kitchen" + Bedroom = 'bedroom' + LivingRoom = 'living-room' + Closet = 'closet' + Hallway = 'hallway' + Bathroom = 'bathroom' + Garage = 'garage' + Balcony = 'balcony' + DiningRoom = 'dining-room' + Utility = 'utility' + Staircase = 'staircase' + + # Object types + Furniture = "furniture" + FloorMat = "FloorMat" + WallDecoration = "wall-decoration" + HandheldItem = "handheld-item" + + # Furniture functions + Storage = "storage" + Seating = "seating" + LoungeSeating = "lounge-seating" + Table = "table" + Bathing = "bathing" + SideTable = "side-table" + Watchable = "watchable" + Desk = "desk" + Bed = "bed" + Sink = "sink" + CeilingLight = "ceiling-light" + Lighting = "lighting" + KitchenCounter = "kitchen-counter" + KitchenAppliance = "kitchen-appliance" + + # Small Object Functions + TableDisplayItem = "table-display-item" + OfficeShelfItem = "office-shelf-item" + KitchenCounterItem = "kitchen-counter-item" + FoodPantryItem = "food-pantry" + BathroomItem = "bathroom-item" + ShelfTrinket = "shelf-trinket" + Dishware = "dishware" + Cookware = "cookware" + Utensils = "utensils" + ClothDrapeItem = "cloth-drape" + + # Object Access Type + AccessTop = "access-top" + AccessFront = "access-front" + AccessAnySide = "access-any-side" + AccessAllSides = "access-all-sides" + + # Object Access Method + AccessStandingNear = "access-stand-near" + AccessSit = "access-stand-near" + AccessOpenDoor = "access-open-door" + AccessHand = "access-with-hand" + + # Special Case Objects + Chair = "chair" + Window = 'window' + Open = 'open' + Entrance = 'entrance' + Door = 'door' + StaircaseWall = 'staircase-wall' + + # Solver feature flags + # TODO these should not be in Semantics + RealPlaceholder = "real-placeholder" + AssetAsPlaceholder = "asset-as-placeholder" + AssetPlaceholderForChildren = "asset-placeholder-for-children" + PlaceholderBBox = 'placeholder-bbox' + SingleGenerator = 'single-generator' + NoRotation = 'no-rotation' + NoCollision = 'no-collision' + NoChildren = 'no-children' + + def __str__(self): + return f'{self.__class__.__name__}({self.value})' + + def __repr__(self): + return f'{self.__class__.__name__}.{self.name}' + +class Subpart(EnumTag): + SupportSurface = "support" + Interior = "interior" + Exterior = "exterior" + Visible = "visible" + Invisible = "invisible" + Bottom = "bottom" + Top = "top" + Side = "side" + Back = "back" + Front = "front" + Ceiling = "ceiling" + Wall = "wall" + + StaircaseWall = "staircase-wall" # TODO Lingjie Remove + + def __str__(self): + return f'{self.__class__.__name__}({self.value})' + + def __repr__(self): + return f'{self.__class__.__name__}.{self.name}' + +@dataclass(frozen=True) +class FromGenerator(Tag): + generator: type + + def __repr__(self): + return f'{self.__class__.__name__}({self.generator.__name__})' + +@dataclass(frozen=True) +class Negated(Tag): + tag: Tag + + def __str__(self): + return "-" + str(self.tag) + + def __repr__(self): + return f"-{repr(self.tag)}" + + def __neg__(self): + return self.tag + + def __post_init__(self): + assert not isinstance(self.tag, Negated), "dont construct double negative tags" + +@dataclass(frozen=True) +class Variable(Tag): + name: str + + def __post_init__(self): + assert isinstance(self.name, str) + + def __repr__(self): + return f'{self.__class__.__name__}({self.name})' + + def __str__(self): + return self.name + +@dataclass(frozen=True) +class SpecificObject(Tag): + name: str + +def decompose_tags(tags: set[Tag]): + + positive, negative = set(), set() + + for t in tags: + match t: + case Negated(tag): + negative.add(tag) + case _: + positive.add(t) + + return positive, negative + +def contradiction(tags: set[Tag]): + + pos, neg = decompose_tags(tags) + + if pos.intersection(neg): + return True + + if len([t for t in pos if isinstance(t, FromGenerator)]) > 1: + return True + if len([t for t in tags if isinstance(t, SpecificObject | Variable)]) > 1: + return True + + return False + +def implies(t1: set[Tag], t2: set[Tag]): + + p1, n1 = decompose_tags(t1) + p2, n2 = decompose_tags(t2) + + return ( + not contradiction(t1) + and p1.issuperset(p2) + and n1.issuperset(n2) + ) + +def satisfies(t1: set[Tag], t2: set[Tag]): + + p1, n1 = decompose_tags(t1) + p2, n2 = decompose_tags(t2) + + return ( + p1.issuperset(p2) + and not n1.intersection(p2) + and not n2.intersection(p1) + ) + +def difference(t1: set[Tag], t2: set[Tag]): + + """Return a set of predicates representing the difference + + If the difference is empty, will return a contradictory set of predicates. + """ + + p1, n1 = decompose_tags(t1) + p2, n2 = decompose_tags(t2) + + pos = p1.union(n2 - n1) + neg = n1.union(p2 - p1) + + return pos.union(Negated(n) for n in neg) + +def to_tag(s: str | Tag | type, fac_context=None) -> Tag: + + if isinstance(s, Tag): + return s + + if type(s) is type: + if not fac_context: + raise ValueError(f"to_tag got {s=} but {fac_context=}") + if s not in fac_context: + raise ValueError(f"Got {s=} of type=type but it was not in fac_context") + return FromGenerator(s) + + assert isinstance(s, str), s + + if s.startswith("-"): + return Negated(to_tag(s[1:])) + + if fac_context is not None: + fac = next((f for f in fac_context.keys() if f.__name__ == s), None) + if fac: + return FromGenerator(fac) + + s = s.strip("\"\'") + + try: + return Semantics[s] + except KeyError: + pass + + try: + return Subpart[s] + except KeyError: + pass + + raise ValueError(f"to_tag got {s=} but could not resolve it. Please see tags.Semantics and tags.Subpart for available tag strings") + +def to_string(tag: Tag | str): + + if isinstance(tag, str): + return tag + + match tag: + case Semantics() | Subpart(): + return tag.value + case StringTag(): + return tag.desc + case FromGenerator(): + return tag.__name__ + case Negated(): + raise ValueError(f'Negated tag {tag=} is not allowed here') + case _: + raise ValueError(f'to_string unhandled {tag=}') + +def to_tag_set(x, fac_context=None): + match x: + case None: + return set() + case set() | list() | tuple() | frozenset(): + return {to_tag(xi, fac_context=fac_context) for xi in x} + case x: + return {to_tag(x, fac_context=fac_context)} \ No newline at end of file diff --git a/infinigen/core/util/bevelling.py b/infinigen/core/util/bevelling.py new file mode 100644 index 000000000..c2a50372e --- /dev/null +++ b/infinigen/core/util/bevelling.py @@ -0,0 +1,79 @@ +# 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: Zeyu Ma + +import bpy +import mathutils +import bmesh +import numpy as np + +from infinigen.core.nodes.node_wrangler import Nodes +from .blender import ViewportMode + + +def special_bounds(obj): + inf = 1e5 + points = [] + for v in obj.data.vertices: + points.append(v.co) + points = np.array(points) + mask = np.sum(points ** 2, axis=-1) ** 0.5 < 0.5 * inf + return points[mask].min(axis=0), points[mask].max(axis=0) + +def on_bound_edges(points, points_min, points_max): + flags = [0, 0, 0] + eps = 1e-4 + for i in range(3): + if abs(points[i] - points_min[i]) < eps: + flags[i] = -1 + elif abs(points[i] - points_max[i]) < eps: + flags[i] = 1 + return flags + +def get_bevel_edges(obj): + inf = 1e5 + points_min, points_max = special_bounds(obj) + bm = bmesh.new() + bm.from_mesh(obj.data) + edges = [] + for edge in bm.edges: + on_bounds_flag = [0, 0, 0] + flags = [] + mags = [] + for i in range(2): + pos = np.array([edge.verts[i].co.x, edge.verts[i].co.y, edge.verts[i].co.z]) + flags.append(on_bound_edges(pos, points_min, points_max)) + mags.append(np.sum(pos ** 2) ** 0.5) + for j in range(3): + on_bounds_flag[j] = flags[0][j] != 0 and flags[0][j] == flags[1][j] + if np.sum(on_bounds_flag) >= 2: + edges.append(edge.index) + elif mags[0] > 0.5 * inf and mags[0] < 1.5 * inf: + edges.append(edge.index) + return edges + +def add_bevel(obj, edges, offset=0.03, segments=8): + with ViewportMode(obj, mode='EDIT'): + bpy.ops.mesh.select_mode(type="EDGE") + bpy.ops.mesh.select_all(action = 'DESELECT') + bm = bmesh.from_edit_mesh(obj.data) + for edge in bm.edges: + if edge.index in edges: + edge.select_set(True) + bpy.ops.mesh.bevel(offset=offset, offset_pct=0, segments=segments, release_confirm=True) + return obj + +def complete_bevel(nw, geometry, preprocess): + inf = 1e5 + geometry = nw.new_node(Nodes.RealizeInstances, [geometry]) + if not preprocess: + return geometry + return nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': (geometry, 0), 'Offset': nw.new_node(Nodes.Vector, attrs={"vector": mathutils.Vector((inf, 0, 0))})}) + +def complete_no_bevel(nw, geometry, preprocess): + inf = 1e5 + geometry = nw.new_node(Nodes.RealizeInstances, [geometry]) + if not preprocess: + return geometry + return nw.new_node(Nodes.SetPosition, input_kwargs={'Geometry': (geometry, 0), 'Offset': nw.new_node(Nodes.Vector, attrs={"vector": mathutils.Vector((2 * inf, 0, 0))})}) diff --git a/infinigen/core/util/blender.py b/infinigen/core/util/blender.py index f339fe006..6c89d854c 100644 --- a/infinigen/core/util/blender.py +++ b/infinigen/core/util/blender.py @@ -1,7 +1,7 @@ # 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: Alex Raistrick, Zeyu Ma, Lahav Lipson, Hei Law, Lingjie Mei +# Authors: Alex Raistrick, Zeyu Ma, Lahav Lipson, Hei Law, Lingjie Mei, Karhan Kayan from collections import defaultdict @@ -44,12 +44,14 @@ def deep_clone_obj(obj, keep_modifiers=False, keep_materials=False): new_obj.data.materials.pop() bpy.context.collection.objects.link(new_obj) return new_obj - + +copy = deep_clone_obj + def get_all_bpy_data_targets(): D = bpy.data return [ D.objects, D.collections, D.movieclips, D.particles, - D.meshes, D.curves, D.armatures, D.node_groups + D.meshes, D.curves, D.armatures, D.node_groups, ] class ViewportMode: @@ -93,17 +95,64 @@ def __init__(self, objects, active=0): self.saved_objs = None self.saved_active = None + def _check_selectable(self): + unlinked = [ + o for o in self.objects + if o.name not in bpy.context.scene.objects + ] + if len(unlinked) > 0: + raise ValueError(f'{SelectObjects.__name__} had objects {unlinked=} which are not in bpy.context.scene.objects and cannot be selected') + + hidden = [ + o for o in self.objects + if o.hide_viewport + ] + if len(hidden) > 0: + raise ValueError(f'{SelectObjects.__name__} had objects {hidden=} which are hidden and cannot be selected') + + def _get_intended_active(self): + if isinstance(self.active, int): + if self.active >= len(self.objects): + return None + else: + return self.objects[self.active] + else: + return self.active + + def _validate(self, error=False): + + if error: + def msg(str): + raise ValueError(str) + else: + msg = logger.warning + + difference = set(self.objects) - set(bpy.context.selected_objects) + if len(difference): + msg( + f"{SelectObjects.__name__} failed to select {self.objects=}, result was {bpy.context.selected_objects=}. " + "The most common cause is that the objects are in a collection with col.hide_viewport=True" + ) + + + intended = self._get_intended_active() + if intended is not None and bpy.context.active_object != intended: + msg( + f"{SelectObjects.__name__} failed to set active object to {intended=}, result was {bpy.context.active_object=}" + ) + def __enter__(self): self.saved_objects = list(bpy.context.selected_objects) self.saved_active = bpy.context.active_object + select_none() select(self.objects) - if len(self.objects): - if isinstance(self.active, int): - bpy.context.view_layer.objects.active = self.objects[self.active] - else: - bpy.context.view_layer.objects.active = self.active + intended = self._get_intended_active() + if intended is not None: + bpy.context.view_layer.objects.active = intended + + self._validate() def __exit__(self, *_): @@ -113,7 +162,7 @@ def enforce_not_deleted(o): return o if o.name in bpy.data.objects else None except ReferenceError: return None - + self.saved_objects = [enforce_not_deleted(o) for o in self.saved_objects] self.saved_objects = [o for o in self.saved_objects if o is not None] @@ -213,24 +262,31 @@ def select_none(): obj.select_set(False) -def select(objs): +def select(objs: bpy.types.Object | list[bpy.types.Object]): select_none() if not isinstance(objs, list): objs = [objs] for o in objs: + if o.name not in bpy.context.scene.objects: + raise ValueError(f'Object {o.name=} not in scene and cant be selected') o.select_set(True) - -def delete(objs): +def delete(objs: bpy.types.Object | list[bpy.types.Object]): if not isinstance(objs, list): objs = [objs] select_none() - select(objs) - with Suppress(): - bpy.ops.object.delete() + for obj in objs: + select(obj) + is_mesh = obj.type == 'MESH' + if is_mesh: + mesh = obj.data + with Suppress(): + bpy.ops.object.delete() + if is_mesh and mesh.users == 0: + bpy.data.meshes.remove(mesh) -def delete_collection(collection): +def delete_collection(collection: bpy.types.Collection): if collection.name in bpy.data.collections: objects = collection.objects bpy.data.collections.remove(collection) @@ -270,10 +326,18 @@ def unlink(obj): c.objects.unlink(o) -def put_in_collection(obj, collection, exclusive=True): - if exclusive: - unlink(obj) - collection.objects.link(obj) +def put_in_collection(objs, collection, exclusive=True): + if isinstance(collection, str): + collection = get_collection(collection) + if isinstance(objs, bpy.types.Object): + objs = [objs] + else: + objs = list(objs) + for o in objs: + if exclusive: + unlink(o) + collection.objects.link(o) + return collection def group_in_collection(objs, name: str, reuse=True, **kwargs): @@ -350,18 +414,63 @@ def spawn_plane(**kwargs): obj.name = name return obj -def spawn_cube(**kwargs): - name = kwargs.pop('name', None) +def spawn_cube(size=1, location=(0, 0, 0), scale=(1, 1, 1), name=None): + bpy.ops.mesh.primitive_cube_add( + size = size, enter_editmode=False, align='WORLD', - **kwargs + location=location, + scale=scale, ) obj = bpy.context.active_object if name is not None: obj.name = name return obj +def spawn_cylinder(radius=1.0, depth=2.0, location=(0, 0, 0), scale=(1, 1, 1), name=None): + + bpy.ops.mesh.primitive_cylinder_add( + radius=radius, + depth=depth, + enter_editmode=False, + align='WORLD', + location=location, + scale=scale, + ) + obj = bpy.context.active_object + if name is not None: + obj.name = name + return obj + +def spawn_sphere(radius=1, location=(0, 0, 0), scale=(1, 1, 1), name=None): + + bpy.ops.mesh.primitive_uv_sphere_add( + radius = radius, + enter_editmode=False, + align='WORLD', + location=location, + scale=scale, + ) + obj = bpy.context.active_object + if name is not None: + obj.name = name + return obj + +def spawn_icosphere(radius=1, location=(0, 0, 0), scale=(1, 1, 1), name=None): + + bpy.ops.mesh.primitive_ico_sphere_add( + radius = radius, + enter_editmode=False, + align='WORLD', + location=location, + scale=scale, + ) + obj = bpy.context.active_object + if name is not None: + obj.name = name + return obj + def clear_scene(keep=[], targets=None, materials=True): D = bpy.data if targets is None: @@ -431,7 +540,19 @@ def get_camera_res(): def set_geomod_inputs(mod, inputs: dict): assert mod.type == 'NODES' for k, v in inputs.items(): + + if k not in mod.node_group.inputs: + raise KeyError(f'Couldnt find {k=} in {mod.node_group.inputs.keys()=}') + soc = mod.node_group.inputs[k] + + if not hasattr(soc, 'default_value'): + if v is not None: + raise ValueError(f'Got non-None value {v=} for {soc.identifier=} which has no default value') + continue + elif v is None: + continue + if isinstance(soc.default_value, (float, int)): v = type(soc.default_value)(v) @@ -491,7 +612,9 @@ def import_mesh(path, **kwargs): 'obj': bpy.ops.import_scene.obj, 'fbx': bpy.ops.import_scene.fbx, 'stl': bpy.ops.import_mesh.stl, - 'ply': bpy.ops.import_mesh.ply} + 'ply': bpy.ops.import_mesh.ply, + 'usdc': bpy.ops.wm.usd_import, + } if ext not in funcs: raise ValueError( @@ -578,10 +701,11 @@ def apply_modifiers(obj, mod=None, quiet=True): con = Suppress() if quiet else nullcontext() with SelectObjects(obj), con: for m in mod: + mod_type = m.type try: bpy.ops.object.modifier_apply(modifier=m.name) except RuntimeError as e: - if m.type == 'NODES': + if mod_type == 'NODES': logging.warning(f'apply_modifers on {obj.name=} {m.name=} raised {e}, ignoring and returning empty mesh for pre-3.5 compatibility reasons') bpy.ops.object.modifier_remove(modifier=m.name) clear_mesh(obj) @@ -591,6 +715,9 @@ def apply_modifiers(obj, mod=None, quiet=True): # geometry nodes occasionally introduces empty material slots in 3.6, we consider this an error and remove them purge_empty_materials(obj) + # geometry nodes occasionally introduces empty material slots in 3.6, we consider this an error and remove them + purge_empty_materials(obj) + def recalc_normals(obj, inside=False): with ViewportMode(obj, mode='EDIT'): @@ -719,7 +846,7 @@ def blender_internal_attr(a): a = a.name if a.startswith('.'): return True - if a in ['material_index', 'uv_map', 'UVMap']: + if a in ['material_index', 'uv_map', 'UVMap', 'sharp_face']: return True return False @@ -731,7 +858,7 @@ def merge_by_distance(obj, face_size): def origin_set(objs, mode, **kwargs): with SelectObjects(objs): bpy.ops.object.origin_set(type=mode, **kwargs) - + def apply_geo(obj): with SelectObjects(obj): for m in obj.modifiers: @@ -744,6 +871,10 @@ def avg_approx_vol(objects): return np.mean([prod(list(o.dimensions)) for o in objects]) def parent_to(a, b, type='OBJECT', keep_transform=False, no_inverse=False, no_transform=False): + + if a.name == b.name: + raise ValueError(f'parent_to expects two distinct objects, got {a=} {b=}') + select_none() with SelectObjects([a, b], active=1): if no_inverse: @@ -755,7 +886,8 @@ def parent_to(a, b, type='OBJECT', keep_transform=False, no_inverse=False, no_tr a.location = (0,0,0) a.rotation_euler = (0,0,0) - assert a.parent is b + if a.parent is not b: + raise ValueError(f'parent_to({a=}, {b=}) failed, after execution we saw {a.parent=}') def apply_matrix_world(obj, verts: np.array): return mutil.dehomogenize(mutil.homogenize(verts) @ np.array(obj.matrix_world).T) @@ -785,7 +917,7 @@ def approve_all_drivers(): def count_objects(): count = 0 for obj in bpy.context.scene.objects: - if obj.type != "MESH": + if obj.type != "MESH": continue count +=1 return count @@ -800,11 +932,11 @@ def count_objects(): def count_instance(): depsgraph = bpy.context.evaluated_depsgraph_get() return len([inst for inst in depsgraph.object_instances if inst.is_instance]) - - + + def bounds(obj): - bbox = np.array(obj.bound_box) - return bbox.min(axis=0), bbox.max(axis=0) + points = np.array(obj.bound_box) + return points.min(axis=0), points.max(axis=0) def create_noise_plane(size=50, cuts=10, std=3, levels=3): bpy.ops.mesh.primitive_grid_add(size=size, x_subdivisions=cuts, y_subdivisions=cuts) @@ -821,4 +953,4 @@ def purge_empty_materials(obj): if m.material is not None: continue bpy.context.object.active_material_index = i - bpy.ops.object.material_slot_remove() \ No newline at end of file + bpy.ops.object.material_slot_remove() diff --git a/infinigen/core/util/camera.py b/infinigen/core/util/camera.py index 0c0dbff68..fc955eb51 100644 --- a/infinigen/core/util/camera.py +++ b/infinigen/core/util/camera.py @@ -1,7 +1,7 @@ # 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: Lahav Lipson +# Authors: Lahav Lipson, Lingjie Mei import numpy as np @@ -21,7 +21,7 @@ # Build intrinsic camera parameters from Blender camera data # -# See notes on this in +# See notes on this in # blender.stackexchange.com/questions/15102/what-is-blenders-camera-projection-matrix-model def get_calibration_matrix_K_from_blender(camd): f_in_mm = camd.lens @@ -35,20 +35,20 @@ def get_calibration_matrix_K_from_blender(camd): if sensor_width_in_mm/sensor_height_in_mm != W/H: vals = f'{(sensor_width_in_mm, sensor_height_in_mm, W, H)=}' raise ValueError(f'Camera sensor has not been properly configured, you probably need to call camera.adjust_camera_sensor on it. {vals}') - + pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y if (camd.sensor_fit == 'VERTICAL'): - # the sensor height is fixed (sensor fit is horizontal), + # the sensor height is fixed (sensor fit is horizontal), # the sensor width is effectively changed with the pixel aspect ratio s_u = resolution_x_in_px * scale / sensor_width_in_mm / pixel_aspect_ratio # pixels per milimeter s_v = resolution_y_in_px * scale / sensor_height_in_mm else: # 'HORIZONTAL' and 'AUTO' - # the sensor width is fixed (sensor fit is horizontal), + # the sensor width is fixed (sensor fit is horizontal), # the sensor height is effectively changed with the pixel aspect ratio pixel_aspect_ratio = scene.render.pixel_aspect_x / scene.render.pixel_aspect_y s_u = resolution_x_in_px * scale / sensor_width_in_mm s_v = resolution_y_in_px * scale * pixel_aspect_ratio / sensor_height_in_mm - + # Parameters of intrinsic calibration matrix K alpha_u = f_in_mm * s_u @@ -64,7 +64,7 @@ def get_calibration_matrix_K_from_blender(camd): return K # Returns camera rotation and translation matrices from Blender. -# +# # There are 3 coordinate systems involved: # 1. The World coordinates: "world" # - right-handed @@ -74,7 +74,7 @@ def get_calibration_matrix_K_from_blender(camd): # - right-handed: negative z look-at direction # 3. The desired computer vision camera coordinates: "cv" # - x is horizontal -# - y is down (to align to the actual pixel coordinates +# - y is down (to align to the actual pixel coordinates # used in digital images) # - right-handed: positive z look-at direction def get_3x4_RT_matrix_from_blender(cam): @@ -84,7 +84,7 @@ def get_3x4_RT_matrix_from_blender(cam): (0, -1, 0), (0, 0, -1))) - # Transpose since the rotation is object rotation, + # Transpose since the rotation is object rotation, # and we want coordinate rotation # R_world2bcam = cam.rotation_euler.to_matrix().transposed() # T_world2bcam = -1*R_world2bcam * location @@ -95,7 +95,7 @@ def get_3x4_RT_matrix_from_blender(cam): # Convert camera location to translation vector used in coordinate changes # T_world2bcam = -1*R_world2bcam*cam.location - # Use location from matrix_world to account for constraints: + # Use location from matrix_world to account for constraints: T_world2bcam = -1*R_world2bcam @ location # Build the coordinate transform matrix from world to computer vision camera @@ -139,16 +139,16 @@ def compute_vis_dists(points, cam): clamped_uv = np.clip(uv, [0, 0], butil.get_camera_res()) clamped_d = np.maximum(d, 0) - + RT_4x4_inv = np.array(Matrix(RT).to_4x4().inverted()) clipped_pos = homogenize((homogenize(clamped_uv) * clamped_d[:, None]) @ np.linalg.inv(K).T) @ RT_4x4_inv.T - + vis_dist = np.linalg.norm(points[:, :-1] - clipped_pos[:, :-1], axis=-1) return d, vis_dist def min_dists_from_cam_trajectory(points, cam, start=None, end=None, verbose=False): - + assert len(points.shape) == 2 and points.shape[-1] == 3 assert cam.type == 'CAMERA' @@ -167,9 +167,13 @@ def min_dists_from_cam_trajectory(points, cam, start=None, end=None, verbose=Fal dists, vis_dists = compute_vis_dists(points, cam) min_dists = np.minimum(dists, min_dists) min_vis_dists = np.minimum(vis_dists, min_vis_dists) - - return min_dists, min_vis_dists - + return min_dists, min_vis_dists +def points_inview(bbox, camera): + proj = np.array(get_3x4_P_matrix_from_blender(camera)[0]) + x, y, z = proj @ np.concatenate([bbox, np.ones((len(bbox), 1))], -1).T + render = bpy.context.scene.render + inview = (z > 0) & (x >= 0) & (y >= 0) & (x / z < render.resolution_x) & (y / z < render.resolution_y) + return inview diff --git a/infinigen/core/util/color.py b/infinigen/core/util/color.py index 90979f273..f91e3c205 100644 --- a/infinigen/core/util/color.py +++ b/infinigen/core/util/color.py @@ -1,5 +1,6 @@ # 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. +# 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, Yiming Zuo, Lingjie Mei, Lahav Lipson @@ -15,6 +16,7 @@ from infinigen.core.util.math import int_hash + @dataclass class ChannelScheme: args: list @@ -30,105 +32,41 @@ def sample(self): v = np.clip(v, *self.clip) return v + U = lambda min, max, **kwargs: ChannelScheme([min, max], dist='uniform', **kwargs) N = lambda m, std, **kwargs: ChannelScheme([m, std], dist='normal', **kwargs) HSV_RANGES = { - 'petal': ( - N(0.95, 1.2, wrap=True), - U(0.2, 0.85), - U(0.2, 0.75)), - 'gem': ( - U(0, 1), - U(0.85, 0.85), - U(0.5, 1)), - 'greenery': ( - U(0.25, 0.33), - N(0.65, 0.03), - U(0.1, 0.45) - ), - 'yellowish': ( - N(0.15, 0.005, wrap=True), - N(0.95, 0.02), - N(0.9, 0.02) - ), - 'red': ( - N(0.0, 0.05, wrap=True), - N(0.9, 0.03), - N(0.6, 0.05) - ), - 'pink': ( - N(0.88, 0.06, wrap=True), - N(0.6, 0.05), - N(0.8, 0.05) - ), - 'white': ( - N(0.0, 0.06, wrap=True), - U(0.0, 0.2, clip=[0, 1]), - N(0.95, 0.02) - ), - 'fog': ( - U(0, 1), - U(0, 0.2), - U(0.8, 1) - ), - 'water': ( - U(0.2, 0.6), - N(0.5, 0.1), - U(0.7, 1) - ), - 'darker_water': ( - U(0.2, 0.6), - N(0.5, 0.1), - U(0.2, 0.3) - ), - 'under_water': ( - U(0.5, 0.7), - U(0.7, 0.95), - U(0.7, 1) - ), - 'eye_schlera': ( - U(0.05, 0.15), - U(0.2, 0.8), - U(0.05, 0.5) - ), - 'eye_pupil': ( - U(0, 1), - U(0.1, 0.9), - U(0.1, 0.9) - ), - 'beak': ( - U(0, 0.13), - U(0, 0.9), - U(0.1, 0.6) - ), - 'fur': ( - U(0, 0.11), - U(0.5, 0.95), - U(0.02, 0.9) - ), - 'pine_needle': ( - N(0.05, 0.02, wrap=True), - U(0.5, 0.93), - U(0.045, 0.4), - ), - 'wet_sand': ( - U(0.05, 0.1), - U(0.65, 0.7), - U(0.05, 0.15), - ), - 'dry_sand': ( - U(0.05, 0.1), - U(0.65, 0.7), - U(0.15, 0.25), - ), - #'dirt': ('uniform', [], []), - #'rock': ('uniform', [], []), - #'creature_fur': ('normal', [0.89, 0.6, 0.2], []), - #'creature_scale': ('uniform', [], []), - #'wood': ('uniform', [], []), + 'petal': (N(0.95, 1.2, wrap=True), U(0.2, 0.85), U(0.2, 0.75)), + 'gem': (U(0, 1), U(0.85, 0.85), U(0.5, 1)), + 'greenery': (U(0.25, 0.33), N(0.65, 0.03), U(0.1, 0.45)), + 'yellowish': (N(0.15, 0.005, wrap=True), N(0.95, 0.02), N(0.9, 0.02)), + 'red': (N(0.0, 0.05, wrap=True), N(0.9, 0.03), N(0.6, 0.05)), + 'pink': (N(0.88, 0.06, wrap=True), N(0.6, 0.05), N(0.8, 0.05)), + 'white': (N(0.0, 0.06, wrap=True), U(0.0, 0.2, clip=[0, 1]), N(0.95, 0.02)), + 'fog': (U(0, 1), U(0, 0.2), U(0.8, 1)), + 'water': (U(0.2, 0.6), N(0.5, 0.1), U(0.7, 1)), + 'darker_water': (U(0.2, 0.6), N(0.5, 0.1), U(0.2, 0.3)), + 'under_water': (U(0.5, 0.7), U(0.7, 0.95), U(0.7, 1)), + 'eye_schlera': (U(0.05, 0.15), U(0.2, 0.8), U(0.05, 0.5)), + 'eye_pupil': (U(0, 1), U(0.1, 0.9), U(0.1, 0.9)), + 'beak': (U(0, 0.13), U(0, 0.9), U(0.1, 0.6)), + 'fur': (U(0, 0.11), U(0.5, 0.95), U(0.02, 0.9)), + 'pine_needle': (N(0.05, 0.02, wrap=True), U(0.5, 0.93), U(0.045, 0.4),), + 'wet_sand': (U(0.05, 0.1), U(0.65, 0.7), U(0.05, 0.15),), + 'dry_sand': (U(0.05, 0.1), U(0.65, 0.7), U(0.15, 0.25),), + 'leather': (U(0.04, 0.07), U(0.80, 1.0), U(0.1, 0.6),), + 'concrete': (U(0.0, 1.0), U(0.02, 0.12), U(0.3, 0.9),), + 'textile': (U(0, 1), U(0.15, 0.7), U(0.1, 0.3),), + 'fabric': (U(0, 1), U(0.3, 0.8), U(0.6, 0.9)) + # 'dirt': ('uniform', [], []), + # 'rock': ('uniform', [], []), + # 'creature_fur': ('normal', [0.89, 0.6, 0.2], []), + # 'creature_scale': ('uniform', [], []), + # 'wood': ('uniform', [], []), } + def color_category(name): if not name in HSV_RANGES: raise ValueError(f'color_category did not recognize {name=}, options are {HSV_RANGES.keys()=}') @@ -137,17 +75,43 @@ def color_category(name): hsv = [s.sample() for s in schemes] return hsv2rgba(hsv) -def hsv2rgba(hsv): + +def hsv2rgba(hsv, *args): # hsv is a len-3 tuple or array c = mathutils.Color() - c.hsv = list(hsv) + if len(args) > 0: + hsv = hsv, *args + c.hsv = np.array([hsv[0] % 1, hsv[1], hsv[2]]) rgba = list(c) + [1] return np.array(rgba) + +def rgb2hsv(rgb, *args): + # hsv is a len-3 tuple or array + c = mathutils.Color() + if len(args) > 0: + rgb = rgb, *args + c.r, c.g, c.b = rgb + return np.array(c.hsv) + + +def srgb_to_linearrgb(c): + if c < 0: return 0 + elif c < 0.04045: return c / 12.92 + else: return ((c + 0.055) / 1.055) ** 2.4 + + +def hex2rgba(h, alpha=1): + r = (h & 0xff0000) >> 16 + g = (h & 0x00ff00) >> 8 + b = (h & 0x0000ff) + return tuple([srgb_to_linearrgb(c / 0xff) for c in (r, g, b)] + [alpha]) + + @gin.configurable def random_color_mapping(color_tuple, scene_seed, hue_stddev): - r,g,b,a = color_tuple + r, g, b, a = color_tuple h, s, v = colorsys.rgb_to_hsv(r, g, b) - color_hash = int_hash((int(h*1e3), scene_seed)) + color_hash = int_hash((int(h * 1e3), scene_seed)) h = np.random.RandomState(color_hash).normal(h, hue_stddev) % 1.0 return colorsys.hsv_to_rgb(h, s, v) + (a,) diff --git a/infinigen/core/util/exporting.py b/infinigen/core/util/exporting.py index 24e9b62af..eb50c61a1 100644 --- a/infinigen/core/util/exporting.py +++ b/infinigen/core/util/exporting.py @@ -29,7 +29,7 @@ def get_mesh_data(obj): verts.foreach_get("co", vert_lookup) vert_lookup = vert_lookup.reshape((-1, 3)) masktag = np.full(len(verts, ), 0, dtype=np.int32) - if 'MaskTag' in obj.data.attributes: + if False and 'MaskTag' in obj.data.attributes: obj.data.attributes['MaskTag'].data.foreach_get("value", masktag) assert (loop_totals.size == 0) or (loop_totals.min() >= 0) assert (indices.size == 0) or (indices.min() >= 0) diff --git a/infinigen/core/util/logging.py b/infinigen/core/util/logging.py index a187ce741..cb2389f90 100644 --- a/infinigen/core/util/logging.py +++ b/infinigen/core/util/logging.py @@ -1,10 +1,11 @@ # 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: +# Authors: # - Lahav Lipson: logging formats, timer format # - Alex Raistrick: Timer # - Alejandro Newell: Suppress +# - Lingjie Mei: disable import os, sys @@ -23,7 +24,7 @@ class Timer: def __init__(self, desc, disable_timer=False, logger=None): self.disable_timer = disable_timer - if self.disable_timer: + if self.disable_timer: return self.name = f'[{desc}]' if logger is None: @@ -54,11 +55,14 @@ def __enter__(self, logfile=os.devnull): sys.stdout.flush() os.close(1) os.open(logfile, os.O_WRONLY) + self.level = logging.root.manager.disable + logging.disable(logging.CRITICAL) def __exit__(self, type, value, traceback): os.close(1) os.dup(self.old) os.close(self.old) + logging.disable(self.level) class LogLevel(): @@ -88,3 +92,7 @@ def create_text_file(log_dir, filename, text=None): (log_dir / filename).touch() if text is not None: (log_dir / filename).write_text(text) + + +class BadSeedError(ValueError): + pass \ No newline at end of file diff --git a/infinigen/core/util/organization.py b/infinigen/core/util/organization.py index ad57e1b38..a2e264688 100644 --- a/infinigen/core/util/organization.py +++ b/infinigen/core/util/organization.py @@ -11,6 +11,7 @@ class Task: Render = "render" GroundTruth = "ground_truth" MeshSave = "mesh_save" + Export = "export" class Materials: diff --git a/infinigen/core/util/pipeline.py b/infinigen/core/util/pipeline.py index 09826fb76..3e19a5445 100644 --- a/infinigen/core/util/pipeline.py +++ b/infinigen/core/util/pipeline.py @@ -39,12 +39,12 @@ def _should_run_stage(self, name, use_chance, prereq): logger.info(f'Skipping run_stage({name}...) due to unmet {prereq=}') return with FixedSeed(int_hash((self.scene_seed, name, 0))): + if not self.params.get(f'{name}_enabled', True): + logger.debug(f'Not running {name} due to manually set not enabled') + return False if use_chance and np.random.uniform() > self.params[f'{name}_chance']: logger.debug(f'Not running {name} due to random chance') return False - if not use_chance and not self.params.get(f'{name}_enabled', True): - logger.debug(f'Not running {name} due to manually set not enabled') - return False return True def save_results(self, path): diff --git a/infinigen/core/util/random.py b/infinigen/core/util/random.py index 1f34bd5f5..156466e96 100644 --- a/infinigen/core/util/random.py +++ b/infinigen/core/util/random.py @@ -18,12 +18,12 @@ from infinigen.core.init import repo_root -def log_uniform(low, high, size=1): +def log_uniform(low, high, size=None): return np.exp(uniform(np.log(low), np.log(high), size)) def sample_json_palette(pallette_name, n_sample=1): - rel = f"infinigen_examples/configs/palette/{pallette_name}.json" + rel = f"infinigen_examples/configs_nature/palette/{pallette_name}.json" with (repo_root()/rel).open('r') as f: color_template = json.load(f) @@ -52,6 +52,7 @@ def sample_json_palette(pallette_name, n_sample=1): return color return color_samples + def random_general(var): if not (isinstance(var, tuple) or isinstance(var, list)): return var @@ -80,13 +81,16 @@ def random_general(var): elif func == "power_uniform": return 10 ** np.random.uniform(*args) elif func == "log_uniform": - return log_uniform(*args)[0] + return log_uniform(*args) elif func == "discrete_uniform": return np.random.randint(args[0], args[1] + 1) elif func == "bool": return np.random.uniform() < args[0] elif func == "choice": - return np.random.choice(args[0], 1, p=args[1])[0] + return np.random.choice(args[0], 1, p=args[1] if len(args) > 1 else None)[0] + elif func == 'categorical': + prob = np.array(args) + return np.random.choice(np.arange(len(args)), p=prob/prob.sum()) elif func == "palette": return sample_json_palette(*args) elif func == "color_category": @@ -202,4 +206,5 @@ def random_color(brightness_lim=1): def sample_registry(reg): classes, weights = zip(*reg) weights = np.array(weights) - return np.random.choice(classes, p=weights/weights.sum()) \ No newline at end of file + return np.random.choice(classes, p=weights/weights.sum()) + diff --git a/infinigen/datagen/configs/base.gin b/infinigen/datagen/configs/base.gin index 8b05f57ea..08eaeb909 100644 --- a/infinigen/datagen/configs/base.gin +++ b/infinigen/datagen/configs/base.gin @@ -11,4 +11,4 @@ sample_scene_spec.config_distribution = [ ("coast", 4), ("arctic", 1), ("snowy_mountain", 1), -] \ No newline at end of file +] diff --git a/infinigen/datagen/configs/compute_platform/local_128GB.gin b/infinigen/datagen/configs/compute_platform/local_128GB.gin index bff783e2b..374dfab6e 100644 --- a/infinigen/datagen/configs/compute_platform/local_128GB.gin +++ b/infinigen/datagen/configs/compute_platform/local_128GB.gin @@ -1,4 +1,4 @@ -include 'compute_platform/local_256GB.gin' +include 'infinigen/datagen/configs/compute_platform/local_256GB.gin' manage_datagen_jobs.num_concurrent=8 diff --git a/infinigen/datagen/configs/compute_platform/local_16GB.gin b/infinigen/datagen/configs/compute_platform/local_16GB.gin index 83041fdad..5150853b8 100644 --- a/infinigen/datagen/configs/compute_platform/local_16GB.gin +++ b/infinigen/datagen/configs/compute_platform/local_16GB.gin @@ -1,4 +1,4 @@ -include 'compute_platform/local_256GB.gin' +include 'infinigen/datagen/configs/compute_platform/local_256GB.gin' manage_datagen_jobs.num_concurrent=1 diff --git a/infinigen/datagen/configs/compute_platform/local_256GB.gin b/infinigen/datagen/configs/compute_platform/local_256GB.gin index 211feaecd..babc757f8 100644 --- a/infinigen/datagen/configs/compute_platform/local_256GB.gin +++ b/infinigen/datagen/configs/compute_platform/local_256GB.gin @@ -22,6 +22,11 @@ queue_combined.cpus = 2 queue_combined.hours = 48 queue_combined.submit_cmd = @local_submit_cmd +# Export +queue_export.cpus = 4 +queue_export.hours = 24 +queue_export.submit_cmd = @local_submit_cmd + # Rendering queue_render.cpus = 4 queue_render.submit_cmd = @local_submit_cmd diff --git a/infinigen/datagen/configs/compute_platform/local_64GB.gin b/infinigen/datagen/configs/compute_platform/local_64GB.gin index 51c6baa03..ca98c6f5d 100644 --- a/infinigen/datagen/configs/compute_platform/local_64GB.gin +++ b/infinigen/datagen/configs/compute_platform/local_64GB.gin @@ -1,4 +1,4 @@ -include 'compute_platform/local_256GB.gin' +include 'infinigen/datagen/configs/compute_platform/local_256GB.gin' manage_datagen_jobs.num_concurrent=3 diff --git a/infinigen/datagen/configs/compute_platform/slurm.gin b/infinigen/datagen/configs/compute_platform/slurm.gin index c12d23ff1..fb9d2434d 100644 --- a/infinigen/datagen/configs/compute_platform/slurm.gin +++ b/infinigen/datagen/configs/compute_platform/slurm.gin @@ -1,10 +1,10 @@ -manage_datagen_jobs.num_concurrent = 'ENVVAR_INFINIGEN_NUMCONCURRENT_TARGET' +manage_datagen_jobs.num_concurrent = 100 slurm_submit_cmd.slurm_account = 'ENVVAR_INFINIGEN_SLURMPARTITION' # change to partitionname string, or None slurm_submit_cmd.slurm_niceness = 10000 get_slurm_banned_nodes.config_path = 'ENVVAR_INFINIGEN_SLURM_EXCLUDENODES_LIST' -jobs_to_launch_next.max_queued_total = 10 -jobs_to_launch_next.max_stuck_at_task = 8 +jobs_to_launch_next.max_queued_total = 40 +jobs_to_launch_next.max_stuck_at_task = 40 # Combined (only used when `stereo_combined.gin` or similar is included) queue_combined.mem_gb = 12 @@ -17,7 +17,7 @@ renderbackup/queue_combined.mem_gb = 24 queue_coarse.mem_gb = 24 queue_coarse.cpus = 4 queue_coarse.hours = 48 -queue_coarse.submit_cmd = @slurm_submit_cmd +queue_coarse.submit_cmd = @coarse/slurm_submit_cmd queue_coarse.exclude_gpus = ['a6000', 'rtx_3090'] # Fine terrain @@ -34,6 +34,13 @@ queue_populate.submit_cmd = @slurm_submit_cmd renderbackup/queue_populate.mem_gb = 24 queue_populate.exclude_gpus = ['a6000', 'rtx_3090'] +# Export +queue_export.mem_gb = 50 +queue_export.cpus = 4 +queue_export.hours = 24 +queue_export.submit_cmd = @slurm_submit_cmd +queue_export.exclude_gpus = ['a6000', 'rtx_3090'] + # Rendering queue_render.submit_cmd = @slurm_submit_cmd queue_render.hours = 48 diff --git a/infinigen/datagen/configs/compute_platform/slurm_1h.gin b/infinigen/datagen/configs/compute_platform/slurm_1h.gin index ab90e08b3..dc3ed03e1 100644 --- a/infinigen/datagen/configs/compute_platform/slurm_1h.gin +++ b/infinigen/datagen/configs/compute_platform/slurm_1h.gin @@ -1,8 +1,9 @@ -include 'compute_platform/slurm.gin' +include 'infinigen/datagen/configs/compute_platform/slurm.gin' slurm_submit_cmd.slurm_niceness = 0 -iterate_scene_tasks.view_block_size = 3 +iterate_scene_tasks.cam_block_size = 2 +slurm_submit_cmd.slurm_niceness = 0 queue_combined.hours = 1 queue_coarse.hours = 1 @@ -14,4 +15,4 @@ queue_opengl.hours = 1 queue_coarse.cpus = 8 -queue_upload.hours = 24 \ No newline at end of file +queue_upload.hours = 24 diff --git a/infinigen/datagen/configs/compute_platform/slurm_cpuheavy.gin b/infinigen/datagen/configs/compute_platform/slurm_cpuheavy.gin index d692aad2b..a13d74e58 100644 --- a/infinigen/datagen/configs/compute_platform/slurm_cpuheavy.gin +++ b/infinigen/datagen/configs/compute_platform/slurm_cpuheavy.gin @@ -1,4 +1,4 @@ -include 'tools/pipeline_configs/compute_platform/slurm.gin' +include 'infinigen/datagen/compute_platform/slurm.gin' iterate_scene_tasks.view_block_size = 2 diff --git a/infinigen/datagen/configs/compute_platform/slurm_high_memory.gin b/infinigen/datagen/configs/compute_platform/slurm_high_memory.gin index b121518db..41f4d6cc2 100644 --- a/infinigen/datagen/configs/compute_platform/slurm_high_memory.gin +++ b/infinigen/datagen/configs/compute_platform/slurm_high_memory.gin @@ -1,4 +1,4 @@ -include 'compute_platform/slurm.gin' +include 'infinigen/datagen/configs/compute_platform/slurm.gin' # Combined (only used when `stereo_combined.gin` or similar is included) queue_combined.mem_gb = 48 @@ -10,4 +10,8 @@ queue_fine_terrain.cpus = 4 queue_populate.mem_gb = 48 queue_populate.cpus = 4 queue_populate.hours = 24 +queue_export.mem_gb = 48 +queue_export.cpus = 4 +queue_export.hours = 24 +queue_export.submit_cmd = @local_submit_cmd renderbackup/queue_populate.mem_gb = 48 diff --git a/infinigen/datagen/configs/data_schema/monocular_flow.gin b/infinigen/datagen/configs/data_schema/monocular_flow.gin index d55133bd9..0bee65ae8 100644 --- a/infinigen/datagen/configs/data_schema/monocular_flow.gin +++ b/infinigen/datagen/configs/data_schema/monocular_flow.gin @@ -1,4 +1,4 @@ -include 'data_schema/monocular.gin' +include 'infinigen/datagen/data_schema/monocular.gin' iterate_scene_tasks.frame_range=(1, 2) diff --git a/infinigen/datagen/configs/data_schema/stereo_video.gin b/infinigen/datagen/configs/data_schema/stereo_video.gin index d0d755393..0f3082e03 100644 --- a/infinigen/datagen/configs/data_schema/stereo_video.gin +++ b/infinigen/datagen/configs/data_schema/stereo_video.gin @@ -1,2 +1,2 @@ -include 'data_schema/monocular_video.gin' +include 'infinigen/datagen/data_schema/monocular_video.gin' iterate_scene_tasks.cam_id_ranges = [1, 2] diff --git a/infinigen/datagen/configs/export.gin b/infinigen/datagen/configs/export.gin new file mode 100644 index 000000000..8493cf94c --- /dev/null +++ b/infinigen/datagen/configs/export.gin @@ -0,0 +1,3 @@ +iterate_scene_tasks.finalize_tasks = [ + {'name': "export", 'func': @queue_export} +] \ No newline at end of file diff --git a/infinigen/datagen/configs/gt_options/opengl_gt_noshortrender.gin b/infinigen/datagen/configs/gt_options/opengl_gt_noshortrender.gin index 8ce7aee7c..fbaf1a66b 100644 --- a/infinigen/datagen/configs/gt_options/opengl_gt_noshortrender.gin +++ b/infinigen/datagen/configs/gt_options/opengl_gt_noshortrender.gin @@ -1,4 +1,4 @@ -include 'gt_options/opengl_gt.gin' # incase someone adds other settings to it +include 'infinigen/datagen/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 diff --git a/infinigen/datagen/configs/indoor_background_configs.gin b/infinigen/datagen/configs/indoor_background_configs.gin new file mode 100644 index 000000000..b75e6b32d --- /dev/null +++ b/infinigen/datagen/configs/indoor_background_configs.gin @@ -0,0 +1,10 @@ +sample_scene_spec.config_distribution = [ + ("forest", 4), + ("river", 4), + ("desert", 4), + ("mountain", 2), + ("canyon", 2), + ("plain", 4), + ("coast", 8), + #("snowy_mountain", 4), # disabled until fast_terrain_assets.gin works for snow sim @mazeyu +] \ No newline at end of file diff --git a/infinigen/datagen/configs/opengl_gt_noshortrender.gin b/infinigen/datagen/configs/opengl_gt_noshortrender.gin deleted file mode 100644 index 5b07d93e1..000000000 --- a/infinigen/datagen/configs/opengl_gt_noshortrender.gin +++ /dev/null @@ -1,7 +0,0 @@ -include '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} -] diff --git a/infinigen/datagen/configs/upload.gin b/infinigen/datagen/configs/upload.gin index d0d71d163..4c8e7b78e 100644 --- a/infinigen/datagen/configs/upload.gin +++ b/infinigen/datagen/configs/upload.gin @@ -1,4 +1,5 @@ iterate_scene_tasks.finalize_tasks = [ + {'name': "export", 'func': @queue_export}, {'name': "upload", 'func': @queue_upload} ] diff --git a/infinigen/datagen/job_funcs.py b/infinigen/datagen/job_funcs.py index 7394ae655..a10906fcd 100644 --- a/infinigen/datagen/job_funcs.py +++ b/infinigen/datagen/job_funcs.py @@ -4,8 +4,10 @@ # Authors: # - Alex Raistrick: refactor, local rendering, video rendering # - Lahav Lipson: stereo version, local rendering +# - David Yan: export integration # - Hei Law: initial version + import re import gin from copy import copy @@ -75,6 +77,38 @@ def queue_upload(folder, submit_cmd, name, taskname, dir_prefix_len=0, method='r res = submit_cmd((func, folder, taskname), folder, name, **kwargs) return res, None + +@gin.configurable +def queue_export( + folder, + submit_cmd, + name, + seed, + configs, + taskname=None, + exclude_gpus=[], + overrides=[], + input_indices=None, output_indices=None, + **kwargs +): + input_suffix = get_suffix(input_indices) + input_folder=f'{folder}/coarse{input_suffix}' + + cmd = get_cmd(seed, 'export', configs, taskname, output_folder=f'{folder}/frames', input_folder=input_folder)+ f''' + LOG_DIR='{folder / "logs"}' + '''.split("\n") + overrides + + with (folder / "run_pipeline.sh").open('a') as f: + f.write(f"{' '.join(' '.join(cmd).split())}\n\n") + + res = submit_cmd(cmd, + folder=folder, + name=name, + gpus=0, + **kwargs + ) + return res, folder + @gin.configurable def queue_coarse( folder, @@ -266,8 +300,22 @@ def queue_render( output_folder = Path(f'{folder}/frames{output_suffix}') + 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, "render", configs, taskname, - input_folder=f'{folder}/fine{input_suffix}', + input_folder=input_folder, output_folder=f'{output_folder}') + f''' render.render_image_func=@{render_type}/render_image LOG_DIR='{folder / "logs"}' @@ -310,7 +358,7 @@ def queue_mesh_save( output_folder.mkdir(parents=True, exist_ok=True) cmd = get_cmd(seed, "mesh_save", configs, taskname, - input_folder=f'{folder}/fine{input_suffix}', + input_folder=f'{folder}/coarse{input_suffix}', output_folder=f'{folder}/savemesh{output_suffix}') + f''' LOG_DIR='{folder / "logs"}' '''.split("\n") + overrides diff --git a/infinigen/datagen/manage_jobs.py b/infinigen/datagen/manage_jobs.py index a20cd78f3..15f68123a 100644 --- a/infinigen/datagen/manage_jobs.py +++ b/infinigen/datagen/manage_jobs.py @@ -34,6 +34,7 @@ import pandas as pd import numpy as np import submitit +import submitit.core.utils from jinja2 import Environment, FileSystemLoader, select_autoescape ORIG_SYS_PATH = list(sys.path) # Make a new instance of sys.path @@ -400,8 +401,6 @@ def infer_crash_reason(stdout_file, stderr_file: Path): return "SIGKILL: 9 (out-of-memory, probably)" elif "SIGCONT" in error_log: return "SIGCONT (timeout?)" - elif "srun: error" in error_log: - return "srun error" if not stdout_file.exists(): return f'{stdout_file} not found' @@ -409,13 +408,29 @@ def infer_crash_reason(stdout_file, stderr_file: Path): return f'{stderr_file} not found' output_text = f"{stdout_file.read_text()}\n{stderr_file.read_text()}\n" - matches = re.findall("(Error:[^\n]+)\n", output_text) + matches = re.findall("([^\.\n]*[Ee]rror):(.*)\n", output_text) - ignore_errors = [ - 'Error: Not freed memory blocks', + ignore_errors = { + + # happens for every failed submitit job, not informative to report in summary + "FailedProcessError", + "CalledProcessError", + + # happens for every failed slurm job on IONIC + "srun: error", + "FailedJobError", + } + + ignore_messages = [ + "Not freed memory blocks" ] - matches = [m for m in matches if not any(w in m for w in ignore_errors)] + matches = [ + f'{m[0]}: {m[1]}' for m in matches if not ( + m[0] in ignore_errors + or any(x in m[1] for x in ignore_messages) + ) + ] if len(matches): return ','.join(matches) diff --git a/infinigen/datagen/util/cleanup.py b/infinigen/datagen/util/cleanup.py index 6923974dc..eef97c5ba 100644 --- a/infinigen/datagen/util/cleanup.py +++ b/infinigen/datagen/util/cleanup.py @@ -5,7 +5,6 @@ # - Lahav Lipson # - Karhan Kayan (cleanup fluid files) - import argparse import shutil from pathlib import Path diff --git a/infinigen/datagen/util/upload_util.py b/infinigen/datagen/util/upload_util.py index 7d52b55d6..7679fec1a 100644 --- a/infinigen/datagen/util/upload_util.py +++ b/infinigen/datagen/util/upload_util.py @@ -1,7 +1,9 @@ # 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: Lahav Lipson +# Authors: +# - Alexander Raistrick: create_upload_payload, metadata +# - Lahav Lipson: initial version import argparse import os diff --git a/infinigen/launch_blender.py b/infinigen/launch_blender.py index 5c294d938..11db8205d 100644 --- a/infinigen/launch_blender.py +++ b/infinigen/launch_blender.py @@ -1,3 +1,9 @@ +# 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 subprocess import argparse from pathlib import Path diff --git a/infinigen/terrain/__init__.py b/infinigen/terrain/__init__.py index d1420bada..bba71c859 100644 --- a/infinigen/terrain/__init__.py +++ b/infinigen/terrain/__init__.py @@ -4,4 +4,4 @@ # Authors: Zeyu Ma -from .core import Terrain \ No newline at end of file +from .core import Terrain, hidden_in_viewport \ No newline at end of file diff --git a/infinigen/terrain/core.py b/infinigen/terrain/core.py index eee150ad1..91eb71e14 100644 --- a/infinigen/terrain/core.py +++ b/infinigen/terrain/core.py @@ -23,8 +23,7 @@ from infinigen.core.util.logging import Timer from infinigen.core.util.math import FixedSeed, int_hash from infinigen.core.util.organization import SurfaceTypes, Attributes, Task, TerrainNames, ElementNames, Transparency, Materials, Assets, ElementTag, Tags, SelectionCriterions -from infinigen.assets.utils.tag import tag_object, tag_system - +from infinigen.core.tagging import tag_object, tag_system from numpy import ascontiguousarray as AC logger = logging.getLogger(__name__) @@ -87,6 +86,8 @@ def __init__( main_terrain=TerrainNames.OpaqueTerrain, under_water=False, min_distance=1, + height_offset=0, + whole_bbox=None, populated_bounds=(-75, 75, -75, 75, -25, 55), bounds=(-500, 500, -500, 500, -500, 500), ): @@ -129,6 +130,10 @@ def __init__( transfer_scene_info(self, scene_infos) Terrain.instance = self + for e in self.elements: + self.elements[e].height_offset = height_offset + self.elements[e].whole_bbox = whole_bbox + def __del__(self): self.cleanup() @@ -393,11 +398,11 @@ def build_terrain_bvh_and_attrs(self, terrain_tags_queries, avoid_border=False, altitude = terrain_mesh.vertices[:, 2] camera_selection_answers[q0] = terrain_mesh.facewise_mean((altitude > min_altitude) & (altitude < max_altitude)) else: - camera_selection_answers[q0] = np.zeros(len(terrain_mesh.vertices), dtype=bool) + camera_selection_answers[q0] = np.zeros(len(terrain_mesh.faces), dtype=bool) for key in self.tag_dict: if set(q).issubset(set(key.split('.'))): - camera_selection_answers[q0] |= (terrain_mesh.vertex_attributes["MaskTag"] == self.tag_dict[key]).reshape(-1) - camera_selection_answers[q0] = terrain_mesh.facewise_mean(camera_selection_answers[q0].astype(np.float64)) + camera_selection_answers[q0] |= (terrain_mesh.face_attributes["MaskTag"] == self.tag_dict[key]).reshape(-1) + camera_selection_answers[q0] = camera_selection_answers[q0].astype(np.float64) if np.abs(np.asarray(terrain_obj.matrix_world) - np.eye(4)).max() > 1e-4: raise ValueError(f"Not all transformations on {terrain_obj.name} have been applied. This function won't work correctly.") @@ -421,22 +426,27 @@ def build_terrain_bvh_and_attrs(self, terrain_tags_queries, avoid_border=False, vertexwise_min_dist = terrain_mesh.facewise_mean(terrain_mesh.vertex_attributes["vertexwise_min_dist"].reshape(-1)) depsgraph = bpy.context.evaluated_depsgraph_get() - terrain_bvh = BVHTree.FromObject(terrain_obj, depsgraph) + scene_bvh = BVHTree.FromObject(terrain_obj, depsgraph) delete(terrain_obj) - return terrain_bvh, camera_selection_answers, vertexwise_min_dist + return scene_bvh, camera_selection_answers, vertexwise_min_dist def tag_terrain(self, obj): if len(obj.data.vertices) == 0: return + + + mesh = Mesh(obj=obj) first_time = 1 #initialize with element tag element_tag = np.zeros(len(obj.data.vertices), dtype=np.int32) obj.data.attributes[Attributes.ElementTag].data.foreach_get("value", element_tag) + element_tag_f = mesh.facewise_intmax(element_tag) + for i in range(ElementTag.total_cnt): - mask_i = element_tag == i + mask_i = element_tag_f == i if mask_i.any(): - obj.data.attributes.new(name=f"TAG_{ElementTag.map[i]}", type="FLOAT", domain='POINT') + obj.data.attributes.new(name=f"TAG_{ElementTag.map[i]}", type="FLOAT", domain='FACE') obj.data.attributes[f"TAG_{ElementTag.map[i]}"].data.foreach_set("value", AC(mask_i.astype(np.float32))) if first_time: # "landscape" is a collective name for terrain and water @@ -445,86 +455,29 @@ def tag_terrain(self, obj): else: tag_object(obj) obj.data.attributes.remove(obj.data.attributes[Attributes.ElementTag]) - # consider cave - if Tags.Cave in obj.data.attributes.keys(): - tag = np.zeros(len(obj.data.vertices), dtype=np.float32) - obj.data.attributes[Tags.Cave].data.foreach_get("value", tag) - tag = tag > 0.5 - if tag.any(): - obj.data.attributes.new(name=f"TAG_{Tags.Cave}", type="FLOAT", domain='POINT') - obj.data.attributes[f"TAG_{Tags.Cave}"].data.foreach_set("value", AC(tag.astype(np.float32))) - tag_object(obj) - - # consider liquid covered - if Tags.LiquidCovered in obj.data.attributes.keys(): - tag = np.zeros(len(obj.data.vertices), dtype=np.float32) - obj.data.attributes[Tags.LiquidCovered].data.foreach_get("value", tag) - tag = tag > 0.5 - obj.data.attributes.remove(obj.data.attributes[Tags.LiquidCovered]) - if tag.any(): - obj.data.attributes.new(name=f"TAG_{Tags.LiquidCovered}", type="FLOAT", domain='POINT') - obj.data.attributes[f"TAG_{Tags.LiquidCovered}"].data.foreach_set("value", AC(tag.astype(np.float32))) - tag_object(obj) - - # consider erosion collection - if Materials.Eroded in obj.data.attributes.keys(): - tag = np.zeros(len(obj.data.vertices), dtype=np.float32) - obj.data.attributes[Materials.Eroded].data.foreach_get("value", tag) - tag = tag > 0.1 - if tag.any(): - obj.data.attributes.new(name=f"TAG_{Materials.Eroded}", type="FLOAT", domain='POINT') - obj.data.attributes[f"TAG_{Materials.Eroded}"].data.foreach_set("value", AC(tag.astype(np.float32))) - tag_object(obj) - - # consider lava - if Materials.Lava in obj.data.attributes.keys(): - tag = np.zeros(len(obj.data.vertices), dtype=np.float32) - obj.data.attributes[Materials.Lava].data.foreach_get("value", tag) - tag = tag > 0.1 - if tag.any(): - obj.data.attributes.new(name=f"TAG_{ElementNames.Liquid}.{Materials.Lava}", type="FLOAT", domain='POINT') - obj.data.attributes[f"TAG_{ElementNames.Liquid}.{Materials.Lava}"].data.foreach_set("value", AC(tag.astype(np.float32))) - tag_object(obj) - - # consider snow - if Materials.Snow in obj.data.attributes.keys(): - tag = np.zeros(len(obj.data.vertices), dtype=np.float32) - obj.data.attributes[Materials.Snow].data.foreach_get("value", tag) - tag = tag > 0.1 - if tag.any(): - obj.data.attributes.new(name=f"TAG_{Materials.Snow}", type="FLOAT", domain='POINT') - obj.data.attributes[f"TAG_{Materials.Snow}"].data.foreach_set("value", AC(tag.astype(np.float32))) - tag_object(obj) - - # consider lower part of upsidedown mountain - if Tags.UpsidedownMountainsLowerPart in obj.data.attributes.keys(): - tag = np.zeros(len(obj.data.vertices), dtype=np.float32) - obj.data.attributes[Tags.UpsidedownMountainsLowerPart].data.foreach_get("value", tag) - obj.data.attributes.remove(obj.data.attributes[Tags.UpsidedownMountainsLowerPart]) - tag = tag > 0.5 - if tag.any(): - obj.data.attributes.new(name=f"TAG_{Tags.UpsidedownMountainsLowerPart}", type="FLOAT", domain='POINT') - obj.data.attributes[f"TAG_{Tags.UpsidedownMountainsLowerPart}"].data.foreach_set("value", AC(tag.astype(np.float32))) - tag_object(obj) - - # consider beach - if Materials.Beach in obj.data.attributes.keys(): - tag = np.zeros(len(obj.data.vertices), dtype=np.float32) - obj.data.attributes[Materials.Beach].data.foreach_get("value", tag) - tag = tag > 0.5 - if tag.any(): - obj.data.attributes.new(name=f"TAG_{Materials.Beach}", type="FLOAT", domain='POINT') - obj.data.attributes[f"TAG_{Materials.Beach}"].data.foreach_set("value", AC(tag.astype(np.float32))) - tag_object(obj) - - if Tags.OutOfView in obj.data.attributes.keys(): - tag = np.zeros(len(obj.data.vertices), dtype=np.float32) - obj.data.attributes[Tags.OutOfView].data.foreach_get("value", tag) - obj.data.attributes.remove(obj.data.attributes[Tags.OutOfView]) - tag = tag > 0.5 - if tag.any(): - obj.data.attributes.new(name=f"TAG_{Tags.OutOfView}", type="FLOAT", domain='POINT') - obj.data.attributes[f"TAG_{Tags.OutOfView}"].data.foreach_set("value", AC(tag.astype(np.float32))) - tag_object(obj) - + + tag_thresholds = [ + (Tags.Cave, 0.5, 0), + (Tags.LiquidCovered, 0.5, 1), + (Materials.Eroded, 0.1, 0), + (Materials.Lava, 0.1, 0), + (Materials.Snow, 0.1, 0), + (Tags.UpsidedownMountainsLowerPart, 0.5, 1), + (Materials.Beach, 0.5, 0), + (Tags.OutOfView, 0.5, 1), + ] + + for tag_name, threshold, to_remove in tag_thresholds: + if tag_name in obj.data.attributes.keys(): + tag = np.zeros(len(obj.data.vertices), dtype=np.float32) + obj.data.attributes[tag_name].data.foreach_get("value", tag) + tag_f = mesh.facewise_mean(tag) + tag_f = tag_f > threshold + if to_remove: + obj.data.attributes.remove(obj.data.attributes[tag_name]) + if tag_f.any(): + obj.data.attributes.new(name=f"TAG_{tag_name}", type="FLOAT", domain='FACE') + obj.data.attributes[f"TAG_{tag_name}"].data.foreach_set("value", AC(tag_f.astype(np.float32))) + tag_object(obj) + self.tag_dict = tag_system.tag_dict diff --git a/infinigen/terrain/elements/core.py b/infinigen/terrain/elements/core.py index 135f1c95b..46f1870cb 100644 --- a/infinigen/terrain/elements/core.py +++ b/infinigen/terrain/elements/core.py @@ -63,9 +63,14 @@ def __init__(self, lib_name, material, transparency): len(self.int_params3), ASINT(self.int_params3), len(self.float_params3), ASFLOAT(self.float_params3), ) self.displacement = [] + self.height_offset = 0 + self.whole_bbox = None def __call__(self, positions, sdf_only=False): + if self.whole_bbox is not None: + mask = (positions >= self.whole_bbox[0].reshape((1, 3))).all(axis=-1) & (positions <= self.whole_bbox[1].reshape((1, 3))).all(axis=-1) + positions[:, 2] += self.height_offset N = len(positions) sdf = AC(np.zeros(N, dtype=np.float32)) auxs = [] @@ -77,6 +82,10 @@ def __call__(self, positions, sdf_only=False): else: auxs.append(None) self.call(N, ASFLOAT(AC(positions.astype(np.float32))), ASFLOAT(sdf), *[POINTER(c_float)() if x is None else ASFLOAT(x) for x in auxs]) + + if self.whole_bbox is not None: + sdf[mask] = 1e6 + ret = {} ret[Vars.SDF] = sdf @@ -93,6 +102,7 @@ def __call__(self, positions, sdf_only=False): for surface in self.displacement: ret.update(surface({Vars.Position: positions, **ret})) ret[Vars.SDF] -= ret.pop(Vars.Offset) + positions[:, 2] -= self.height_offset return ret def get_heightmap(self, X, Y): diff --git a/infinigen/terrain/utils/camera.py b/infinigen/terrain/utils/camera.py index b81367d53..59b7bd58c 100644 --- a/infinigen/terrain/utils/camera.py +++ b/infinigen/terrain/utils/camera.py @@ -43,7 +43,7 @@ def get_expanded_fov(cam_pose0, cam_poses, fov): bounds[1] = max(bounds[1], p[0] / p[2]) bounds[2] = min(bounds[2], p[1] / p[2]) bounds[3] = max(bounds[3], p[1] / p[2]) - return (max(-bounds[2], bounds[3]) * 2, max(-bounds[0], bounds[1]) * 2) + return (np.arctan(max(-bounds[2], bounds[3])) * 2, np.arctan(max(-bounds[0], bounds[1])) * 2) @gin.configurable diff --git a/infinigen/terrain/utils/kernelizer_util.py b/infinigen/terrain/utils/kernelizer_util.py index 5c68303e5..27b5f998d 100644 --- a/infinigen/terrain/utils/kernelizer_util.py +++ b/infinigen/terrain/utils/kernelizer_util.py @@ -68,7 +68,7 @@ class FieldsType: AttributeType.Int: FieldsType.Value, AttributeType.FloatVector: FieldsType.Vector, AttributeType.FloatColor: FieldsType.Color, - AttributeType.Boolean: FieldsType.Boolean, + AttributeType.Boolean: FieldsType.Value, } ATTRTYPE_NP = { diff --git a/infinigen/terrain/utils/mesh.py b/infinigen/terrain/utils/mesh.py index 9b878b33a..77715d08a 100644 --- a/infinigen/terrain/utils/mesh.py +++ b/infinigen/terrain/utils/mesh.py @@ -37,6 +37,18 @@ def object_to_vertex_attributes(obj, specified=None, skip_internal=True): vertex_attributes[attr] = tmp.reshape((len(obj.data.vertices), -1)) return vertex_attributes +def object_to_face_attributes(obj, specified=None, skip_internal=True): + face_attributes = {} + for attr in obj.data.attributes.keys(): + if skip_internal and butil.blender_internal_attr(attr): + continue + if ((specified is None) or (specified is not None and attr in specified)) and obj.data.attributes[attr].domain == "FACE": + type_key = obj.data.attributes[attr].data_type + tmp = np.zeros(len(obj.data.polygons) * ATTRTYPE_DIMS[type_key], dtype=np.float32) + obj.data.attributes[attr].data.foreach_get(ATTRTYPE_FIELDS[type_key], tmp) + face_attributes[attr] = tmp.reshape((len(obj.data.polygons), -1)) + return face_attributes + def objectdata_from_VF(vertices, faces): new_mesh = bpy.data.meshes.new("") new_mesh.vertices.add(len(vertices)) @@ -75,6 +87,7 @@ def __init__(self, normal_mode=NormalMode.Mean, obj=None, mesh_only=False, **kwargs ): self.normal_mode = normal_mode + self.face_attributes = {} if path is not None: geometry = trimesh.load(path, process=False).geometry key = list(geometry.keys())[0] @@ -116,6 +129,7 @@ def __init__(self, normal_mode=NormalMode.Mean, if not mesh_only: vertex_attributes = object_to_vertex_attributes(obj) _trimesh.vertex_attributes.update(vertex_attributes) + self.face_attributes.update(object_to_face_attributes(obj)) for key in kwargs: setattr(self, key, kwargs[key]) else: diff --git a/infinigen/tools/blendscript_import_infinigen.py b/infinigen/tools/blendscript_import_infinigen.py index f4c419361..428ee5e14 100644 --- a/infinigen/tools/blendscript_import_infinigen.py +++ b/infinigen/tools/blendscript_import_infinigen.py @@ -1,3 +1,8 @@ +# 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 ''' @@ -25,7 +30,7 @@ from infinigen.core import init, surface from infinigen_examples import generate_nature -init.apply_gin_configs(Path(pwd)/'infinigen_examples/configs', ['base.gin'], skip_unknown=True) +init.apply_gin_configs(Path(pwd)/'infinigen_examples/configs_nature', ['base.gin'], skip_unknown=True) surface.registry.initialize_from_gin() logging.basicConfig( diff --git a/infinigen/tools/blendscript_path_append.py b/infinigen/tools/blendscript_path_append.py index e7acb0f1c..06cae49a5 100644 --- a/infinigen/tools/blendscript_path_append.py +++ b/infinigen/tools/blendscript_path_append.py @@ -1,3 +1,9 @@ +# 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 os, sys pwd = os.getcwd() sys.path.append(pwd) \ No newline at end of file diff --git a/infinigen/tools/config/demo_config.yaml b/infinigen/tools/config/demo_config.yaml new file mode 100644 index 000000000..a655aa272 --- /dev/null +++ b/infinigen/tools/config/demo_config.yaml @@ -0,0 +1,12 @@ + +scene: + num_static_objs: 5 + num_rearrange_objs: 5 + ground_size: 2.0 + support_scene: 'scene_000' + support_restitution: 0.5 + support_static_friction: 0.6 + support_dynamic_friction: 0.4 + camera_resolution: (1280, 720) + robot_base_ori: [0, 0, -90] + diff --git a/infinigen/tools/convert_displacement.py b/infinigen/tools/convert_displacement.py new file mode 100644 index 000000000..2e28c108d --- /dev/null +++ b/infinigen/tools/convert_displacement.py @@ -0,0 +1,124 @@ +# 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: David Yan + +import bpy +from infinigen.core.nodes.node_wrangler import geometry_node_group_empty_new + +visited_nodes = [] + +def find_connected(node, origin_socket_index, tree_origin_socket = False): #WIP, unused + if node in visited_nodes: + return + visited_nodes.append(node) + + if node.type == "GROUP": + find_connected(node.node_tree.nodes["Group Output"], origin_socket_index, True) + + if tree_origin_socket: + for link in node.inputs[origin_socket_index].links: + from_node = link.from_node + find_connected(from_node, from_node.outputs[:].index(link.from_socket)) + else: + for index, _ in enumerate(node.inputs): + for link in node.inputs[index].links: + from_node = link.from_node + find_connected(from_node, from_node.outputs[:].index(link.from_socket)) + +def remove_unconnected(node_tree): #WIP, unused + nodes = node_tree.nodes + for node in nodes: + if node not in visited_nodes: + nodes.remove(node) + continue + if node.type == "GROUP": + remove_unconnected(node.node_tree) + +def copy_nodes(shader_node_tree, geo_node_tree): #WIP, unused + shader_nodes = shader_node_tree.nodes + for shader_node in shader_nodes: + if shader_node.type == "GROUP": + geo_node = geo_node_tree.nodes.new("GeometryNodeGroup") + copy_nodes(geo_node.node_tree, shader_node.node_tree) + else: + try: + geo_node = geo_node_tree.nodes.new(shader_node.bl_idname) + geo_node.location = shader_node.location + geo_node.width = shader_node.width + except RuntimeError: + continue + + +def bake_vertex_colors(obj): + bpy.context.scene.render.engine = 'CYCLES' + bpy.context.scene.cycles.device = "GPU" + bpy.context.scene.cycles.samples = 1 + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + vertColor = bpy.context.object.data.color_attributes.new(name="Displacement",domain='POINT',type='FLOAT_COLOR') + bpy.context.object.data.attributes.active_color = vertColor + bpy.ops.object.bake(type='EMIT', pass_filter={'COLOR'}, target ='VERTEX_COLORS') + obj.select_set(False) + +def create_modifier(obj, scale_val, apply_geo_modifier ): + modifier = obj.modifiers.new("Displacement", "NODES") + modifier.node_group = geometry_node_group_empty_new() + nodes = modifier.node_group.nodes + normal = nodes.new(type = 'GeometryNodeInputNormal') + attribute = nodes.new(type = 'GeometryNodeInputNamedAttribute') + attribute.data_type = "FLOAT_COLOR" + attribute.inputs[0].default_value = "Displacement" + set_pos = nodes.new(type = 'GeometryNodeSetPosition') + mult = nodes.new(type = 'ShaderNodeVectorMath') + mult.operation = 'MULTIPLY' + scale = nodes.new(type = 'ShaderNodeVectorMath') + scale.operation = 'SCALE' + scale.inputs["Scale"].default_value = scale_val + output = nodes["Group Output"] + input = nodes["Group Input"] + + modifier.node_group.links.new(input.outputs["Geometry"], set_pos.inputs["Geometry"]) + modifier.node_group.links.new(attribute.outputs[2], mult.inputs[0]) # index 2 must be hardcoded + modifier.node_group.links.new(normal.outputs["Normal"], mult.inputs[1]) + modifier.node_group.links.new(mult.outputs["Vector"], scale.inputs["Vector"]) + modifier.node_group.links.new(scale.outputs["Vector"], set_pos.inputs["Offset"]) + modifier.node_group.links.new(set_pos.outputs["Geometry"], output.inputs["Geometry"]) + + if apply_geo_modifier: + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.modifier_apply(modifier="Displacement") + obj.select_set(False) + +def convert_shader_displacement(obj, apply_geo_modifier = True): + displaced_materials = {} + + for slot in obj.material_slots: + mat = slot.material + nodes = mat.node_tree.nodes + if nodes.get("Displacement"): + scale_val = nodes["Displacement"].inputs["Scale"].default_value + displacement_link = nodes["Displacement"].inputs["Height"].links[0] + displace_socket = displacement_link.from_socket + bsdf_link = nodes["Material Output"].inputs["Surface"].links[0] + bsdf_socket = bsdf_link.from_socket + mat.node_tree.links.remove(displacement_link) + mat.node_tree.links.new(displace_socket, nodes["Material Output"].inputs["Surface"]) + displaced_materials[mat] = bsdf_socket + + if len(displaced_materials) != 0: + bake_vertex_colors(obj) + create_modifier(obj, scale_val, apply_geo_modifier) + + for mat in displaced_materials: + mat = slot.material + mat.node_tree.links.remove(nodes["Material Output"].inputs["Surface"].links[0]) + mat.node_tree.links.new(displaced_materials[mat], nodes["Material Output"].inputs["Surface"]) + + + + + + + diff --git a/infinigen/tools/export.py b/infinigen/tools/export.py index 21da43bc9..5b04bf8b9 100644 --- a/infinigen/tools/export.py +++ b/infinigen/tools/export.py @@ -10,12 +10,17 @@ import shutil import subprocess import logging +import gin +import math from pathlib import Path -FORMAT_CHOICES = ["fbx", "obj", "usdc", "usda" "stl", "ply"] +import gin + +FORMAT_CHOICES = ["fbx", "obj", "usdc", "usda", "stl", "ply"] BAKE_TYPES = {'DIFFUSE': 'Base Color', 'ROUGHNESS': 'Roughness'} # 'EMIT':'Emission' # "GLOSSY": 'Specular', 'TRANSMISSION':'Transmission' don't export -SPECIAL_BAKE = {'METAL': 'Metallic'} +SPECIAL_BAKE = {'METAL': 'Metallic', 'NORMAL': 'Normal'} +ALL_BAKE = BAKE_TYPES | SPECIAL_BAKE def apply_all_modifiers(obj): for mod in obj.modifiers: @@ -37,7 +42,7 @@ def realizeInstances(obj): geo_group = mod.node_group outputNode = geo_group.nodes['Group Output'] - logging.info(f"Realizing instances on {mod.name}") + logging.info(f"Realizing instances on {mod}") link = outputNode.inputs[0].links[0] from_socket = link.from_socket geo_group.links.remove(link) @@ -86,8 +91,57 @@ def handle_geo_modifiers(obj, export_usd): if not export_usd: realizeInstances(obj) - -def clean_names(): + +def split_glass_mats(): + split_objs = [] + for obj in bpy.data.objects: + if any(exclude in obj.name for exclude in ['BowlFactory', 'CupFactory', 'OvenFactory', 'BottleFactory']): + continue + for slot in obj.material_slots: + mat = slot.material + if mat is None: + continue + if ('shader_glass' in mat.name or 'shader_lamp_bulb' in mat.name) and len(obj.material_slots) >= 2: + logging.info(f'Splitting {obj}') + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.separate(type='MATERIAL') + bpy.ops.object.mode_set(mode='OBJECT') + obj.select_set(False) + split_objs.append(obj.name) + break + + matches = [obj for split_obj in split_objs for obj in bpy.data.objects if split_obj in obj.name] + for match in matches: + mat = match.material_slots[0].material + if mat is None: + continue + if ('shader_glass' in mat.name or 'shader_lamp_bulb' in mat.name): + match.name = f'{match.name}_SPLIT_GLASS' + +def clean_names(obj = None): + if obj is not None: + obj.name = (obj.name).replace(' ','_') + obj.name = (obj.name).replace('.','_') + + if obj.type == 'MESH': + for uv_map in obj.data.uv_layers: + uv_map.name = uv_map.name.replace('.', '_') + + for mat in bpy.data.materials: + if (mat is None): continue + mat.name = (mat.name).replace(' ','_') + mat.name = (mat.name).replace('.','_') + + for slot in obj.material_slots: + mat = slot.material + if (mat is None): + continue + mat.name = (mat.name).replace(' ','_') + mat.name = (mat.name).replace('.','_') + return + for obj in bpy.data.objects: obj.name = (obj.name).replace(' ','_') obj.name = (obj.name).replace('.','_') @@ -101,32 +155,76 @@ def clean_names(): mat.name = (mat.name).replace(' ','_') mat.name = (mat.name).replace('.','_') -def remove_obj_parents(): - for obj in bpy.data.objects: - world_loc = obj.matrix_world.to_translation() +def remove_obj_parents(obj = None): + if obj is not None : + old_location = obj.matrix_world.to_translation() + obj.parent = None + obj.matrix_world.translation = old_location + return + + for obj in bpy.data.objects: + old_location = obj.matrix_world.to_translation() obj.parent = None - obj.matrix_world.translation = world_loc + obj.matrix_world.translation = old_location + +def delete_objects(): + logging.info("Deleting placeholders collection") + collection_name = "placeholders" + collection = bpy.data.collections.get(collection_name) + + if collection: + for scene in bpy.data.scenes: + if collection.name in scene.collection.children: + scene.collection.children.unlink(collection) + + for obj in collection.objects: + bpy.data.objects.remove(obj, do_unlink=True) + + def delete_child_collections(parent_collection): + for child_collection in parent_collection.children: + delete_child_collections(child_collection) + bpy.data.collections.remove(child_collection) + + delete_child_collections(collection) + bpy.data.collections.remove(collection) + + if bpy.data.objects.get("Grid"): + bpy.data.objects.remove(bpy.data.objects["Grid"], do_unlink=True) + + if bpy.data.objects.get("atmosphere"): + bpy.data.objects.remove(bpy.data.objects["atmosphere"], do_unlink=True) + + if bpy.data.objects.get("KoleClouds"): + bpy.data.objects.remove(bpy.data.objects["KoleClouds"], do_unlink=True) + +def rename_all_meshes(obj = None): + if obj is not None: + if obj.data and obj.data.users == 1: + obj.data.name = obj.name + return + + for obj in bpy.data.objects: + if obj.data and obj.data.users == 1: + obj.data.name = obj.name def update_visibility(): outliner_area = next(a for a in bpy.context.screen.areas if a.type == 'OUTLINER') space = outliner_area.spaces[0] space.show_restrict_column_viewport = True # Global visibility (Monitor icon) - revealed_collections = [] - hidden_objs = [] + collection_view = {} + obj_view = {} for collection in bpy.data.collections: + collection_view[collection] = collection.hide_render collection.hide_viewport = False #reenables viewports for all - # enables renders for all collections - if collection.hide_render: - collection.hide_render = False - revealed_collections.append(collection) + collection.hide_render = False # enables renders for all collections + # disables viewports and renders for all objs for obj in bpy.data.objects: + obj_view[obj] = obj.hide_render obj.hide_viewport = True - if not obj.hide_render: - hidden_objs.append(obj) - obj.hide_render = True - - return revealed_collections, hidden_objs + obj.hide_render = True + + return collection_view, obj_view def uv_unwrap(obj): obj.select_set(True) @@ -139,7 +237,7 @@ def uv_unwrap(obj): bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT') try: - bpy.ops.uv.smart_project() + bpy.ops.uv.smart_project(angle_limit=0.7) except RuntimeError: logging.info("UV Unwrap failed, skipping mesh") bpy.ops.object.mode_set(mode='OBJECT') @@ -166,7 +264,7 @@ def apply_baked_tex(obj, paramDict={}): if "ExportUV" not in uv_layer.name: logging.info(f"Removed extraneous UV Layer {uv_layer}") obj.data.uv_layers.remove(uv_layer) - + for slot in obj.material_slots: mat = slot.material if (mat is None): @@ -176,8 +274,7 @@ def apply_baked_tex(obj, paramDict={}): logging.info("Reapplying baked texs on " + mat.name) # delete all nodes except baked nodes and bsdf - excludedNodes = [type + '_node' for type in BAKE_TYPES] - excludedNodes.extend([type + '_node' for type in SPECIAL_BAKE]) + excludedNodes = [type + '_node' for type in ALL_BAKE] excludedNodes.extend(['Material Output','Principled BSDF']) for n in nodes: if n.name not in excludedNodes: @@ -201,37 +298,47 @@ def apply_baked_tex(obj, paramDict={}): # create the new shader node links links.new(output.inputs[0], principled_bsdf_node.outputs[0]) - for type in BAKE_TYPES: - if not nodes.get(type + '_node'): continue - tex_node = nodes[type + '_node'] - links.new(principled_bsdf_node.inputs[BAKE_TYPES[type]], tex_node.outputs[0]) - for type in SPECIAL_BAKE: + for type in ALL_BAKE: if not nodes.get(type + '_node'): continue tex_node = nodes[type + '_node'] - links.new(principled_bsdf_node.inputs[BAKE_TYPES[type]], tex_node.outputs[0]) - + if type == 'NORMAL': + normal_node = nodes.new('ShaderNodeNormalMap') + links.new(normal_node.inputs['Color'], tex_node.outputs[0]) + links.new(principled_bsdf_node.inputs[ALL_BAKE[type]], normal_node.outputs[0]) + continue + links.new(principled_bsdf_node.inputs[ALL_BAKE[type]], tex_node.outputs[0]) + # bring back cleared param values if mat.name in paramDict: principled_bsdf_node.inputs['Metallic'].default_value = paramDict[mat.name]['Metallic'] principled_bsdf_node.inputs['Sheen'].default_value = paramDict[mat.name]['Sheen'] principled_bsdf_node.inputs['Clearcoat'].default_value = paramDict[mat.name]['Clearcoat'] -def create_glass_shader(node_tree): +def create_glass_shader(node_tree, export_usd): nodes = node_tree.nodes - color = nodes['Glass BSDF'].inputs[0].default_value - roughness = nodes['Glass BSDF'].inputs[1].default_value - ior = nodes['Glass BSDF'].inputs[2].default_value + if nodes.get('Glass BSDF'): + color = nodes['Glass BSDF'].inputs[0].default_value + roughness = nodes['Glass BSDF'].inputs[1].default_value + ior = nodes['Glass BSDF'].inputs[2].default_value + if nodes.get('Principled BSDF'): nodes.remove(nodes['Principled BSDF']) principled_bsdf_node = nodes.new('ShaderNodeBsdfPrincipled') - principled_bsdf_node.inputs['Base Color'].default_value = color - principled_bsdf_node.inputs['Roughness'].default_value = roughness - principled_bsdf_node.inputs['IOR'].default_value = ior + + if nodes.get('Glass BSDF'): + principled_bsdf_node.inputs['Base Color'].default_value = color + principled_bsdf_node.inputs['Roughness'].default_value = roughness + principled_bsdf_node.inputs['IOR'].default_value = ior + else: + principled_bsdf_node.inputs['Roughness'].default_value = 0 + principled_bsdf_node.inputs['Transmission'].default_value = 1 + if export_usd: + principled_bsdf_node.inputs['Alpha'].default_value = 0 node_tree.links.new(principled_bsdf_node.outputs[0], nodes['Material Output'].inputs[0]) -def process_glass_materials(obj): +def process_glass_materials(obj, export_usd): for slot in obj.material_slots: mat = slot.material if (mat is None or not mat.use_nodes): continue @@ -239,15 +346,20 @@ def process_glass_materials(obj): outputNode = nodes['Material Output'] if nodes.get('Glass BSDF'): if outputNode.inputs[0].links[0].from_node.bl_idname == 'ShaderNodeBsdfGlass': - create_glass_shader(mat.node_tree) + logging.info(f"Creating glass material on {obj.name}") else: logging.info(f"Non-trivial glass material on {obj.name}, material export will be inaccurate") + create_glass_shader(mat.node_tree, export_usd) + elif 'glass' in mat.name or 'shader_lamp_bulb' in mat.name: + logging.info(f"Creating glass material on {obj.name}") + create_glass_shader(mat.node_tree, export_usd) def bake_pass( obj, dest: Path, img_size, - bake_type, + bake_type, + export_usd ): img = bpy.data.images.new(f'{obj.name}_{bake_type}',img_size,img_size) @@ -275,15 +387,22 @@ def bake_pass( img_node = nodes.new('ShaderNodeTexImage') img_node.name = f'{bake_type}_node' img_node.image = img + img_node.select = True nodes.active = img_node img_node.select = True - if len(output.inputs[0].links) != 0: - surface_node = output.inputs[0].links[0].from_node - if surface_node.bl_idname == 'ShaderNodeBsdfPrincipled' and len(surface_node.inputs[BAKE_TYPES[bake_type]].links) == 0: # trivial bsdf graph - logging.info(f"{mat.name} has no procedural input for {bake_type}, not using baked textures") - bake_exclude_mats[mat] = img_node - continue + if len(output.inputs[0].links) == 0: + logging.info(f"{mat.name} has no surface output, not using baked textures") + bake_exclude_mats[mat] = img_node + continue + + surface_node = output.inputs[0].links[0].from_node + if (bake_type in ALL_BAKE and surface_node.bl_idname == 'ShaderNodeBsdfPrincipled' and + len(surface_node.inputs[ALL_BAKE[bake_type]].links) == 0): # trivial bsdf graph + + logging.info(f"{mat.name} has no procedural input for {bake_type}, not using baked textures") + bake_exclude_mats[mat] = img_node + continue bake_obj = True @@ -296,7 +415,8 @@ def bake_pass( logging.info(f'Baking {bake_type} pass') bpy.ops.object.bake(type=internal_bake_type, pass_filter={'COLOR'}, save_mode='EXTERNAL') img.filepath_raw = str(file_path) - img.save() + if not export_usd: + img.save() logging.info(f"Saving to {file_path}") else: logging.info(f"No necessary materials to bake on {obj.name}, skipping bake") @@ -304,7 +424,7 @@ def bake_pass( for mat, img_node in bake_exclude_mats.items(): mat.node_tree.nodes.remove(img_node) -def bake_metal(obj, dest, img_size): # metal baking is not really set up for node graphs w/ 2 mixed BSDFs. +def bake_metal(obj, dest, img_size, export_usd): # metal baking is not really set up for node graphs w/ 2 mixed BSDFs. metal_map_mats = [] for slot in obj.material_slots: mat = slot.material @@ -325,21 +445,42 @@ def bake_metal(obj, dest, img_size): # metal baking is not really set up for nod metal_map_mats.append(mat) if len(metal_map_mats) != 0: - bake_pass(obj, dest, img_size, 'METAL') + bake_pass(obj, dest, img_size, 'METAL', export_usd) for mat in metal_map_mats: + nodes = mat.node_tree.nodes + outputNode = nodes["Material Output"] + principled_bsdf_node = nodes['Principled BSDF'] links.remove(outputNode.inputs[0].links[0]) links.new(outputNode.inputs[0], principled_bsdf_node.outputs[0]) + +def bake_normals(obj, dest, img_size, export_usd): + bake_obj = False + for slot in obj.material_slots: + mat = slot.material + if (mat is None or not mat.use_nodes): continue + nodes = mat.node_tree.nodes + if nodes.get('Material Output'): + outputNode = nodes['Material Output'] + else: continue + + if len(outputNode.inputs['Displacement'].links) != 0: + bake_obj = True + + if bake_obj: + bake_pass(obj, dest, img_size, 'NORMAL', export_usd) + def remove_params(mat, node_tree): - paramDict = {} nodes = node_tree.nodes + paramDict = {} if nodes.get('Material Output'): output = nodes['Material Output'] elif nodes.get('Group Output'): output = nodes['Group Output'] else: raise ValueError("Could not find material output node") + if nodes.get('Principled BSDF') and output.inputs[0].links[0].from_node.bl_idname == 'ShaderNodeBsdfPrincipled': principled_bsdf_node = nodes['Principled BSDF'] metal = principled_bsdf_node.inputs['Metallic'].default_value # store metallic value and set to 0 @@ -349,21 +490,75 @@ def remove_params(mat, node_tree): principled_bsdf_node.inputs['Metallic'].default_value = 0 principled_bsdf_node.inputs['Sheen'].default_value = 0 principled_bsdf_node.inputs['Clearcoat'].default_value = 0 + return paramDict + + for node in nodes: + if node.type == 'GROUP': + paramDict = remove_params(mat, node.node_tree) + if len(paramDict) != 0: + return paramDict + return paramDict def process_interfering_params(obj): for slot in obj.material_slots: mat = slot.material if (mat is None or not mat.use_nodes): continue - paramDict = remove_params(mat, mat.node_tree) - if len(paramDict) == 0: - for node in mat.node_tree.nodes: # only handles one level of sub-groups - if node.type == 'GROUP': - paramDict = remove_params(mat, node.node_tree) - + paramDict = remove_params(mat, mat.node_tree) return paramDict -def bake_object(obj, dest, img_size): +def skipBake(obj): + if not obj.data.materials: + logging.info("No material on mesh, skipping...") + return True + + if len(obj.data.vertices) == 0: + logging.info("Mesh has no vertices, skipping ...") + return True + + return False + +def triangulate_meshes(): + logging.info("Triangulating Meshes") + for obj in bpy.context.scene.objects: + if obj.type == 'MESH': + view_state = obj.hide_viewport + obj.hide_viewport = False + bpy.context.view_layer.objects.active = obj + obj.select_set(True) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + logging.info(f"Triangulating {obj}") + bpy.ops.mesh.quads_convert_to_tris() + bpy.ops.object.mode_set(mode='OBJECT') + obj.select_set(False) + obj.hide_viewport = view_state + +def adjust_wattages(): + logging.info("Adjusting light wattage") + for obj in bpy.context.scene.objects: + if obj.type == 'LIGHT' and obj.data.type == 'POINT': + light = obj.data + if hasattr(light, 'energy') and hasattr(light, 'shadow_soft_size'): + X = light.energy + r = light.shadow_soft_size + # candelas * 1000 / (4 * math.pi * r**2). additionally units come out of blender at 1/100 scale + new_wattage = (X * 20 / (4 * math.pi)) * 1000 / (4 * math.pi * r**2) * 100 + light.energy = new_wattage + +def set_center_of_mass(): + logging.info("Resetting center of mass of objects") + for obj in bpy.context.scene.objects: + if not obj.hide_render: + view_state = obj.hide_viewport + obj.hide_viewport = False + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') + obj.select_set(False) + obj.hide_viewport = view_state + +def bake_object(obj, dest, img_size, export_usd): if not uv_unwrap(obj): return @@ -375,30 +570,20 @@ def bake_object(obj, dest, img_size): if mat is not None: slot.material = mat.copy() # we duplicate in the case of distinct meshes sharing materials - process_glass_materials(obj) - - bake_metal(obj, dest, img_size) + process_glass_materials(obj, export_usd) + + bake_metal(obj, dest, img_size, export_usd) + bake_normals(obj, dest, img_size, export_usd) paramDict = process_interfering_params(obj) for bake_type in BAKE_TYPES: - bake_pass(obj, dest, img_size, bake_type) + bake_pass(obj, dest, img_size, bake_type, export_usd) apply_baked_tex(obj, paramDict) obj.select_set(False) -def skipBake(obj): - if not obj.data.materials: - logging.info("No material on mesh, skipping...") - return True - - if len(obj.data.vertices) == 0: - logging.info("Mesh has no vertices, skipping ...") - return True - - return False - def bake_scene(folderPath: Path, image_res, vertex_colors, export_usd): for obj in bpy.data.objects: @@ -409,23 +594,24 @@ def bake_scene(folderPath: Path, image_res, vertex_colors, export_usd): logging.info("Not mesh, skipping ...") continue - if skipBake(obj): continue + if skipBake(obj): + continue if format == "stl": continue - + obj.hide_render = False obj.hide_viewport = False if vertex_colors: bakeVertexColors(obj) else: - bake_object(obj, folderPath, image_res) + bake_object(obj, folderPath, image_res, export_usd) obj.hide_render = True obj.hide_viewport = True -def run_export(exportPath: Path, format: str, vertex_colors: bool, individual_export: bool): +def run_blender_export(exportPath: Path, format: str, vertex_colors: bool, individual_export: bool): assert exportPath.parent.exists() exportPath = str(exportPath) @@ -442,11 +628,21 @@ def run_export(exportPath: Path, format: str, vertex_colors: bool, individual_ex else: bpy.ops.export_scene.fbx(filepath = exportPath, path_mode='COPY', embed_textures = True, use_selection=individual_export) - if format == "stl": bpy.ops.export_mesh.stl(filepath = exportPath, use_selection = individual_export) - - if format == "ply": bpy.ops.wm.ply_export(filepath = exportPath, export_selected_objects = individual_export) - - if format in ["usda", "usdc"]: bpy.ops.wm.usd_export(filepath = exportPath, export_textures=True, use_instancing=True, selected_objects_only=individual_export) + if format == "stl": + bpy.ops.export_mesh.stl(filepath = exportPath, use_selection = individual_export) + + if format == "ply": + bpy.ops.wm.ply_export(filepath = exportPath, export_selected_objects = individual_export) + + if format in ["usda", "usdc"]: + bpy.ops.wm.usd_export( + filepath=exportPath, + export_textures=True, + #use_instancing=True, + overwrite_textures=True, + selected_objects_only=individual_export, + root_prim_path='/World' + ) def export_scene( input_blend: Path, @@ -455,28 +651,98 @@ def export_scene( task_uniqname=None, **kwargs, ): - bpy.ops.wm.open_mainfile(filepath=str(input_blend)) - - folder = output_folder/input_blend.name + folder = output_folder/f"export_{input_blend.name}" folder.mkdir(exist_ok=True, parents=True) result = 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 +# side effects: will remove parents of inputted obj and clean its name, hides viewport of all objects +def export_single_obj(obj: bpy.types.Object, output_folder: Path, format='usdc', image_res= 1024, vertex_colors=False): + + export_usd = format in ["usda", "usdc"] + + export_folder = output_folder + export_folder.mkdir(exist_ok=True) + export_file = export_folder/output_folder.with_suffix(f'.{format}').name + + logging.info(f"Exporting to directory {export_folder=}") + + remove_obj_parents(obj) + rename_all_meshes(obj) + + collection_views, obj_views = update_visibility() + + bpy.context.scene.render.engine = 'CYCLES' + bpy.context.scene.cycles.device = 'GPU' + bpy.context.scene.cycles.samples = 1 # choose render sample + # Set the tile size + bpy.context.scene.cycles.tile_x = image_res + bpy.context.scene.cycles.tile_y = image_res + + if obj.type != 'MESH' or obj not in list(bpy.context.view_layer.objects): + raise ValueError("Object not mesh") + + if export_usd: + apply_all_modifiers(obj) + else: + realizeInstances(obj) + apply_all_modifiers(obj) + + if not skipBake(obj) and format != "stl": + if vertex_colors: + bakeVertexColors(obj) + else: + obj.hide_render = False + obj.hide_viewport = False + bake_object(obj, export_folder/'textures', image_res, export_usd) + obj.hide_render = True + obj.hide_viewport = True + + for collection, status in collection_views.items(): + collection.hide_render = status + + for obj, status in obj_views.items(): + obj.hide_render = status + + clean_names(obj) + + old_loc = obj.location.copy() + obj.location = (0, 0, 0) + + if obj.type != 'MESH' or obj.hide_render or len(obj.data.vertices) == 0 or obj not in list(bpy.context.view_layer.objects): + raise ValueError("Object is not mesh or hidden from render") + + export_subfolder = export_folder/obj.name + export_subfolder.mkdir(exist_ok=True) + export_file = export_subfolder/f'{obj.name}.{format}' + + logging.info(f"Exporting file to {export_file=}") + obj.hide_viewport = False + obj.select_set(True) + run_blender_export(export_file, format, vertex_colors, individual_export=True) + obj.select_set(False) + obj.location = old_loc + + return export_file + +@gin.configurable def export_curr_scene( output_folder: Path, - format: str, - image_res: int, + format='usdc', + image_res= 1024, vertex_colors=False, individual_export=False, + omniverse_export=False, pipeline_folder=None, task_uniqname=None ) -> Path: - + + export_usd = format in ["usda", "usdc"] export_folder = output_folder @@ -484,12 +750,13 @@ def export_curr_scene( export_file = export_folder/output_folder.with_suffix(f'.{format}').name logging.info(f"Exporting to directory {export_folder=}") - - # remove grid - if bpy.data.objects.get("Grid"): - bpy.data.objects.remove(bpy.data.objects["Grid"], do_unlink=True) remove_obj_parents() + delete_objects() + triangulate_meshes() + if omniverse_export: + split_glass_mats() + rename_all_meshes() scatter_cols = [] if export_usd: @@ -507,23 +774,20 @@ def export_curr_scene( # if obj.type == 'MESH' and len(obj.data.polygons) == 0: # if scatter_cols is not None: # if any(x in scatter_cols for x in obj.users_collection): - # continue + # continue # logging.info(f"{obj.name} has no faces, removing...") # bpy.data.objects.remove(obj, do_unlink=True) - revealed_collections, hidden_objs = update_visibility() + collection_views, obj_views = update_visibility() for obj in bpy.data.objects: if obj.type != 'MESH' or obj not in list(bpy.context.view_layer.objects): continue - viewport_status = obj.hide_viewport - obj.hide_viewport = False if export_usd: apply_all_modifiers(obj) else: realizeInstances(obj) apply_all_modifiers(obj) - obj.hide_viewport = viewport_status bpy.context.scene.render.engine = 'CYCLES' bpy.context.scene.cycles.device = 'GPU' @@ -540,16 +804,26 @@ def export_curr_scene( export_usd=export_usd ) - for collection in revealed_collections: - logging.info(f"Hiding collection {collection.name} from render") - collection.hide_render = True - - for obj in hidden_objs: - logging.info(f"Unhiding object {obj.name} from render") - obj.hide_render = False + for collection, status in collection_views.items(): + collection.hide_render = status + for obj, status in obj_views.items(): + obj.hide_render = status + clean_names() + for obj in bpy.data.objects: + obj.hide_viewport = obj.hide_render + + if omniverse_export: + adjust_wattages() + set_center_of_mass() + # remove 0 polygon meshes + for obj in bpy.data.objects: + if obj.type == 'MESH' and len(obj.data.polygons) == 0: + logging.info(f"{obj.name} has no faces, removing...") + bpy.data.objects.remove(obj, do_unlink=True) + if individual_export: bpy.ops.object.select_all(action='SELECT') bpy.ops.object.location_clear() # send all objects to (0,0,0) @@ -565,22 +839,24 @@ def export_curr_scene( logging.info(f"Exporting file to {export_file=}") obj.hide_viewport = False obj.select_set(True) - run_export(export_file, format, vertex_colors, individual_export) + run_blender_export(export_file, format, vertex_colors, individual_export) obj.select_set(False) else: logging.info(f"Exporting file to {export_file=}") - run_export(export_file, format, vertex_colors, individual_export) + run_blender_export(export_file, format, vertex_colors, individual_export) - return export_folder + return export_file def main(args): - args.output_folder.mkdir(exist_ok=True) - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(filename= args.output_folder/'export_logs.log', level=logging.DEBUG, filemode = "w+") targets = sorted(list(args.input_folder.iterdir())) for blendfile in targets: + if blendfile.stem == 'solve_state': + shutil.copy(blendfile, args.output_folder/'solve_state.json') + if not blendfile.suffix == '.blend': print(f'Skipping non-blend file {blendfile}') continue @@ -592,10 +868,11 @@ def main(args): image_res=args.resolution, vertex_colors=args.vertex_colors, individual_export=args.individual, + omniverse_export=args.omniverse ) # wanted to use shutil here but kept making corrupted files - subprocess.call(['zip', '-r', str(folder.absolute().with_suffix('.zip')), str(folder.absolute())]) - + subprocess.call(['zip', '-r', str(folder.with_suffix('.zip')), str(folder)]) + bpy.ops.wm.quit_blender() def make_args(): @@ -609,6 +886,7 @@ def make_args(): parser.add_argument('-v', '--vertex_colors', action = 'store_true') parser.add_argument('-r', '--resolution', default= 1024, type=int) parser.add_argument('-i', '--individual', action = 'store_true') + parser.add_argument('-o', '--omniverse', action = 'store_true') args = parser.parse_args() diff --git a/infinigen/tools/indoor_profile.py b/infinigen/tools/indoor_profile.py new file mode 100644 index 000000000..b55ad020e --- /dev/null +++ b/infinigen/tools/indoor_profile.py @@ -0,0 +1,83 @@ +# 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: David Yan + +from pathlib import Path +import re +from datetime import timedelta +from collections import defaultdict +import argparse +import pandas as pd +from tabulate import tabulate + + +''' +The following function s attributed to FObersteiner from Stack Overflow at https://stackoverflow.com/a/64662985 +and is licensed under CC-BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/deed.en#ref-appropriate-credit). +David Yan used this code WITHOUT modification. +''' +def td_to_str(td): + """ + convert a timedelta object td to a string in HH:MM:SS format. + """ + if (pd.isnull(td)): + return td + hours, remainder = divmod(td.total_seconds(), 3600) + minutes, seconds = divmod(remainder, 60) + return f'{int(hours):02}:{int(minutes):02}:{int(seconds):02}' + +def main(dir : Path): + coarse_data = defaultdict(list) + render_data = defaultdict(list) + with open(dir/"finished_seeds.txt") as f: + seeds = f.read().splitlines() + + for seed in seeds: + try: + coarse_log = open(dir/seed/'logs'/'coarse.err').read() + render_log = open(next((dir/seed/"logs").glob('shortrender*.err'))).read() + except: + continue + + for name, h, m, s in re.findall(r'\[INFO\] \| \[(.*?)\] finished in ([0-9]+):([0-9]+):([0-9]+)', coarse_log): + timedelta_obj = timedelta(hours=int(h), minutes=int(m), seconds=int(s)) + if (timedelta_obj.total_seconds() < 1): continue + coarse_data[name].append(timedelta_obj) + + for name, h, m, s in re.findall(r'\[INFO\] \| \[(.*?)\] finished in ([0-9]+):([0-9]+):([0-9]+)', render_log): + timedelta_obj = timedelta(hours=int(h), minutes=int(m), seconds=int(s)) + if (timedelta_obj.total_seconds() < 1): continue + render_data[name].append(timedelta_obj) + + coarse_stats = make_stats(pd.DataFrame.from_dict(coarse_data, orient='index')) + render_stats = make_stats(pd.DataFrame.from_dict(render_data, orient='index')) + + for column in coarse_stats: + coarse_stats[column] = coarse_stats[column].dt.round('1s').map(lambda x: td_to_str(x)) + + for column in coarse_stats: + render_stats[column] = render_stats[column].dt.round('1s').map(lambda x: td_to_str(x)) + + print(coarse_stats.sort_values("median", ascending=False)) + print(render_stats.sort_values("median", ascending=False)) + + +def make_stats(data_df): + stats = pd.DataFrame() + stats['mean'] = data_df.mean(axis=1) + stats['median'] = data_df.median(axis=1) + stats['90%'] = data_df.quantile(0.9, axis=1) + stats['95%'] = data_df.quantile(0.95, axis=1) + stats['99%'] = data_df.quantile(0.99, axis=1) + return stats + +def make_args(): + parser = argparse.ArgumentParser() + parser.add_argument('-d', '--dir', type=Path) + args = parser.parse_args() + return args + +if __name__ == '__main__': + args = make_args() + main(args.dir) diff --git a/infinigen/tools/isaac_sim.py b/infinigen/tools/isaac_sim.py new file mode 100644 index 000000000..0f704b2a1 --- /dev/null +++ b/infinigen/tools/isaac_sim.py @@ -0,0 +1,171 @@ +# 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: David Yan, Beining Han + +# Acknowledgement: This file draws inspiration from https://docs.omniverse.nvidia.com/isaacsim/latest/index.html + +import numpy as np + +from omni.isaac.kit import SimulationApp +CONFIG = {"renderer": "RayTracedLighting", "headless": False} +simulation_app = SimulationApp(launch_config=CONFIG) + +import omni +import json +from omni.isaac.core import World +from pxr import Usd,UsdGeom, UsdLux, Sdf +from omni.isaac.core.utils.prims import create_prim +from omni.isaac.core.utils.nucleus import get_assets_root_path +from omni.isaac.core.prims import XFormPrim +from omni.kit.commands import execute as omni_exec +from omni.isaac.core.utils.extensions import enable_extension +enable_extension("omni.isaac.examples") +from omni.isaac.core.utils.nucleus import get_assets_root_path +from omni.isaac.core.utils.types import ArticulationAction +from omni.isaac.core.controllers import BaseController +from omni.isaac.wheeled_robots.robots import WheeledRobot +from omni.physx.scripts import utils + +class RobotController(BaseController): + def __init__(self): + super().__init__(name="robot_controller") + + def forward(self): + return ArticulationAction(joint_velocities=[2,2]) + +class InfinigenIsaacScene(object): + def __init__(self, cfg): + self.cfg = cfg + self.world = World(stage_units_in_meters=1.0, backend='numpy', physics_dt=1/400.) + self.world._physics_context.set_gravity(-9.8) + self.scene = self.world.scene + self._support = None + self.setup_scene() + + def setup_scene(self): + self._add_infinigen_scene() + self._add_lighting() + self._add_robot() + + def _add_infinigen_scene(self): + create_prim(prim_path="/World/Support", + usd_path=self.cfg.scene_path, + semantic_label='scene') + self._support = XFormPrim(prim_path="/World/Support", name="Support") + + stage = omni.usd.get_context().get_stage() + + prims = [prim for prim in stage.Traverse() if prim.IsA(UsdGeom.Mesh)] + if self.cfg.json_path is None: + for prim in prims: + utils.setStaticCollider(prim) + self.scene.add(self._support) + return + + with open(self.cfg.json_path) as json_file: + relations = json.load(json_file)["objs"] + + obj_to_target = {} + for key, value in relations.items(): + obj = value.get("obj") + if obj: + obj_to_target[obj.replace('(', '_').replace(')', '_').replace('.', '_')] = key + + for prim in prims: + prim_name = prim.GetName() + target = obj_to_target.get(prim_name) + + if 'SPLIT' in prim_name: + do_not_cast_shadows = prim.CreateAttribute('primvars:doNotCastShadows', Sdf.ValueTypeNames.Bool) + do_not_cast_shadows.Set(True) + + if 'terrain' in prim_name: + continue + + if not target: + utils.setStaticCollider(prim) + continue + + if any(x["relation"]["relation_type"] == "StableAgainst" and "Subpart(wall)" in x["relation"].get("parent_tags") or "Subpart(ceiling)" in x["relation"].get("parent_tags") for x in relations[target]["relations"]): + utils.setStaticCollider(prim) + else: + utils.setRigidBody(prim, 'convexDecomposition', False) + + self.scene.add(self._support) + + def _add_lighting(self): + omni_exec( + "CreatePrim", + prim_path='/World/DomeLight', + prim_type="DomeLight", + select_new_prim=False, + attributes={ + UsdLux.Tokens.inputsIntensity: 5000, + UsdLux.Tokens.inputsColor: (0.7, 0.88, 1.0) + }, + create_default_xform=True, + ) + omni_exec( + "CreatePrim", + prim_path='/World/DistantLight', + prim_type="DistantLight", + select_new_prim=False, + attributes={ + UsdLux.Tokens.inputsIntensity: 8000 + }, + create_default_xform=True, + ) + + def _get_camera_loc(self): + stage = omni.usd.get_context().get_stage() + prim = stage.GetPrimAtPath("/World/Support/CameraRigs_0_0") + xform = UsdGeom.Xformable(prim) + transform_matrix = xform.ComputeLocalToWorldTransform(Usd.TimeCode.Default()) + translation = transform_matrix.ExtractTranslation() + translation[2] = 0 + return translation, [1, 0, 0, 0] + + def _add_robot(self): + robot_path = get_assets_root_path() + "/Isaac/Robots/Jetbot/jetbot.usd" + init_pos, _ = self._get_camera_loc() + init_pos[-1] += 0.3 + self.robot = self.scene.add( + WheeledRobot( + prim_path="/World/Robot", + name="Robot", + wheel_dof_names=["left_wheel_joint", "right_wheel_joint"], + create_robot=True, + usd_path=robot_path, + position=init_pos + ) + ) + self.robot.set_local_scale(np.array([4, 4, 4])) + self.controller = RobotController() + self.world.reset() + + def apply_action(self): + self.robot.apply_action(self.controller.forward()) + + def reset(self): + self.world.reset() + + def run(self): + self.world.reset() + while simulation_app.is_running(): + self.apply_action() + self.world.step(render=True) + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('--scene-path', type=str) + parser.add_argument('--json-path', type=str) + args = parser.parse_args() + + scene = InfinigenIsaacScene(args) + + scene.reset() + scene.run() + simulation_app.close() + diff --git a/infinigen/tools/perceptual/analysis.ipynb b/infinigen/tools/perceptual/analysis.ipynb new file mode 100644 index 000000000..cd1641f06 --- /dev/null +++ b/infinigen/tools/perceptual/analysis.ipynb @@ -0,0 +1,411 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import statsmodels.api as sm\n", + "\n", + "def A_count_proportion(df):\n", + " A_count = 0\n", + " B_count = 0\n", + " for index, _ in df.iterrows():\n", + " row = df.iloc[index]\n", + " if \"Program A\" in row['Answer.category.label']:\n", + " A_count += 1\n", + " elif \"Program B\" in row['Answer.category.label']:\n", + " B_count += 1\n", + " number_of_successes = A_count # number of times program A (or B) was chosen as more realistic\n", + " n = (A_count+B_count) # total number of submissions\n", + "\n", + " # Confidence level: 99%\n", + " confidence_level = 0.99\n", + " alpha = 1 - confidence_level\n", + "\n", + " # Calculate the confidence interval\n", + " ci_low, ci_upp = sm.stats.proportion_confint(number_of_successes, n, alpha=alpha, method='binom_test')\n", + "\n", + " return A_count/(A_count+B_count),ci_low, ci_upp\n", + "\n", + "def B_count_proportion(df):\n", + " A_count = 0\n", + " B_count = 0\n", + " for index, _ in df.iterrows():\n", + " row = df.iloc[index]\n", + " if \"Program A\" in row['Answer.category.label']:\n", + " A_count += 1\n", + " elif \"Program B\" in row['Answer.category.label']:\n", + " B_count += 1\n", + " number_of_successes = B_count # number of times program A (or B) was chosen as more realistic\n", + " n = (A_count+B_count) # total number of submissions\n", + "\n", + " # Confidence level: 99%\n", + " confidence_level = 0.99\n", + " alpha = 1 - confidence_level\n", + "\n", + " # Calculate the confidence interval\n", + " ci_low, ci_upp = sm.stats.proportion_confint(number_of_successes, n, alpha=alpha, method='binom_test')\n", + "\n", + " return B_count/(A_count+B_count),ci_low, ci_upp\n", + "\n", + "def count_errors(df):\n", + " error_count = 0\n", + " not_sure_count = 0\n", + " for index, _ in df.iterrows():\n", + " row = df.iloc[index]\n", + " if 'Yes' in row['Answer.category.label']:\n", + " error_count += 1\n", + " if 'Not Sure' in row['Answer.category.label']:\n", + " not_sure_count += 1\n", + " return error_count/(df.shape[0]-not_sure_count)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Infinigen first person layout is more realistic than ATISS 0.693 of the time. 99% confidence interval: 0.590 - 0.783\n", + "Infinigen first person is more realistic than ATISS 0.713 of the time. 99% confidence interval: 0.611 - 0.802\n", + "Infinigen first person layout is more realistic than Sceneformer 0.560 of the time. 99% confidence interval: 0.453 - 0.661\n", + "Infinigen first person is more realistic than Sceneformer 0.667 of the time. 99% confidence interval: 0.561 - 0.759\n", + "Infinigen first person layout is more realistic than FastSynth 0.853 of the time. 99% confidence interval: 0.766 - 0.917\n", + "Infinigen first person is more realistic than FastSynth 0.907 of the time. 99% confidence interval: 0.829 - 0.954\n", + "Infinigen first person layout is more realistic than Procthor 0.944 of the time. 99% confidence interval: 0.873 - 0.979\n", + "Infinigen first person is more realistic than Procthor 0.893 of the time. 99% confidence interval: 0.813 - 0.946\n", + "Infinigen overhead layout is more realistic than ATISS 0.393 of the time. 99% confidence interval: 0.295 - 0.500\n", + "Infinigen overhead is more realistic than ATISS 0.480 of the time. 99% confidence interval: 0.376 - 0.586\n", + "Infinigen overhead layout is more realistic than Sceneformer 0.573 of the time. 99% confidence interval: 0.466 - 0.675\n", + "Infinigen overhead is more realistic than Sceneformer 0.620 of the time. 99% confidence interval: 0.513 - 0.719\n", + "Infinigen overhead layout is more realistic than FastSynth 0.560 of the time. 99% confidence interval: 0.453 - 0.661\n", + "Infinigen overhead is more realistic than FastSynth 0.453 of the time. 99% confidence interval: 0.350 - 0.561\n" + ] + } + ], + "source": [ + "src = './results/infinigen-ATISS-first-person-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen first person layout is more realistic than ATISS {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-ATISS-first-person-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen first person is more realistic than ATISS {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "src = './results/infinigen-sceneformer-first-person-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen first person layout is more realistic than Sceneformer {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "src = './results/infinigen-sceneformer-first-person-realism.csv' \n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen first person is more realistic than Sceneformer {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "src = './results/infinigen-fastsynth-first-person-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen first person layout is more realistic than FastSynth {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "\n", + "src = './results/infinigen-fastsynth-first-person-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen first person is more realistic than FastSynth {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-procthor-first-person-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen first person layout is more realistic than Procthor {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-procthor-first-person-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen first person is more realistic than Procthor {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "\n", + "\n", + "src = './results/infinigen-ATISS-overhead-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen overhead layout is more realistic than ATISS {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-ATISS-overhead-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen overhead is more realistic than ATISS {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "src = './results/infinigen-sceneformer-overhead-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen overhead layout is more realistic than Sceneformer {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-sceneformer-overhead-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen overhead is more realistic than Sceneformer {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-fastsynth-overhead-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen overhead layout is more realistic than FastSynth {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-fastsynth-overhead-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen overhead is more realistic than FastSynth {A_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {A_count_proportion(df)[1]:.3f} - {A_count_proportion(df)[2]:.3f}')\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Infinigen first person has 0.175 errors\n" + ] + } + ], + "source": [ + "src = './results/infinigen-first-person-errors.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Infinigen first person has {count_errors(df):.3f} errors')" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ATISS first person layout is more realistic than Infinigen 0.307 of the time. 99% confidence interval: 0.217 - 0.410\n", + "ATISS first person is more realistic than Infinigen 0.287 of the time. 99% confidence interval: 0.198 - 0.389\n", + "Sceneformer first person layout is more realistic than Infinigen 0.440 of the time. 99% confidence interval: 0.339 - 0.547\n", + "Sceneformer first person is more realistic than Infinigen 0.333 of the time. 99% confidence interval: 0.241 - 0.439\n", + "FastSynth first person layout is more realistic than Infinigen 0.147 of the time. 99% confidence interval: 0.083 - 0.234\n", + "Procthor first person layout is more realistic than Infinigen 0.056 of the time. 99% confidence interval: 0.021 - 0.127\n", + "Procthor first person is more realistic than Infinigen 0.107 of the time. 99% confidence interval: 0.054 - 0.187\n", + "FastSynth first person is more realistic than Infinigen 0.093 of the time. 99% confidence interval: 0.046 - 0.171\n", + "ATISS overhead layout is more realistic than Infinigen 0.607 of the time. 99% confidence interval: 0.500 - 0.705\n", + "ATISS overhead is more realistic than Infinigen 0.520 of the time. 99% confidence interval: 0.414 - 0.624\n", + "sceneformer overhead layout is more realistic than Infinigen 0.427 of the time. 99% confidence interval: 0.325 - 0.534\n", + "sceneformer overhead is more realistic than Infinigen 0.380 of the time. 99% confidence interval: 0.281 - 0.487\n", + "fastsynth overhead layout is more realistic than Infinigen 0.440 of the time. 99% confidence interval: 0.339 - 0.547\n", + "fastsynth overhead is more realistic than Infinigen 0.547 of the time. 99% confidence interval: 0.439 - 0.650\n" + ] + } + ], + "source": [ + "src = './results/infinigen-ATISS-first-person-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'ATISS first person layout is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-ATISS-first-person-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'ATISS first person is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "src = './results/infinigen-sceneformer-first-person-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Sceneformer first person layout is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "src = './results/infinigen-sceneformer-first-person-realism.csv' \n", + "df = pd.read_csv(src)\n", + "print(f'Sceneformer first person is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "src = './results/infinigen-fastsynth-first-person-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'FastSynth first person layout is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "src = './results/infinigen-fastsynth-first-person-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'FastSynth first person is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "src = './results/infinigen-procthor-first-person-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Procthor first person layout is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-procthor-first-person-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'Procthor first person is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "\n", + "\n", + "src = './results/infinigen-ATISS-overhead-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'ATISS overhead layout is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-ATISS-overhead-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'ATISS overhead is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "\n", + "src = './results/infinigen-sceneformer-overhead-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'sceneformer overhead layout is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-sceneformer-overhead-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'sceneformer overhead is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-fastsynth-overhead-layout-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'fastsynth overhead layout is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n", + "src = './results/infinigen-fastsynth-overhead-realism.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'fastsynth overhead is more realistic than Infinigen {B_count_proportion(df)[0]:.3f} of the time. 99% confidence interval: {B_count_proportion(df)[1]:.3f} - {B_count_proportion(df)[2]:.3f}')\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "procthor first person has 0.252 errors\n" + ] + } + ], + "source": [ + "src = './results/procthor-first-person-errors.csv'\n", + "df = pd.read_csv(src)\n", + "print(f'procthor first person has {count_errors(df):.3f} errors')" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.795, 0.7496838235735617, 0.8348538077362546)" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import statsmodels.api as sm\n", + "\n", + "def A_count_proportion(dataframes):\n", + " A_count = 0\n", + " total_B_count = 0\n", + " for df in dataframes:\n", + " B_count = 0\n", + " for index, row in df.iterrows():\n", + " if \"Program A\" in row['Answer.category.label']:\n", + " A_count += 1\n", + " else: # Assuming any other label is a different Program B\n", + " B_count += 1\n", + " total_B_count += B_count\n", + "\n", + " number_of_successes = A_count # number of times program A was chosen as more realistic\n", + " n = A_count + total_B_count # total number of submissions\n", + "\n", + " # Confidence level: 99%\n", + " confidence_level = 0.99\n", + " alpha = 1 - confidence_level\n", + "\n", + " # Calculate the confidence interval\n", + " ci_low, ci_upp = sm.stats.proportion_confint(number_of_successes, n, alpha=alpha, method='binom_test')\n", + "\n", + " return A_count / n, ci_low, ci_upp\n", + "\n", + "src = './results/infinigen-ATISS-first-person-realism.csv'\n", + "df1 = pd.read_csv(src)\n", + "\n", + "src = './results/infinigen-sceneformer-first-person-realism.csv' \n", + "df2 = pd.read_csv(src)\n", + "\n", + "src = './results/infinigen-fastsynth-first-person-realism.csv'\n", + "df3 = pd.read_csv(src)\n", + "\n", + "src = './results/infinigen-procthor-first-person-realism.csv'\n", + "df4 = pd.read_csv(src)\n", + "\n", + "A_count_proportion([df1,df2,df3,df4])" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(0.7601351351351351, 0.7124366959489067, 0.8029941663408575)" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "src = './results/infinigen-ATISS-first-person-layout-realism.csv'\n", + "df1 = pd.read_csv(src)\n", + "\n", + "src = './results/infinigen-sceneformer-first-person-layout-realism.csv' \n", + "df2 = pd.read_csv(src)\n", + "\n", + "src = './results/infinigen-fastsynth-first-person-layout-realism.csv'\n", + "df3 = pd.read_csv(src)\n", + "\n", + "src = './results/infinigen-procthor-first-person-layout-realism.csv'\n", + "df4 = pd.read_csv(src)\n", + "\n", + "A_count_proportion([df1,df2,df3,df4])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "infinigen_indoors", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/infinigen/tools/perceptual/create_pairs.py b/infinigen/tools/perceptual/create_pairs.py new file mode 100644 index 000000000..4f86e65ac --- /dev/null +++ b/infinigen/tools/perceptual/create_pairs.py @@ -0,0 +1,187 @@ +# 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: Karhan Kayan + +import sys +import cv2 +import os +import numpy as np +import matplotlib.pyplot as plt + +import sys + + +import pandas as pd +from PIL import Image +from tqdm import tqdm + +from PIL import Image +from PIL import Image, ImageDraw, ImageFont +import random + +def merge_images(image_path1, image_path2, text1='Program A', text2='Program B', strip_width=5): + # Open the images + image1 = Image.open(image_path1) + image2 = Image.open(image_path2) + + # Resize the larger image to match the width of the smaller one + if image1.width > image2.width: + # Calculate new height to maintain aspect ratio + new_height = int((image2.width / image1.width) * image1.height) + image1 = image1.resize((image2.width, new_height)) + elif image2.width > image1.width: + # Calculate new height to maintain aspect ratio + new_height = int((image1.width / image2.width) * image2.height) + image2 = image2.resize((image1.width, new_height)) + + # Determine the max height + max_height = max(image1.height, image2.height) + + # Create a new image with the combined width plus the strip width and the max height + combined_width = image1.width + image2.width + strip_width + combined_image = Image.new('RGB', (combined_width, max_height), 'black') + + # Paste the two images into the new image + # Adjust the position if one image is shorter than the other + image1_y = (max_height - image1.height) // 2 + image2_y = (max_height - image2.height) // 2 + + combined_image.paste(image1, (0, image1_y)) + combined_image.paste(image2, (image1.width + strip_width, image2_y)) + + # Add text + font_size = 40 + draw = ImageDraw.Draw(combined_image) + try: + # Load a specific TrueType or OpenType font file + font = ImageFont.truetype("/System/Library/Fonts/Supplemental/Arial Black.ttf", font_size) + except IOError: + # If the specific font file is not found, load the default font + print('Font not found, using default font.') + font = ImageFont.load_default() + + text_color = (255, 0, 0) # White color + + # Calculate text position + text1_x = 10 + text1_y = 10 + text2_x = image1.width + strip_width + 10 + text2_y = 10 + + draw.text((text1_x, text1_y), text1, fill=text_color, font=font) + draw.text((text2_x, text2_y), text2, fill=text_color, font=font) + + # Save the combined image + return combined_image + +from PIL import Image, ImageDraw, ImageFont + +def merge_images2(image_path1, image_path2, text1='Program A', text2='Program B', strip_width=5): + # Open the images + image1 = Image.open(image_path1) + image2 = Image.open(image_path2) + + # Resize the larger image to match the height of the smaller one + if image1.height > image2.height: + # Calculate new width to maintain aspect ratio + new_width = int((image2.height / image1.height) * image1.width) + image1 = image1.resize((new_width, image2.height)) + elif image2.height > image1.height: + # Calculate new width to maintain aspect ratio + new_width = int((image1.height / image2.height) * image2.width) + image2 = image2.resize((new_width, image1.height)) + + # Determine the max width after resizing + max_width = image1.width + image2.width + strip_width + + # Create a new image with the max width and the combined height + combined_image = Image.new('RGB', (max_width, image1.height), 'black') + + # Paste the two images into the new image + image1_x = (max_width - image1.width - image2.width - strip_width) // 2 + image2_x = image1_x + image1.width + strip_width + + combined_image.paste(image1, (image1_x, 0)) + combined_image.paste(image2, (image2_x, 0)) + + # Add text + font_size = 40 + draw = ImageDraw.Draw(combined_image) + try: + # Load a specific TrueType or OpenType font file + font = ImageFont.truetype("/System/Library/Fonts/Supplemental/Arial.ttf", font_size) + except IOError: + # If the specific font file is not found, load the default font + print('Font not found, using default font.') + font = ImageFont.load_default() + + text_color = (255, 0, 0) # Red color + + # Calculate text position + text1_x = 10 + text1_y = 10 + text2_x = image1.width + strip_width + 10 + text2_y = 10 + + + draw.text((text1_x, text1_y), text1, fill=text_color, font=font) + draw.text((text2_x, text2_y), text2, fill=text_color, font=font) + + # Save the combined image + return combined_image + + + +if __name__ == '__main__': + # methods = ['eevee', 'fastsynth'] + # perspective = 'first_person' + main_directory = sys.argv[1] + methods = sys.argv[2] + perspective = sys.argv[3] + output_directory = sys.argv[4] + + random_seed = 1234 # You can choose any number as the seed + random.seed(random_seed) + + k = 50 + # Set your main directory, methods, and perspective here + + + if not os.path.exists(output_directory): + os.makedirs(output_directory) + + # Building paths for both methods + path_method1 = os.path.join(main_directory, methods[0], perspective) + path_method2 = os.path.join(main_directory, methods[1], perspective) + + # List of images in each method's perspective directory + images_method1 = os.listdir(path_method1) + images_method2 = os.listdir(path_method2) + + # Randomly select k images from each list (or all images if there are fewer than k) + random_images_method1 = random.sample(images_method1, min(k, len(images_method1))) + random_images_method2 = random.sample(images_method2, min(k, len(images_method2))) + + # Iterate over each randomly selected image in method1 and pair it with each randomly selected image in method2 + for img1 in tqdm(random_images_method1): + for img2 in random_images_method2: + image_path_1 = os.path.join(path_method1, img1) + image_path_2 = os.path.join(path_method2, img2) + # Extracting image identifiers + img_0_id = img1.split('.')[0] + img_1_id = img2.split('.')[0] + + # skip if not image + if not (image_path_1.endswith('.png') or image_path_1.endswith('.jpg')): + continue + if not (image_path_2.endswith('.png') or image_path_2.endswith('.jpg')): + continue + + # Creating a unique filename for the merged image + merged_filename = f'{perspective}-{methods[0]}-{img_0_id}-{methods[1]}-{img_1_id}.jpg' + merged_image_path = os.path.join(output_directory, merged_filename) + + # Merge and save images + merged_img = merge_images2(image_path_1, image_path_2) + merged_img.save(merged_image_path, 'JPEG') diff --git a/infinigen/tools/perceptual/create_submission.py b/infinigen/tools/perceptual/create_submission.py new file mode 100644 index 000000000..b3245051c --- /dev/null +++ b/infinigen/tools/perceptual/create_submission.py @@ -0,0 +1,37 @@ +# 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: Karhan Kayan + +import os +import random +import csv +import sys + +def select_random_files_to_csv(folder_path, k, output_directory): + # Get all files in the folder + files = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))] + + # Select k random files + selected_files = random.sample(files, min(k, len(files))) + + # Create CSV file with the name of the folder + folder_name = os.path.basename(folder_path) + csv_file_path = os.path.join(output_directory, f'{folder_name}-{k}.csv') + + # Write the selected file names to the CSV + with open(csv_file_path, 'w', newline='') as csvfile: + writer = csv.writer(csvfile) + writer.writerow(['image_url']) # Header + for file in selected_files: + writer.writerow([file]) + + print(f'CSV file created at {csv_file_path}') + + +if __name__ == '__main__': + k = 50 + output_directory = sys.argv[1] + folder_path = sys.argv[2] + # Example usage + select_random_files_to_csv(folder_path, k, output_directory) diff --git a/infinigen/tools/perceptual/perceptual_extract.py b/infinigen/tools/perceptual/perceptual_extract.py new file mode 100644 index 000000000..563fe59a1 --- /dev/null +++ b/infinigen/tools/perceptual/perceptual_extract.py @@ -0,0 +1,34 @@ +# 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: Karhan Kayan + +import os +import shutil +import sys + + +if __name__ == '__main__': + input_directory = sys.argv[1] + output_directory = sys.argv[2] + + # Supported image formats + image_formats = ['.png', '.jpg', '.jpeg', '.bmp', '.gif', '.tiff'] + + if not os.path.exists(output_directory): + os.makedirs(output_directory) + + for folder_name in os.listdir(input_directory): + folder_path = os.path.join(input_directory, folder_name) + + if os.path.isdir(folder_path): + # Find the image file inside the folder + for file_name in os.listdir(folder_path): + if any(file_name.lower().endswith(ext) for ext in image_formats): + old_file_path = os.path.join(folder_path, file_name) + new_file_name = f'{folder_name}.png' # Change the extension if needed + new_file_path = os.path.join(output_directory, new_file_name) + + # Move and rename the image file + shutil.copy(old_file_path, new_file_path) + break # Assuming only one image per folder diff --git a/infinigen/tools/perceptual/rename.py b/infinigen/tools/perceptual/rename.py new file mode 100644 index 000000000..8f51cd65a --- /dev/null +++ b/infinigen/tools/perceptual/rename.py @@ -0,0 +1,25 @@ +# 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: Karhan Kayan + +import os +import sys + + +if __name__ == '__main__': + # Set the directory containing your files + directory = sys.argv[1] + + # List all files in the directory + files = os.listdir(directory) + + # Sort files if necessary + files.sort() # This sorts in lexicographical order + + # Rename each file + for i, filename in enumerate(files, start=1): + old_path = os.path.join(directory, filename) + _, file_extension = os.path.splitext(filename) + new_path = os.path.join(directory, f'{i}{file_extension}') + os.rename(old_path, new_path) \ No newline at end of file diff --git a/infinigen/tools/results/visualize_planar_graph.py b/infinigen/tools/results/visualize_planar_graph.py new file mode 100644 index 000000000..b88480b8b --- /dev/null +++ b/infinigen/tools/results/visualize_planar_graph.py @@ -0,0 +1,71 @@ +# 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: Lingjie Mei + +import math +import os +import sys +from pathlib import Path + +import matplotlib.pyplot as plt + +sys.path.insert(0, os.getcwd()) +from PIL import Image +from infinigen.core.util.math import FixedSeed +# noinspection PyUnresolvedReferences +from infinigen.core.util import blender as butil +from infinigen_examples.generate_individual_assets import make_args +from infinigen.core.constraints.example_solver.room import GraphMaker + +def build_scene(idx, path): + with FixedSeed(idx): + factory = GraphMaker(idx) + graph = factory.make_graph(idx) + factory.draw(graph) + (path / 'images').mkdir(exist_ok=True) + imgpath = path / f"images/image_{idx:03d}.png" + plt.savefig(imgpath) + plt.clf() + + +def make_grid(args, path, n): + files = [] + for filename in sorted(os.listdir(f'{path}/images')): + if filename.endswith('.png'): + files.append(f'{path}/images/{filename}') + files = files[:n] + if len(files) == 0: + print('No images found') + return + with Image.open(files[0]) as i: + x, y = i.size + sz_x = list(sorted(range(1, n + 1), key=lambda x: abs(math.ceil(n / x) / x - args.best_ratio)))[0] + sz_y = math.ceil(n / sz_x) + img = Image.new('RGBA', (sz_x * x, sz_y * y)) + for idx, file in enumerate(files): + with Image.open(file) as i: + img.paste(i, (idx % sz_x * x, idx // sz_x * y)) + img.save(f'{path}/grid.png') + + +def main(args): + path = Path(os.getcwd()) / 'outputs' + path.mkdir(exist_ok=True) + fac_path = path / GraphMaker.__name__ + if fac_path.exists() and args.skip_existing: + return + fac_path.mkdir(exist_ok=True) + n_images = args.n_images + for idx in range(args.n_images): + fac_path.mkdir(exist_ok=True) + build_scene(idx, fac_path) + + make_grid(args, fac_path, n_images) + + +if __name__ == '__main__': + args = make_args() + args.no_mod = args.no_mod or args.fire + with FixedSeed(1): + main(args) diff --git a/infinigen/tools/suffixes.py b/infinigen/tools/suffixes.py index b2126f8e7..3a8ce7bbe 100644 --- a/infinigen/tools/suffixes.py +++ b/infinigen/tools/suffixes.py @@ -43,6 +43,7 @@ def parse_suffix(s): if len(s_parts) == len(SUFFIX_ORDERING) + 1: s_parts = s_parts[1:] # discard leading filename / description etc - assert len(s_parts) == len(SUFFIX_ORDERING), s + if len(s_parts) != len(SUFFIX_ORDERING): + return None return {SUFFIX_ORDERING[i]: int(s_parts[i]) for i in range(len(s_parts))} diff --git a/infinigen/tools/terrain/palette/readme.md b/infinigen/tools/terrain/palette/readme.md index 1f964d395..fe9e8568d 100644 --- a/infinigen/tools/terrain/palette/readme.md +++ b/infinigen/tools/terrain/palette/readme.md @@ -32,7 +32,7 @@ After manually comemnt out them, you have: ![](demo4.png) -Then you move the ready palatte to location: `infinigen/infinigen_examples/configs/palette` +Then you move the ready palatte to location: `infinigen/infinigen_examples/configs_nature/palette` ## Step 3 diff --git a/infinigen_examples/asset_parameters.py b/infinigen_examples/asset_parameters.py new file mode 100644 index 000000000..dd3d9d1ae --- /dev/null +++ b/infinigen_examples/asset_parameters.py @@ -0,0 +1,45 @@ +# 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: Lingjie Mei +import numpy as np + +from infinigen.assets.clothes import blanket +from infinigen.assets.materials import metal, fabrics, ceramic +from infinigen.assets.materials.woods import wood +from infinigen.assets.scatters.clothes import ClothesCover +from infinigen.assets.seating import ChairFactory +from infinigen.assets.tableware import PotFactory, PanFactory, FruitContainerFactory +from infinigen.core.surface import NoApply + +parameters = { + 'ChairFactory': { + 'factories': [ChairFactory] * 16, + 'globals': { + }, + 'individuals': [{}, {'arm_mid': [-.03, -.03, .09], 'leg_height': .5, 'leg_x_offset': 0}, + {'arm_mid': [0, 0, 0], 'leg_height': .6, 'leg_x_offset': .02}, + {'arm_mid': [.03, .09, -.03], 'leg_height': .7, 'leg_x_offset': .05}, {}, + {'leg_offset_bar': (.2, .4), 'seat_front': 1., 'back_vertical_cuts': 1}, + {'leg_offset_bar': (.4, .6), 'seat_front': 1.1, 'back_vertical_cuts': 2}, + {'leg_offset_bar': (.6, .8), 'seat_front': 1.2, 'back_vertical_cuts': 3}, {}] + [{}] * 7, + 'repeat': 12, + 'indices': [0] * 9 + list(range(1, 8)), + 'scene_idx': 4, + }, + + 'PanFactory': { + 'factories': [PanFactory] * 8 + [PanFactory] * 2 + [PotFactory] * 3 + [FruitContainerFactory] * 3, + 'globals': { + }, + 'individuals': [{}, {'scale': .1, 'depth': .3, 'x_handle': 2, }, {'scale': .12, 'depth': .5, 'x_handle': 1.5}, + {'scale': .15, 'depth': .8, 'x_handle': 1.2}, {}, + {'s_handle': .8, 'r_expand': 1, 'x_guard': 1, }, + {'s_handle': 1., 'r_expand': 1.15, 'x_guard': 1.3}, + {'s_handle': 1.2, 'r_expand': 1.3, 'x_guard': 1.6}, {}] + [{}] * 7, + 'repeat': 12, + 'indices': [0] * 9 + list(range(1, 8)), + 'scene_idx': 2, + }, + +} diff --git a/infinigen_examples/configs/disable_assets/no_assets.gin b/infinigen_examples/configs/disable_assets/no_assets.gin deleted file mode 100644 index 5c8297d39..000000000 --- a/infinigen_examples/configs/disable_assets/no_assets.gin +++ /dev/null @@ -1,47 +0,0 @@ -compose_scene.fancy_clouds_chance = 0.0 - -compose_scene.trees_chance = 0.0 -compose_scene.bushes_chance = 0.0 -compose_scene.ground_creatures_chance = 0.0 -compose_scene.flying_creatures_chance = 0.0 -compose_scene.clouds_chance = 0.0 -compose_scene.boulders_chance = 0.0 -compose_scene.glowing_rocks_chance = 0.0 -compose_scene.rocks_chance = 0.0 -compose_scene.ground_leaves_chance = 0.0 -compose_scene.ground_twigs_chance = 0.0 -compose_scene.chopped_trees_chance = 0.0 -compose_scene.grass_chance = 0.0 -compose_scene.flowers_chance = 0.0 -compose_scene.kelp_chance = 0.0 -compose_scene.cactus_chance = 0.0 -compose_scene.rain_particles_chance = 0.0 -compose_scene.snow_particles_chance = 0.0 -compose_scene.leaf_particles_chance = 0.0 -compose_scene.dust_particles_chance = 0.0 -compose_scene.camera_based_lighting_chance = 0.0 -compose_scene.wind_chance = 0.0 -compose_scene.turbulence_chance = 0.0 -compose_scene.ferns_chance = 0.0 - -compose_scene.fish_school_chance = 0.0 -compose_scene.marine_snow_particles_chance = 0.0 - -compose_scene.corals_chance = 0.0 -compose_scene.seaweed_chance = 0.0 -compose_scene.urchin_chance = 0.0 -compose_scene.seashells_chance = 0.0 -compose_scene.jellyfish_chance = 0.0 -compose_scene.mushroom_chance = 0.0 -compose_scene.pinecone_chance = 0.0 -compose_scene.monocots_chance = 0.0 -compose_scene.pine_needle_chance = 0.0 -compose_scene.caustics_chance = 0.0 - -compose_scene.decorative_plants_chance = 0.0 - -populate_scene.moss_chance = 0.0 -populate_scene.lichen_chance = 0.0 -populate_scene.slime_mold_chance = 0.0 -populate_scene.ivy_chance = 0.0 -populate_scene.snow_layer_chance = 0.0 \ No newline at end of file diff --git a/infinigen_examples/configs/disable_assets/no_creatures.gin b/infinigen_examples/configs/disable_assets/no_creatures.gin deleted file mode 100644 index 2a751337f..000000000 --- a/infinigen_examples/configs/disable_assets/no_creatures.gin +++ /dev/null @@ -1,4 +0,0 @@ -compose_scene.ground_creatures_chance = 0.0 -compose_scene.flying_creatures_chance = 0.0 -compose_scene.fish_school_chance = 0.0 -compose_scene.bug_swarm_chance = 0.0 diff --git a/infinigen_examples/configs/disable_assets/no_particles.gin b/infinigen_examples/configs/disable_assets/no_particles.gin deleted file mode 100644 index ed26effe0..000000000 --- a/infinigen_examples/configs/disable_assets/no_particles.gin +++ /dev/null @@ -1,5 +0,0 @@ -compose_scene.rain_particles_chance = 0.0 -compose_scene.snow_particles_chance = 0.0 -compose_scene.leaf_particles_chance = 0.0 -compose_scene.dust_particles_chance = 0.0 -compose_scene.marine_snow_particles_chance = 0.0 \ No newline at end of file diff --git a/infinigen_examples/configs/disable_assets/no_rocks.gin b/infinigen_examples/configs/disable_assets/no_rocks.gin deleted file mode 100644 index 3ecb60b8e..000000000 --- a/infinigen_examples/configs/disable_assets/no_rocks.gin +++ /dev/null @@ -1,2 +0,0 @@ -compose_scene.boulders_chance = 0.0 -compose_scene.rocks_chance = 0.0 \ No newline at end of file diff --git a/infinigen_examples/configs/performance/simple.gin b/infinigen_examples/configs/performance/simple.gin deleted file mode 100644 index 9d3c44c86..000000000 --- a/infinigen_examples/configs/performance/simple.gin +++ /dev/null @@ -1,5 +0,0 @@ -include 'performance/dev.gin' -include 'disable_assets/no_creatures.gin' -include 'performance/fast_terrain_assets.gin' -run_erosion.n_iters = [1,1] -full/configure_render_cycles.num_samples = 100 \ No newline at end of file diff --git a/infinigen_examples/configs/scene_types/cave.gin b/infinigen_examples/configs/scene_types/cave.gin deleted file mode 100644 index 739d942a9..000000000 --- a/infinigen_examples/configs/scene_types/cave.gin +++ /dev/null @@ -1,60 +0,0 @@ -compose_scene.land_domain_tags = 'landscape,-liquid_covered' -compose_scene.nonliving_domain_tags = 'landscape' -compose_scene.underwater_domain_tags = 'landscape,liquid_covered' - -compose_scene.trees_chance = 0.4 -compose_scene.rocks_chance = 0.8 -compose_scene.glowing_rocks_chance = 1 -compose_scene.grass_chance = 0.4 -compose_scene.ferns_chance = 0.6 -compose_scene.mushroom_chance = 0.8 - -compose_scene.snow_particles_chance=0.0 -compose_scene.leaves_chance=0.0 -compose_scene.rain_particles_chance=0.0 - -surface.registry.liquid_collection = [ - ('water', 0.7), - ('lava', 0.3) -] - -compose_scene.bug_swarm_chance=0.0 - -compose_scene.ground_creatures_chance = 0.0 -compose_scene.ground_creature_registry = [ - (@CarnivoreFactory, 1), - (@HerbivoreFactory, 0.3), - (@BirdFactory, 0.5), - (@BeetleFactory, 1) -] - -compose_scene.flying_creatures_chance=0.2 -compose_scene.flying_creature_registry = [ - (@DragonflyFactory, 1), -] - -atmosphere_light_haze.shader_atmosphere.enable_scatter = False -configure_render_cycles.exposure = 1.3 - - -# scene composition config -scene.caves_chance = 1 -scene.ground_chance = 0.5 -Caves.randomness = 0 -Caves.height_offset = -4 -Caves.frequency = 0.01 -Caves.noise_scale = ("uniform", 2, 5) -Caves.n_lattice = 1 -Caves.is_horizontal = 1 -Caves.scale_increase = 1 - -scene.waterbody_chance = 0.8 -Waterbody.height = -5 - -# camera selection config -Terrain.populated_bounds = (-25, 25, -25, 25, -25, 0) -keep_cam_pose_proposal.terrain_coverage_range = (1, 1) -camera_selection_ranges_ratio.closeup = ("closeup", 4, 0, 0.3) -camera_selection_tags_ratio.cave = (0.3, 1) - -SphericalMesher.r_min = 0.2 diff --git a/infinigen_examples/configs/scene_types/coral_reef.gin b/infinigen_examples/configs/scene_types/coral_reef.gin deleted file mode 100644 index 86f02b746..000000000 --- a/infinigen_examples/configs/scene_types/coral_reef.gin +++ /dev/null @@ -1,11 +0,0 @@ -include 'scene_types/under_water.gin' - -compose_scene.kelp_chance = 0.1 -compose_scene.urchin_chance = 0.1 - -compose_scene.corals_chance = 1. -compose_scene.seaweed_chance = 0.2 -compose_scene.seashell_chance = 0.7 -compose_scene.jellyfish_chance = 0.0 - -water.geo.with_waves=True \ No newline at end of file diff --git a/infinigen_examples/configs/scene_types/desert.gin b/infinigen_examples/configs/scene_types/desert.gin deleted file mode 100644 index f4cf0cf1c..000000000 --- a/infinigen_examples/configs/scene_types/desert.gin +++ /dev/null @@ -1,46 +0,0 @@ - -compose_scene.trees_chance = 0.25 -compose_scene.cactus_chance = .7 -compose_scene.ground_leaves_chance = 0.5 -compose_scene.ground_twigs_chance = 0.3 -compose_scene.chopped_trees_chance = 0.1 -compose_scene.grass_chance = 0.1 -compose_scene.ferns_chance = 0.0 -compose_scene.pine_needle_chance = 0.05 -compose_scene.fancy_clouds_chance = 0.0 - -compose_scene.tree_density = 0.02 - -compose_scene.rain_particles_chance = 0.0 -compose_scene.snow_particles_chance = 0 -compose_scene.leaf_particles_chance = 0.05 -compose_scene.dust_particles_chance = 0.0 - -atmosphere_light_haze.shader_atmosphere.density = ("uniform", 0, 0.0015) -atmosphere_light_haze.shader_atmosphere.anisotropy = 0 - -animate_cameras.follow_poi_chance=0.5 - -compose_scene.ground_creatures_chance = 1.0 -compose_scene.ground_creature_registry = [ - (@SnakeFactory, 1) -] - -surface.registry.ground_collection = [ - ('sand', 1), -] - -surface.registry.mountain_collection = [ - ("sandstone", 1), -] - -# scene composition config -LandTiles.tiles = ["Mountain"] -LandTiles.randomness = 1 -LandTiles.tile_heights = [-2] -LandTiles.tile_density = 0.25 -WarpedRocks.slope_shift = -3 -Ground.with_sand_dunes = 1 - - -scene.waterbody_chance = 0 \ No newline at end of file diff --git a/infinigen_examples/configs/scene_types/forest.gin b/infinigen_examples/configs/scene_types/forest.gin deleted file mode 100644 index ef6d37cc7..000000000 --- a/infinigen_examples/configs/scene_types/forest.gin +++ /dev/null @@ -1,58 +0,0 @@ -Ground.scale = 10 -multi_mountains_params.height = 15 -multi_mountains_params.min_freq = ("uniform", 0.008, 0.012) -multi_mountains_params.max_freq = ("uniform", 0.024, 0.036) -scene.warped_rocks_chance = 0 - -compose_scene.inview_distance = 40 -placement.populate_all.dist_cull = 40 - -surface.registry.ground_collection = [ - ('mud', 2), - ('dirt', 1), - ('soil', 1), -] - -surface.registry.mountain_collection = [ - ('dirt', 1), - ('soil', 1), - ('cracked_ground', 0.5) -] - -compose_scene.ground_creatures_chance = 0.1 -compose_scene.ground_creature_registry = [ - #(@CarnivoreFactory, 2), - #(@HerbivoreFactory, 0.8), - (@SnakeFactory, 2) -] - -compose_scene.flying_creatures_chance = 0.7 -compose_scene.flying_creature_registry = [ - (@DragonflyFactory, 1), - (@FlyingBirdFactory, 0.2) -] - -compose_scene.bug_swarm_chance = 0.0 - -compose_scene.trees_chance = 1.0 -compose_scene.tree_density = 0.11 - -compose_scene.ground_leaves_chance = 0.7 -compose_scene.ground_twigs_chance = 0.7 -compose_scene.chopped_trees_chance = 0.3 -compose_scene.grass_chance = 0.4 -compose_scene.ferns_chance = 0.6 -compose_scene.flowers_chance = 0.4 -compose_scene.monocots_chance = 0.5 -compose_scene.mushroom_chance = 0.3 -compose_scene.pinecone_chance = 0.5 -compose_scene.pine_needle_chance = 0.6 - -populate_scene.slime_mold_chance = 0.0 -populate_scene.ivy_chance = 0.0 -populate_scene.lichen_chance = 0.6 -populate_scene.mushroom_chance = 0.0 -populate_scene.moss_chance = 0.0 - -compose_scene.rain_particles_chance = 0.0 -compose_scene.leaf_particles_chance = 0.7 \ No newline at end of file diff --git a/infinigen_examples/configs/scene_types/kelp_forest.gin b/infinigen_examples/configs/scene_types/kelp_forest.gin deleted file mode 100644 index e054dad15..000000000 --- a/infinigen_examples/configs/scene_types/kelp_forest.gin +++ /dev/null @@ -1,24 +0,0 @@ -include 'scene_types/under_water.gin' - -multi_mountains_params.height = ("uniform", 1, 4) -multi_mountains_params.min_freq = ("uniform", 0.01, 0.015) -multi_mountains_params.max_freq = ("uniform", 0.03, 0.06) - -compose_scene.glowing_rocks_chance = 0. -compose_scene.ground_leaves_chance = 0.2 -compose_scene.ground_twigs_chance = 0.4 -compose_scene.chopped_trees_chance = 0. - -compose_scene.kelp_chance = 1.0 -compose_scene.urchin_chance = 0.7 - -compose_scene.corals_chance = 0.0 -compose_scene.seaweed_chance = 0.8 -compose_scene.seashell_chance = 0.8 -compose_scene.jellyfish_chance = 0.0 - -water.shader.volume_density = ("uniform", 0.09, 0.13) -water.shader.anisotropy = ("uniform", 0.45, 0.7) -water.geo.with_waves=False - -camera.camera_pose_proposal.pitch = ("clip_gaussian", 90, 15, 60, 140) diff --git a/infinigen_examples/configs/scene_types/plain.gin b/infinigen_examples/configs/scene_types/plain.gin deleted file mode 100644 index a3bbe0a66..000000000 --- a/infinigen_examples/configs/scene_types/plain.gin +++ /dev/null @@ -1,28 +0,0 @@ -compose_scene.ground_creature_registry = [ - (@CarnivoreFactory, 1), - (@HerbivoreFactory, 1.5), - (@BirdFactory, 0.7), -] - -compose_scene.ground_creatures_chance = 0.0 -compose_scene.ground_creature_registry = [ - (@CarnivoreFactory, 2), - (@HerbivoreFactory, 0.8), - (@SnakeFactory, 2) -] - -compose_scene.flying_creatures_chance = 0.7 -compose_scene.flying_creature_registry = [ - (@DragonflyFactory, 1), - (@FlyingBirdFactory, 0.1) -] - -compose_scene.grass_select_max = 0.35 -compose_scene.tree_density = 0.02 -compose_scene.grass_chance = 1 - -compose_scene.bug_swarm_chance=0.0 - -# scene composition config -scene.landtiles_chance = 0 -scene.warped_rocks_chance = 0 diff --git a/infinigen_examples/configs/scene_types/under_water.gin b/infinigen_examples/configs/scene_types/under_water.gin deleted file mode 100644 index 0ec73b52a..000000000 --- a/infinigen_examples/configs/scene_types/under_water.gin +++ /dev/null @@ -1,81 +0,0 @@ -multi_mountains_params.height = ("uniform", 4, 10) -multi_mountains_params.min_freq = ("uniform", 0.01, 0.015) -multi_mountains_params.max_freq = ("uniform", 0.03, 0.06) - -keep_cam_pose_proposal.terrain_coverage_range = (0.4, 1) -camera.camera_pose_proposal.altitude = ("clip_gaussian", 3.5, 0.7, 2, 6) -camera.camera_pose_proposal.pitch = ("clip_gaussian", 90, 15, 60, 110) - -compose_scene.trees_chance = 0. -compose_scene.bushes_chance = 0. -compose_scene.creatures_chance = 1.0 -compose_scene.glowing_rocks_chance = 0.05 -compose_scene.rocks_chance = 0.5 -compose_scene.ground_leaves_chance = 0.05 -compose_scene.ground_twigs_chance = 0.05 -compose_scene.chopped_trees_chance = 0.1 -compose_scene.grass_chance = 0.05 -compose_scene.kelp_chance = 0.7 -compose_scene.cactus_chance = 0. -compose_scene.ferns_chance = 0.01 -compose_scene.camera_based_lighting_chance = 1.0 - -compose_scene.corals_chance = 0.8 -compose_scene.seaweed_chance = 0.8 -compose_scene.seashell_chance = 0.8 -compose_scene.urchin_chance = 0.8 -compose_scene.jellyfish_chance = 0.0 -compose_scene.mushroom_chance = 0.0 -compose_scene.pinecone_chance = 0.0 -compose_scene.pine_needle_chance = 0.0 -compose_scene.boulders_chance = 0.0 -compose_scene.monocots_chance = 0.0 - -water.geo.water_detail = ("uniform", 0.2, 0.5) -water.geo.water_height = ("uniform", 0.05, 0.15) -water.geo.with_ripples = 0 -water.shader.volume_density = ("uniform", 0.03, 0.05) -water.shader.color = ("color_category", 'under_water') -water.shader.colored = 0 -water.shader.emissive_foam = 0 -water.is_ocean = False - - -compose_scene.wind_chance = 0 -compose_scene.rain_particles_chance = 0 -compose_scene.snow_particles_chance = 0 -compose_scene.leaf_particles_chance = 0 -compose_scene.dust_particles_chance = 0 -compose_scene.marine_snow_particles_chance = 0 -compose_scene.caustics_chance = 0.6 - -atmosphere_light_haze.shader_atmosphere.enable_scatter = False -compose_scene.fancy_clouds_chance=0.0 - -surface.registry.rock_collection = [ - ('stone', 1), - ('mountain', 0.5), -] - -compose_scene.ground_creatures_chance = 0.4 -compose_scene.ground_creature_registry = [ - (@CrustaceanFactory, 1), -] -compose_scene.flying_creatures_chance = 0.0 -compose_scene.bug_swarm_chance = 0.0 -compose_scene.fish_school_chance = 0.3 - -# scene composition config -LandTiles.tile_heights = [-20] -Ground.height = -20 -Atmosphere.hacky_offset = 0.1 -WarpedRocks.slope_shift = -23 -scene.waterbody_chance = 1 - -Terrain.under_water = 1 - -compose_scene.turbulence_chance = 0.7 -turbulence_effector.strength = ("uniform", 0, 7) -turbulence_effector.size = ("uniform", 1.5, 4.5) -turbulence_effector.flow = 1 -turbulence_effector.noise = 10 \ No newline at end of file diff --git a/infinigen_examples/configs/trailer_video.gin b/infinigen_examples/configs/trailer_video.gin deleted file mode 100644 index 86f8a555f..000000000 --- a/infinigen_examples/configs/trailer_video.gin +++ /dev/null @@ -1,8 +0,0 @@ -export.spherical = False # use OcMesher -AnimPolicyRandomWalkLookaround.speed=('uniform', 1, 2.5), -AnimPolicyRandomWalkLookaround.step_speed_mult=('uniform', 0.5, 2), -AnimPolicyRandomWalkLookaround.yaw_sampler=('uniform',-20, 20), -AnimPolicyRandomWalkLookaround.step_range=('clip_gaussian', 3, 5, 0.5, 10), -AnimPolicyRandomWalkLookaround.rot_vars=(5, 0, 5), -AnimPolicyRandomWalkLookaround.motion_dir_zoff=('clip_gaussian', 0, 90, 0, 180) -execute_tasks.fps = 16 \ No newline at end of file diff --git a/infinigen_examples/configs_indoor/base.gin b/infinigen_examples/configs_indoor/base.gin new file mode 100644 index 000000000..bfb6cff9b --- /dev/null +++ b/infinigen_examples/configs_indoor/base.gin @@ -0,0 +1,65 @@ +include 'infinigen_examples/configs_nature/base.gin' +include 'infinigen_examples/configs_nature/base_surface_registry.gin' +include 'infinigen_examples/configs_nature/natural.gin' +include 'infinigen_examples/configs_nature/performance/fast_terrain_assets.gin' + +# overriden in fast_solve.gin if present +compose_indoors.solve_steps_large = 500 +compose_indoors.solve_steps_medium = 200 +compose_indoors.solve_steps_small = 300 + +SimulatedAnnealingSolver.initial_temp = 3 +SimulatedAnnealingSolver.final_temp = 0.001 +SimulatedAnnealingSolver.finetune_pct = 0.15 +SimulatedAnnealingSolver.max_invalid_candidates = 5 + +render_image.use_dof = True +animate_cameras.follow_poi_chance=0.0 +camera.camera_pose_proposal.altitude = ("clip_gaussian", 1.5, 0.8, 0.5, 2.2) +camera.camera_pose_proposal.pitch = ("clip_gaussian", 90, 15, 60, 95) +camera.camera_pose_proposal.focal_length = 15 +export.spherical = False # spherical mesher doesnt support short focal length / wide fov + +camera.spawn_camera_rigs.n_camera_rigs = 1 +camera.spawn_camera_rigs.camera_rig_config = [ + {'loc': (0, 0, 0), 'rot_euler': (0, 0, 0)}, + {'loc': (0.075, 0, 0), 'rot_euler': (0, 0, 0)} +] +walk_same_altitude.z_move_up = 0 +keep_cam_pose_proposal.min_terrain_distance = 0.7 # stuff will inevitably be closer to the camera indoors + +# animating the camera takes more search time when indoors +animate_trajectory.max_step_tries=40 +animate_trajectory.max_full_retries=20 +compute_base_views.min_candidates_ratio=10 +compute_base_views.max_tries=50000 +compose_indoors.animate_cameras_enabled = False # not yet working robustly + +group_collections.config = [ + {'name': 'assets', 'hide_viewport': True, 'hide_render': True}, # collections of assets used by scatters + {'name': 'placeholders', 'hide_viewport': True, 'hide_render': True}, # low-res markers / proxies for where assets will be spawned + {'name': 'unique_assets', 'hide_viewport': False, 'hide_render': False}, # actual hi-res assets spawned at each placeholder location +] + +configure_render_cycles.exposure = 3 +configure_render_cycles.denoise = False +configure_render_cycles.adaptive_threshold = 0.005 + +nishita_lighting.strength = 0.25 +nishita_lighting.sun_elevation = ("clip_gaussian", 40, 25, 6, 70) + +compose_indoors.lights_off_chance=0.2 +compose_indoors.skirting_floor_chance=0.7 +compose_indoors.skirting_ceiling_chance=0.2 + +compose_indoors.near_distance = 60 + +compose_indoors.invisible_room_ceilings_enabled = False +compose_indoors.overhead_cam_enabled = False +compose_indoors.hide_other_rooms_enabled = False + +# for create_outdoor_backdrop +compose_indoors.fancy_clouds_chance = 0.5 +compose_indoors.grass_chance = 0.5 +compose_indoors.rocks_chance = 0.5 +compose_indoors.near_distance = 20 \ No newline at end of file diff --git a/infinigen_examples/configs/base_surface_registry.gin b/infinigen_examples/configs_indoor/base_surface_registry.gin similarity index 100% rename from infinigen_examples/configs/base_surface_registry.gin rename to infinigen_examples/configs_indoor/base_surface_registry.gin diff --git a/infinigen_examples/configs_indoor/disable/no_details.gin b/infinigen_examples/configs_indoor/disable/no_details.gin new file mode 100644 index 000000000..4de7d683e --- /dev/null +++ b/infinigen_examples/configs_indoor/disable/no_details.gin @@ -0,0 +1,9 @@ +compose_indoors.room_doors_enabled = False +compose_indoors.room_windows_enabled = False + +compose_indoors.room_floors_enabled = False +compose_indoors.room_walls_enabled = False +compose_indoors.room_ceilings_enabled = False + +compose_indoors.skirting_floor_enabled = False +compose_indoors.skirting_ceiling_enabled = False \ No newline at end of file diff --git a/infinigen_examples/configs_indoor/disable/no_objects.gin b/infinigen_examples/configs_indoor/disable/no_objects.gin new file mode 100644 index 000000000..0dfeea0e6 --- /dev/null +++ b/infinigen_examples/configs_indoor/disable/no_objects.gin @@ -0,0 +1,3 @@ +compose_indoors.solve_large_enabled = False +compose_indoors.solve_medium_enabled = False +compose_indoors.solve_small_enabled = False \ No newline at end of file diff --git a/infinigen_examples/configs_indoor/export_upload.gin b/infinigen_examples/configs_indoor/export_upload.gin new file mode 100644 index 000000000..f52381b88 --- /dev/null +++ b/infinigen_examples/configs_indoor/export_upload.gin @@ -0,0 +1,2 @@ +export_curr_scene.format = 'obj' +export_curr_scene.individual_export = True diff --git a/infinigen_examples/configs_indoor/fast_solve.gin b/infinigen_examples/configs_indoor/fast_solve.gin new file mode 100644 index 000000000..db561577c --- /dev/null +++ b/infinigen_examples/configs_indoor/fast_solve.gin @@ -0,0 +1,10 @@ +RoomSolver.n_divide_trials = 60 +MultistoryRoomSolver.n_divide_trials = 60 +RoomSolver.iters_mult = 120 +MultistoryRoomSolver.iters_mult = 120 + +compose_indoors.solve_steps_large = 150 +compose_indoors.solve_steps_medium = 40 +compose_indoors.solve_steps_small = 10 + +compose_indoors.terrain_enabled = False diff --git a/infinigen_examples/configs_indoor/multistory.gin b/infinigen_examples/configs_indoor/multistory.gin new file mode 100644 index 000000000..d1735a868 --- /dev/null +++ b/infinigen_examples/configs_indoor/multistory.gin @@ -0,0 +1,14 @@ +execute_tasks.generate_resolution = (720, 720) +get_sensor_coords.H = 720 +get_sensor_coords.W = 720 +MultistoryRoomSolver.n_divide_trials = 100 + +compose_indoors.terrain_enabled=False +compose_indoors.solve_large_enabled=False +compose_indoors.solve_small_enabled=False +compose_indoors.solve_medium_enabled=False + +compose_indoors.topview=True +compose_indoors.topview_rot_x=45 +compose_indoors.topview_rot_z=-45 +Solver.multistory=True diff --git a/infinigen_examples/configs/natural.gin b/infinigen_examples/configs_indoor/natural.gin similarity index 100% rename from infinigen_examples/configs/natural.gin rename to infinigen_examples/configs_indoor/natural.gin diff --git a/infinigen_examples/configs_indoor/overhead.gin b/infinigen_examples/configs_indoor/overhead.gin new file mode 100644 index 000000000..caa0b913c --- /dev/null +++ b/infinigen_examples/configs_indoor/overhead.gin @@ -0,0 +1,15 @@ +compose_indoors.invisible_room_ceilings_enabled = True +compose_indoors.hide_other_rooms_enabled = True + +compose_indoors.pose_cameras_enabled = False +compose_indoors.overhead_cam_enabled = True + +compose_indoors.terrain_enabled = False +compose_indoors.nature_backdrop_enabled = False + +compose_indoors.lights_off_chance = 0.0 + +compose_indoors.skirting_floor_chance = 0.0 +compose_indoors.skirting_ceiling_chance = 0.0 + + diff --git a/infinigen_examples/configs_indoor/singleroom.gin b/infinigen_examples/configs_indoor/singleroom.gin new file mode 100644 index 000000000..3425975e8 --- /dev/null +++ b/infinigen_examples/configs_indoor/singleroom.gin @@ -0,0 +1,2 @@ +BlueprintSolidifier.enable_open=False +restrict_solving.solve_max_rooms=1 \ No newline at end of file diff --git a/infinigen_examples/configs_indoor/studio.gin b/infinigen_examples/configs_indoor/studio.gin new file mode 100644 index 000000000..fcd21565a --- /dev/null +++ b/infinigen_examples/configs_indoor/studio.gin @@ -0,0 +1 @@ +GraphMaker.room_children='studio' \ No newline at end of file diff --git a/infinigen_examples/configs_indoor/topview.gin b/infinigen_examples/configs_indoor/topview.gin new file mode 100644 index 000000000..b91616af8 --- /dev/null +++ b/infinigen_examples/configs_indoor/topview.gin @@ -0,0 +1,8 @@ +execute_tasks.generate_resolution = (720, 720) +get_sensor_coords.H = 720 +get_sensor_coords.W = 720 + +compose_indoors.topview=True +compose_indoors.terrain_enabled=False +compose_indoors.solve_large_enabled=False +compose_indoors.solve_small_enabled=False diff --git a/infinigen_examples/configs/.gitignore b/infinigen_examples/configs_nature/.gitignore similarity index 100% rename from infinigen_examples/configs/.gitignore rename to infinigen_examples/configs_nature/.gitignore diff --git a/infinigen_examples/configs/asset_demo.gin b/infinigen_examples/configs_nature/asset_demo.gin similarity index 71% rename from infinigen_examples/configs/asset_demo.gin rename to infinigen_examples/configs_nature/asset_demo.gin index 87ac99931..c19c8ea99 100644 --- a/infinigen_examples/configs/asset_demo.gin +++ b/infinigen_examples/configs_nature/asset_demo.gin @@ -1,9 +1,9 @@ -compose_scene.inview_distance = 30 +compose_nature.inview_distance = 30 full/configure_render_cycles.min_samples = 50 full/configure_render_cycles.num_samples = 300 configure_blender.motion_blur = False -configure_blender.use_dof = True +render_image.use_dof = True full/render_image.passes_to_save = [] \ No newline at end of file diff --git a/infinigen_examples/configs/base.gin b/infinigen_examples/configs_nature/base.gin similarity index 67% rename from infinigen_examples/configs/base.gin rename to infinigen_examples/configs_nature/base.gin index 5a0af02c9..b10263a26 100644 --- a/infinigen_examples/configs/base.gin +++ b/infinigen_examples/configs_nature/base.gin @@ -14,83 +14,83 @@ save_obj_and_instances.output_folder="saved_mesh.obj" util.logging.create_text_file.log_dir = %LOG_DIR placement.populate_all.dist_cull = 70 -compose_scene.inview_distance = 70 -compose_scene.near_distance = 20 -compose_scene.center_distance = 35 +compose_nature.inview_distance = 70 +compose_nature.near_distance = 20 +compose_nature.center_distance = 35 -compose_scene.land_domain_tags = 'landscape,-liquid_covered,-cave,-beach' -compose_scene.nonliving_domain_tags = 'landscape,-cave' -compose_scene.underwater_domain_tags = 'landscape,liquid_covered,-cave' +compose_nature.land_domain_tags = 'landscape,-liquid_covered,-cave,-beach' +compose_nature.nonliving_domain_tags = 'landscape,-cave' +compose_nature.underwater_domain_tags = 'landscape,liquid_covered,-cave' -compose_scene.terrain_enabled = True -compose_scene.lighting_enabled = True -compose_scene.coarse_terrain_enabled = True -compose_scene.terrain_surface_enabled = True +compose_nature.terrain_enabled = True +compose_nature.lighting_enabled = True +compose_nature.coarse_terrain_enabled = True +compose_nature.terrain_surface_enabled = True -compose_scene.simulated_river_enabled=False -compose_scene.tilted_river_enabled=False +compose_nature.simulated_river_enabled=False +compose_nature.tilted_river_enabled=False -compose_scene.fancy_clouds_chance = 0.6 +compose_nature.fancy_clouds_chance = 0.6 -compose_scene.trees_chance = 0.85 -compose_scene.bushes_chance = 0.7 -compose_scene.clouds_chance = 0.0 -compose_scene.boulders_chance = 0.7 +compose_nature.trees_chance = 0.85 +compose_nature.bushes_chance = 0.7 +compose_nature.clouds_chance = 0.0 +compose_nature.boulders_chance = 0.7 -compose_scene.glowing_rocks_chance = 0.0 -compose_scene.rocks_chance = 0.9 +compose_nature.glowing_rocks_chance = 0.0 +compose_nature.rocks_chance = 0.9 -compose_scene.ground_leaves_chance = 0.7 -compose_scene.ground_twigs_chance = 0.7 -compose_scene.chopped_trees_chance = 0.7 +compose_nature.ground_leaves_chance = 0.7 +compose_nature.ground_twigs_chance = 0.7 +compose_nature.chopped_trees_chance = 0.7 -compose_scene.grass_chance = 0.8 -compose_scene.ferns_chance = 0.25 -compose_scene.monocots_chance = 0.15 +compose_nature.grass_chance = 0.8 +compose_nature.ferns_chance = 0.25 +compose_nature.monocots_chance = 0.15 -compose_scene.flowers_chance = 0.2 -compose_scene.kelp_chance = 0.0 -compose_scene.cactus_chance = 0.0 -compose_scene.coconut_trees_chance = 0.0 -compose_scene.palm_trees_chance = 0.0 +compose_nature.flowers_chance = 0.2 +compose_nature.kelp_chance = 0.0 +compose_nature.cactus_chance = 0.0 +compose_nature.coconut_trees_chance = 0.0 +compose_nature.palm_trees_chance = 0.0 -compose_scene.instanced_trees_chance = 0.0 # conditioned on trees_chance as prereq +compose_nature.instanced_trees_chance = 0.0 # conditioned on trees_chance as prereq -compose_scene.fish_school_chance = 0.0 -compose_scene.bug_swarm_chance = 0.0 +compose_nature.fish_school_chance = 0.0 +compose_nature.bug_swarm_chance = 0.0 -compose_scene.rain_particles_chance = 0.0 -compose_scene.snow_particles_chance = 0.0 -compose_scene.leaf_particles_chance = 0.0 -compose_scene.dust_particles_chance = 0.0 -compose_scene.marine_snow_particles_chance = 0.0 -compose_scene.camera_based_lighting_chance = 0.0 +compose_nature.rain_particles_chance = 0.0 +compose_nature.snow_particles_chance = 0.0 +compose_nature.leaf_particles_chance = 0.0 +compose_nature.dust_particles_chance = 0.0 +compose_nature.marine_snow_particles_chance = 0.0 +compose_nature.camera_based_lighting_chance = 0.0 -compose_scene.wind_chance = 0.5 -compose_scene.turbulence_chance = 0.3 +compose_nature.wind_chance = 0.5 +compose_nature.turbulence_chance = 0.3 wind_effector.strength = ('uniform', 0, 0.02) turbulence_effector.strength = ('uniform', 0, 0.02) turbulence_effector.noise = ('uniform', 0, 0.015) -compose_scene.corals_chance = 0.0 -compose_scene.seaweed_chance = 0.0 -compose_scene.seashells_chance = 0.0 -compose_scene.urchin_chance = 0.0 -compose_scene.jellyfish_chance = 0.0 +compose_nature.corals_chance = 0.0 +compose_nature.seaweed_chance = 0.0 +compose_nature.seashells_chance = 0.0 +compose_nature.urchin_chance = 0.0 +compose_nature.jellyfish_chance = 0.0 -compose_scene.mushroom_chance = 0 # TEMP -compose_scene.pinecone_chance = 0.1 -compose_scene.pine_needle_chance = 0.1 -compose_scene.caustics_chance = 0.0 -compose_scene.decorative_plants_chance = 0.1 +compose_nature.mushroom_chance = 0 # TEMP +compose_nature.pinecone_chance = 0.1 +compose_nature.pine_needle_chance = 0.1 +compose_nature.caustics_chance = 0.0 +compose_nature.decorative_plants_chance = 0.1 -compose_scene.cached_fire = False +compose_nature.cached_fire = False populate_scene.cached_fire = False -compose_scene.cached_fire_trees_chance= 0 -compose_scene.cached_fire_bushes_chance = 0 -compose_scene.cached_fire_boulders_chance = 0.0 -compose_scene.cached_fire_cactus_chance = 0 +compose_nature.cached_fire_trees_chance= 0 +compose_nature.cached_fire_bushes_chance = 0 +compose_nature.cached_fire_boulders_chance = 0.0 +compose_nature.cached_fire_cactus_chance = 0 @@ -183,8 +183,8 @@ camera.spawn_camera_rigs.camera_rig_config = [ {'loc': (0.075, 0, 0), 'rot_euler': (0, 0, 0)} ] -camera_selection_tags_ratio.liquid = (0, 0.5) -camera_selection_keep_in_animation.liquid = True +compose_nature.camera_selection_tags_ratio = {"liquid": (0, 0.5)} # often overridden by scenetypes +compose_nature.camera_selection_anim_criterion_keys = {"liquid": True} # TERRAIN SEED # assets.materials.ice.geo_ice.random_seed = %OVERALL_SEED @@ -201,16 +201,16 @@ assets.materials.mountain.shader.random_seed = %OVERALL_SEED assets.materials.sand.shader.random_seed = %OVERALL_SEED assets.materials.water.shader.random_seed = %OVERALL_SEED -compose_scene.ground_creatures_chance = 0.0 -compose_scene.ground_creature_registry = [ +compose_nature.ground_creatures_chance = 0.0 +compose_nature.ground_creature_registry = [ (@CarnivoreFactory, 1), (@HerbivoreFactory, 1), (@BirdFactory, 1), (@SnakeFactory, 1) ] -compose_scene.flying_creatures_chance=0.1 -compose_scene.flying_creature_registry = [ +compose_nature.flying_creatures_chance=0.1 +compose_nature.flying_creature_registry = [ (@FlyingBirdFactory, 1), (@DragonflyFactory, 0.1), ] @@ -225,6 +225,6 @@ group_collections.config = [ {'name': 'animhelper', 'hide_viewport': False, 'hide_render': True}, # curves and iks ] -include 'base_surface_registry.gin' -include 'natural.gin' +include 'infinigen_examples/configs_nature/base_surface_registry.gin' +include 'infinigen_examples/configs_nature/natural.gin' diff --git a/infinigen_examples/configs_nature/base_surface_registry.gin b/infinigen_examples/configs_nature/base_surface_registry.gin new file mode 100644 index 000000000..d224ddcf0 --- /dev/null +++ b/infinigen_examples/configs_nature/base_surface_registry.gin @@ -0,0 +1,67 @@ +surface.registry.ground_collection = [ + ('mud', 2), + ('sand', 1), + ('cobble_stone', 1), + ('cracked_ground', 1), + ('dirt', 1), + ('stone', 1), + ('soil', 1), + ('chunkyrock', 0), +] + +surface.registry.beach = [ + ('sand', 10), + ('cracked_ground', 1), + ('dirt', 1), + ('stone', 1), + ('soil', 1), +] + +surface.registry.eroded = [ + ('sand', 1), + ('cracked_ground', 1), + ('dirt', 1), + ('stone', 1), + ('soil', 1), +] + +surface.registry.mountain_collection = [ + ('mountain', 10), + ("sandstone", 2), +] + +surface.registry.rock_collection = [ + # ('aluminumdisp2tut', 0.5), + ('stone', 1), + ('mountain', 5), + # ('ice', 1), +] + +surface.registry.liquid_collection = [ + ('water', 0.95), + # ('lava', 0.05), +] + +surface.registry.lava = [ + ('lava', 1), +] + +surface.registry.snow = [ + ('snow', 1), +] + +surface.registry.atmosphere = [ + ('atmosphere_light_haze', 1), +] + +surface.registry.bark = [ + ('bark_birch', 0.1), + ('bark_random', 0.9), + #('wood', 0.01), +] + +surface.registry.greenery = [ + ('simple_greenery', 1), +] + +surface.registry.smooth_categories = 0 diff --git a/infinigen_examples/configs_nature/disable_assets/no_assets.gin b/infinigen_examples/configs_nature/disable_assets/no_assets.gin new file mode 100644 index 000000000..b835452b5 --- /dev/null +++ b/infinigen_examples/configs_nature/disable_assets/no_assets.gin @@ -0,0 +1,47 @@ +compose_nature.fancy_clouds_chance = 0.0 + +compose_nature.trees_chance = 0.0 +compose_nature.bushes_chance = 0.0 +compose_nature.ground_creatures_chance = 0.0 +compose_nature.flying_creatures_chance = 0.0 +compose_nature.clouds_chance = 0.0 +compose_nature.boulders_chance = 0.0 +compose_nature.glowing_rocks_chance = 0.0 +compose_nature.rocks_chance = 0.0 +compose_nature.ground_leaves_chance = 0.0 +compose_nature.ground_twigs_chance = 0.0 +compose_nature.chopped_trees_chance = 0.0 +compose_nature.grass_chance = 0.0 +compose_nature.flowers_chance = 0.0 +compose_nature.kelp_chance = 0.0 +compose_nature.cactus_chance = 0.0 +compose_nature.rain_particles_chance = 0.0 +compose_nature.snow_particles_chance = 0.0 +compose_nature.leaf_particles_chance = 0.0 +compose_nature.dust_particles_chance = 0.0 +compose_nature.camera_based_lighting_chance = 0.0 +compose_nature.wind_chance = 0.0 +compose_nature.turbulence_chance = 0.0 +compose_nature.ferns_chance = 0.0 + +compose_nature.fish_school_chance = 0.0 +compose_nature.marine_snow_particles_chance = 0.0 + +compose_nature.corals_chance = 0.0 +compose_nature.seaweed_chance = 0.0 +compose_nature.urchin_chance = 0.0 +compose_nature.seashells_chance = 0.0 +compose_nature.jellyfish_chance = 0.0 +compose_nature.mushroom_chance = 0.0 +compose_nature.pinecone_chance = 0.0 +compose_nature.monocots_chance = 0.0 +compose_nature.pine_needle_chance = 0.0 +compose_nature.caustics_chance = 0.0 + +compose_nature.decorative_plants_chance = 0.0 + +populate_scene.moss_chance = 0.0 +populate_scene.lichen_chance = 0.0 +populate_scene.slime_mold_chance = 0.0 +populate_scene.ivy_chance = 0.0 +populate_scene.snow_layer_chance = 0.0 \ No newline at end of file diff --git a/infinigen_examples/configs_nature/disable_assets/no_creatures.gin b/infinigen_examples/configs_nature/disable_assets/no_creatures.gin new file mode 100644 index 000000000..1546e3aab --- /dev/null +++ b/infinigen_examples/configs_nature/disable_assets/no_creatures.gin @@ -0,0 +1,4 @@ +compose_nature.ground_creatures_chance = 0.0 +compose_nature.flying_creatures_chance = 0.0 +compose_nature.fish_school_chance = 0.0 +compose_nature.bug_swarm_chance = 0.0 diff --git a/infinigen_examples/configs_nature/disable_assets/no_particles.gin b/infinigen_examples/configs_nature/disable_assets/no_particles.gin new file mode 100644 index 000000000..7e2dd2b28 --- /dev/null +++ b/infinigen_examples/configs_nature/disable_assets/no_particles.gin @@ -0,0 +1,5 @@ +compose_nature.rain_particles_chance = 0.0 +compose_nature.snow_particles_chance = 0.0 +compose_nature.leaf_particles_chance = 0.0 +compose_nature.dust_particles_chance = 0.0 +compose_nature.marine_snow_particles_chance = 0.0 \ No newline at end of file diff --git a/infinigen_examples/configs_nature/disable_assets/no_rocks.gin b/infinigen_examples/configs_nature/disable_assets/no_rocks.gin new file mode 100644 index 000000000..aa630414e --- /dev/null +++ b/infinigen_examples/configs_nature/disable_assets/no_rocks.gin @@ -0,0 +1,2 @@ +compose_nature.boulders_chance = 0.0 +compose_nature.rocks_chance = 0.0 \ No newline at end of file diff --git a/infinigen_examples/configs/extras/experimental.gin b/infinigen_examples/configs_nature/extras/experimental.gin similarity index 56% rename from infinigen_examples/configs/extras/experimental.gin rename to infinigen_examples/configs_nature/extras/experimental.gin index cba3d47fd..f82010755 100644 --- a/infinigen_examples/configs/extras/experimental.gin +++ b/infinigen_examples/configs_nature/extras/experimental.gin @@ -1,7 +1,7 @@ # things that are not quite fully working correctly, but you can use if you please configure_blender.motion_blur = True # not fully supported in ground truth -compose_scene.rain_particles_chance = 0.1 # doesnt look good when not using motion blur +compose_nature.rain_particles_chance = 0.1 # doesnt look good when not using motion blur -# compose_scene.marine_snow_particles_chance = 0.1 # TODO only put this in underwater scenes +# compose_nature.marine_snow_particles_chance = 0.1 # TODO only put this in underwater scenes # water.is_ocean = ("bool", 0.5) # TODO put this only in underwater scenes diff --git a/infinigen_examples/configs/extras/overhead.gin b/infinigen_examples/configs_nature/extras/overhead.gin similarity index 59% rename from infinigen_examples/configs/extras/overhead.gin rename to infinigen_examples/configs_nature/extras/overhead.gin index 028b8295a..869c87e76 100644 --- a/infinigen_examples/configs/extras/overhead.gin +++ b/infinigen_examples/configs_nature/extras/overhead.gin @@ -3,8 +3,8 @@ camera.camera_pose_proposal.altitude = ("clip_gaussian", 30, 20, 17, 70) camera.camera_pose_proposal.pitch = ("clip_gaussian", 0, 30, 0, 15) placement.populate_all.dist_cull = 70 -compose_scene.inview_distance = 70 -compose_scene.near_distance = 40 -compose_scene.center_distance = 40 +compose_nature.inview_distance = 70 +compose_nature.near_distance = 40 +compose_nature.center_distance = 40 -compose_scene.animate_cameras_enabled = False \ No newline at end of file +compose_nature.animate_cameras_enabled = False \ No newline at end of file diff --git a/infinigen_examples/configs/extras/stereo_training.gin b/infinigen_examples/configs_nature/extras/stereo_training.gin similarity index 86% rename from infinigen_examples/configs/extras/stereo_training.gin rename to infinigen_examples/configs_nature/extras/stereo_training.gin index 1d5d989ba..6f47d6a8a 100644 --- a/infinigen_examples/configs/extras/stereo_training.gin +++ b/infinigen_examples/configs_nature/extras/stereo_training.gin @@ -8,9 +8,9 @@ atmosphere_light_haze.shader_atmosphere.enable_scatter = False water.shader.enable_scatter = False # dont include tiny particles, they arent sufficiently visible -compose_scene.rain_particles_chance = 0 -compose_scene.dust_particles_chance = 0 -compose_scene.snow_particles_chance = 0 +compose_nature.rain_particles_chance = 0 +compose_nature.dust_particles_chance = 0 +compose_nature.snow_particles_chance = 0 # eliminate lava, the emissive surface is too noisy # now by default lava is a separate material diff --git a/infinigen_examples/configs/extras/use_cached_fire.gin b/infinigen_examples/configs_nature/extras/use_cached_fire.gin similarity index 62% rename from infinigen_examples/configs/extras/use_cached_fire.gin rename to infinigen_examples/configs_nature/extras/use_cached_fire.gin index 0c1750088..bbb37a35e 100644 --- a/infinigen_examples/configs/extras/use_cached_fire.gin +++ b/infinigen_examples/configs_nature/extras/use_cached_fire.gin @@ -4,14 +4,14 @@ populate_scene.creatures_fire_on_the_fly_chance = 0 populate_scene.boulders_fire_on_the_fly_chance = 0 populate_scene.cactus_fire_on_the_fly_chance = 0 -compose_scene.cached_fire_trees_chance= 0.5 -compose_scene.cached_fire_bushes_chance = 1 -compose_scene.cached_fire_boulders_chance = 0.3 -compose_scene.cached_fire_cactus_chance = 0.4 +compose_nature.cached_fire_trees_chance= 0.5 +compose_nature.cached_fire_bushes_chance = 1 +compose_nature.cached_fire_boulders_chance = 0.3 +compose_nature.cached_fire_cactus_chance = 0.4 configure_render_cycles.exposure = 0.4 -compose_scene.cached_fire = True +compose_nature.cached_fire = True populate_scene.cached_fire = True FireCachingSystem.asset_folder = "" \ No newline at end of file diff --git a/infinigen_examples/configs/extras/use_on_the_fly_fire.gin b/infinigen_examples/configs_nature/extras/use_on_the_fly_fire.gin similarity index 76% rename from infinigen_examples/configs/extras/use_on_the_fly_fire.gin rename to infinigen_examples/configs_nature/extras/use_on_the_fly_fire.gin index 625c1150b..c2959782e 100644 --- a/infinigen_examples/configs/extras/use_on_the_fly_fire.gin +++ b/infinigen_examples/configs_nature/extras/use_on_the_fly_fire.gin @@ -1,13 +1,13 @@ populate_scene.trees_fire_on_the_fly_chance = 0.1 -#compose_scene.trees_chance = 1 +#compose_nature.trees_chance = 1 populate_scene.bushes_fire_on_the_fly_chance = 0.8 -#compose_scene.bushes_chance = 1 +#compose_nature.bushes_chance = 1 populate_scene.creatures_fire_on_the_fly_chance = 0 populate_scene.boulders_fire_on_the_fly_chance = 0.1 populate_scene.cactus_fire_on_the_fly_chance = 0.1 -compose_scene.glowing_rocks_chance = 0.0 +compose_nature.glowing_rocks_chance = 0.0 -compose_scene.cached_fire = False +compose_nature.cached_fire = False LandTiles.land_process = None #scene.voronoi_rocks_chance = 1 #animate_cameras.follow_poi_chance=0 diff --git a/infinigen_examples/configs/monocular.gin b/infinigen_examples/configs_nature/monocular.gin similarity index 100% rename from infinigen_examples/configs/monocular.gin rename to infinigen_examples/configs_nature/monocular.gin diff --git a/infinigen_examples/configs_nature/natural.gin b/infinigen_examples/configs_nature/natural.gin new file mode 100644 index 000000000..67ce4ac03 --- /dev/null +++ b/infinigen_examples/configs_nature/natural.gin @@ -0,0 +1,3 @@ +# assets.materials.water.shader.color = ("palette", "water") +assets.materials.mountain.shader.color = ("palette", "mountain soil") +assets.materials.sandstone.shader.color = ("palette", "sandstone") \ No newline at end of file diff --git a/infinigen_examples/configs/noisy_video.gin b/infinigen_examples/configs_nature/noisy_video.gin similarity index 100% rename from infinigen_examples/configs/noisy_video.gin rename to infinigen_examples/configs_nature/noisy_video.gin diff --git a/infinigen_examples/configs/palette/desert.json b/infinigen_examples/configs_nature/palette/desert.json similarity index 100% rename from infinigen_examples/configs/palette/desert.json rename to infinigen_examples/configs_nature/palette/desert.json diff --git a/infinigen_examples/configs/palette/mountain soil.json b/infinigen_examples/configs_nature/palette/mountain soil.json similarity index 100% rename from infinigen_examples/configs/palette/mountain soil.json rename to infinigen_examples/configs_nature/palette/mountain soil.json diff --git a/infinigen_examples/configs/palette/sandstone.json b/infinigen_examples/configs_nature/palette/sandstone.json similarity index 100% rename from infinigen_examples/configs/palette/sandstone.json rename to infinigen_examples/configs_nature/palette/sandstone.json diff --git a/infinigen_examples/configs/palette/water.json b/infinigen_examples/configs_nature/palette/water.json similarity index 100% rename from infinigen_examples/configs/palette/water.json rename to infinigen_examples/configs_nature/palette/water.json diff --git a/infinigen_examples/configs/performance/dev.gin b/infinigen_examples/configs_nature/performance/dev.gin similarity index 61% rename from infinigen_examples/configs/performance/dev.gin rename to infinigen_examples/configs_nature/performance/dev.gin index ad0fed64b..413ff7e34 100644 --- a/infinigen_examples/configs/performance/dev.gin +++ b/infinigen_examples/configs_nature/performance/dev.gin @@ -7,10 +7,10 @@ OpaqueSphericalMesher.pixels_per_cube = 4 TransparentSphericalMesher.pixels_per_cube = 4 target_face_size.global_multiplier = 2 -compose_scene.ground_creatures_chance = 0.0 -compose_scene.flying_creatures_chance = 0.0 +compose_nature.ground_creatures_chance = 0.0 +compose_nature.flying_creatures_chance = 0.0 -compose_scene.inview_distance = 35 +compose_nature.inview_distance = 35 placement.populate_all.dist_cull = 35 -compose_scene.near_distance = 10 -compose_scene.center_distance = 20 \ No newline at end of file +compose_nature.near_distance = 10 +compose_nature.center_distance = 20 \ No newline at end of file diff --git a/infinigen_examples/configs/performance/fast_terrain_assets.gin b/infinigen_examples/configs_nature/performance/fast_terrain_assets.gin similarity index 100% rename from infinigen_examples/configs/performance/fast_terrain_assets.gin rename to infinigen_examples/configs_nature/performance/fast_terrain_assets.gin diff --git a/infinigen_examples/configs/performance/high_quality_terrain.gin b/infinigen_examples/configs_nature/performance/high_quality_terrain.gin similarity index 100% rename from infinigen_examples/configs/performance/high_quality_terrain.gin rename to infinigen_examples/configs_nature/performance/high_quality_terrain.gin diff --git a/infinigen_examples/configs/performance/reuse_terrain_assets.gin b/infinigen_examples/configs_nature/performance/reuse_terrain_assets.gin similarity index 100% rename from infinigen_examples/configs/performance/reuse_terrain_assets.gin rename to infinigen_examples/configs_nature/performance/reuse_terrain_assets.gin diff --git a/infinigen_examples/configs_nature/performance/simple.gin b/infinigen_examples/configs_nature/performance/simple.gin new file mode 100644 index 000000000..e5ec006cf --- /dev/null +++ b/infinigen_examples/configs_nature/performance/simple.gin @@ -0,0 +1,5 @@ +include 'infinigen_examples/configs_nature/performance/dev.gin' +include 'infinigen_examples/configs_nature/disable_assets/no_creatures.gin' +include 'infinigen_examples/configs_nature/performance/fast_terrain_assets.gin' +run_erosion.n_iters = [1,1] +full/configure_render_cycles.num_samples = 100 \ No newline at end of file diff --git a/infinigen_examples/configs/scene_types/arctic.gin b/infinigen_examples/configs_nature/scene_types/arctic.gin similarity index 76% rename from infinigen_examples/configs/scene_types/arctic.gin rename to infinigen_examples/configs_nature/scene_types/arctic.gin index 57bcb33d2..f0c337735 100644 --- a/infinigen_examples/configs/scene_types/arctic.gin +++ b/infinigen_examples/configs_nature/scene_types/arctic.gin @@ -6,7 +6,7 @@ surface.registry.rock_collection = [ ('ice', 1), ] -compose_scene.ground_creature_registry = [ +compose_nature.ground_creature_registry = [ (@CarnivoreFactory, 0), (@HerbivoreFactory, 0.3), (@BirdFactory, 1), @@ -29,12 +29,12 @@ scene.warped_rocks_chance = 0 scene.ground_ice_chance = 1 scene.waterbody_chance = 1 -include 'disable_assets/no_assets.gin' +include 'infinigen_examples/configs_nature/disable_assets/no_assets.gin' -compose_scene.wind_chance = 0.5 -compose_scene.turbulence_chance = 0.5 -compose_scene.boulders_chance = 0.3 -compose_scene.rocks_chance = 0.3 +compose_nature.wind_chance = 0.5 +compose_nature.turbulence_chance = 0.5 +compose_nature.boulders_chance = 0.3 +compose_nature.rocks_chance = 0.3 shader_atmosphere.density = 0 water.geo.water_height = ("uniform", 0.002, 0.004) diff --git a/infinigen_examples/configs/scene_types/canyon.gin b/infinigen_examples/configs_nature/scene_types/canyon.gin similarity index 62% rename from infinigen_examples/configs/scene_types/canyon.gin rename to infinigen_examples/configs_nature/scene_types/canyon.gin index efa40eb25..ab4f3fdc3 100644 --- a/infinigen_examples/configs/scene_types/canyon.gin +++ b/infinigen_examples/configs_nature/scene_types/canyon.gin @@ -7,9 +7,11 @@ LandTiles.randomness = 1 # camera selection config keep_cam_pose_proposal.terrain_coverage_range = (0.5, 0.9) -camera_selection_ranges_ratio.altitude = ("altitude", 16, 1e9, 0.01, 1) +compose_nature.camera_selection_ranges_ratio = { + "altitude": ("altitude", 16, 1e9, 0.01, 1) +} -compose_scene.ground_creatures_chance = 0.2 -compose_scene.ground_creature_registry = [ +compose_nature.ground_creatures_chance = 0.2 +compose_nature.ground_creature_registry = [ (@SnakeFactory, 0.9) ] diff --git a/infinigen_examples/configs_nature/scene_types/cave.gin b/infinigen_examples/configs_nature/scene_types/cave.gin new file mode 100644 index 000000000..fe15e98db --- /dev/null +++ b/infinigen_examples/configs_nature/scene_types/cave.gin @@ -0,0 +1,65 @@ +compose_nature.land_domain_tags = 'landscape,-liquid_covered' +compose_nature.nonliving_domain_tags = 'landscape' +compose_nature.underwater_domain_tags = 'landscape,liquid_covered' + +compose_nature.trees_chance = 0.4 +compose_nature.rocks_chance = 0.8 +compose_nature.glowing_rocks_chance = 1 +compose_nature.grass_chance = 0.4 +compose_nature.ferns_chance = 0.6 +compose_nature.mushroom_chance = 0.8 + +compose_nature.snow_particles_chance=0.0 +compose_nature.leaves_chance=0.0 +compose_nature.rain_particles_chance=0.0 + +surface.registry.liquid_collection = [ + ('water', 0.7), + ('lava', 0.3) +] + +compose_nature.bug_swarm_chance=0.0 + +compose_nature.ground_creatures_chance = 0.0 +compose_nature.ground_creature_registry = [ + (@CarnivoreFactory, 1), + (@HerbivoreFactory, 0.3), + (@BirdFactory, 0.5), + (@BeetleFactory, 1) +] + +compose_nature.flying_creatures_chance=0.2 +compose_nature.flying_creature_registry = [ + (@DragonflyFactory, 1), +] + +atmosphere_light_haze.shader_atmosphere.enable_scatter = False +configure_render_cycles.exposure = 1.3 + + +# scene composition config +scene.caves_chance = 1 +scene.ground_chance = 0.5 +Caves.randomness = 0 +Caves.height_offset = -4 +Caves.frequency = 0.01 +Caves.noise_scale = ("uniform", 2, 5) +Caves.n_lattice = 1 +Caves.is_horizontal = 1 +Caves.scale_increase = 1 + +scene.waterbody_chance = 0.8 +Waterbody.height = -5 + +# camera selection config +Terrain.populated_bounds = (-25, 25, -25, 25, -25, 0) +keep_cam_pose_proposal.terrain_coverage_range = (1, 1) +compose_nature.camera_selection_ranges_ratio = { + "closeup": ("closeup", 4, 0, 0.3) +} +compose_nature.camera_selection_tags_ratio = { + "liquid": (0, 0.5), + "cave": (0.3, 1) +} + +SphericalMesher.r_min = 0.2 diff --git a/infinigen_examples/configs/scene_types/cliff.gin b/infinigen_examples/configs_nature/scene_types/cliff.gin similarity index 67% rename from infinigen_examples/configs/scene_types/cliff.gin rename to infinigen_examples/configs_nature/scene_types/cliff.gin index 2092e6def..346a52db9 100644 --- a/infinigen_examples/configs/scene_types/cliff.gin +++ b/infinigen_examples/configs_nature/scene_types/cliff.gin @@ -1,4 +1,4 @@ -compose_scene.ground_creature_registry = [ +compose_nature.ground_creature_registry = [ (@CarnivoreFactory, 0.2), (@HerbivoreFactory, 1), (@BirdFactory, 1), @@ -15,9 +15,11 @@ Ground.height = -15 # camera selection config Terrain.populated_bounds = (-25, 25, -25, 25, -15, 35) keep_cam_pose_proposal.terrain_coverage_range = (0, 0.8) -camera_selection_ranges_ratio.altitude = ("altitude", 10, 1e9, 0.01, 1) +compose_nature.camera_selection_ranges_ratio = { + "altitude": ("altitude", 10, 1e9, 0.01, 1) +} -compose_scene.flying_creatures_chance=0.6 -compose_scene.flying_creature_registry = [ +compose_nature.flying_creatures_chance=0.6 +compose_nature.flying_creature_registry = [ (@FlyingBirdFactory, 1), ] \ No newline at end of file diff --git a/infinigen_examples/configs/scene_types/coast.gin b/infinigen_examples/configs_nature/scene_types/coast.gin similarity index 73% rename from infinigen_examples/configs/scene_types/coast.gin rename to infinigen_examples/configs_nature/scene_types/coast.gin index b17b52154..9371c782b 100644 --- a/infinigen_examples/configs/scene_types/coast.gin +++ b/infinigen_examples/configs_nature/scene_types/coast.gin @@ -29,16 +29,18 @@ shader_atmosphere.anisotropy = 1 shader_atmosphere.density = 0 # camera selection config -camera_selection_tags_ratio.liquid = (0.05, 0.6) -camera_selection_tags_ratio.beach = (0.05, 0.6) +compose_nature.camera_selection_tags_ratio = { + "liquid": (0.05, 0.6), + "beach": (0.05, 0.6), +} -compose_scene.ground_creatures_chance = 0.0 -compose_scene.ground_creature_registry = [ +compose_nature.ground_creatures_chance = 0.0 +compose_nature.ground_creature_registry = [ (@BirdFactory, 0.1), (@CrabFactory, 1) ] -compose_scene.max_ground_creatures = 8 -compose_scene.flying_creatures_chance=0.6 -compose_scene.flying_creature_registry = [ +compose_nature.max_ground_creatures = 8 +compose_nature.flying_creatures_chance=0.6 +compose_nature.flying_creature_registry = [ (@FlyingBirdFactory, 1) ] \ No newline at end of file diff --git a/infinigen_examples/configs_nature/scene_types/coral_reef.gin b/infinigen_examples/configs_nature/scene_types/coral_reef.gin new file mode 100644 index 000000000..12917127d --- /dev/null +++ b/infinigen_examples/configs_nature/scene_types/coral_reef.gin @@ -0,0 +1,11 @@ +include 'infinigen_examples/configs_nature/scene_types/under_water.gin' + +compose_nature.kelp_chance = 0.1 +compose_nature.urchin_chance = 0.1 + +compose_nature.corals_chance = 1. +compose_nature.seaweed_chance = 0.2 +compose_nature.seashell_chance = 0.7 +compose_nature.jellyfish_chance = 0.0 + +water.geo.with_waves=True \ No newline at end of file diff --git a/infinigen_examples/configs_nature/scene_types/desert.gin b/infinigen_examples/configs_nature/scene_types/desert.gin new file mode 100644 index 000000000..80fca8ff8 --- /dev/null +++ b/infinigen_examples/configs_nature/scene_types/desert.gin @@ -0,0 +1,46 @@ + +compose_nature.trees_chance = 0.25 +compose_nature.cactus_chance = .7 +compose_nature.ground_leaves_chance = 0.5 +compose_nature.ground_twigs_chance = 0.3 +compose_nature.chopped_trees_chance = 0.1 +compose_nature.grass_chance = 0.1 +compose_nature.ferns_chance = 0.0 +compose_nature.pine_needle_chance = 0.05 +compose_nature.fancy_clouds_chance = 0.0 + +compose_nature.tree_density = 0.02 + +compose_nature.rain_particles_chance = 0.0 +compose_nature.snow_particles_chance = 0 +compose_nature.leaf_particles_chance = 0.05 +compose_nature.dust_particles_chance = 0.0 + +atmosphere_light_haze.shader_atmosphere.density = ("uniform", 0, 0.0015) +atmosphere_light_haze.shader_atmosphere.anisotropy = 0 + +animate_cameras.follow_poi_chance=0.5 + +compose_nature.ground_creatures_chance = 1.0 +compose_nature.ground_creature_registry = [ + (@SnakeFactory, 1) +] + +surface.registry.ground_collection = [ + ('sand', 1), +] + +surface.registry.mountain_collection = [ + ("sandstone", 1), +] + +# scene composition config +LandTiles.tiles = ["Mountain"] +LandTiles.randomness = 1 +LandTiles.tile_heights = [-2] +LandTiles.tile_density = 0.25 +WarpedRocks.slope_shift = -3 +Ground.with_sand_dunes = 1 + + +scene.waterbody_chance = 0 \ No newline at end of file diff --git a/infinigen_examples/configs_nature/scene_types/forest.gin b/infinigen_examples/configs_nature/scene_types/forest.gin new file mode 100644 index 000000000..3e2838b00 --- /dev/null +++ b/infinigen_examples/configs_nature/scene_types/forest.gin @@ -0,0 +1,58 @@ +Ground.scale = 10 +multi_mountains_params.height = 15 +multi_mountains_params.min_freq = ("uniform", 0.008, 0.012) +multi_mountains_params.max_freq = ("uniform", 0.024, 0.036) +scene.warped_rocks_chance = 0 + +compose_nature.inview_distance = 40 +placement.populate_all.dist_cull = 40 + +surface.registry.ground_collection = [ + ('mud', 2), + ('dirt', 1), + ('soil', 1), +] + +surface.registry.mountain_collection = [ + ('dirt', 1), + ('soil', 1), + ('cracked_ground', 0.5) +] + +compose_nature.ground_creatures_chance = 0.1 +compose_nature.ground_creature_registry = [ + #(@CarnivoreFactory, 2), + #(@HerbivoreFactory, 0.8), + (@SnakeFactory, 2) +] + +compose_nature.flying_creatures_chance = 0.7 +compose_nature.flying_creature_registry = [ + (@DragonflyFactory, 1), + (@FlyingBirdFactory, 0.2) +] + +compose_nature.bug_swarm_chance = 0.0 + +compose_nature.trees_chance = 1.0 +compose_nature.tree_density = 0.11 + +compose_nature.ground_leaves_chance = 0.7 +compose_nature.ground_twigs_chance = 0.7 +compose_nature.chopped_trees_chance = 0.3 +compose_nature.grass_chance = 0.4 +compose_nature.ferns_chance = 0.6 +compose_nature.flowers_chance = 0.4 +compose_nature.monocots_chance = 0.5 +compose_nature.mushroom_chance = 0.3 +compose_nature.pinecone_chance = 0.5 +compose_nature.pine_needle_chance = 0.6 + +populate_scene.slime_mold_chance = 0.0 +populate_scene.ivy_chance = 0.0 +populate_scene.lichen_chance = 0.6 +populate_scene.mushroom_chance = 0.0 +populate_scene.moss_chance = 0.0 + +compose_nature.rain_particles_chance = 0.0 +compose_nature.leaf_particles_chance = 0.7 \ No newline at end of file diff --git a/infinigen_examples/configs_nature/scene_types/kelp_forest.gin b/infinigen_examples/configs_nature/scene_types/kelp_forest.gin new file mode 100644 index 000000000..55acc05e3 --- /dev/null +++ b/infinigen_examples/configs_nature/scene_types/kelp_forest.gin @@ -0,0 +1,24 @@ +include 'infinigen_examples/configs_nature/scene_types/under_water.gin' + +multi_mountains_params.height = ("uniform", 1, 4) +multi_mountains_params.min_freq = ("uniform", 0.01, 0.015) +multi_mountains_params.max_freq = ("uniform", 0.03, 0.06) + +compose_nature.glowing_rocks_chance = 0. +compose_nature.ground_leaves_chance = 0.2 +compose_nature.ground_twigs_chance = 0.4 +compose_nature.chopped_trees_chance = 0. + +compose_nature.kelp_chance = 1.0 +compose_nature.urchin_chance = 0.7 + +compose_nature.corals_chance = 0.0 +compose_nature.seaweed_chance = 0.8 +compose_nature.seashell_chance = 0.8 +compose_nature.jellyfish_chance = 0.0 + +water.shader.volume_density = ("uniform", 0.09, 0.13) +water.shader.anisotropy = ("uniform", 0.45, 0.7) +water.geo.with_waves=False + +camera.camera_pose_proposal.pitch = ("clip_gaussian", 90, 15, 60, 140) diff --git a/infinigen_examples/configs/scene_types/mountain.gin b/infinigen_examples/configs_nature/scene_types/mountain.gin similarity index 55% rename from infinigen_examples/configs/scene_types/mountain.gin rename to infinigen_examples/configs_nature/scene_types/mountain.gin index 052535055..7a8696c0e 100644 --- a/infinigen_examples/configs/scene_types/mountain.gin +++ b/infinigen_examples/configs_nature/scene_types/mountain.gin @@ -1,10 +1,10 @@ -compose_scene.flying_creatures_chance=0.7 -compose_scene.max_flying_creatures = 3 +compose_nature.flying_creatures_chance=0.7 +compose_nature.max_flying_creatures = 3 animate_cameras.follow_poi_chance=0.0 -compose_scene.trees_chance = 0.5 -compose_scene.tree_density = 0.06 +compose_nature.trees_chance = 0.5 +compose_nature.tree_density = 0.06 # scene composition config scene.upsidedown_mountains_chance = 0.4 diff --git a/infinigen_examples/configs_nature/scene_types/plain.gin b/infinigen_examples/configs_nature/scene_types/plain.gin new file mode 100644 index 000000000..8162b6679 --- /dev/null +++ b/infinigen_examples/configs_nature/scene_types/plain.gin @@ -0,0 +1,28 @@ +compose_nature.ground_creature_registry = [ + (@CarnivoreFactory, 1), + (@HerbivoreFactory, 1.5), + (@BirdFactory, 0.7), +] + +compose_nature.ground_creatures_chance = 0.0 +compose_nature.ground_creature_registry = [ + (@CarnivoreFactory, 2), + (@HerbivoreFactory, 0.8), + (@SnakeFactory, 2) +] + +compose_nature.flying_creatures_chance = 0.7 +compose_nature.flying_creature_registry = [ + (@DragonflyFactory, 1), + (@FlyingBirdFactory, 0.1) +] + +compose_nature.grass_select_max = 0.35 +compose_nature.tree_density = 0.02 +compose_nature.grass_chance = 1 + +compose_nature.bug_swarm_chance=0.0 + +# scene composition config +scene.landtiles_chance = 0 +scene.warped_rocks_chance = 0 diff --git a/infinigen_examples/configs/scene_types/river.gin b/infinigen_examples/configs_nature/scene_types/river.gin similarity index 67% rename from infinigen_examples/configs/scene_types/river.gin rename to infinigen_examples/configs_nature/scene_types/river.gin index 52a7c1654..c753c31c3 100644 --- a/infinigen_examples/configs/scene_types/river.gin +++ b/infinigen_examples/configs_nature/scene_types/river.gin @@ -6,20 +6,20 @@ surface.registry.erosion_collection = [ ('soil', 1), ] -compose_scene.ground_creatures_chance = 0.0 -compose_scene.ground_creature_registry = [ +compose_nature.ground_creatures_chance = 0.0 +compose_nature.ground_creature_registry = [ (@CarnivoreFactory, 2), (@HerbivoreFactory, 0.5), (@SnakeFactory, 0.5) ] -compose_scene.flying_creatures_chance = 0.7 -compose_scene.flying_creature_registry = [ +compose_nature.flying_creatures_chance = 0.7 +compose_nature.flying_creature_registry = [ (@DragonflyFactory, 1), (@FlyingBirdFactory, 0.2) ] -compose_scene.rain_particles_chance = 0.0 +compose_nature.rain_particles_chance = 0.0 populate_scene.slime_mold_chance = 0.3 populate_scene.ivy_chance = 0.3 populate_scene.lichen_chance = 0.3 @@ -38,5 +38,9 @@ scene.warped_rocks_chance = 0 # camera selection config Terrain.populated_bounds = (-25, 25, -25, 25, -15, 35) -camera_selection_ranges_ratio.altitude = ("altitude", -1e9, 0.75, 0.1, 1) -camera_selection_tags_ratio.liquid = (0.05, 1) +compose_nature.camera_selection_ranges_ratio = { + "altitude": ("altitude", -1e9, 0.75, 0.1, 1) +} +compose_nature.camera_selection_tags_ratio = { + "liquid": (0.05, 1) +} diff --git a/infinigen_examples/configs/scene_types/snowy_mountain.gin b/infinigen_examples/configs_nature/scene_types/snowy_mountain.gin similarity index 77% rename from infinigen_examples/configs/scene_types/snowy_mountain.gin rename to infinigen_examples/configs_nature/scene_types/snowy_mountain.gin index 09737c895..b9ea8d81f 100644 --- a/infinigen_examples/configs/scene_types/snowy_mountain.gin +++ b/infinigen_examples/configs_nature/scene_types/snowy_mountain.gin @@ -1,4 +1,4 @@ -include 'disable_assets/no_assets.gin' +include 'infinigen_examples/configs_nature/disable_assets/no_assets.gin' surface.registry.rock_collection = [ ('mountain', 1), @@ -7,7 +7,7 @@ surface.registry.mountain_collection = [ ('mountain', 1), ] -compose_scene.snow_particles_chance = 0.5 +compose_nature.snow_particles_chance = 0.5 shader_atmosphere.density = 0 nishita_lighting.sun_elevation = ("spherical_sample", 10, 30) @@ -21,14 +21,14 @@ scene.voronoi_grains_chance = 0 tile_directions.MultiMountains = "initial" -compose_scene.flying_creatures_chance = 0.5 -compose_scene.flying_creature_registry = [ +compose_nature.flying_creatures_chance = 0.5 +compose_nature.flying_creature_registry = [ (@FlyingBirdFactory, 1), ] assets.materials.mountain.shader.layered_mountain = 0 assets.materials.mountain.shader.snowy = 0 # TODO: re-enable once terrain flickering resolved -compose_scene.boulders_chance = 1 +compose_nature.boulders_chance = 1 camera.camera_pose_proposal.pitch = ("clip_gaussian", 90, 30, 90, 100) keep_cam_pose_proposal.terrain_coverage_range = (0.3, 1) diff --git a/infinigen_examples/configs_nature/scene_types/under_water.gin b/infinigen_examples/configs_nature/scene_types/under_water.gin new file mode 100644 index 000000000..551914e95 --- /dev/null +++ b/infinigen_examples/configs_nature/scene_types/under_water.gin @@ -0,0 +1,82 @@ +multi_mountains_params.height = ("uniform", 4, 10) +multi_mountains_params.min_freq = ("uniform", 0.01, 0.015) +multi_mountains_params.max_freq = ("uniform", 0.03, 0.06) + +keep_cam_pose_proposal.terrain_coverage_range = (0.4, 1) +camera.camera_pose_proposal.altitude = ("clip_gaussian", 3.5, 0.7, 2, 6) +camera.camera_pose_proposal.pitch = ("clip_gaussian", 90, 15, 60, 110) + +compose_nature.trees_chance = 0. +compose_nature.bushes_chance = 0. +compose_nature.creatures_chance = 1.0 +compose_nature.glowing_rocks_chance = 0.05 +compose_nature.rocks_chance = 0.5 +compose_nature.ground_leaves_chance = 0.05 +compose_nature.ground_twigs_chance = 0.05 +compose_nature.chopped_trees_chance = 0.1 +compose_nature.grass_chance = 0.05 +compose_nature.kelp_chance = 0.7 +compose_nature.cactus_chance = 0. +compose_nature.ferns_chance = 0.01 +compose_nature.camera_based_lighting_chance = 1.0 + +compose_nature.corals_chance = 0.8 +compose_nature.seaweed_chance = 0.8 +compose_nature.seashell_chance = 0.8 +compose_nature.urchin_chance = 0.8 +compose_nature.jellyfish_chance = 0.0 +compose_nature.mushroom_chance = 0.0 +compose_nature.pinecone_chance = 0.0 +compose_nature.pine_needle_chance = 0.0 +compose_nature.boulders_chance = 0.0 +compose_nature.monocots_chance = 0.0 + +water.geo.water_detail = ("uniform", 0.2, 0.5) +water.geo.water_height = ("uniform", 0.05, 0.15) +water.geo.with_ripples = 0 +water.shader.volume_density = ("uniform", 0.03, 0.05) +water.shader.color = ("color_category", 'under_water') +water.shader.colored = 0 +water.shader.emissive_foam = 0 +water.shader.mix_surface = True +water.is_ocean = False + + +compose_nature.wind_chance = 0 +compose_nature.rain_particles_chance = 0 +compose_nature.snow_particles_chance = 0 +compose_nature.leaf_particles_chance = 0 +compose_nature.dust_particles_chance = 0 +compose_nature.marine_snow_particles_chance = 0 +compose_nature.caustics_chance = 0.6 + +atmosphere_light_haze.shader_atmosphere.enable_scatter = False +compose_nature.fancy_clouds_chance=0.0 + +surface.registry.rock_collection = [ + ('stone', 1), + ('mountain', 0.5), +] + +compose_nature.ground_creatures_chance = 0.4 +compose_nature.ground_creature_registry = [ + (@CrustaceanFactory, 1), +] +compose_nature.flying_creatures_chance = 0.0 +compose_nature.bug_swarm_chance = 0.0 +compose_nature.fish_school_chance = 0.3 + +# scene composition config +LandTiles.tile_heights = [-20] +Ground.height = -20 +Atmosphere.hacky_offset = 0.1 +WarpedRocks.slope_shift = -23 +scene.waterbody_chance = 1 + +Terrain.under_water = 1 + +compose_nature.turbulence_chance = 0.7 +turbulence_effector.strength = ("uniform", 0, 7) +turbulence_effector.size = ("uniform", 1.5, 4.5) +turbulence_effector.flow = 1 +turbulence_effector.noise = 10 \ No newline at end of file diff --git a/infinigen_examples/configs/scene_types_fluidsim/simulated_river.gin b/infinigen_examples/configs_nature/scene_types_fluidsim/simulated_river.gin similarity index 75% rename from infinigen_examples/configs/scene_types_fluidsim/simulated_river.gin rename to infinigen_examples/configs_nature/scene_types_fluidsim/simulated_river.gin index 0e88eb715..f38852e70 100644 --- a/infinigen_examples/configs/scene_types_fluidsim/simulated_river.gin +++ b/infinigen_examples/configs_nature/scene_types_fluidsim/simulated_river.gin @@ -1,4 +1,4 @@ -include 'scene_types/river.gin' +include 'infinigen_examples/configs_nature/scene_types/river.gin' UniformMesher.enclosed=1 animate_cameras.policy_registry = @cam/AnimPolicyRandomForwardWalk @@ -9,15 +9,16 @@ cam/AnimPolicyRandomForwardWalk.yaw_dist = ("uniform",-10, 10) keep_cam_pose_proposal.min_terrain_distance = 1 -compose_scene.hero_boulders_chance = 0.0 -compose_scene.simulated_river_enabled = True +compose_nature.hero_boulders_chance = 0.0 +compose_nature.simulated_river_enabled = True terrain.elements.landtiles.LandTiles.y_tilt = 0 terrain.elements.landtiles.LandTiles.y_tilt_clip = 0 camera_pose_proposal.override_loc = (0.45, -24, 8) -camera_pose_proposal.override_rot = (-120, 180, -178) - +camera_pose_proposal.pitch = -120 +camera_pose_proposal.roll = 180 +camera_pose_proposal.yaw = -178 assets.boulder.create_placeholder.boulder_scale = 1 LandTiles.land_process = None diff --git a/infinigen_examples/configs/scene_types_fluidsim/tilted_river.gin b/infinigen_examples/configs_nature/scene_types_fluidsim/tilted_river.gin similarity index 56% rename from infinigen_examples/configs/scene_types_fluidsim/tilted_river.gin rename to infinigen_examples/configs_nature/scene_types_fluidsim/tilted_river.gin index 209b5068a..cfa621615 100644 --- a/infinigen_examples/configs/scene_types_fluidsim/tilted_river.gin +++ b/infinigen_examples/configs_nature/scene_types_fluidsim/tilted_river.gin @@ -1,4 +1,4 @@ -include 'scene_types/river.gin' +include 'infinigen_examples/configs_nature/scene_types/river.gin' UniformMesher.enclosed=1 animate_cameras.policy_registry = @cam/AnimPolicyRandomForwardWalk @@ -10,18 +10,21 @@ cam/AnimPolicyRandomForwardWalk.yaw_dist = ("uniform",-10, 10) keep_cam_pose_proposal.min_terrain_distance = 1 -compose_scene.hero_boulders_chance = 1.0 +compose_nature.hero_boulders_chance = 1.0 terrain.elements.landtiles.LandTiles.y_tilt = 0.7 terrain.elements.landtiles.LandTiles.y_tilt_clip = 11 -camera_pose_proposal.override_loc = (0.678843,-30.5532, 11.7858) -camera_pose_proposal.override_rot = (-110, 180, -178) +camera_pose_proposal.location_sample = (0.678843,-30.5532, 11.7858) +#camera_pose_proposal.override_rot = (-110, 180, -178) +camera_pose_proposal.roll = 180 +camera_pose_proposal.yaw = -178 +camera_pose_proposal.pitch = -110 -compose_scene.tilted_river_enabled = True +compose_nature.tilted_river_enabled = True -assets.boulder.create_placeholder.boulder_scale = 3 +boulder.create_placeholder.boulder_scale = 3 LandTiles.land_process = None -core.render.hide_water = True +render.hide_water = True compute_base_views.min_candidates_ratio = 1 walk_same_altitude.ignore_missed_rays = True \ No newline at end of file diff --git a/infinigen_examples/configs_nature/trailer_video.gin b/infinigen_examples/configs_nature/trailer_video.gin new file mode 100644 index 000000000..52d7ec78a --- /dev/null +++ b/infinigen_examples/configs_nature/trailer_video.gin @@ -0,0 +1,8 @@ +export.spherical = False # use OcMesher +AnimPolicyRandomWalkLookaround.speed = ('uniform', 0.5, 1) +AnimPolicyRandomWalkLookaround.step_speed_mult = 1 +AnimPolicyRandomWalkLookaround.yaw_sampler = ('uniform',-20, 20) +AnimPolicyRandomWalkLookaround.step_range = ('clip_gaussian', 7, 5, 4, 10) +AnimPolicyRandomWalkLookaround.rot_vars = (5, 0, 5) +AnimPolicyRandomWalkLookaround.motion_dir_zoff = ('clip_gaussian', 0, 100, 0, 180) + diff --git a/infinigen_examples/generate_asset_demo.py b/infinigen_examples/generate_asset_demo.py index e587a501f..e7374933c 100644 --- a/infinigen_examples/generate_asset_demo.py +++ b/infinigen_examples/generate_asset_demo.py @@ -117,11 +117,11 @@ def compose_scene( # find a flat spot on the terrain to do the demo\ terrain = Terrain(scene_seed, surface.registry, task='coarse', on_the_fly_asset_folder=output_folder/"assets") terrain_mesh = terrain.coarse_terrain() - terrain_bvh = bvhtree.BVHTree.FromObject(terrain_mesh, bpy.context.evaluated_depsgraph_get()) + scene_bvh = bvhtree.BVHTree.FromObject(terrain_mesh, bpy.context.evaluated_depsgraph_get()) if asset_factory is not None: center = find_flat_location( terrain_mesh, - terrain_bvh, + scene_bvh, rad=camera_circle_radius * 1.5, alt=camera_altitude * 1.5 ) @@ -145,7 +145,7 @@ def compose_scene( # snap all the locations the floor for i, l in enumerate(locs): - floorloc, *_ = terrain_bvh.ray_cast(Vector(l), Vector((0, 0, -1))) + floorloc, *_ = scene_bvh.ray_cast(Vector(l), Vector((0, 0, -1))) if floorloc is None: raise ValueError('Found a hole in the terain') locs[i] = np.array(floorloc + Vector(asset_offset)) @@ -207,8 +207,8 @@ def main(): init.apply_gin_configs( configs=args.configs, overrides=args.overrides, - configs_folder='infinigen_examples/configs', - mandatory_folders=['infinigen_examples/configs/scene_types'], + configs_folder='infinigen_examples/configs_nature', + mandatory_folders=['infinigen_examples/configs_nature/scene_types'], skip_unknown=True ) diff --git a/infinigen_examples/generate_asset_parameters.py b/infinigen_examples/generate_asset_parameters.py new file mode 100644 index 000000000..eb40bd499 --- /dev/null +++ b/infinigen_examples/generate_asset_parameters.py @@ -0,0 +1,329 @@ +# 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: +# - Lingjie Mei +# - Alex Raistrick +# - Karhan Kayan - add fire option + +import importlib +import math +import os +import random +import subprocess +import traceback +from collections.abc import Callable +from itertools import product +from pathlib import Path +import logging + +from infinigen.assets.materials.woods import non_wood_tile, wood_tile +from infinigen.assets.utils.object import new_cube, origin2lowest, center +from infinigen.core.init import configure_cycles_devices +from infinigen.core.surface import write_attr_data +from infinigen.core.util.blender import deep_clone_obj +from infinigen_examples.asset_parameters import parameters +from infinigen_examples.generate_individual_assets import make_args, setup_camera +from infinigen_examples.util.test_utils import load_txt_list + +logging.basicConfig( + format='[%(asctime)s.%(msecs)03d] [%(name)s] [%(levelname)s] | %(message)s', + datefmt='%H:%M:%S', level=logging.WARNING +) + +import bpy +import gin +import numpy as np +from PIL import Image + +from infinigen.assets.fluid.fluid import set_obj_on_fire +from infinigen.core.tagging import tag_system +from infinigen.assets.lighting import ( + sky_lighting, hdri_lighting, three_point_lighting, holdout_lighting, + CeilingLightFactory, +) + +from infinigen.core import surface, init +from infinigen.core.placement import factory +from infinigen.core.util.camera import points_inview + +from infinigen.assets.utils.misc import subclasses +from infinigen.assets.utils.decorate import read_base_co, read_co, read_normal + +from infinigen.core.util.math import FixedSeed +# noinspection PyUnresolvedReferences +from infinigen.core.util import blender as butil + + +def build_scene_asset(args, factory_name, idx): + params = parameters[factory_name]['globals'].copy() + i = idx // parameters[factory_name]['repeat'] + params.update(parameters[factory_name]['individuals'].copy()[i]) + factory = parameters[factory_name]['factories'][i] + idx = parameters[factory_name]['indices'][i] + with FixedSeed(idx): + factory = factory(idx) + for k, v in params.items(): + setattr( + factory, k, + v() if isinstance(v, Callable) and hasattr(v, '__name__') and v.__name__ == '' else v + ) + with FixedSeed(idx): + if hasattr(factory, 'post_init'): + factory.post_init() + with FixedSeed(idx): + try: + if args.spawn_placeholder: + ph = factory.spawn_placeholder(idx, (0, 0, 0), (0, 0, 0)) + asset = factory.spawn_asset(idx, placeholder=ph) + else: + asset = factory.spawn_asset(idx) + except Exception as e: + traceback.print_exc() + print(f'{factory}.spawn_asset({idx=}) FAILED!! {e}') + raise e + with FixedSeed(idx): + factory.finalize_assets(asset) + origin2lowest(asset, True) + bpy.context.view_layer.objects.active = asset + parent = asset + if asset.type == 'EMPTY': + meshes = [o for o in asset.children_recursive if o.type == 'MESH'] + sizes = [] + for m in meshes: + co = read_co(m) + sizes.append((np.amax(co, 0) - np.amin(co, 0)).sum()) + i = np.argmax(np.array(sizes)) + asset = meshes[i] + asset.location = -center(asset) + asset.location[-1] = 0 + butil.apply_transform(asset, True) + if not args.no_mod: + if parent.animation_data is not None: + drivers = parent.animation_data.drivers.values() + for d in drivers: + parent.driver_remove(d.data_path) + if not args.no_ground: + plane = new_cube() + plane.scale = [2.5] * 3 + co = read_co(asset) + plane.location = asset.location + plane.location[-1] += np.min(co[:, -1]) + 2.5 + butil.apply_transform(plane, True) + plane_ = deep_clone_obj(plane) + plane_.location[-1] -= .1 + plane_.scale = (1.5,) * 3 + normal = read_normal(plane) + write_attr_data(plane, 'ground', normal[:, -1] < -.5, 'INT', 'FACE') + idx = parameters[factory_name]['scene_idx'] + with FixedSeed(idx): + wood_tile.apply(plane, selection='ground') + non_wood_tile.apply(plane, selection='!ground', vertical=True) + factory = CeilingLightFactory(0) + factory.light_factory.params['Wattage'] = factory.light_factory.params['Wattage'] * 20 + light = factory.spawn_asset(0) + light.location[-1] = np.min(co[:, -1]) + 5 - .5 + + return asset + + +def build_scene(path, idx, factory_name, args): + scene = bpy.context.scene + scene.render.engine = 'CYCLES' + scene.render.resolution_x, scene.render.resolution_y = map(int, args.resolution.split('x')) + scene.cycles.samples = args.samples + configure_cycles_devices(True) + t = idx / (args.frame_end / args.cycles) + args.cam_angle = args.cam_angle[0], args.cam_angle[1], (np.abs(t - np.round(t)) * 2) * 180 + if not args.fire: + bpy.context.scene.render.film_transparent = args.film_transparent + bpy.context.scene.world.node_tree.nodes['Background'].inputs[0].default_value[-1] = 0 + + if idx % parameters[factory_name]['repeat'] == 0: + butil.clear_scene() + camera, center = setup_camera(args) + asset = build_scene_asset(args, factory_name, idx) + + with FixedSeed(args.lighting): + if args.hdri: + hdri_lighting.add_lighting() + elif args.three_point: + holdout_lighting.add_lighting() + three_point_lighting.add_lighting(asset) + else: + sky_lighting.add_lighting(camera) + nodes = bpy.data.worlds['World'].node_tree.nodes + sky_texture = [n for n in nodes if n.name.startswith('Sky Texture')][-1] + sky_texture.sun_elevation = np.deg2rad(args.elevation) + sky_texture.sun_rotation = np.pi * .75 + + else: + camera, center = setup_camera(args) + asset = list(o for o in bpy.data.objects if 'Factory' in o.name and o.parent is None)[0] + + if args.scale_reference: + bpy.ops.mesh.primitive_cylinder_add(radius=0.3, depth=1.8, location=(4.9, 4.9, 1.8 / 2)) + + if args.cam_center > 0 and asset: + co = read_base_co(asset) + asset.location + center.location = (np.amin(co, 0) + np.amax(co, 0)) / 2 + center.location[-1] += args.cam_zoff + + if args.cam_dist <= 0 and asset: + if 'Factory' in factory_name: + adjust_cam_distance(asset, camera, args.margin) + else: + adjust_cam_distance(asset, camera, args.margin, .75) + + cam_info_ng = bpy.data.node_groups.get('nodegroup_active_cam_info') + if cam_info_ng is not None: + cam_info_ng.nodes['Object Info'].inputs['Object'].default_value = camera + + if args.save_blend: + (path / 'scenes').mkdir(exist_ok=True) + butil.save_blend(f"{path}/scenes/scene_{idx:03d}.blend", autopack=True) + tag_system.save_tag(f"{path}/MaskTag.json") + + if args.fire: + bpy.data.worlds['World'].node_tree.nodes["Background.001"].inputs[1].default_value = 0.04 + bpy.context.scene.view_settings.exposure = -2 + + if args.render == 'image': + (path / 'images').mkdir(exist_ok=True) + imgpath = path / f"images/image_{idx:03d}.png" + scene.render.filepath = str(imgpath) + bpy.ops.render.render(write_still=True) + elif args.render == 'video': + bpy.context.scene.frame_end = args.frame_end + t = f"frame / {args.frame_end / args.cycles}" + parent(asset).driver_add('rotation_euler')[-1].driver.expression = f'(abs({t}-round({t}))*2-.5)*{np.pi}' + (path / 'frames' / f'scene_{idx:03d}').mkdir(parents=True, exist_ok=True) + imgpath = path / f"frames/scene_{idx:03d}/frame_###.png" + scene.render.filepath = str(imgpath) + bpy.ops.render.render(animation=True) + + +def parent(obj): + return obj if obj.parent is None else obj.parent + + +def adjust_cam_distance(asset, camera, margin, percent=.999): + co = read_base_co(asset) * asset.scale + co += asset.location + lowest = np.amin(co, 0) + highest = np.amax(co, 0) + interp = np.linspace(lowest, highest, 11) + bbox = np.array(list(product(*zip(*interp)))) + for cam_dist in np.exp(np.linspace(-1., 5.5, 500)): + camera.location[1] = -cam_dist + bpy.context.view_layer.update() + inview = points_inview(bbox, camera) + if inview.sum() / inview.size >= percent: + camera.location[1] *= 1 + margin + bpy.context.view_layer.update() + break + else: + camera.location[1] = -6 + + +def make_grid(args, path, n): + files = [] + for filename in sorted(os.listdir(f'{path}/images')): + if filename.endswith('.png'): + files.append(f'{path}/images/{filename}') + files = files[:n] + if len(files) == 0: + print('No images found') + return + with Image.open(files[0]) as i: + x, y = i.size + for i, name in enumerate([path.stem, f'{path.stem}_']): + if args.zoom: + img = Image.new('RGBA', (2 * x, y)) + sz = int(np.floor(np.sqrt(n - .9))) + if i > 0: + random.shuffle(files) + with Image.open(files[0]) as i: + img.paste(i, (0, 0)) + for idx in range(sz ** 2): + with Image.open(files[min(idx + 1, len(files) - 1)]) as i: + img.paste(i.resize((x // sz, y // sz)), (x + (idx % sz) * (x // sz), idx // sz * (y // sz))) + img.save(f'{path}/{name}.png') + else: + sz_x = list(sorted(range(1, n + 1), key=lambda x: abs(math.ceil(n / x) / x - args.best_ratio)))[0] + sz_y = math.ceil(n / sz_x) + img = Image.new('RGBA', (sz_x * x, sz_y * y)) + if i > 0: + random.shuffle(files) + for idx, file in enumerate(files): + with Image.open(file) as i: + img.paste(i, (idx % sz_x * x, idx // sz_x * y)) + img.save(f'{path}/{name}.png') + + +def main(args): + bpy.context.window.workspace = bpy.data.workspaces['Geometry Nodes'] + + init.apply_gin_configs('infinigen_examples/configs_indoor', skip_unknown=True) + surface.registry.initialize_from_gin() + + 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: + name = '_'.join(args.factories) + path = Path(os.getcwd()) / 'outputs' / name + path.mkdir(exist_ok=True) + + factories = list(args.factories) + if 'ALL_ASSETS' in factories: + factories += [f.__name__ for f in subclasses(factory.AssetFactory)] + factories.remove('ALL_ASSETS') + if 'ALL_SCATTERS' in factories: + factories += [f.stem for f in Path('surfaces/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') + if '.txt' in factories[0]: + factories = [f.split('.')[-1] for f in load_txt_list(factories[0], skip_sharp=False)] + + for fac in factories: + fac_path = path / fac + if fac_path.exists() and args.skip_existing: + continue + fac_path.mkdir(exist_ok=True) + n_images = args.n_images + if not args.postprocessing_only: + for idx in range(args.n_images): + try: + build_scene(fac_path, idx, fac, args) + except Exception as e: + print(e) + continue + if args.render == 'image': + make_grid(args, fac_path, n_images) + if args.render == 'video': + (fac_path / 'videos').mkdir(exist_ok=True) + for i in range(n_images): + subprocess.run( + f'ffmpeg -y -r 24 -pattern_type glob -i "{fac_path}/frames/scene_{i:03d}/frame*.png" ' + f'{fac_path}/videos/video_{i:03d}.mp4', shell=True + ) + + +if __name__ == '__main__': + args = make_args() + args.no_mod = args.no_mod or args.fire + args.film_transparent = args.film_transparent and not args.hdri + args.n_images = len(parameters[args.factories[0]]['factories']) * parameters[args.factories[0]]['repeat'] + with FixedSeed(1): + main(args) diff --git a/infinigen_examples/generate_individual_assets.py b/infinigen_examples/generate_individual_assets.py index b32c282f1..6f8e8f9f0 100644 --- a/infinigen_examples/generate_individual_assets.py +++ b/infinigen_examples/generate_individual_assets.py @@ -1,8 +1,9 @@ # 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 +# 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: +# Authors: # - Lingjie Mei # - Alex Raistrick # - Karhan Kayan - add fire option @@ -11,20 +12,19 @@ import importlib import math import os +import random import re import subprocess -import sys import traceback from itertools import product from pathlib import Path import logging from multiprocessing import Pool -logging.basicConfig( - format='[%(asctime)s.%(msecs)03d] [%(name)s] [%(levelname)s] | %(message)s', - datefmt='%H:%M:%S', - level=logging.WARNING -) +from infinigen.core.init import configure_cycles_devices + +logging.basicConfig(format='[%(asctime)s.%(msecs)03d] [%(name)s] [%(levelname)s] | %(message)s', + datefmt='%H:%M:%S', level=logging.WARNING) import bpy import gin @@ -34,43 +34,26 @@ import submitit from infinigen.assets.fluid.fluid import set_obj_on_fire -from infinigen.assets.utils.decorate import assign_material, read_base_co -from infinigen.assets.utils.tag import tag_object, tag_nodegroup, tag_system -from infinigen.assets.lighting import sky_lighting +from infinigen.core.tagging import tag_system +from infinigen.assets.lighting import sky_lighting, hdri_lighting, three_point_lighting, holdout_lighting from infinigen.core import surface, init -from infinigen.core.placement import density, factory -from infinigen.infinigen_gpl.extras.enable_gpu import enable_gpu +from infinigen.core.placement import density, factory +from infinigen.core.util.camera import points_inview + +from infinigen.assets.utils.misc import assign_material, subclasses +# from infinigen.core.rendering.render import enable_gpu +from infinigen.assets.utils.decorate import read_base_co, read_co + from infinigen.core.util.math import FixedSeed -from infinigen.core.util.camera import get_3x4_P_matrix_from_blender -from infinigen.core.util.logging import Suppress +# noinspection PyUnresolvedReferences from infinigen.core.util import blender as butil from infinigen.tools import export -def load_txt_list(path, skip_sharp=False): - res = (Path(__file__).parent/path).read_text().splitlines() - res = [ - f.lstrip('#').lstrip(' ') - for f in res if - len(f) > 0 and not '#' in f - ] - print(res) - return res - -def load_txt_list(path, skip_sharp=False): - res = (Path(__file__).parent/path).read_text().splitlines() - res = [ - f.lstrip('#').lstrip(' ') - for f in res if - len(f) > 0 and not '#' in f - ] - print(res) - return res - -from . import generate_nature # to load most/all factory.AssetFactory subclasses - -def build_scene_asset(factory_name, idx): +from infinigen_examples.util.test_utils import load_txt_list + +def build_scene_asset(args, factory_name, idx): factory = None for subdir in os.listdir('infinigen/assets'): with gin.unlock_config(): @@ -83,7 +66,11 @@ def build_scene_asset(factory_name, idx): with FixedSeed(idx): factory = factory(idx) try: - asset = factory.spawn_asset(idx) + if args.spawn_placeholder: + ph = factory.spawn_placeholder(idx, (0, 0, 0), (0, 0, 0)) + asset = factory.spawn_asset(idx, placeholder=ph) + else: + asset = factory.spawn_asset(idx) except Exception as e: traceback.print_exc() print(f'{factory}.spawn_asset({idx=}) FAILED!! {e}') @@ -103,27 +90,28 @@ def build_scene_asset(factory_name, idx): meshes = [o for o in asset.children_recursive if o.type == 'MESH'] sizes = [] for m in meshes: - co = read_base_co(m) + co = read_co(m) sizes.append((np.amax(co, 0) - np.amin(co, 0)).sum()) i = np.argmax(np.array(sizes)) asset = meshes[i] - if not args.fire: + if not args.no_mod: if parent.animation_data is not None: drivers = parent.animation_data.drivers.values() for d in drivers: parent.driver_remove(d.data_path) - co = read_base_co(asset) + co = read_co(asset) x_min, x_max = np.amin(co, 0), np.amax(co, 0) parent.location = -(x_min[0] + x_max[0]) / 2, -(x_min[1] + x_max[1]) / 2, 0 butil.apply_transform(parent, loc=True) - bpy.ops.mesh.primitive_grid_add(size=5, x_subdivisions=400, y_subdivisions=400) - plane = bpy.context.active_object - plane.location[-1] = x_min[-1] - plane.is_shadow_catcher = True - material = bpy.data.materials.new('plane') - material.use_nodes = True - material.node_tree.nodes['Principled BSDF'].inputs[0].default_value = .015, .009, .003, 1 - assign_material(plane, material) + if not args.no_ground: + bpy.ops.mesh.primitive_grid_add(size=5, x_subdivisions=400, y_subdivisions=400) + plane = bpy.context.active_object + plane.location[-1] = x_min[-1] + plane.is_shadow_catcher = True + material = bpy.data.materials.new('plane') + material.use_nodes = True + material.node_tree.nodes['Principled BSDF'].inputs[0].default_value = .015, .009, .003, 1 + assign_material(plane, material) return asset @@ -143,15 +131,32 @@ def build_scene_surface(factory_name, idx): material.use_nodes = True material.node_tree.nodes['Principled BSDF'].inputs[0].default_value = .015, .009, .003, 1 assign_material(plane, material) - + if type(scatter) is type: + scatter = scatter(idx) scatter.apply(plane, selection=density.placement_mask(.15, .45)) asset = plane except ModuleNotFoundError: try: with gin.unlock_config(): - template = importlib.import_module(f'infinigen.assets.materials.{factory_name}') - bpy.ops.mesh.primitive_ico_sphere_add(radius=.8, subdivisions=9) - asset = bpy.context.active_object + try: + template = importlib.import_module(f'infinigen.assets.materials.{factory_name}') + except: + for subdir in os.listdir('infinigen/assets/materials'): + with gin.unlock_config(): + module = importlib.import_module( + f'infinigen.assets.materials.{subdir.split(".")[0]}') + if hasattr(module, factory_name): + template = getattr(module, factory_name) + break + else: + raise Exception(f'{factory_name} not Found.') + if hasattr(template, 'make_sphere'): + asset = template.make_sphere() + else: + bpy.ops.mesh.primitive_ico_sphere_add(radius=.8, subdivisions=9) + asset = bpy.context.active_object + if type(template) is type: + template = template(idx) template.apply(asset) except ModuleNotFoundError: raise Exception(f'{factory_name} not Found.') @@ -169,11 +174,9 @@ def build_and_save_asset(payload: dict): if args.seed > 0: idx = args.seed - if args.gpu: - enable_gpu() - path = args.output_folder / factory_name - if path and args.skip_existing: + if (path / f"images/image_{idx:03d}.png").exists() and args.skip_existing: + print(f'Skipping {path}') return path.mkdir(exist_ok=True) @@ -182,24 +185,31 @@ def build_and_save_asset(payload: dict): scene.render.resolution_x, scene.render.resolution_y = map(int, args.resolution.split('x')) scene.cycles.samples = args.samples butil.clear_scene() + configure_cycles_devices() if not args.fire: bpy.context.scene.render.film_transparent = args.film_transparent bpy.context.scene.world.node_tree.nodes['Background'].inputs[0].default_value[-1] = 0 camera, center = setup_camera(args) - with FixedSeed(args.lighting): - sky_lighting.add_lighting(camera) - nodes = bpy.data.worlds['World'].node_tree.nodes - sky_texture = [n for n in nodes if n.name.startswith('Sky Texture')][-1] - sky_texture.sun_elevation = np.deg2rad(args.elevation) - sky_texture.sun_rotation = np.pi * .75 - if 'Factory' in factory_name: - asset = build_scene_asset(factory_name, idx) + asset = build_scene_asset(args, factory_name, idx) else: asset = build_scene_surface(factory_name, idx) + with FixedSeed(args.lighting + idx): + if args.hdri: + hdri_lighting.add_lighting() + elif args.three_point: + holdout_lighting.add_lighting() + three_point_lighting.add_lighting(asset) + else: + sky_lighting.add_lighting(camera) + nodes = bpy.data.worlds['World'].node_tree.nodes + sky_texture = [n for n in nodes if n.name.startswith('Sky Texture')][-1] + sky_texture.sun_elevation = np.deg2rad(args.elevation) + sky_texture.sun_rotation = np.pi * .75 + if args.scale_reference: bpy.ops.mesh.primitive_cylinder_add(radius=0.3, depth=1.8, location=(4.9, 4.9, 1.8 / 2)) @@ -209,7 +219,10 @@ def build_and_save_asset(payload: dict): center.location[-1] += args.cam_zoff if args.cam_dist <= 0 and asset: - adjust_cam_distance(asset, camera, args.margin) + if 'Factory' in factory_name: + adjust_cam_distance(asset, camera, args.margin) + else: + adjust_cam_distance(asset, camera, args.margin, .75) cam_info_ng = bpy.data.node_groups.get('nodegroup_active_cam_info') if cam_info_ng is not None: @@ -217,7 +230,7 @@ def build_and_save_asset(payload: dict): if args.save_blend: (path / 'scenes').mkdir(exist_ok=True) - bpy.ops.wm.save_as_mainfile(filepath=f"{path}/scenes/scene_{idx:03d}.blend", filter_backup=True) + butil.save_blend(f"{path}/scenes/scene_{idx:03d}.blend", autopack=True) tag_system.save_tag(f"{path}/MaskTag.json") if args.fire: @@ -251,35 +264,21 @@ def build_and_save_asset(payload: dict): image_res=args.export_texture_res ) - if args.export is not None: - export_path = path/'export'/f'export_{idx:03d}' - export_path.mkdir(exist_ok=True, parents=True) - export.export_curr_scene( - export_path, - format=args.export, - image_res=args.export_texture_res - ) - - def parent(obj): return obj if obj.parent is None else obj.parent - -def adjust_cam_distance(asset, camera, margin): - co = read_base_co(asset) +def adjust_cam_distance(asset, camera, margin, percent=.999): + co = read_base_co(asset) * asset.scale co += asset.location lowest = np.amin(co, 0) highest = np.amax(co, 0) - bbox = np.array(list(product(*zip(lowest, highest)))) - render = bpy.context.scene.render - for cam_dist in np.exp(np.linspace(-.5, 3.5, 100)): + interp = np.linspace(lowest, highest, 11) + bbox = np.array(list(product(*zip(*interp)))) + for cam_dist in np.exp(np.linspace(-1., 5.5, 500)): camera.location[1] = -cam_dist bpy.context.view_layer.update() - proj = np.array(get_3x4_P_matrix_from_blender(camera)[0]) - x, y, z = proj @ np.concatenate([bbox, np.ones((len(bbox), 1))], -1).T - inview = (np.all(z > 0) and np.all(x >= 0) and np.all(z > 0) and np.all( - x / z < render.resolution_x) and np.all(y / z < render.resolution_y)) - if inview: + inview = points_inview(bbox, camera) + if inview.sum() / inview.size >= percent: camera.location[1] *= 1 + margin bpy.context.view_layer.update() break @@ -298,13 +297,29 @@ def make_grid(args, path, n): return with Image.open(files[0]) as i: x, y = i.size - sz_x = list(sorted(range(1, n + 1), key=lambda x: abs(math.ceil(n / x) / x - args.best_ratio)))[0] - sz_y = math.ceil(n / sz_x) - img = Image.new('RGBA', (sz_x * x, sz_y * y)) - for idx, file in enumerate(files): - with Image.open(file) as i: - img.paste(i, (idx % sz_x * x, idx // sz_x * y)) - img.save(f'{path}/grid.png') + for i, name in enumerate([path.stem, f'{path.stem}_']): + if args.zoom: + img = Image.new('RGBA', (2 * x, y)) + sz = int(np.floor(np.sqrt(n - .9))) + if i > 0: + random.shuffle(files) + with Image.open(files[0]) as i: + img.paste(i, (0, 0)) + for idx in range(sz ** 2): + with Image.open(files[min(idx + 1, len(files) - 1)]) as i: + img.paste(i.resize((x // sz, y // sz)), (x + (idx % sz) * (x // sz), idx // sz * (y // sz))) + img.save(f'{path}/{name}.png') + else: + sz_x = list(sorted(range(1, n + 1), key=lambda x: abs(math.ceil(n / x) / x - args.best_ratio)))[0] + sz_y = math.ceil(n / sz_x) + img = Image.new('RGBA', (sz_x * x, sz_y * y)) + if i > 0: + random.shuffle(files) + for idx, file in enumerate(files): + with Image.open(file) as i: + img.paste(i, (idx % sz_x * x, idx // sz_x * y)) + img.save(f'{path}/{name}.png') + print(f'{path}/{name}.png generated') def setup_camera(args): @@ -350,24 +365,34 @@ def mapfunc(f, its, args): jobs = executor.map_array(f, its) for j in jobs: print(f'Job finished {j.wait()}') - def main(args): bpy.context.window.workspace = bpy.data.workspaces['Geometry Nodes'] - - init.apply_gin_configs('infinigen_examples/configs') + + init.apply_gin_configs('infinigen_examples/configs_indoor', skip_unknown=True) surface.registry.initialize_from_gin() 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.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: + name = '_'.join(args.factories) + + if args.output_folder is None: + args.output_folder = Path(os.getcwd()) / 'outputs' + + path = Path(args.output_folder) / name + path.mkdir(exist_ok=True) + factories = list(args.factories) if 'ALL_ASSETS' in factories: factories += [f.__name__ for f in subclasses(factory.AssetFactory)] @@ -378,8 +403,9 @@ def main(args): if 'ALL_MATERIALS' in factories: factories += [f.stem for f in Path('infinigen/assets/materials').iterdir()] factories.remove('ALL_MATERIALS') - - args.output_folder.mkdir(exist_ok=True) + has_txt = '.txt' in factories[0] + if has_txt: + factories = [f.split('.')[-1] for f in load_txt_list(factories[0], skip_sharp=False)] if not args.postprocessing_only: for fac in factories: @@ -389,12 +415,21 @@ def main(args): ] mapfunc(build_and_save_asset, targets, args) - for fac in factories: + for j, fac in enumerate(factories): fac_path = args.output_folder/fac - assert fac_path.exists() - if args.render == 'image': + assert fac_path.exists(); f'{fac_path} does not exist' + if has_txt: + for i in range(args.n_images): + img_path = fac_path / 'images' / f'image_{i:03d}.png' + if img_path.exists(): + subprocess.run( + f'cp -f {img_path} {path}/{fac}_{i:03d}.png', shell=True + ) + else: + print(f'{img_path} does not exist') + elif args.render == 'image': make_grid(args, fac_path, args.n_images) - if args.render == 'video': + elif args.render == 'video': (fac_path / 'videos').mkdir(exist_ok=True) for i in range(args.n_images): subprocess.run( @@ -402,48 +437,62 @@ def main(args): f'{fac_path}/videos/video_{i:03d}.mp4', shell=True) + def snake_case(s): return '_'.join( re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', s.replace('-', ' '))).split()).lower() def make_args(): parser = argparse.ArgumentParser() - parser.add_argument('--output_folder', type=Path) + parser.add_argument('-o', '--output_folder', type=Path, default=None) parser.add_argument('-f', '--factories', default=[], nargs='+', help="List factories/surface scatters/surface materials you want to render") - parser.add_argument('-n', '--n_images', default=4, type=int, help="Number of scenes to render") - parser.add_argument("-m", '--margin', default=.1, + parser.add_argument('-n', '--n_images', default=1, type=int, help="Number of scenes to render") + parser.add_argument("-m", '--margin', default=.01, help="Margin between the asset the boundary of the image when automatically adjusting " "the camera") parser.add_argument('-R', '--resolution', default='1024x1024', type=str, help="Image resolution widthxheight") parser.add_argument('-p', '--samples', default=200, type=int, help="Blender cycles samples") parser.add_argument('-l', '--lighting', default=0, type=int, help="Lighting seed") - parser.add_argument('-o', '--cam_zoff', '--z_offset', type=float, default=.0, + parser.add_argument('-Z', '--cam_zoff', '--z_offset', type=float, default=.0, help="Additional offset on Z axis for camera look-at positions") parser.add_argument('-s', '--save_blend', action='store_true', help="Whether to save .blend file") parser.add_argument('-e', '--elevation', default=60, type=float, help="Elevation of the sun") parser.add_argument('--cam_dist', default=0, type=float, - help="Distance from the camera to the look-at position") - parser.add_argument('-a', '--cam_angle', default=(-30, 0, 0), type=float, nargs='+', - help="Camera rotation in XYZ") + help="Distance from the camera to the look-at position" + ) + parser.add_argument( + '-a', '--cam_angle', default=(-30, 0, 45), type=float, nargs='+', + help="Camera rotation in XYZ" + ) + parser.add_argument('-O', '--offset', default=(0, 0, 0), type=float, nargs='+', help='asset location') parser.add_argument('-c', '--cam_center', default=1, type=int, help="Camera rotation in XYZ") - parser.add_argument('-r', '--render', default='image', type=str, choices=['image', 'video', 'none'], + parser.add_argument( + '-r', '--render', default='image', type=str, choices=['image', 'video', 'none'], help="Whether to render the scene in images or video") parser.add_argument('-b', '--best_ratio', default=9 / 16, type=float, help="Best aspect ratio for compiling the images into asset grid") - parser.add_argument('-F', '--fire', action = 'store_true') - parser.add_argument('-I', '--fire_res', default = 100, type = int) - parser.add_argument('-U', '--fire_duration', default = 30, type = int) + parser.add_argument('-F', '--fire', action='store_true') + parser.add_argument('-I', '--fire_res', default=100, type=int) + parser.add_argument('-U', '--fire_duration', default=30, type=int) parser.add_argument('-t', '--film_transparent', default=1, type=int, help="Whether the background is transparent") parser.add_argument('-E', '--frame_end', type=int, default=120, help="End of frame in videos") + parser.add_argument('-g', '--gpu', action='store_true', help="Whether to use gpu in rendering") parser.add_argument('-C', '--cycles', type=float, default=1, help="render video cycles") parser.add_argument('-A', '--scale_reference', action='store_true', help="Add the scale reference") parser.add_argument('-S', '--skip_existing', action='store_true', help="Skip existing scenes and renders") parser.add_argument('-P', '--postprocessing_only', action='store_true', help="Only run postprocessing") parser.add_argument('-D', '--seed', type=int, default=-1, help="Run a specific seed.") - parser.add_argument('-d', '--debug', action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) + 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") + parser.add_argument('-G', '--no_ground', action='store_true', help="no ground") + parser.add_argument('-W', '--spawn_placeholder', action='store_true', help="spawn placeholder") + parser.add_argument('-z', '--zoom', action='store_true', help="zoom first figure") parser.add_argument('--n_workers', type=int, default=1) parser.add_argument('--slurm', action='store_true') @@ -453,7 +502,10 @@ def make_args(): return init.parse_args_blender(parser) + if __name__ == '__main__': args = make_args() + args.no_mod = args.no_mod or args.fire + args.film_transparent = args.film_transparent and not args.hdri with FixedSeed(1): main(args) diff --git a/infinigen_examples/generate_indoors.py b/infinigen_examples/generate_indoors.py new file mode 100644 index 000000000..80fac6cd8 --- /dev/null +++ b/infinigen_examples/generate_indoors.py @@ -0,0 +1,426 @@ +# 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. + +import argparse +from pathlib import Path +import logging +from time import time +from numpy import deg2rad +import pprint +import copy + +logging.basicConfig( + format='[%(asctime)s.%(msecs)03d] [%(module)s] [%(levelname)s] | %(message)s', + datefmt='%H:%M:%S', + level=logging.INFO +) + +import bpy +from mathutils import Vector +import gin +import numpy as np +import trimesh +from numpy import deg2rad + +from infinigen.assets import (lighting) +from infinigen.assets.wall_decorations.skirting_board import make_skirting_board +from infinigen.assets.utils.decorate import read_co +from infinigen.terrain import Terrain +from infinigen.assets.materials import invisible_to_camera + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r, + example_solver, + checks, + usage_lookup +) + +from infinigen.assets import ( + fluid, + cactus, + cactus, + trees, + monocot, + rocks, + underwater, + creatures, + lighting, + weather +) +from infinigen.assets.scatters import grass, pebbles +from infinigen.assets.utils.decorate import read_co + +from infinigen.core.placement import density, camera as cam_util, split_in_view + +from infinigen_examples.indoor_constraint_examples import home_constraints +from infinigen.core import ( + execute_tasks, + surface, + init, + placement, + tags as t, + tagging +) +from infinigen.core.util import (blender as butil, pipeline) + +from infinigen.core.constraints.example_solver.room import decorate as room_dec, constants +from infinigen.core.constraints.example_solver import state_def, greedy, populate, Solver + +from infinigen.core.constraints.example_solver.room.constants import WALL_HEIGHT +from infinigen.core.util.camera import points_inview + +from infinigen_examples.generate_nature import compose_nature # so gin can find it +from infinigen_examples.util import constraint_util as cu +from infinigen_examples.util.generate_indoors_util import ( + create_outdoor_backdrop, + place_cam_overhead, + overhead_view, + hide_other_rooms, + restrict_solving, + apply_greedy_restriction +) + +logger = logging.getLogger(__name__) + +def default_greedy_stages(): + + """Returns descriptions of what will be covered by each greedy stage of the solver. + + Any domain containing one or more VariableTags is greedy: it produces many separate domains, + one for each possible assignment of the unresolved variables. + """ + + on_floor = cl.StableAgainst({}, cu.floortags) + on_wall = cl.StableAgainst({}, cu.walltags) + on_ceiling = cl.StableAgainst({}, cu.ceilingtags) + side = cl.StableAgainst({}, cu.side) + + all_room = r.Domain({t.Semantics.Room, -t.Semantics.Object}) + all_obj = r.Domain({t.Semantics.Object, -t.Semantics.Room}) + + all_obj_in_room = all_obj.with_relation(cl.AnyRelation(), all_room.with_tags(cu.variable_room)) + primary = all_obj_in_room.with_relation(-cl.AnyRelation(), all_obj) + + greedy_stages = {} + + greedy_stages['rooms'] = all_room + + greedy_stages['on_floor'] = primary.with_relation(on_floor, all_room) + greedy_stages['on_wall'] = ( + primary.with_relation(-on_floor, all_room) + .with_relation(-on_ceiling, all_room) + .with_relation(on_wall, all_room) + ) + greedy_stages['on_ceiling'] = ( + primary.with_relation(-on_floor, all_room) + .with_relation(on_ceiling, all_room) + .with_relation(-on_wall, all_room) + ) + + secondary = all_obj.with_relation(cl.AnyRelation(), primary.with_tags(cu.variable_obj)) + + greedy_stages['side_obj'] = secondary.with_relation(side, all_obj) + nonside = secondary.with_relation(-side, all_obj) + + greedy_stages['obj_ontop_obj'] = nonside.with_relation(cu.ontop, all_obj).with_relation(-cu.on, all_obj) + greedy_stages['obj_on_support'] = nonside.with_relation(cu.on, all_obj).with_relation(-cu.ontop, all_obj) + + + return greedy_stages + + +all_vars = [cu.variable_room, cu.variable_obj] + +@gin.configurable +def compose_indoors(output_folder: Path, scene_seed: int, **overrides): + p = pipeline.RandomStageExecutor(scene_seed, output_folder, overrides) + + logger.debug(overrides) + + def add_coarse_terrain(): + terrain = Terrain( + scene_seed, + surface.registry, + task='coarse', + on_the_fly_asset_folder=output_folder / "assets" + ) + terrain_mesh = terrain.coarse_terrain() + # placement.density.set_tag_dict(terrain.tag_dict) + return terrain, terrain_mesh + + terrain, terrain_mesh = p.run_stage('terrain', add_coarse_terrain, use_chance=False, default=(None, None)) + p.run_stage('sky_lighting', lighting.sky_lighting.add_lighting, use_chance=False) + + consgraph = home_constraints() + stages = default_greedy_stages() + checks.check_all(consgraph, stages, all_vars) + + stages, consgraph, limits = restrict_solving(stages, consgraph) + + if overrides.get('restrict_single_supported_roomtype', False): + restrict_parent_rooms = { + np.random.choice([ + + # Only these roomtypes have constraints written in home_constraints. + # Others will be empty-ish besides maybe storage and plants + # TODO: add constraints to home_constraints for garages, offices, balconies, etc + + t.Semantics.Bedroom, + t.Semantics.LivingRoom, + t.Semantics.Kitchen, + t.Semantics.Bathroom, + t.Semantics.DiningRoom + ]) + } + logger.info(f'Restricting to {restrict_parent_rooms}') + apply_greedy_restriction(stages, restrict_parent_rooms, cu.variable_room) + + solver = Solver(output_folder=output_folder) + def solve_rooms(): + return solver.solve_rooms(scene_seed, consgraph, stages['rooms']) + state: state_def.State = p.run_stage('solve_rooms', solve_rooms, use_chance=False) + + def solve_large(): + assignments = greedy.iterate_assignments( + stages['on_floor'], state, all_vars, limits, nonempty=True + ) + for i, vars in enumerate(assignments): + solver.solve_objects( + consgraph, + stages['on_floor'], + var_assignments=vars, + n_steps=overrides['solve_steps_large'], + desc=f"on_floor_{i}", + abort_unsatisfied=overrides.get('abort_unsatisfied_large', False) + ) + return solver.state + state = p.run_stage('solve_large', solve_large, use_chance=False, default=state) + + solved_rooms = [ + state.objs[assignment[cu.variable_room]].obj + for assignment in greedy.iterate_assignments( + stages['on_floor'], state, [cu.variable_room], limits + ) + ] + solved_bound_points = np.concatenate([butil.bounds(r) for r in solved_rooms]) + solved_bbox = (np.min(solved_bound_points, axis=0), np.max(solved_bound_points, axis=0)) + + house_bbox = np.concatenate([butil.bounds(obj) for obj in solver.get_bpy_objects(r.Domain({t.Semantics.Room}))]) + house_bbox = (np.min(house_bbox, axis=0), np.max(house_bbox, axis=0)) + + camera_rigs = placement.camera.spawn_camera_rigs() + + def pose_cameras(): + + nonroom_objs = [ + o.obj for o in state.objs.values() if t.Semantics.Room not in o.tags + ] + scene_objs = solved_rooms + nonroom_objs + + scene_preprocessed = placement.camera.camera_selection_preprocessing( + terrain=None, + scene_objs=scene_objs + ) + + solved_floor_surface = butil.join_objects([ + tagging.extract_tagged_faces(o, {t.Subpart.SupportSurface}) + for o in solved_rooms + ]) + + placement.camera.configure_cameras( + camera_rigs, + scene_preprocessed=scene_preprocessed, + init_surfaces=solved_floor_surface + ) + + return scene_preprocessed + + 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=[]) + p.run_stage('animate_cameras', animate_cameras, use_chance=False, prereq='pose_cameras') + + p.run_stage( + 'populate_intermediate_pholders', + populate.populate_state_placeholders, + solver.state, + filter=t.Semantics.AssetPlaceholderForChildren, + final=False, + use_chance=False + ) + + def solve_medium(): + n_steps = overrides['solve_steps_medium'] + for i, vars in enumerate(greedy.iterate_assignments(stages['on_wall'], state, all_vars, limits)): + solver.solve_objects(consgraph, stages['on_wall'], vars, n_steps, desc=f"on_wall_{i}") + for i, vars in enumerate(greedy.iterate_assignments(stages['on_ceiling'], state, all_vars, limits)): + solver.solve_objects(consgraph, stages['on_ceiling'], vars, n_steps, desc=f"on_ceiling_{i}") + for i, vars in enumerate(greedy.iterate_assignments(stages['side_obj'], state, all_vars, limits)): + solver.solve_objects(consgraph, stages['side_obj'], vars, n_steps, desc=f"side_obj_{i}") + return solver.state + state = p.run_stage('solve_medium', solve_medium, use_chance=False, default=state) + + def solve_small(): + n_steps = overrides['solve_steps_small'] + for i, vars in enumerate(greedy.iterate_assignments(stages['obj_ontop_obj'], state, all_vars, limits)): + solver.solve_objects(consgraph, stages['obj_ontop_obj'], vars, n_steps, desc=f"obj_ontop_obj_{i}") + for i, vars in enumerate(greedy.iterate_assignments(stages['obj_on_support'], state, all_vars, limits)): + solver.solve_objects(consgraph, stages['obj_on_support'], vars, n_steps, desc=f"obj_on_support_{i}") + #for i, vars in enumerate(greedy.iterate_assignments(stages['tertiary'], state, all_vars, limits)): + # solver.solve_objects(consgraph, stages['tertiary'], vars, n_steps, desc=f"tertiary_{i}") + return solver.state + state = p.run_stage('solve_small', solve_small, use_chance=False, default=state) + + p.run_stage('populate_assets', populate.populate_state_placeholders, state, use_chance=False) + + door_filter = r.Domain({t.Semantics.Door}, [(cl.AnyRelation(), stages['rooms'])]) + window_filter = r.Domain({t.Semantics.Window}, [(cl.AnyRelation(), stages['rooms'])]) + p.run_stage('room_doors', lambda: room_dec.populate_doors(solver.get_bpy_objects(door_filter)), use_chance=False) + p.run_stage('room_windows', lambda: room_dec.populate_windows(solver.get_bpy_objects(window_filter)), use_chance=False) + + room_meshes = solver.get_bpy_objects(r.Domain({t.Semantics.Room})) + p.run_stage('room_stairs', lambda: room_dec.room_stairs(state, room_meshes), use_chance=False) + p.run_stage('skirting_floor', lambda: make_skirting_board(room_meshes, t.Subpart.SupportSurface)) + p.run_stage('skirting_ceiling', lambda: make_skirting_board(room_meshes, t.Subpart.Ceiling)) + + rooms_meshed = butil.get_collection('placeholders:room_meshes') + rooms_split = room_dec.split_rooms(list(rooms_meshed.objects)) + + p.run_stage('room_walls', room_dec.room_walls, rooms_split['wall'].objects, use_chance=False) + p.run_stage('room_pillars', room_dec.room_pillars, state, rooms_split['wall'].objects, use_chance=False) + p.run_stage('room_floors', room_dec.room_floors, rooms_split['floor'].objects, use_chance=False) + p.run_stage('room_ceilings', room_dec.room_ceilings, rooms_split['ceiling'].objects, use_chance=False) + + #state.print() + state.to_json(output_folder / 'solve_state.json') + + cam = cam_util.get_camera(0, 0) + + def turn_off_lights(): + for o in bpy.data.objects: + if o.type == 'LIGHT' and not o.data.cycles.is_portal: + print(f'Deleting {o.name}') + butil.delete(o) + p.run_stage('lights_off', turn_off_lights) + + def invisible_room_ceilings(): + rooms_split['exterior'].hide_viewport = True + rooms_split['exterior'].hide_render = True + invisible_to_camera.apply(list(rooms_split['ceiling'].objects)) + invisible_to_camera.apply([o for o in bpy.data.objects if 'CeilingLight' in o.name]) + p.run_stage('invisible_room_ceilings', invisible_room_ceilings, use_chance=False) + + p.run_stage( + 'overhead_cam', + place_cam_overhead, + cam=camera_rigs[0], + bbox=solved_bbox, + use_chance=False + ) + + p.run_stage( + 'hide_other_rooms', + hide_other_rooms, + state, + rooms_split, + keep_rooms=[r.name for r in solved_rooms], + use_chance=False + ) + + height = p.run_stage( + 'nature_backdrop', + create_outdoor_backdrop, + terrain, + house_bbox=house_bbox, + cam=cam, + p=p, + params=overrides, + use_chance=False, + prereq='terrain', + default=0, + ) + + if overrides.get('topview', False): + rooms_split['exterior'].hide_viewport = True + rooms_split['ceiling'].hide_viewport = True + rooms_split['exterior'].hide_render = True + rooms_split['ceiling'].hide_render = True + for group in ['wall', 'floor']: + for wall in rooms_split[group].objects: + for mat in wall.data.materials: + for n in mat.node_tree.nodes: + if n.type == 'BSDF_PRINCIPLED': + n.inputs['Alpha'].default_value = overrides.get('alpha_walls', 1.) + bbox = np.concatenate([read_co(r) + np.array(r.location)[np.newaxis, :] for r in rooms_meshed.objects]) + camera = camera_rigs[0].children[0] + camera_rigs[0].location = 0, 0, 0 + camera_rigs[0].rotation_euler = 0, 0, 0 + bpy.contexScene.camera = camera + rot_x = deg2rad(overrides.get('topview_rot_x', 0)) + rot_z = deg2rad(overrides.get('topview_rot_z', 0)) + camera.rotation_euler = rot_x, 0, rot_z + mean = np.mean(bbox, 0) + for cam_dist in np.exp(np.linspace(1., 5., 500)): + camera.location = mean[0] + cam_dist * np.sin(rot_x) * np.sin(rot_z), mean[1] - cam_dist * np.sin( + rot_x) * np.cos(rot_z), mean[2] - WALL_HEIGHT / 2 + cam_dist * np.cos(rot_x) + bpy.context.view_layer.update() + inview = points_inview(bbox, camera) + if inview.all(): + for area in bpy.contexScreen.areas: + if area.type == 'VIEW_3D': + area.spaces.active.region_3d.view_perspective = 'CAMERA' + break + break + + return { + "height_offset": height, + "whole_bbox": house_bbox, + } + + + +def main(args): + scene_seed = init.apply_scene_seed(args.seed) + init.apply_gin_configs( + configs=args.configs, + overrides=args.overrides, + configs_folder='infinigen_examples/configs_indoor' + ) + constants.initialize_constants() + + execute_tasks.main(compose_scene_func=compose_indoors, input_folder=args.input_folder, + output_folder=args.output_folder, task=args.task, task_uniqname=args.task_uniqname, + scene_seed=scene_seed) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--output_folder', type=Path) + parser.add_argument('--input_folder', type=Path, default=None) + parser.add_argument('-s', '--seed', default=None, help="The seed used to generate the scene") + parser.add_argument('-t', '--task', nargs='+', default=['coarse'], + choices=['coarse', 'populate', 'fine_terrain', 'ground_truth', 'render', 'mesh_save', 'export']) + parser.add_argument('-g', '--configs', nargs='+', default=['base'], + help='Set of config files for gin (separated by spaces) ' + 'e.g. --gin_config file1 file2 (exclude .gin from path)') + parser.add_argument('-p', '--overrides', nargs='+', default=[], + help='Parameter settings that override config defaults ' + 'e.g. --gin_param module_1.a=2 module_2.b=3') + parser.add_argument('--task_uniqname', type=str, default=None) + parser.add_argument('-d', '--debug', type=str, nargs='*', default=None) + + args = init.parse_args_blender(parser) + logging.getLogger("infinigen").setLevel(logging.INFO) + logging.getLogger("infinigen.core.nodes.node_wrangler").setLevel(logging.CRITICAL) + + 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) + + main(args) \ No newline at end of file diff --git a/infinigen_examples/generate_material_balls.py b/infinigen_examples/generate_material_balls.py new file mode 100644 index 000000000..6a6c4be8c --- /dev/null +++ b/infinigen_examples/generate_material_balls.py @@ -0,0 +1,203 @@ +# 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: +# - Lingjie Mei +# - Alex Raistrick +# - Karhan Kayan - add fire option + +import argparse +import importlib +import math +import os +import random +import re +import subprocess +import traceback +from itertools import product +from pathlib import Path +import logging + +from numpy.random import uniform +from tqdm import tqdm + +from infinigen.assets.materials import metal_shader_list +from infinigen.assets.materials.woods import tiled_wood +from infinigen_examples.generate_individual_assets import adjust_cam_distance, make_args, parent, setup_camera +from infinigen_examples.util.test_utils import load_txt_list + +logging.basicConfig(format='[%(asctime)s.%(msecs)03d] [%(name)s] [%(levelname)s] | %(message)s', + datefmt='%H:%M:%S', level=logging.WARNING) + +import bpy +import gin +import numpy as np +from PIL import Image + +from infinigen.assets.fluid.fluid import set_obj_on_fire +from infinigen.core.tagging import tag_system +from infinigen.assets.lighting import sky_lighting, hdri_lighting, three_point_lighting, holdout_lighting + +from infinigen.core import surface, init +from infinigen.core.placement import density, factory +from infinigen.core.util.camera import points_inview + +from infinigen.assets.utils.misc import assign_material, subclasses +from infinigen.core.rendering.render import enable_gpu +from infinigen.assets.utils.decorate import read_base_co, read_co + +from infinigen.core.util.math import FixedSeed +# noinspection PyUnresolvedReferences +from infinigen.core.util import blender as butil + + +def build_scene_surface(factory_name, idx): + try: + with gin.unlock_config(): + try: + template = importlib.import_module(f'infinigen.assets.materials.{factory_name}') + except: + for subdir in os.listdir('infinigen/assets/materials'): + with gin.unlock_config(): + module = importlib.import_module(f'infinigen.assets.materials.{subdir.split(".")[0]}') + if hasattr(module, factory_name): + template = getattr(module, factory_name) + break + else: + raise Exception(f'{factory_name} not Found.') + if type(template) is type: + template = template(idx) + if hasattr(template, 'make_sphere'): + asset = template.make_sphere() + else: + bpy.ops.mesh.primitive_ico_sphere_add(radius=1, subdivisions=7) + asset = bpy.context.active_object + + with FixedSeed(idx): + if 'metal' in factory_name or 'sofa_fabric' in factory_name: + template.apply(asset, scale=.1) + elif 'hardwood' in factory_name: + template.apply(asset, rotation=(np.pi / 2, 0, 0)) + elif 'brick' in factory_name: + template.apply(asset, height=uniform(.25, .3)) + else: + template.apply(asset) + except ModuleNotFoundError: + raise Exception(f'{factory_name} not Found.') + return asset + + +def build_scene(path, factory_names, args): + scene = bpy.context.scene + scene.render.engine = 'CYCLES' + scene.render.resolution_x, scene.render.resolution_y = map(int, args.resolution.split('x')) + scene.cycles.samples = args.samples + butil.clear_scene() + + if not args.fire: + bpy.context.scene.render.film_transparent = args.film_transparent + bpy.context.scene.world.node_tree.nodes['Background'].inputs[0].default_value[-1] = 0 + camera, center = setup_camera(args) + + scale = .3 + assets = [] + with tqdm(total=len(factory_names)) as pbar: + for idx, factory_name in enumerate(factory_names): + asset = build_scene_surface(factory_name, idx) + assets.append(asset) + asset.name = factory_name + pbar.update(1) + margin = scale * 2.2 + size = 5 + for i in range(len(assets)): + assets[i].scale = [scale] * 3 + butil.apply_transform(assets[i]) + assets[i].location = (i // size) * margin, (i % size) * margin, scale + + bpy.ops.mesh.primitive_grid_add(size=1, x_subdivisions=400, y_subdivisions=400) + asset = bpy.context.active_object + asset.scale = [scale * len(assets) / size * 4] * 3 + asset.location = (len(assets) // size - 1) * margin / 2, size // 2 * margin * .8, 0 + tiled_wood.apply(asset, hscale=10, vscale=3) + + with FixedSeed(args.lighting): + if args.hdri: + hdri_lighting.add_lighting() + elif args.three_point: + holdout_lighting.add_lighting() + three_point_lighting.add_lighting(asset) + else: + sky_lighting.add_lighting(camera) + nodes = bpy.data.worlds['World'].node_tree.nodes + sky_texture = [n for n in nodes if n.name.startswith('Sky Texture')][-1] + sky_texture.sun_elevation = np.deg2rad(args.elevation) + sky_texture.sun_rotation = np.pi * .75 + + if args.scale_reference: + bpy.ops.mesh.primitive_cylinder_add(radius=0.3, depth=1.8, location=(4.9, 4.9, 1.8 / 2)) + + if args.cam_center > 0 and asset: + co = read_base_co(asset) + asset.location + center.location = (np.amin(co, 0) + np.amax(co, 0)) / 2 + center.location[-1] += args.cam_zoff + + if args.cam_dist <= 0 and asset: + adjust_cam_distance(asset, camera, args.margin, .6) + + cam_info_ng = bpy.data.node_groups.get('nodegroup_active_cam_info') + if cam_info_ng is not None: + cam_info_ng.nodes['Object Info'].inputs['Object'].default_value = camera + + if args.save_blend: + (path / 'scenes').mkdir(exist_ok=True) + butil.save_blend(f"{path}/scenes/scene_{idx:03d}.blend", autopack=True) + + +def main(args): + bpy.context.window.workspace = bpy.data.workspaces['Geometry Nodes'] + + init.apply_gin_configs('infinigen_examples/configs_indoor', skip_unknown=True) + surface.registry.initialize_from_gin() + + 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: + name = '_'.join(args.factories) + path = Path(os.getcwd()) / 'outputs' / name + path.mkdir(exist_ok=True) + + if args.gpu: + enable_gpu() + + factories = list(args.factories) + if 'ALL_ASSETS' in factories: + factories += [f.__name__ for f in subclasses(factory.AssetFactory)] + factories.remove('ALL_ASSETS') + if 'ALL_SCATTERS' in factories: + factories += [f.stem for f in Path('surfaces/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') + if '.txt' in factories[0]: + factories = [f.split('.')[-1] for f in load_txt_list(factories[0], skip_sharp=False)] + + try: + build_scene(path, factories, args) + except Exception as e: + print(e) + + +if __name__ == '__main__': + args = make_args() + args.no_mod = args.no_mod or args.fire + args.film_transparent = args.film_transparent and not args.hdri + with FixedSeed(1): + main(args) diff --git a/infinigen_examples/generate_nature.py b/infinigen_examples/generate_nature.py index 6a78ec178..0a4d7cfee 100644 --- a/infinigen_examples/generate_nature.py +++ b/infinigen_examples/generate_nature.py @@ -67,8 +67,8 @@ from infinigen.core import execute_tasks, surface, init @gin.configurable -def compose_scene(output_folder, scene_seed, **params): - +def compose_nature(output_folder, scene_seed, **params): + p = pipeline.RandomStageExecutor(scene_seed, output_folder, params) def add_coarse_terrain(): @@ -82,7 +82,7 @@ def add_coarse_terrain(): terrain_mesh = butil.create_noise_plane() density.set_tag_dict({}) - terrain_bvh = mathutils.bvhtree.BVHTree.FromObject(terrain_mesh, bpy.context.evaluated_depsgraph_get()) + scene_bvh = mathutils.bvhtree.BVHTree.FromObject(terrain_mesh, bpy.context.evaluated_depsgraph_get()) land_domain = params.get('land_domain_tags') underwater_domain = params.get('underwater_domain_tags') @@ -168,14 +168,20 @@ def add_cactus(terrain_mesh): def camera_preprocess(): camera_rigs = cam_util.spawn_camera_rigs() - scene_preprocessed = cam_util.camera_selection_preprocessing(terrain, terrain_mesh) + scene_preprocessed = cam_util.camera_selection_preprocessing( + terrain, + terrain_mesh, + tags_ratio=params.get('camera_selection_tags_ratio'), + ranges_ratio=params.get('camera_selection_ranges_ratio'), + anim_criterion_keys=params.get('camera_selection_anim_criterion_keys', False), + ) return camera_rigs, scene_preprocessed camera_rigs, scene_preprocessed = p.run_stage('camera_preprocess', camera_preprocess, use_chance=False) bbox = terrain.get_bounding_box() if terrain is not None else butil.bounds(terrain_mesh) p.run_stage( 'pose_cameras', - lambda: cam_util.configure_cameras(camera_rigs, bbox, scene_preprocessed), + lambda: cam_util.configure_cameras(camera_rigs, scene_preprocessed, init_bounding_box=bbox), use_chance=False ) cam = cam_util.get_camera(0, 0) @@ -194,7 +200,7 @@ def camera_preprocess(): def add_ground_creatures(target): fac_class = sample_registry(params['ground_creature_registry']) - fac = fac_class(int_hash((scene_seed, 0)), bvh=terrain_bvh, animation_mode='idle') + fac = fac_class(int_hash((scene_seed, 0)), bvh=scene_bvh, animation_mode='idle') n = params.get('max_ground_creatures', randint(1, 4)) selection = density.placement_mask(select_thresh=0, tag='beach', altitude_range=(-0.5, 0.5)) \ if fac_class is creatures.CrabFactory else 1 @@ -204,14 +210,14 @@ def add_ground_creatures(target): def flying_creatures(): fac_class = sample_registry(params['flying_creature_registry']) - fac = fac_class(randint(1e7), bvh=terrain_bvh, animation_mode='idle') + fac = fac_class(randint(1e7), bvh=scene_bvh, animation_mode='idle') n = params.get('max_flying_creatures', randint(2, 7)) col = placement.scatter_placeholders_mesh(terrain_center, fac, num_placeholders=n, overall_density=1, altitude=0.2) return list(col.objects) pois += p.run_stage('flying_creatures', flying_creatures, default=[]) p.run_stage('animate_cameras', lambda: cam_util.animate_cameras( - camera_rigs, scene_preprocessed, pois=pois), use_chance=False) + camera_rigs, bbox, scene_preprocessed, pois=pois), use_chance=False) with logging_util.Timer('Compute coarse terrain frustrums'): terrain_inview, *_ = split_in_view.split_inview( @@ -394,22 +400,25 @@ def add_snow_particles(): p.run_stage('tilted_river', add_tilted_river, use_chance=False) p.save_results(output_folder/'pipeline_coarse.csv') - return terrain, terrain_mesh + return { + "height_offset": 0, + "whole_bbox": None, + } def main(args): scene_seed = init.apply_scene_seed(args.seed) - mandatory_exclusive = [Path('infinigen_examples/configs/scene_types')] + mandatory_exclusive = [Path('infinigen_examples/configs_nature/scene_types')] init.apply_gin_configs( configs=args.configs, overrides=args.overrides, - configs_folder='infinigen_examples/configs', + configs_folder='infinigen_examples/configs_nature', mandatory_folders=mandatory_exclusive, mutually_exclusive_folders=mandatory_exclusive, ) execute_tasks.main( - compose_scene_func=compose_scene, + compose_scene_func=compose_nature, input_folder=args.input_folder, output_folder=args.output_folder, task=args.task, @@ -424,7 +433,7 @@ def main(args): parser.add_argument('--input_folder', type=Path, default=None) parser.add_argument('-s', '--seed', default=None, help="The seed used to generate the scene") parser.add_argument('-t', '--task', nargs='+', default=['coarse'], - choices=['coarse', 'populate', 'fine_terrain', 'ground_truth', 'render', 'mesh_save']) + choices=['coarse', 'populate', 'fine_terrain', 'ground_truth', 'render', 'mesh_save', 'export']) parser.add_argument('-g', '--configs', nargs='+', default=['base'], help='Set of config files for gin (separated by spaces) ' 'e.g. --gin_config file1 file2 (exclude .gin from path)') diff --git a/infinigen_examples/indoor_asset_semantics.py b/infinigen_examples/indoor_asset_semantics.py new file mode 100644 index 000000000..9ae3196c2 --- /dev/null +++ b/infinigen_examples/indoor_asset_semantics.py @@ -0,0 +1,345 @@ +# 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 + +from infinigen.assets import ( + appliances, + bathroom, + decor, + elements, + lighting, + seating, + shelves, + table_decorations, + tables, + tableware, + wall_decorations, + windows, + clothes +) + +from infinigen.core.tags import Semantics, Subpart, FromGenerator + +def home_asset_usage(): + + """ Defines what generators are consider to fulfill what roles in a home setting. + + The primary effect of this to determine what types of objects are returned by the square brackets [ ] operator in home_constraints + + You can define these however you like - use + + See the `Semantics` class in `infinigen.core.tags` for a list of possible semantics, or add your own. + + """ + + # TODO: this whole used_as will be integrated into the constraint language. Currently there are two paralell semantics trees, one to define the tags and one to use them. + + used_as = {} + + # region small objects + + used_as[Semantics.Dishware] = { + tableware.PlateFactory, + tableware.BowlFactory, + tableware.WineglassFactory, + tableware.PanFactory, + tableware.PotFactory, + tableware.CupFactory, + } + used_as[Semantics.Cookware] = { + tableware.PotFactory, + tableware.PanFactory + } + used_as[Semantics.Utensils] = { + tableware.SpoonFactory, + tableware.KnifeFactory, + tableware.ChopsticksFactory, + tableware.ForkFactory, + } + + used_as[Semantics.FoodPantryItem] = { + tableware.CanFactory, + tableware.FoodBagFactory, + tableware.FoodBoxFactory, + tableware.JarFactory, + tableware.BottleFactory + } + + used_as[Semantics.TableDisplayItem] = { + tableware.FruitContainerFactory, + table_decorations.VaseFactory, + tableware.BowlFactory, + tableware.PotFactory + } + + used_as[Semantics.OfficeShelfItem] = { + table_decorations.BookStackFactory, + table_decorations.BookColumnFactory, + elements.NatureShelfTrinketsFactory, + } + + used_as[Semantics.KitchenCounterItem] = set().union( + used_as[Semantics.Dishware], + used_as[Semantics.Cookware], + { + table_decorations.BookColumnFactory, + tableware.JarFactory, + } + ) + + used_as[Semantics.BathroomItem] = { + tableware.BottleFactory, + tableware.BowlFactory, + clothes.TowelFactory, + } + + used_as[Semantics.ClothDrapeItem] = { + # objects that can be strewn about / draped over furniture + #clothes.BlanketFactory, + clothes.PantsFactory, + clothes.ShirtFactory, + } + + used_as[Semantics.HandheldItem] = set.union( + used_as[Semantics.Utensils], + used_as[Semantics.FoodPantryItem], + used_as[Semantics.TableDisplayItem], + used_as[Semantics.OfficeShelfItem], + used_as[Semantics.ClothDrapeItem], + used_as[Semantics.Dishware], + ) + + # endregion + + # region furniture + + used_as[Semantics.Sink] = { + table_decorations.SinkFactory, + bathroom.BathroomSinkFactory, + bathroom.StandingSinkFactory + } + + used_as[Semantics.Storage] = { + shelves.SimpleBookcaseFactory, + shelves.CellShelfFactory, + shelves.LargeShelfFactory, + shelves.KitchenCabinetFactory, + shelves.SingleCabinetFactory + } + + used_as[Semantics.SideTable] = { + shelves.SidetableDeskFactory, + tables.SideTableFactory + } + + used_as[Semantics.Table] = set.union( + used_as[Semantics.SideTable], + { + tables.TableDiningFactory, + tables.TableCocktailFactory, + shelves.SimpleDeskFactory, + tables.CoffeeTableFactory, + } + ) + + used_as[Semantics.Chair] = { + seating.BarChairFactory, + seating.ChairFactory, + seating.OfficeChairFactory + } + + used_as[Semantics.LoungeSeating] = { + seating.SofaFactory, + seating.ArmChairFactory, + } + + used_as[Semantics.Seating] = set.union( + used_as[Semantics.Chair], + used_as[Semantics.LoungeSeating], + ) + + used_as[Semantics.KitchenAppliance] = { + appliances.DishwasherFactory, + appliances.OvenFactory, + appliances.BeverageFridgeFactory, + appliances.MicrowaveFactory + } + + used_as[Semantics.KitchenCounter] = { + shelves.KitchenSpaceFactory, + shelves.KitchenIslandFactory, + } + + used_as[Semantics.Bed] = { + seating.BedFactory, + } + + used_as[Semantics.Furniture] = set().union( + used_as[Semantics.Storage], + used_as[Semantics.Table], + used_as[Semantics.Seating], + used_as[Semantics.KitchenCounter], + used_as[Semantics.KitchenAppliance], + used_as[Semantics.Bed], + { + bathroom.StandingSinkFactory, + bathroom.ToiletFactory, + bathroom.BathtubFactory, + + seating.SofaFactory, + shelves.TVStandFactory, + } + ) + + # endregion furniture + + used_as[Semantics.WallDecoration] = { + wall_decorations.WallArtFactory, + wall_decorations.MirrorFactory, + wall_decorations.BalloonFactory + } + + used_as[Semantics.Door] = { + elements.doors.GlassPanelDoorFactory, + elements.doors.LiteDoorFactory, + elements.doors.LouverDoorFactory, + elements.doors.PanelDoorFactory, + } + + used_as[Semantics.Window] = { + windows.WindowFactory + } + + used_as[Semantics.CeilingLight] = { + lighting.CeilingLightFactory, + } + + used_as[Semantics.Lighting] = set().union( + used_as[Semantics.CeilingLight], + { + lighting.LampFactory, + lighting.FloorLampFactory, + lighting.DeskLampFactory, + } + ) + + used_as[Semantics.Object] = set().union( + + used_as[Semantics.Furniture], + used_as[Semantics.Sink], + used_as[Semantics.Door], + used_as[Semantics.Window], + used_as[Semantics.WallDecoration], + used_as[Semantics.HandheldItem], + used_as[Semantics.Lighting], + { + tableware.PlantContainerFactory, + tableware.LargePlantContainerFactory, + decor.AquariumTankFactory, + + appliances.TVFactory, + appliances.MonitorFactory, + + elements.RugFactory, + bathroom.HardwareFactory, + } + ) + + # region Extra metadata about assets + # TODO be move outside of the semantics heirarchy and into separate AssetFactory.metadata classvar + + used_as[Semantics.RealPlaceholder] = { + appliances.MonitorFactory, + appliances.TVFactory, + + bathroom.BathroomSinkFactory, + bathroom.StandingSinkFactory, + bathroom.ToiletFactory, + + decor.AquariumTankFactory, + elements.RackFactory, + elements.RugFactory, + + seating.BedFrameFactory, + seating.BedFactory, + seating.ChairFactory, + + shelves.KitchenSpaceFactory, + + tables.TableCocktailFactory, + + table_decorations.BookColumnFactory, + table_decorations.BookFactory, + table_decorations.BookStackFactory, + table_decorations.SinkFactory, + + tableware.BowlFactory, + tableware.FoodBoxFactory, + tableware.FruitContainerFactory, + tableware.LargePlantContainerFactory, + tableware.PlantContainerFactory, + tableware.PotFactory, + + wall_decorations.BalloonFactory, + wall_decorations.MirrorFactory, + wall_decorations.WallArtFactory, + shelves.SingleCabinetFactory, + shelves.KitchenCabinetFactory, + shelves.CellShelfFactory, + elements.NatureShelfTrinketsFactory + } + + used_as[Semantics.AssetAsPlaceholder] = set() + + used_as[Semantics.AssetPlaceholderForChildren] = { + shelves.SimpleBookcaseFactory, + shelves.CellShelfFactory, + shelves.SingleCabinetFactory, + shelves.KitchenCabinetFactory, + shelves.LargeShelfFactory, + table_decorations.SinkFactory, + tables.TableCocktailFactory + } + + used_as[Semantics.PlaceholderBBox] = { + seating.SofaFactory, + appliances.OvenFactory, + } + + used_as[Semantics.SingleGenerator] = set().union( + used_as[Semantics.Dishware], + used_as[Semantics.Utensils], + { + lighting.CeilingLightFactory, + lighting.CeilingClassicLampFactory, + seating.ChairFactory, + seating.BarChairFactory, + seating.OfficeChairFactory + } + ).difference({ + tableware.CupFactory + }) + + used_as[Semantics.NoRotation] = set().union( + used_as[Semantics.WallDecoration], + { + bathroom.HardwareFactory, + lighting.CeilingLightFactory, # rotationally symetric + } + ) + + used_as[Semantics.NoCollision] = { + elements.RugFactory, + } + + used_as[Semantics.NoChildren] = { + elements.RugFactory, + wall_decorations.MirrorFactory, + wall_decorations.WallArtFactory, + lighting.CeilingLightFactory, + } + + # endregion + + return used_as \ No newline at end of file diff --git a/infinigen_examples/indoor_constraint_examples.py b/infinigen_examples/indoor_constraint_examples.py new file mode 100644 index 000000000..cda7d78b3 --- /dev/null +++ b/infinigen_examples/indoor_constraint_examples.py @@ -0,0 +1,807 @@ +# 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 + +from collections import OrderedDict + +import numpy as np +import random +from numpy.random import uniform, normal, randint + +import infinigen +import gin + +from infinigen.assets import ( + appliances, + bathroom, + decor, + elements, + lighting, + seating, + shelves, + table_decorations, + tables, + tableware, + wall_decorations, + windows, + clothes +) + +from infinigen.core.constraints import ( + constraint_language as cl, + example_solver, + usage_lookup +) + +from infinigen import assets + +from infinigen.core.util.math import clip_gaussian +from infinigen.core.tags import Semantics, Subpart, FromGenerator + +from .indoor_asset_semantics import home_asset_usage +from .util import constraint_util as cu + +def sample_home_constraint_params(): + return dict( + has_tv = uniform() < 0.5, + has_aquarium_tank = uniform() < 0.15, + has_birthday_balloons = uniform() < 0.15, + has_cocktail_tables = uniform() < 0.15, + has_kitchen_barstools = uniform() < 0.15, + ) + +def home_constraints(): + + """Construct a constraint graph which incentivizes realistic home layouts. + + Result will contain both hard constraints (`constraints`) and soft constraints (`score_terms`). + + Notes for developers: + - This function is typically evaluated ONCE. It is not called repeatedly during the optimization process. + - To debug values you will need to inject print statements into impl_bindings.py or evaluate.py. Better debugging tools will come soon. + - Similarly, most `lambda:` statements below will only be evaluated once to construct the graph - do not assume they will be re-evaluated during optimization. + - Available constraint options are in `infinigen/core/constraints/constraint_language/__init__.py`. + - You can easily add new constraint functions by adding them here, and defining evaluator functions for them in `impl_bindings.py` + - Using newly added constraint types as hard constraints may be rejected by our hard constraint solver + - It is quite easy to specify an impossible constraint program, or one that our solver cannot solve: + - By default, failing to solve the program correctly is just printed as a warning, and we still return the scene. + - You can cause failed optimization results to crash instead using `-p solve_objects.abort_unsatisfied=True` in the command line. + - More documentation coming soon, and feel free to ask questions on Github Issues! + + """ + + used_as = home_asset_usage() + usage_lookup.initialize_from_dict(used_as) + + rooms = cl.scene()[{Semantics.Room, -Semantics.Object}] + obj = cl.scene()[{Semantics.Object, -Semantics.Room}] + + cutters = cl.scene()[Semantics.Cutter] + window = cutters[Semantics.Window] + doors = cutters[Semantics.Door] + + constraints = OrderedDict() + score_terms = OrderedDict() + + #region overall fullness + + furniture = obj[Semantics.Furniture].related_to(rooms, cu.on_floor) + wallfurn = furniture.related_to(rooms, cu.against_wall) + storage = wallfurn[Semantics.Storage] + + params = sample_home_constraint_params() + + for k, v in params.items(): + print(f"{home_constraints.__name__} params - {k}: {v}") + + score_terms['fullness'] = rooms.sum(lambda r: ( + obj.count().maximize(weight=4) # TODO re-incorporate more precise fullness scores above + + obj.volume().maximize(weight=1) + )) + + #endregion + + #region furniture + + score_terms['furniture_aesthetics'] = wallfurn.sum(lambda t: ( + t.distance(wallfurn).hinge(0.2, 0.6).maximize(weight=0.6) + + cl.accessibility_cost(t, furniture).minimize(weight=5) + + cl.accessibility_cost(t, rooms).minimize(weight=10) + )) + + + constraints['storage'] = rooms.all(lambda r: ( + storage.related_to(r).count().in_range(1, 7) + )) + score_terms['storage'] = rooms.sum(lambda r: ( + cl.accessibility_cost(storage.related_to(r), furniture.related_to(r), dist=0.5).minimize(weight=5) + + cl.accessibility_cost(storage.related_to(r), r, dist=0.5).minimize(weight=5) + )) + + #endregion furntiure + + score_terms['portal_accessibility'] = ( + # make sure the fronts of objects are accessible where applicable + + #### disabled since its generally fine to block floor-to-ceiling windows a little + #window.sum(lambda t: ( + # cl.accessibility_cost(t, furniture, np.array([0, -1, 0])) + #)).minimize(weight=2) + + + doors.sum(lambda t: ( + cl.accessibility_cost(t, furniture, cu.front_dir, dist=4) + + cl.accessibility_cost(t, furniture, cu.back_dir, dist=4) + )).minimize(weight=5) + ) + + #region WALL/FLOOR COVERINGS + walldec = obj[Semantics.WallDecoration].related_to(rooms, cu.flush_wall) + wall_art = walldec[wall_decorations.WallArtFactory] + mirror = walldec[wall_decorations.MirrorFactory] + rugs = obj[elements.RugFactory].related_to(rooms, cu.on_floor) + + constraints['rugs'] = rooms.all(lambda r: ( + rugs.related_to(r).distance(rugs) >= 1 + )) + + score_terms['rugs'] = rooms.all(lambda r: ( + cl.center_stable_surface_dist(rugs.related_to(r)).minimize(weight=1) + )) + + vertical_diff = lambda o, r: (o.distance(r, cu.floortags) - o.distance(r, cu.ceilingtags)).abs() + + constraints['wall_decorations'] = rooms.all(lambda r: ( + wall_art.related_to(r).count().in_range(0, 6) + * mirror.related_to(r).count().in_range(0, 1) + * walldec.related_to(r).all(lambda t: t.distance(r, cu.floortags) > 0.6) + #walldec.all(lambda t: ( + # (vertical_diff(t, r).abs() < 1.5) * + # (t.distance(cutters) > 0.1) + #)) + )) + score_terms['wall_decorations'] = rooms.sum(lambda r: ( + + walldec.related_to(r).sum(lambda w: ( + + vertical_diff(w, r).abs().minimize(weight=1) + + w.distance(walldec).maximize(weight=1) + + w.distance(window).hinge(0.25, 10).maximize(weight=1) + + + cl.angle_alignment_cost(w, r, cu.floortags).minimize(weight=5) + + cl.accessibility_cost(w, furniture, dist=1).minimize(weight=5) + + cl.center_stable_surface_dist(w).minimize(weight=1) + )) + )) + + score_terms['floor_covering'] = ( + rugs.sum(lambda rug: ( + rug.distance(rooms, cu.walltags).maximize(weight=3) + + cl.angle_alignment_cost(rug, rooms, cu.walltags).minimize(weight=3) + )) + ) + #endregion + + #region PLANTS + small_plants = obj[tableware.PlantContainerFactory].related_to(storage, cu.ontop) + big_plants = ( + obj[tableware.LargePlantContainerFactory] + .related_to(rooms, cu.on_floor) + .related_to(rooms, cu.against_wall) + ) + constraints['plants'] = rooms.all(lambda r: ( + big_plants.related_to(r).count().in_range(0, 1) * + small_plants.related_to(storage.related_to(r)).count().in_range(0, 5) + )) + score_terms['plants'] = rooms.sum(lambda r: ( + + big_plants.related_to(r).sum(lambda p: p.distance(doors)).maximize(weight=5) + + + ( # small plants should be near window for sunlight + small_plants + .related_to(storage.related_to(r)) + .sum(lambda p: p.distance(window.related_to(r))) + ).minimize(weight=1) + )) + #endregion + + #region SIDETABLE + sidetable = furniture[Semantics.SideTable].related_to(furniture, cu.side_by_side) + + score_terms['sidetable'] = rooms.sum(lambda r: ( + sidetable.related_to(r).sum(lambda t: ( + t.distance(r, cu.walltags).minimize(weight=1) + )) + )) + #endregion + + #region DESKS + desks = wallfurn[shelves.SimpleDeskFactory] + deskchair = furniture[seating.OfficeChairFactory].related_to(desks, cu.front_against) + monitors = obj[appliances.MonitorFactory] + constraints['desk'] = rooms.all(lambda r: ( + desks.related_to(r).all(lambda t: ( + deskchair.related_to(r).related_to(t).count().in_range(0, 1) * + monitors.related_to(t, cu.ontop).count().equals(1) * + (obj[Semantics.OfficeShelfItem].related_to(t, cu.on).count() >= 0) * + (deskchair.related_to(r).related_to(t).count() == 1) + )) + )) + + score_terms['desk'] = rooms.sum(lambda r: desks.sum(lambda d: ( + + obj.related_to(d).count().maximize(weight=3) + + + d.distance(doors.related_to(r)).maximize(weight=0.1) + + + cl.accessibility_cost(d, furniture.related_to(r)).minimize(weight=3) + + cl.accessibility_cost(d, r).minimize(weight=3) + + + monitors.related_to(d).sum(lambda m: ( + cl.accessibility_cost(m, r, dist=2).minimize(weight=3) + + cl.accessibility_cost(m, obj.related_to(r), dist=0.5).minimize(weight=3) + + m.distance(r, cu.walltags).hinge(0.1, 1e7).minimize(weight=1) + )) + + + deskchair.distance(rooms, cu.walltags).maximize(weight=1) + ))) + + #endregion + + #region ALL LIGHTING RULES + + lights = obj[Semantics.Lighting] + floor_lamps = lights[lighting.FloorLampFactory].related_to(rooms, cu.on_floor).related_to(rooms, cu.against_wall) + #constraints['lighting'] = rooms.all(lambda r: ( + # # dont put redundant lights close to eachother (including lamps, ceiling lights, etc) + # lights.related_to(r).all(lambda l: l.distance(lights.related_to(r)) >= 2) + #)) + + #endregion + + #region CEILING LIGHTS + ceillights = lights[lighting.CeilingLightFactory] + + constraints['ceiling_lights'] = rooms.all(lambda r: ( + ceillights.related_to(r, cu.hanging).count().in_range(1, 4) + )) + score_terms['ceiling_lights'] = rooms.sum(lambda r: ( + (ceillights.count() / r.volume(dims=2)).hinge(0.08, 0.15).minimize(weight=5) + + ceillights.mean(lambda t: ( + t.distance(r, cu.walltags).pow(0.5) * 1.5 + + t.distance(ceillights).pow(0.2) * 2 + )).maximize(weight=1) + )) + #endregion + + #region LAMPS + lamps = lights[lighting.DeskLampFactory].related_to(furniture, cu.ontop) + constraints['lamps'] = rooms.all(lambda r: ( + + # allow 0-2 lamps per room, placed on any sensible object + lamps.related_to(storage.related_to(r)).count().in_range(0, 2) + #* lamps.related_to(sidetable.related_to(r)).count().in_range(0, 2) + #* lamps.related_to(desks.related_to(r, cu.on), cu.ontop).count().in_range(0, 1) + + * ( # pull-string lamps look extremely unnatural when too far off the ground + lamps.related_to(storage.related_to(r)) + .all(lambda l: + l.distance(r, cu.floortags).in_range(0.5, 1.5) + ) + ) + + )) + + score_terms['lamps'] = lamps.sum(lambda l: ( + cl.center_stable_surface_dist(l.related_to(sidetable)).minimize(weight=1) + + l.distance(lamps).maximize(weight=1) + )) + #endregion + + # region CLOSETS + closets = rooms[Semantics.Closet].excludes(cu.room_types) + constraints['closets'] = closets.all(lambda r: ( + (storage.related_to(r).count() >= 1) * + ceillights.related_to(r, cu.hanging).count().in_range(0, 1) * + (walldec.related_to(r).count() == 0) # special case exclusion - no paintings etc in closets + )) + score_terms['closets'] = closets.all(lambda r: ( + storage.related_to(r).count().maximize(weight=2) * + obj.related_to(storage.related_to(r)).count().maximize(weight=2) + )) + + # NOTE: closets also have special-case behavior below depending on what room they are adjacent to + # endregion + + #region BEDROOMS + bedrooms = rooms[Semantics.Bedroom].excludes(cu.room_types) + beds = wallfurn[Semantics.Bed][seating.BedFactory] + constraints['bedroom'] = bedrooms.all(lambda r: ( + + beds.related_to(r).count().in_range(1, 2) * + + ( + sidetable.related_to(r) + .related_to(beds.related_to(r), cu.leftright_leftright) + .count().in_range(0, 2) + ) * + + rugs.related_to(r).count().in_range(0, 2) * + + desks.related_to(r).count().in_range(0, 1) * + storage.related_to(r).count().in_range(2, 5) * + + floor_lamps.related_to(r).count().in_range(0, 1) * + + storage.related_to(r).all(lambda s: ( + (obj[Semantics.OfficeShelfItem].related_to(s, cu.on).count() >= 0) + )) + )) + + score_terms['bedroom'] = bedrooms.sum(lambda r: ( + beds.related_to(r).count().maximize(weight=3) + + beds.related_to(r).sum(lambda t: cl.distance(r, doors)).maximize(weight=0.5) + + sidetable.related_to(r).sum(lambda t: t.distance(beds.related_to(r))).minimize(weight=3) + )) + + #endregion + + #region KITCHENS + kitchens = rooms[Semantics.Kitchen].excludes(cu.room_types) + + countertops = furniture[Semantics.KitchenCounter] + wallcounter = countertops[shelves.KitchenSpaceFactory].related_to(rooms, cu.against_wall) + island = countertops[shelves.KitchenIslandFactory] + barchairs = furniture[seating.BarChairFactory] + + constraints['kitchen_counters'] = kitchens.all(lambda r: ( + wallcounter.related_to(r).count().in_range(1, 2) * + island.related_to(r).count().in_range(0, 1) + )) + + if params['has_kitchen_barstools']: + constraints['kitchen_barchairs'] = kitchens.all(lambda r: ( + barchairs.related_to(island.related_to(r), cu.front_against).count().in_range(0, 4) + )) + + score_terms['kitchen_counters'] = kitchens.sum(lambda r: ( + + # try to fill 40-60% of kitchen floorplan with countertops (additive with typical furniture incentive) + ( + countertops.related_to(r).volume(dims=2) + / r.volume(dims=2).clamp_min(1) # avoid div by 0 + ).hinge(0.4, 0.6).minimize(weight=10) + + + # cluster countertops together + countertops.related_to(r).sum( + lambda c: countertops.related_to(r).mean(lambda c2: + c.distance(c2) + ) + ).minimize(weight=3) + + )) + + constraints['kitchen_island_placement'] = kitchens.all(lambda r: + wallcounter.related_to(r).all(lambda t: ( + t.distance(island.related_to(r)).in_range(0.7, 3) + )) * + island.related_to(r).all(lambda t: ( + t.distance(wallcounter.related_to(r)).in_range(0.7, 3) * + (t.distance(r, cu.walltags) > 2) + )) + ) + + score_terms['kitchen_island_placement'] = kitchens.sum(lambda r: ( + island.sum(lambda t: ( + cl.angle_alignment_cost(t, wallcounter) + + cl.angle_alignment_cost(t, r, cu.walltags) + )).minimize(weight=1) + + island.distance(r, cu.walltags).hinge(3, 1e7).minimize(weight=10) + + wallcounter.sum(lambda t: + cl.focus_score(t, island.related_to(r)).minimize(weight=5) + ) + )) + + sink_flush_on_counter = cl.StableAgainst(cu.bottom, {Subpart.SupportSurface}, margin=0.001) + sink_against_wall = cl.StableAgainst(cu.back, cu.walltags, margin=0.1) + kitchen_sink = ( + obj[Semantics.Sink][table_decorations.SinkFactory] + .related_to(countertops, sink_flush_on_counter) + ) + constraints['kitchen_sink'] = kitchens.all(lambda r: ( + + # those sinks can be on either type of counter + kitchen_sink.related_to(wallcounter.related_to(r)).count().in_range(0, 1) + * kitchen_sink.related_to(island.related_to(r)).count().in_range(0, 1) # island sinks dont need to be against wall + + * countertops.related_to(r).all(lambda c: ( + kitchen_sink.related_to(c).all( + lambda s: s.distance(c, cu.side).in_range(0.05, 0.2) + ) + )) + )) + + score_terms['kitchen_sink'] = kitchens.sum(lambda r: ( + + countertops.sum(lambda c: kitchen_sink.related_to(c).sum(lambda s: ( + (s.volume(dims=2) / c.volume(dims=2)).hinge(0.2, 0.4).minimize(weight=10) + ))) + + + island.related_to(r).sum(lambda isl:( # sinks on islands must be near to edge and oriented outwards + kitchen_sink.related_to(isl).sum(lambda s: ( + cl.angle_alignment_cost(s, isl, cu.side).minimize(weight=10) + + cl.distance(s, isl, cu.side).hinge(0.05, 0.07).minimize(weight=10) + )) + )) + + )) + + kitchen_appliances = obj[Semantics.KitchenAppliance] + kitchen_appliances_big = kitchen_appliances.related_to(kitchens, cu.on_floor).related_to(kitchens, cu.against_wall) + microwaves = kitchen_appliances[appliances.MicrowaveFactory].related_to(wallcounter, cu.on) + + constraints['kitchen_appliance'] = kitchens.all(lambda r: ( + + kitchen_appliances_big[appliances.DishwasherFactory].related_to(r).count().in_range(0, 1) + * kitchen_appliances_big[appliances.BeverageFridgeFactory].related_to(r).count().in_range(0, 1) + * (kitchen_appliances_big[appliances.OvenFactory].related_to(r).count() == 1) + + * (wallfurn[shelves.KitchenCabinetFactory].related_to(r).count() >= 0) + + * (microwaves.related_to(wallcounter.related_to(r)).count().in_range(0, 1)) + )) + + score_terms['kitchen_appliance'] = kitchens.sum(lambda r: ( + kitchen_appliances.sum(lambda t: ( + t.distance(wallcounter.related_to(r)).minimize(weight=1) + + cl.accessibility_cost(t, r, dist=1).minimize(weight=10) + + cl.accessibility_cost(t, furniture.related_to(r), dist=1).minimize(weight=10) + + t.distance(island.related_to(r)).hinge(0.7, 1e7).minimize(weight=10) + )) + )) + + obj_on_counter = lambda r: obj.related_to(countertops.related_to(r), cu.on) + constraints['kitchen_objects'] = kitchens.all(lambda r: ( + + (obj_on_counter(r)[Semantics.KitchenCounterItem].count() >= 0) + + * (obj[Semantics.FoodPantryItem].related_to(storage.related_to(r), cu.on).count() >= 0) + + * island.related_to(r).all(lambda t: ( + obj[Semantics.TableDisplayItem].related_to(t, cu.ontop).count().in_range(0, 4) + )) + )) + + score_terms['kitchen_objects'] = kitchens.sum(lambda r: ( + ( + obj.related_to(wallcounter, cu.on) + .sum(lambda t: t.distance(r, cu.walltags)) + .minimize(weight=3) + ) + + cl.center_stable_surface_dist( + obj.related_to(island.related_to(r), cu.ontop) + ).minimize(weight=1) + )) + + # disabled for now bc tertiary + #constraints['kitchen_appliance_objects'] = kitchens.all(lambda r: ( + # wallfurn[appliances.DishwasherFactory].related_to(r).all(lambda r: ( + # (obj[Semantics.Cookware].related_to(r, cu.on).count() >= 0) * + # (obj[Semantics.Dishware].related_to(r, cu.on).count() >= 0 + # )) * + # wallfurn[appliances.OvenFactory].related_to(r).all(lambda r: ( + # (obj[Semantics.Cookware].related_to(r, cu.on).count() >= 0) + # )) + #))) + + closet_kitchen = closets.related_to(kitchens, cl.RoomNeighbour()) + constraints['closet_kitchen'] = closet_kitchen.all(lambda r: ( + obj[Semantics.FoodPantryItem].related_to(storage.related_to(r), cu.on).count() >= 0 + )) + score_terms['closet_kitchen'] = closet_kitchen.sum(lambda r: ( + storage.related_to(r).count().maximize(weight=2) + + obj[Semantics.FoodPantryItem].related_to(storage.related_to(r), cu.on).count().maximize(weight=5) + )) + + #score_terms['kitchen_table'] # todo diningtable or hightop + + #endregion + + #region LIVINGROOMS + + livingrooms = rooms[Semantics.LivingRoom].excludes(cu.room_types) + sofas = furniture[seating.SofaFactory] + tvstands = wallfurn[shelves.TVStandFactory] + coffeetables = furniture[tables.CoffeeTableFactory] + + sofa_back_near_wall = cl.StableAgainst(cu.back, cu.walltags, margin=uniform(0.1, 0.3)) + sofa_side_near_wall = cl.StableAgainst(cu.side, cu.walltags, margin=uniform(0.1, 0.3)) + freestanding = lambda o, r: ( + o + .related_to(r) + .related_to(r, -sofa_back_near_wall) + #.related_to(r, -cu.side_against_wall) + ) + + constraints['sofa'] = livingrooms.all(lambda r: ( + #sofas.related_to(r).count().in_range(2, 3) + sofas.related_to(r, sofa_back_near_wall).count().in_range(2, 4) + #* sofas.related_to(r, sofa_side_near_wall).count().in_range(0, 1) + + * freestanding(sofas, r).all(lambda t: ( # frustrum infront of freestanding sofa must directly contain tvstand + cl.accessibility_cost(t, tvstands.related_to(r), dist=3) > 0.7 + )) + + * sofas.all(lambda t: ( + cl.accessibility_cost(t, furniture.related_to(r), dist=2).in_range(0, 0.5) + * cl.accessibility_cost(t, r, dist=1).in_range(0, 0.5) + )) + + #* ( # allow a storage object behind non-wall sofas + # storage.related_to(r) + # .related_to(freestanding(sofas, r)) + # .count().in_range(0, 1) + #) + )) + + constraints['sofa_positioning'] = rooms.all(lambda r: (sofas.all(lambda s: ( + (cl.accessibility_cost(s, rooms, dist=3) < 0.5) + * (cl.focus_score(s, tvstands.related_to(r)) > 0.5) # must face or perpendicular to TVStand + )))) + + score_terms['sofa'] = livingrooms.sum(lambda r: ( + + sofas.volume().maximize(weight=10) + + + sofas.related_to(r).sum(lambda t: ( + + t.distance(sofas.related_to(r)).hinge(0, 1).minimize(weight=1) + + t.distance(tvstands.related_to(r)).hinge(2, 3).minimize(weight=5) + + + cl.focus_score(t, tvstands.related_to(r)).maximize(weight=5) + + cl.angle_alignment_cost(t, tvstands.related_to(r), cu.front).minimize(weight=1) + + cl.focus_score(t, coffeetables.related_to(r)).maximize(weight=2) + + + cl.accessibility_cost(t, r, dist=3).minimize(weight=3) + )) + + + freestanding(sofas, r).sum(lambda t: ( + cl.angle_alignment_cost(t, tvstands.related_to(r)).minimize(weight=5) + + cl.angle_alignment_cost(t, r, cu.walltags).minimize(weight=3) + + cl.center_stable_surface_dist(t).minimize(weight=0.5) + )) + )) + + tvs = obj[appliances.TVFactory].related_to(tvstands, cu.ontop) + + if params['has_tv']: + constraints['tv'] = livingrooms.all(lambda r: ( + tvstands.related_to(r).all(lambda t: ( + (tvs.related_to(t).count() == 1) + + * tvs.related_to(t).all(lambda tv: + cl.accessibility_cost(tv, r, dist=1).in_range(0, 0.1) + ) + )) + )) + + score_terms['tvstand'] = rooms.all(lambda r: (tvstands.sum(lambda stand: ( + tvs.related_to(stand).volume().maximize(weight=1) + + + stand.distance(window).maximize(weight=1) # penalize being very close to window. avoids tv blocking window. + + cl.accessibility_cost(stand, furniture).minimize(weight=3) + + + cl.center_stable_surface_dist(stand).minimize(weight=5) # center tvstand against wall (also tries to do vertical & floor but those are constrained) + + cl.center_stable_surface_dist(tvs.related_to(stand)).minimize(weight=1) + )))) + + constraints['livingroom'] = livingrooms.all(lambda r: ( + storage.related_to(r).count().in_range(1, 5) + + * tvstands.related_to(r).count().equals(1) + + * ( # allow sidetables next to any sofa + sidetable.related_to(r) + .related_to(sofas.related_to(r), cu.side_by_side) + .count().in_range(0, 2) + ) + + * desks.related_to(r).count().in_range(0, 1) + * coffeetables.related_to(r).count().in_range(0, 1) + * coffeetables.related_to(r).all(lambda t: ( + (obj[Semantics.OfficeShelfItem].related_to(t, cu.on).count().in_range(0, 3)) + )) + + * ( + rugs + .related_to(r) + #.related_to(furniture.related_to(r), cu.side_by_side) + .count().in_range(0, 2) + ) + )) + + score_terms['livingroom'] = livingrooms.sum(lambda r: ( + + coffeetables.related_to(r).sum(lambda t: ( + + # ideal coffeetable-to-tv distance according to google + t.distance(sofas.related_to(r)).hinge(0.45, 0.6).minimize(weight=5) + + + cl.angle_alignment_cost(t, sofas.related_to(r), cu.front).minimize(weight=5) + + cl.focus_score(sofas.related_to(r), t).maximize(weight=5) + )) + )) + + + constraints['livingroom_objects'] = livingrooms.all(lambda r: ( + storage.all(lambda t: ( + (obj[Semantics.OfficeShelfItem].related_to(t, cu.on).count() >= 0) + )) * + coffeetables.all(lambda t: ( + obj[Semantics.TableDisplayItem].related_to(t, cu.ontop).count().in_range(0, 1) * + (obj[Semantics.OfficeShelfItem].related_to(t, cu.on).count() >= 0) + )) + )) + + #endregion + + #region DININGROOMS + + diningtables = furniture[Semantics.Table][tables.TableDiningFactory] + diningchairs = furniture[Semantics.Chair][seating.ChairFactory] + constraints['dining_chairs'] = rooms.all(lambda r: ( + diningtables.related_to(r).all(lambda t: ( + diningchairs.related_to(r).related_to(t, cu.front_against).count().in_range(3, 6) + )) + )) + + score_terms['dining_chairs'] = rooms.all(lambda r: ( + diningchairs.related_to(r).count().maximize(weight=5) + + diningchairs.related_to(r).sum(lambda t: t.distance(diningchairs.related_to(r))).maximize(weight=3) + #cl.reflectional_asymmetry(diningchairs.related_to(r), diningtables.related_to(r)).minimize(weight=1) + #cl.rotational_asymmetry(diningchairs.related_to(r)).minimize(weight=1) + )) + + constraints['dining_table_objects'] = rooms.all(lambda r: ( + diningtables.related_to(r).all(lambda t: ( + obj[Semantics.TableDisplayItem].related_to(t, cu.ontop).count().in_range(0, 2) * + (obj[Semantics.Utensils].related_to(t, cu.ontop).count() >= 0) * + (obj[Semantics.Dishware].related_to(t, cu.ontop).count().in_range(0, 2)) + )) + )) + + score_terms['dining_table_objects'] = rooms.sum(lambda r: ( + cl.center_stable_surface_dist( + obj[Semantics.TableDisplayItem] + .related_to(diningtables.related_to(r), cu.ontop) + ).minimize(weight=1) + )) + + diningrooms = rooms[Semantics.DiningRoom].excludes(cu.room_types) + constraints['diningroom'] = diningrooms.all(lambda r: ( + (diningtables.related_to(r).count() == 1) * + storage.related_to(r).all(lambda t: ( + (obj[Semantics.Dishware].related_to(t, cu.on).count() >= 0) * + (obj[Semantics.OfficeShelfItem].related_to(t, cu.on).count().in_range(0, 5)) + )) + )) + score_terms['diningroom'] = diningrooms.sum(lambda r: ( + + diningtables.related_to(r).distance(r, cu.walltags).maximize(weight=10) + + cl.angle_alignment_cost(diningtables.related_to(r), r, cu.walltags).minimize(weight=10) + + cl.center_stable_surface_dist(diningtables.related_to(r)).minimize(weight=1) + )) + #endregion + + #region BATHROOMS + bathrooms = rooms[Semantics.Bathroom].excludes(cu.room_types) + toilet = wallfurn[bathroom.ToiletFactory] + bathtub = wallfurn[bathroom.BathtubFactory] + sink = wallfurn[bathroom.StandingSinkFactory] + hardware = obj[bathroom.HardwareFactory].related_to(bathrooms, cu.against_wall) + constraints['bathroom'] = bathrooms.all(lambda r: ( + + mirror.related_to(r).related_to(r, cu.flush_wall).count().equals(1) * + sink.related_to(r).count().equals(1) * + toilet.related_to(r).count().equals(1) * + + storage.related_to(r).all(lambda t: ( + (obj[Semantics.BathroomItem].related_to(t, cu.on).count() >= 0) + )) + )) + + score_terms['toilet'] = rooms.all(lambda r: ( + toilet.distance(doors).maximize(weight=1) + + toilet.distance(furniture).maximize(weight=1) + + toilet.distance(sink).maximize(weight=1) + + cl.accessibility_cost(toilet, furniture, dist=2).minimize(weight=10) + )) + + constraints['bathtub'] = bathrooms.all(lambda r: ( + bathtub.related_to(r).count().in_range(0, 1) * + hardware.related_to(r).count().in_range(1, 4) + )) + score_terms['bathtub'] = bathrooms.all(lambda r: ( + + bathtub.sum(lambda t: t.distance(hardware)).minimize(weight=0.2) + + sink.sum(lambda t: t.distance(hardware)).minimize(weight=0.2) + + + hardware.sum(lambda t: ( + t.distance(rooms, cu.floortags) + .hinge(0.5, 1) + .minimize(weight=15) + )) + + )) + + score_terms['bathroom'] = ( + mirror.related_to(bathrooms).distance(sink).minimize(weight=0.2) + + cl.accessibility_cost(mirror, furniture, cu.down_dir).maximize(weight=3) + ) + #endregion + + #region MISC OBJECTS + + if params['has_aquarium_tank']: + + aqtank = lambda r: obj[decor.AquariumTankFactory].related_to(storage.related_to(r), cu.ontop) + + constraints['aquarium_tank'] = ( + aqtank(rooms).count().in_range(0, 1) + ) + score_terms['aquarium_tank'] = rooms.all(lambda r: ( + aqtank(r).distance(r, cu.walltags).hinge(0.05, 0.1).minimize(weight=1) + )) + + if params['has_birthday_balloons']: + balloons = obj[wall_decorations.BalloonFactory].related_to(rooms, cu.against_wall) + constraints['birthday_balloons'] = ( + balloons.related_to(rooms, cu.against_wall).count().in_range(0, 3) + ) + score_terms['birthday_balloons'] = rooms.all(lambda r: ( + balloons.sum(lambda b: b.distance(r, cu.floortags).hinge(1.6, 2.5).minimize(weight=1)) + )) + + if params['has_cocktail_tables']: + + cocktail_table = ( + furniture[tables.TableCocktailFactory] + .related_to(rooms, cu.on_floor) + .related_to(rooms, cu.against_wall) + ) + + constraints['cocktail_tables'] = diningrooms.all(lambda r: ( + cocktail_table.related_to(r).count().in_range(0, 3) + *( + barchairs.related_to(cocktail_table.related_to(r), cu.front_against) + .count().in_range(0, 4) + ) + * ( + obj[tableware.WineglassFactory] + .related_to(cocktail_table.related_to(r), cu.ontop) + .count().in_range(0, 4) + ) + )) + score_terms['cocktail_tables'] = diningrooms.sum(lambda r: ( + cocktail_table.related_to(r).sum(lambda t: ( + + t.distance(r, cu.walltags).hinge(0.5, 1).minimize(weight=1) + + t.distance(cocktail_table.related_to(r)).hinge(1, 2).minimize(weight=1) + + + barchairs.related_to(t).sum( + lambda c: c.distance(barchairs.related_to(t)) + ).maximize(weight=1) + )) + )) + + #endregion + + return cl.Problem( + constraints=constraints, + score_terms=score_terms, + ) + +all_constraint_funcs = [ + home_constraints +] diff --git a/infinigen_examples/util/constraint_util.py b/infinigen_examples/util/constraint_util.py new file mode 100644 index 000000000..5345a586a --- /dev/null +++ b/infinigen_examples/util/constraint_util.py @@ -0,0 +1,63 @@ +# 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 numpy as np + +from infinigen.core import tags as t +from infinigen import assets as a +from infinigen.core.constraints import ( + constraint_language as cl, + example_solver, + usage_lookup +) + +room_types = { + t.Semantics.Kitchen, + t.Semantics.Bedroom, + t.Semantics.LivingRoom, + t.Semantics.Closet, + t.Semantics.Hallway, + t.Semantics.Bathroom, + t.Semantics.Garage, + t.Semantics.Balcony, + t.Semantics.DiningRoom, + t.Semantics.Utility, + t.Semantics.Staircase, +} + +all_sides = {t.Subpart.Bottom, t.Subpart.Top, t.Subpart.Front, t.Subpart.Back} +walltags = {t.Subpart.Wall, t.Subpart.Visible, -t.Subpart.SupportSurface, -t.Subpart.Ceiling} +floortags = {t.Subpart.SupportSurface, t.Subpart.Visible, -t.Subpart.Wall, -t.Subpart.Ceiling} +ceilingtags = {t.Subpart.Visible, t.Subpart.Ceiling, -t.Subpart.Wall, -t.Subpart.SupportSurface} + +front_dir = np.array([0, 1, 0]) +back_dir = np.array([0, -1, 0]) +down_dir = np.array([0, 0, -1]) + +bottom = {t.Subpart.Bottom, -t.Subpart.Top, -t.Subpart.Front, -t.Subpart.Back} +back = {t.Subpart.Back, -t.Subpart.Top, -t.Subpart.Front} +top = {t.Subpart.Top, -t.Subpart.Back, -t.Subpart.Bottom, -t.Subpart.Front} +side = {-t.Subpart.Top, -t.Subpart.Bottom, -t.Subpart.Back, -t.Subpart.SupportSurface} +front = {t.Subpart.Front, -t.Subpart.Top, -t.Subpart.Bottom, -t.Subpart.Back} +leftright = {-t.Subpart.Top, -t.Subpart.Bottom, -t.Subpart.Back, -t.Subpart.Front, -t.Subpart.SupportSurface} + +on_floor = cl.StableAgainst(bottom, floortags, margin=0.01) +flush_wall = cl.StableAgainst(back, walltags, margin=0.02) +against_wall = cl.StableAgainst(back, walltags, margin=0.07) +spaced_wall = cl.StableAgainst(back, walltags, margin=0.8) +hanging = cl.StableAgainst(top, ceilingtags, margin=0.05) +side_against_wall = cl.StableAgainst(side, walltags, margin=0.05) + +ontop = cl.StableAgainst(bottom, top) +on = cl.StableAgainst(bottom, {t.Subpart.SupportSurface}) + +front_against = cl.StableAgainst(front, side, margin=0.05, check_z=False) #check_z=False +leftright_leftright = cl.StableAgainst(leftright, leftright, margin=0.05) +side_by_side = cl.StableAgainst(side, side) +back_to_back = cl.StableAgainst(back, back) + +variable_room = t.Variable('room') +variable_obj = t.Variable('obj') \ No newline at end of file diff --git a/infinigen_examples/util/generate_indoors_util.py b/infinigen_examples/util/generate_indoors_util.py new file mode 100644 index 000000000..ea41810f1 --- /dev/null +++ b/infinigen_examples/util/generate_indoors_util.py @@ -0,0 +1,265 @@ +# 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 logging +import typing + +import bpy +from mathutils import Vector + +import gin +import numpy as np +from numpy.random import uniform, normal, randint +import trimesh + +from infinigen.terrain import Terrain, hidden_in_viewport +from infinigen.terrain.utils import Mesh +from infinigen.assets.materials import invisible_to_camera + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r, + usage_lookup +) + +from infinigen.assets import weather +from infinigen.assets.scatters import grass, pebbles +from infinigen.core.placement import density, split_in_view +from infinigen.core.util import (blender as butil, pipeline) +from infinigen.core.util.camera import points_inview +from infinigen.core import tags as t + +from . import constraint_util as cu + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +def within_bbox_2d(verts, bbox): + return ( + (verts[:, 0] > bbox[0][0]) & + (verts[:, 0] < bbox[1][0]) & + (verts[:, 1] > bbox[0][1]) & + (verts[:, 1] < bbox[1][1]) + ) + +def create_outdoor_backdrop( + terrain: Terrain, + house_bbox: tuple, + cam, + p: pipeline.RandomStageExecutor, + params: dict +): + + all_vertices = [] + for name in terrain.terrain_objs: + if name not in hidden_in_viewport: + all_vertices.append(Mesh(obj=terrain.terrain_objs[name]).vertices) + + all_vertices = np.concatenate(all_vertices) + all_mask = within_bbox_2d(all_vertices, house_bbox) + + if not all_mask.any(): + height = 0 + else: + height = all_vertices[all_mask, 2].max() + + extra_zoff = uniform(0, 4) # deliberately float above the terrain. + height += extra_zoff + + for obj in terrain.terrain_objs.values(): + obj.location[2] -= height + butil.apply_transform(obj, loc=True) + + main_terrain = bpy.data.objects['OpaqueTerrain'] + verts = np.zeros(3 * len(main_terrain.data.vertices), float) + main_terrain.data.vertices.foreach_get('co', verts) + verts = verts.reshape(-1, 3) + mask = within_bbox_2d(verts, house_bbox) + + with butil.ViewportMode(main_terrain, mode='EDIT'): + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.mesh.select_all(action='DESELECT') + split_in_view.select_vertmask(main_terrain, mask) + with butil.ViewportMode(main_terrain, mode='EDIT'): + bpy.ops.mesh.select_more() + bpy.ops.mesh.delete(type='VERT') + + p.run_stage('fancy_clouds', weather.kole_clouds.add_kole_clouds) + + terrain_inview, *_ = split_in_view.split_inview(main_terrain, verbose=True, outofview=False, + print_areas=True, cam=cam, vis_margin=2, dist_max=params['near_distance'], hide_render=True, + suffix='inview') + + def add_grass(target): + select_max = params.get('grass_select_max', 0.5) + selection = density.placement_mask(normal_dir=(0, 0, 1), scale=0.1, return_scalar=True, + select_thresh=uniform(select_max / 2, select_max)) + grass.apply(target, selection=selection) + + p.run_stage('grass', add_grass, terrain_inview) + + def add_rocks(target): + selection = density.placement_mask(scale=0.15, select_thresh=0.5, normal_thresh=0.7, return_scalar=True) + _, rock_col = pebbles.apply(target, selection=selection) + return rock_col + + p.run_stage('rocks', add_rocks, terrain_inview) + return height + +def place_cam_overhead(cam: bpy.types.Object, bbox: tuple[np.array]): + + butil.spawn_point_cloud('place_cam_overhead', bbox) + + mins, maxs = bbox + cam.location = (maxs + mins) / 2 + cam.rotation_euler = (0, 0, 0) + for cam_dist in np.exp(np.linspace(-1., 5.5, 500)): + cam.location[-1] = cam_dist + bpy.context.view_layer.update() + inview = points_inview(bbox, cam.children[0]) + if inview.all(): + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + area.spaces.active.region_3d.view_perspective = 'CAMERA' + break + return + + +def overhead_view(cam, room_name): + room_name = room_name.split('.')[0] + + for o in bpy.data.objects: + if '.exterior' in o.name: + o.hide_viewport = True + o.hide_render = True + elif '.ceiling' in o.name: + invisible_to_camera.apply(o) + + floor = bpy.data.objects[room_name + '.floor'] + cam.location = floor.location + Vector((0, 0, 10)) + cam.rotation_euler = (0, 0, 0) + +def hide_other_rooms(state, rooms_split, keep_rooms: list[str]): + + for col in rooms_split.values(): + for o in col.objects: + if any( + roomname.split('.')[0] in o.name + for roomname in keep_rooms + ): + continue + o.hide_viewport = True + o.hide_render = True + + hide_cutters = [ + o + for k, os in state.objs.items() + if t.Semantics.Cutter in os.tags and not any( + rel.target_name == roomname + for rel in os.relations + for roomname in keep_rooms + ) + for o in butil.iter_object_tree(os.obj) + ] + for o in hide_cutters: + o.hide_render = True + o.hide_viewport = True + bpy.context.scene.render.film_transparent = True + +def apply_greedy_restriction( + stages: dict[str, r.Domain], + filter_tags: set[str], + var: t.Variable, + scope_domain: r.Domain = None, +): + filter_tags = t.to_tag_set(filter_tags, fac_context=usage_lookup._factory_lookup) + for k, d in stages.items(): + if scope_domain is not None and not d.intersects(scope_domain): + continue + stages[k], match = r.domain_tag_substitute(d, var, r.Domain(filter_tags).with_tags(var), return_match=True) + logger.info(f'{apply_greedy_restriction.__name__} restricting {k=} to {filter_tags=} for {var=}') + +@gin.configurable +def restrict_solving( + stages: dict[str, r.Domain], + problem: cl.Problem, + + # typically provided by gin + restrict_parent_rooms: set[str] = None, + restrict_parent_objs: set[str] = None, + restrict_child_primary: set[str] = None, + restrict_child_secondary: set[str] = None, + solve_max_rooms: int = None, + solve_max_parent_obj: int = None, + consgraph_filters: typing.Iterable[str] = None, +): + + """Restricts solving to a subset of the full house or constraint graph. + + Parameters + ---------- + stages : the original set of greedy stages + problem : the original constraint specification + restrict_parent_rooms : limit solving to only use these rooms as parent rooms + restrict_parent_objs : limit solving to only use these objects as parent objects + restrict_child_primary : limit solving to only place primary objects of these types (e.g, only place diningtables, no shelves etc) + restrict_child_secondary : if specified, limit solving to only place secondary objects of these types (e.g only place mugs, no plates etc) + solve_max_rooms : only place objects in at most this many rooms (e.g.. only 1 room has objects in it) + solve_max_parent_obj : only place objects onto at most this many parent objects (e.g. only 1 shelf has objects on it) + + Returns + ------- + stages : the modified set of greedy stages + problem : the modified constraint specification + limits : set of object-quantity-limits for solving + + """ + + obj_domain = r.Domain({t.Semantics.Object}) + primary_obj_domain = r.Domain({t.Semantics.Object}, [(-cl.AnyRelation(), obj_domain)]) + secondary_obj_domain = r.Domain({t.Semantics.Object}, [(cl.AnyRelation(), obj_domain)]) + + if restrict_parent_rooms is not None: + apply_greedy_restriction(stages, restrict_parent_rooms, cu.variable_room) + + if restrict_parent_objs is not None: + apply_greedy_restriction(stages, restrict_parent_objs, cu.variable_obj) + + if restrict_child_primary is not None: + restrict_child_primary = t.to_tag_set(restrict_child_primary, fac_context=usage_lookup._factory_lookup) + for k, d in stages.items(): + if d.intersects(primary_obj_domain): + logger.info(f'restrict_solving applying restrict_child_primary, limiting {k} to objects satisfying {restrict_child_primary}') + stages[k] = d.intersection(r.Domain(restrict_child_primary)) + + if restrict_child_secondary is not None: + restrict_child_secondary = t.to_tag_set(restrict_child_secondary, fac_context=usage_lookup._factory_lookup) + for k, d in stages.items(): + if d.intersects(secondary_obj_domain): + logger.info(f'restrict_solving applying restrict_child_secondary, limiting {k} to objects satisfying {restrict_child_primary}') + stages[k] = d.intersection(r.Domain(restrict_child_secondary)) + + quantity_limits = { + cu.variable_room: solve_max_rooms, + cu.variable_obj: solve_max_parent_obj + } + + if consgraph_filters is not None: + if isinstance(consgraph_filters, str): + consgraph_filters = [consgraph_filters] + assert isinstance(consgraph_filters, typing.Iterable) + old_counts = (len(problem.constraints), len(problem.score_terms)) + + filter = lambda d: { + k: v for k, v in d.items() + if any(fi in k for fi in consgraph_filters) + } + problem = cl.Problem(filter(problem.constraints), filter(problem.score_terms)) + + new_counts = (len(problem.constraints), len(problem.score_terms)) + logger.info(f'restrict_solving filtered consgraph from {old_counts=} {new_counts=} using {consgraph_filters=}') + + return stages, problem, quantity_limits \ No newline at end of file diff --git a/infinigen_examples/util/test_utils.py b/infinigen_examples/util/test_utils.py new file mode 100644 index 000000000..2892bd7f4 --- /dev/null +++ b/infinigen_examples/util/test_utils.py @@ -0,0 +1,60 @@ +# 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 + +from pathlib import Path +import importlib +import pdb + +import gin +import bpy + +from infinigen.core import surface +from infinigen.core.util import blender as butil, math as mutil +from infinigen.core import init +from infinigen.core.constraints.example_solver.room import constants + +def setup_gin(configs_folder, configs=None, overrides=None): + gin.clear_config() + init.apply_gin_configs( + configs_folder=Path(configs_folder), + configs=configs, + overrides=overrides, + skip_unknown=True, + finalize_config=False + ) + surface.registry.initialize_from_gin() + gin.unlock_config() + + with mutil.FixedSeed(0): + constants.initialize_constants() + + +def import_item(name): + *path_parts, name = name.split('.') + with gin.unlock_config(): + + try: + return importlib.import_module('.' + name, '.'.join(path_parts)) + except ModuleNotFoundError: + mod = importlib.import_module('.'.join(path_parts)) + return getattr(mod, name) + +def load_txt_list(path: Path, skip_sharp=True): + + path = Path(path) + pathabs = path.absolute() + + if not pathabs.exists(): + raise FileNotFoundError(f'{path=} resolved to {pathabs=} which does not exist') + + res = pathabs.read_text().splitlines() + res = [ + f.lstrip('#').lstrip(' ') + for f in res if + (not f.startswith('#') or not skip_sharp) + and len(f) > 0 + ] + return res diff --git a/log.txt b/log.txt new file mode 100644 index 000000000..517b8103e --- /dev/null +++ b/log.txt @@ -0,0 +1,18262 @@ +commit a2880ed6024ee66476a7c5f0311973e1a368f8f2 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 3 lines to infinigen/datagen/configs/export.gin. Contributed as part of Infinigen-Indoors by David Yan. + +commit 5799d04378407f7c38e4c4e14feca96057d74042 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 10 lines to infinigen/datagen/configs/indoor_background_configs.gin. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d8d29f06702a503c687e78c424688b59ed7e06c5 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 2 lines to infinigen/assets/decor/aquarium_tank.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 967cd920797cefda6946ebea9a4b712aab5d12d2 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 15 lines to infinigen/assets/decor/aquarium_tank.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit a53846ff5697191fdf31b1e867e577173f7757d4 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 97 lines to infinigen/assets/decor/aquarium_tank.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit ad7e685d457575dea212ae9db19258176adab396 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 1 lines to infinigen/assets/decor/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d6d5db4594fa201ad6f96ad577a219f40bf31505 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 6 lines to infinigen/assets/windows/__init__.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit b95f42aeb0e432f8fb1c85aab97088caa011e777 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 2 lines to infinigen/assets/windows/window.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit b0ad83e089ae92a288d2f19b4b4489e5dd9985ff +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 51 lines to infinigen/assets/windows/window.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 290ad43054732d51b487dd581b9742f266fc8460 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 203 lines to infinigen/assets/windows/window.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 4926ed7f93550c14fa1b724d62e417381226dd33 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 779 lines to infinigen/assets/windows/window.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit d7a4cbc3fadc990f956fcd2692d2c344344072f0 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 17 lines to infinigen/assets/table_decorations/book.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 30346ae3a72e8ef32782706d4418c8b89e7f31af +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 17 lines to infinigen/assets/table_decorations/book.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 661a2f937ce44577b2593aaacd75766f949b6f96 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 181 lines to infinigen/assets/table_decorations/book.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 507bc958c7cd167e4f03374b7420841c6a107d7c +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 324 lines to infinigen/assets/table_decorations/utils.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 9d797df96982c2c7c46c5bf19afab6f52f25d34e +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 1 lines to infinigen/assets/table_decorations/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 43158158b74af7bbff2ea966d4068eb98a5088c4 +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 1 lines to infinigen/assets/table_decorations/__init__.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 31e053fde4b582ab8b006f505abcc4237785e60e +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 1 lines to infinigen/assets/table_decorations/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 82659a743d753ef34fa6aaed5795a407653d898f +Author: pvl-bot +Date: Sun Jun 16 23:16:33 2024 -0700 + + Add 1 lines to infinigen/assets/table_decorations/vase.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 4ce599f72d31b21593eeb761dcbc93f06ea88e54 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 4 lines to infinigen/assets/table_decorations/vase.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 6d029f33e4fc8632abd71c74e10ced13ac2b0e06 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 31 lines to infinigen/assets/table_decorations/vase.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 251c3323b477bd46c1b76097bf1a86d0422103e8 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 67 lines to infinigen/assets/table_decorations/vase.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 3673bd9addb86ef19ba616a39f441e065ffa43ec +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 198 lines to infinigen/assets/table_decorations/vase.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 17eeb5ea4609c23d99c571bed2a530aa04fcecef +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 5 lines to infinigen/assets/table_decorations/sink.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 22e4bab7ccd66079ba856c2da5841f7f42e95c88 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 62 lines to infinigen/assets/table_decorations/sink.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit bfffcc718eebcb71abbac3f40f98e3cd6f96bb26 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 102 lines to infinigen/assets/table_decorations/sink.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 32fe356161b1211a2ea21683ce23a4c386c200c5 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 117 lines to infinigen/assets/table_decorations/sink.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 232d51bf497c34f104cf76ad251b0755d37b95f8 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 225 lines to infinigen/assets/table_decorations/sink.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit ac425f57ecce19a0926d6bc538d9c454e161720c +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 274 lines to infinigen/assets/table_decorations/sink.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit dc577759538978d4f37368891d05e455bc705727 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 4 lines to infinigen/assets/seating/chairs/seats/curvy_seats.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit b796a28d2e4f14864dffe37effec2cf949d055f2 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 46 lines to infinigen/assets/seating/chairs/seats/curvy_seats.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit db4a31eefb74d73ccf38ade5240b0798746bdb5c +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 100 lines to infinigen/assets/seating/chairs/seats/curvy_seats.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 82d691ba20db24fa23305aa0a345ba6ab0c45a2c +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 2 lines to infinigen/assets/seating/chairs/seats/round_seats.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit c1be072ff44524bc56caff58e0d57586a77713ee +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 39 lines to infinigen/assets/seating/chairs/seats/round_seats.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 81121be9907b9889d5d5edf78e220ac4d1f5cb2d +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 1 lines to infinigen/assets/seating/chairs/bar_chair.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit efb0781287d01576d117fa0ec0eea312138a41ab +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 3 lines to infinigen/assets/seating/chairs/bar_chair.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit f7ae6f6ebcc24fc39778347c063e05fea35b5356 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 15 lines to infinigen/assets/seating/chairs/bar_chair.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 79e8a8bfe40545f30724f61f0435932211f95d3b +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 37 lines to infinigen/assets/seating/chairs/bar_chair.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit eb3bcead798cdcac5725f31c9b6a2fea34a79b3b +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 117 lines to infinigen/assets/seating/chairs/bar_chair.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit cc312dba6416a9cd829367857a6e6bb5cfdea149 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 7 lines to infinigen/assets/seating/chairs/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 742a9bde5bddd1c98ea68a5e602a38c49510dfd5 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 14 lines to infinigen/assets/seating/chairs/chair.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 5c0f875d7464aa157de1a5787d85c4e92c1078e7 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 22 lines to infinigen/assets/seating/chairs/chair.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit b67001ffdabc22106fe1ee46d8a3d64c7f87ba12 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 328 lines to infinigen/assets/seating/chairs/chair.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit e2eadbee16f7f1855316366686016de432fe824a +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 12 lines to infinigen/assets/seating/chairs/office_chair.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit bccbfc7c07d51e631bca4ce2ffdfcbc7619cade1 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 18 lines to infinigen/assets/seating/chairs/office_chair.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 3ad3ba7cd71652e5922bc93a6b6cc6d4b16d80aa +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 36 lines to infinigen/assets/seating/chairs/office_chair.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 8d379da567082d5220c684de7962dc3bca611a20 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 140 lines to infinigen/assets/seating/chairs/office_chair.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 2cd5fde810f6d246009ec3d48d20518cbd0945df +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 2 lines to infinigen/assets/seating/sofa.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 9c7e927d6fab7ab3cd40829f851bccf6707c96bf +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 4 lines to infinigen/assets/seating/sofa.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 51386633d851a40f67c86f62f518ee8979817db2 +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 18 lines to infinigen/assets/seating/sofa.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit f1092c7d6439f1111e2a000c9fac8730ff9ba59c +Author: pvl-bot +Date: Sun Jun 16 23:16:32 2024 -0700 + + Add 75 lines to infinigen/assets/seating/sofa.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 24897d944f697f4b736c02a0e2a276bce3391af4 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 239 lines to infinigen/assets/seating/sofa.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d291d57d0b58d4d2f602ce9fb56c003f7ee45a6a +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 405 lines to infinigen/assets/seating/sofa.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit 44492af609664e71269a3338b0da394c805c287f +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 7 lines to infinigen/assets/seating/bedframe.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 6c3e78b9f21c7667b29ea6dcd7f08b449f82add1 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 172 lines to infinigen/assets/seating/bedframe.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 82e3bbd29d6ecb4b7cc7859bc24006211efd3a1e +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 186 lines to infinigen/assets/seating/bed.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 719d71574366e13b1f25a79418e70be51879d4cc +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 2 lines to infinigen/assets/seating/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit c27b36c6e528ab28e3659de402be9c67fe844550 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 9 lines to infinigen/assets/seating/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 96be2a15d86fd42917373414bd56ed5f0e59bcb2 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 5 lines to infinigen/assets/seating/pillow.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 7476bb3d477591dbea2bc7fc31b5a7432de78a82 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 112 lines to infinigen/assets/seating/pillow.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 418830e3d2c43a2e53d1e628ea1ab687ef67e5a7 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 3 lines to infinigen/assets/seating/mattress.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 04a797719f28c0b9d60524bdefb990fed2764c1b +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 123 lines to infinigen/assets/seating/mattress.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit cc44ca40188a29613fd3d6041fe09e514fee142d +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 34 lines to infinigen/assets/lighting/holdout_lighting.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 05656cb0c4352348ba67cca3eaeb102a4ba37ed7 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 33 lines to infinigen/assets/lighting/hdri_lighting.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit eecce753d2dfb0c306cce5734a350bbf563f8e7a +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 3 lines to infinigen/assets/lighting/ceiling_lights.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 20593ad4182a4e801d38af2e0c5022ddfce68364 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 34 lines to infinigen/assets/lighting/ceiling_lights.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit a18083caf2d0a02471fef3336c47e218f891aa00 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 35 lines to infinigen/assets/lighting/ceiling_lights.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 2fd8fff7ffce0249105e768e63a3a7f9c8bc6d7d +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 145 lines to infinigen/assets/lighting/ceiling_lights.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 7e964a5b0c97c18c3544c98bc8a99f528a917937 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 1 lines to infinigen/assets/lighting/three_point_lighting.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ca322453ecacbe8af3283847a356fad21c57c2bd +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 27 lines to infinigen/assets/lighting/three_point_lighting.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit f544bfb3893912e3cc1400d415c3ba74148e96d9 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 9 lines to infinigen/assets/lighting/lamp.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 48aebf3bb533ae0afc860ae9115f7286ef0485c7 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 32 lines to infinigen/assets/lighting/lamp.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit a677764a37b3f3767f7298572ce4673a6da32111 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 75 lines to infinigen/assets/lighting/lamp.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 0fdcf6b61300031b75a5d77c83c33e10c9af224b +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 114 lines to infinigen/assets/lighting/lamp.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit c7a2cd3392638d2b4d17c61f367de8c7a90f1e34 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 318 lines to infinigen/assets/lighting/lamp.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 6051bb06ff4c07b8ec219fea183f7db3f897d38d +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 56 lines to infinigen/assets/lighting/indoor_lights.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 65e5eafc9dcacac32f14e4c7e6852eadff8447d2 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 2 lines to infinigen/assets/lighting/ceiling_classic_lamp.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 9ef918e63e9b52c8d9f4e968308068a6aafcce49 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 5 lines to infinigen/assets/lighting/ceiling_classic_lamp.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d848ff6e9e6499e520464dd39ca327f33cf09518 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 232 lines to infinigen/assets/lighting/ceiling_classic_lamp.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit 46aceff291cf28a1de61b772afebbd8db6a577df +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 9 lines to infinigen/assets/scatters/clothes.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit c3da2edc9d1138d95fe3cc65b0c280c7621e5797 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 69 lines to infinigen/assets/scatters/clothes.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit f0550f9410127ddb84040219c0917cad06b10820 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 1 lines to infinigen/assets/elements/nature_shelf_trinkets/generate.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 758f3ffe84f8ec8e746ee1e7db7ff8d333c8e8cc +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 1 lines to infinigen/assets/elements/nature_shelf_trinkets/generate.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit a64c3bb41482525812294f4e3e9a17d0eee5583c +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 16 lines to infinigen/assets/elements/nature_shelf_trinkets/generate.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 68b4d91eb3cae215eb0e39a4f64e276d41ece306 +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 80 lines to infinigen/assets/elements/nature_shelf_trinkets/generate.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit 9a174c4e5584fea243383c3ce19b2f1bcf6492eb +Author: pvl-bot +Date: Sun Jun 16 23:16:31 2024 -0700 + + Add 4 lines to infinigen/assets/elements/doors/casing.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 09a38e9b593111d8790e266485174aebfb2b3fcd +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 58 lines to infinigen/assets/elements/doors/casing.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit dfbf582c656f7c11976b9bd77079f49afdc91928 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 4 lines to infinigen/assets/elements/doors/base.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit e823115f6b324a16775df3cc563d9d3c83ccbf80 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 5 lines to infinigen/assets/elements/doors/base.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 67cb84726faf578bd2c415376dfe5dcfd08289ae +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 208 lines to infinigen/assets/elements/doors/base.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit f40a580f07936e4ef7a317f04cf6d80d4bf983e3 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 92 lines to infinigen/assets/elements/doors/panel.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit b988978d154121e172003b9443f2864f8e141ce2 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 55 lines to infinigen/assets/elements/doors/lite.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 18592ec491d767ed7d0a1f190e5750db5ccf414a +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 80 lines to infinigen/assets/elements/doors/louver.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 664c4a4d48c23840bbb4abfc5363610097a541b0 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 33 lines to infinigen/assets/elements/doors/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 23295ac304f69ba0e02409b51a5f3aceda16e26f +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 3 lines to infinigen/assets/elements/staircases/spiral.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ee6412ee2b226388442f382ddec68a975c930ac7 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 4 lines to infinigen/assets/elements/staircases/spiral.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 644c10af55d2ef92db16bfeed1b763bf7fd244ef +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 55 lines to infinigen/assets/elements/staircases/spiral.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit b635d284d52ab1f1805a8abf37f720288aff248e +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 3 lines to infinigen/assets/elements/staircases/curved.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit e188fc64f29830bfd155b7323f7f19e113d3db7d +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 4 lines to infinigen/assets/elements/staircases/curved.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit c0bcbe19e692117b9b3ee5be25066972b51a6c3a +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 60 lines to infinigen/assets/elements/staircases/curved.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit e7605ec909ac5ccf26f085cd0bfa21b6a36edf8d +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 3 lines to infinigen/assets/elements/staircases/straight.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 79d2823a8ce6b20259cd0c026028873d93023998 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 4 lines to infinigen/assets/elements/staircases/straight.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 01ecc4b07a8f014ff1539dfb12986d2450a38b93 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 5 lines to infinigen/assets/elements/staircases/straight.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 9edd3de55126c59dd961725c75fb669276697856 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 592 lines to infinigen/assets/elements/staircases/straight.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 65e63943c4c7477ffe866263c9846ee0c7951396 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 32 lines to infinigen/assets/elements/staircases/cantilever.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 662d39f85213136ab71d588ee9bfdf653bcf274d +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 18 lines to infinigen/assets/elements/staircases/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit c8217b58eadc8960102fa3b92d07bdf1a35dd1e4 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 145 lines to infinigen/assets/elements/staircases/l_shaped.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit ebb5aad01803bcbe8226094b31e5ee999e30468d +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 33 lines to infinigen/assets/elements/staircases/generate.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit a21b6e90d804c65eaccc64aff5f0c7d3659b5833 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 3 lines to infinigen/assets/elements/staircases/u_shaped.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 0e6c023542651027401aa2715c84e86c08030d31 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 4 lines to infinigen/assets/elements/staircases/u_shaped.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit d0dbb0e43aefb3c4eba6b03bf2fa255fc960279a +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 151 lines to infinigen/assets/elements/staircases/u_shaped.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 45e790e917ebafe2a640f9d42c551fc64ca38ab7 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 6 lines to infinigen/assets/elements/warehouses/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 6095f72899b84b5ad800a7c18d19a467d4f2fa5d +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 2 lines to infinigen/assets/elements/warehouses/pallet.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit d73fa6d20268beeb8c4c0c8149da937d9e05c0fa +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 85 lines to infinigen/assets/elements/warehouses/pallet.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 403907546b33290bc72d3bb856b9a3c411695a56 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 2 lines to infinigen/assets/elements/warehouses/rack.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit b239d307e18bf464a8eca2bbe9c95dbb5899eb5f +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 166 lines to infinigen/assets/elements/warehouses/rack.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 1e34fd0a6657cdd155178275e5f55fd2749b3b2a +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 123 lines to infinigen/assets/elements/pillars.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit fe82da6c9595e6cfabb5fdfbffe89b1099ac8a5a +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 1 lines to infinigen/assets/elements/rug.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 04df9e273de641025e9a389a808fb5f0710140c6 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 5 lines to infinigen/assets/elements/rug.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 5217e1d9d3512f1e36c2ca3f4bd7e70c0cce54d0 +Author: pvl-bot +Date: Sun Jun 16 23:16:30 2024 -0700 + + Add 58 lines to infinigen/assets/elements/rug.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit ea5ee3624aebaaee3d8f02d3d452e36d990dddea +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 1 lines to infinigen/assets/elements/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 20196bdbb1954245ca79a66ed3974510e6a94456 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 9 lines to infinigen/assets/elements/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 8666934337e5f38686a12de490cf394362a85738 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 8 lines to infinigen/assets/utils/uv.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit f3618de2748eebac61457c3fc2a7eb6aa7fea9a9 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 193 lines to infinigen/assets/utils/uv.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit a31095485520b02dc5ece45ecd70c26a51b71c65 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 43 lines to infinigen/assets/utils/extract_nodegroup_parts.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit dd6f5c0abad11a70d927bdd1648248d34452721d +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 53 lines to infinigen/assets/utils/autobevel.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 0fe354525ad8d697030e48a8d4b8170cc5978a7e +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 142 lines to infinigen/assets/utils/shapes.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit c19ae3b741e9abcd6f57c2c7b4a9a85ad0e7c978 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 87 lines to infinigen/assets/utils/bbox_from_mesh.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit fd4750ea9cdd640d1bead4123862fc58e702c813 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 3 lines to infinigen/assets/wall_decorations/balloon.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 70dec85102d7e06a7c184f874d65f4a5d1b9a1c3 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 75 lines to infinigen/assets/wall_decorations/balloon.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 827c956af9ffe26163a15f3ce028882451a1b63f +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 1 lines to infinigen/assets/wall_decorations/skirting_board.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit f1196a2bbea49300cdf930048952fd2348510dd5 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 18 lines to infinigen/assets/wall_decorations/skirting_board.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 8408175e69922f09dfae7303e3910cdd65cbb3fb +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 67 lines to infinigen/assets/wall_decorations/skirting_board.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 0842cb85156afa5cd2134b40789e999760b484eb +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 200 lines to infinigen/assets/wall_decorations/skirting_board.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 73e73f8501fb83b2124e07f31e21474d3032679c +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 4 lines to infinigen/assets/wall_decorations/wall_art.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit b774582124d00c9f5189389641081efe1e62010d +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 13 lines to infinigen/assets/wall_decorations/wall_art.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 0e3c27bcca6f5784c424b557c2c12f8f28eb6e43 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 73 lines to infinigen/assets/wall_decorations/wall_art.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 9b5e875b969e9cd171c2bb134765b4ed03c7cebf +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 8 lines to infinigen/assets/wall_decorations/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit c8b575819e9c53cada27d62494de47834a00824d +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 126 lines to infinigen/assets/wall_decorations/wall_shelf.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 339c93bafe702eaf0cf8178de5e134c2d6cc9f86 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 14 lines to infinigen/assets/wall_decorations/range_hood.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit dcb850eb3e113c62b97dabdb10fb2c017c53cc4b +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 20 lines to infinigen/assets/wall_decorations/range_hood.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 610416aee0efe06f47b8b19a2354e24b64220d1a +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 157 lines to infinigen/assets/wall_decorations/range_hood.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 214f6613e5a7b14c9e10d678cf4915f9794c2463 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 2 lines to infinigen/assets/organizer/basket.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit b7222b97d93b94bd856259eea4cdfa7a071b205c +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 304 lines to infinigen/assets/organizer/basket.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit b0a60402c9290eef410323fc7f5bffc7020520fb +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 9 lines to infinigen/assets/organizer/__init__.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit e86fd25ea089d46cbf1b9e718a458b55c64bb331 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 3 lines to infinigen/assets/organizer/hook.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 2a2cc9419a8e7644666ba2192b92bbae84ebbb4f +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 384 lines to infinigen/assets/organizer/hook.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit 8b1ad27d5042c2af6ac6150766bb4f6c19297ec1 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 2 lines to infinigen/assets/organizer/plate_rack.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit d21911b57df052d0635368e842fa1018838d656c +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 333 lines to infinigen/assets/organizer/plate_rack.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit fc36d0de783f0fd1fedbf095f233f1af3d28f3f5 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 1 lines to infinigen/assets/appliances/oven.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 2e5601f935772a8b6705c1acc0ade2fbb19a0ed4 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 32 lines to infinigen/assets/appliances/oven.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit de924b79140f35fe02778cee2bc3e4de444e7e47 +Author: pvl-bot +Date: Sun Jun 16 23:16:29 2024 -0700 + + Add 48 lines to infinigen/assets/appliances/oven.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 8ec2597f07d4ca21d58a8139fa50929f2017208a +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 172 lines to infinigen/assets/appliances/oven.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit d9bee11e8a63829f6693af71c8e58120733807dc +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 336 lines to infinigen/assets/appliances/oven.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit b8f867ba511a7a7567c904e322ac732f68ecc807 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 626 lines to infinigen/assets/appliances/oven.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 2b64c425c2d3febcb39841bec831b101731ed4de +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 4 lines to infinigen/assets/appliances/tv.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit e67d3495bc2924f81722e6b0da320e5ae09c355b +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 5 lines to infinigen/assets/appliances/tv.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 4799521049fb41d543dd5d1200b00c841d6d7f85 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 21 lines to infinigen/assets/appliances/tv.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 25992cd5d74711dd80d77009fb519c0d90a638b9 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 191 lines to infinigen/assets/appliances/tv.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 0265f6788749baf227f69a7664ea8e64df10a864 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 1 lines to infinigen/assets/appliances/__init__.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit bfa4527c8801880c0a5c30b07e9d53c10c0c7182 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 2 lines to infinigen/assets/appliances/__init__.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 81758e1b71406d33a2f7fc22fd5ba3c4ff2fef55 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 2 lines to infinigen/assets/appliances/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 85b8c3ff277b555afcefc7c5e966319ab77db9b0 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 26 lines to infinigen/assets/appliances/microwave.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit 379bf0e8ea6906515d9ff926f0ecc5acc01cd336 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 41 lines to infinigen/assets/appliances/microwave.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit d681b3936397cc62f6492d82c099652536f49164 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 118 lines to infinigen/assets/appliances/microwave.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit d774272384c322edbeea24f9114c5992f0cb37e7 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 262 lines to infinigen/assets/appliances/microwave.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 374cbbaac1c4eea9c6d91e7ba2ce1d36319d4c36 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 37 lines to infinigen/assets/appliances/dishwasher.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit fd831e3cbaccad056d94cbb3143ae7659471023a +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 57 lines to infinigen/assets/appliances/dishwasher.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit f04c5ed58c8c41f3b76df5376a34149d1b8cbeab +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 191 lines to infinigen/assets/appliances/dishwasher.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit dfbf77ac8e0e5772687d11ea72705c5791ce8fb0 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 644 lines to infinigen/assets/appliances/dishwasher.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 07d44cc326a5973bdd5274807e18686205e994e4 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 1 lines to infinigen/assets/appliances/beverage_fridge.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 81af6f1ba6fab5799409f6d16490dc95488cc88f +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 35 lines to infinigen/assets/appliances/beverage_fridge.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit d1b0d664f5fc10fe7bd9a2bde80f39261e998085 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 42 lines to infinigen/assets/appliances/beverage_fridge.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit cc2306691abc1134c6dc47fccf6d3d0f0c4fee2c +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 223 lines to infinigen/assets/appliances/beverage_fridge.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 9dfe85ec2044f8d6c7036b4e29ee61f4ab2d442b +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 434 lines to infinigen/assets/appliances/beverage_fridge.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit c9a029331d47dfab5af97347a031ade62b0896a5 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 1 lines to infinigen/assets/shelves/doors.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 315fa23a929e3d19ecbf9d7ba7abe40aab81764f +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 2 lines to infinigen/assets/shelves/doors.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit b7a4432f6f585f385a8b62aeb5827c03474663e4 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 6 lines to infinigen/assets/shelves/doors.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit d92e73376f735bcf8d0c197783d94695d9198ab4 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 6 lines to infinigen/assets/shelves/doors.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit ce87ddafd34eabd3636d0a4fda3c51b0fa180332 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 724 lines to infinigen/assets/shelves/doors.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit 34d20a32898db715b2010b0ab81d6d0055b9c488 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 3 lines to infinigen/assets/shelves/kitchen_cabinet.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 7115cd353c1bbac5725b96a51dbcfc605fcfbdb2 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 7 lines to infinigen/assets/shelves/kitchen_cabinet.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit cc1a3aeca3404dcd57ae3001e7790dcfc12e8653 +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 9 lines to infinigen/assets/shelves/kitchen_cabinet.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 4f577be8db059becd357584229140c0044fe694b +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 13 lines to infinigen/assets/shelves/kitchen_cabinet.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 96e55c262af4b0b7aaae608f1b9d12109b7d549b +Author: pvl-bot +Date: Sun Jun 16 23:16:28 2024 -0700 + + Add 16 lines to infinigen/assets/shelves/kitchen_cabinet.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 6774437197583f4ecd75f4295faf05c9fd487bd5 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 280 lines to infinigen/assets/shelves/kitchen_cabinet.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit 6cdc8add656110b67780bacad7a80c59168a1f41 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 1 lines to infinigen/assets/shelves/single_cabinet.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 57f309a93ce2982385f274c02232f8da5537a1c5 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 3 lines to infinigen/assets/shelves/single_cabinet.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ab10ebe25105e3c4cb2b9021ae58f929c4e348e6 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 7 lines to infinigen/assets/shelves/single_cabinet.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 807b5ae03ffee1915c9b70879e9ae22716eebfc7 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 9 lines to infinigen/assets/shelves/single_cabinet.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 746130cc91509a702372fb73bafe87fffc82d89a +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 215 lines to infinigen/assets/shelves/single_cabinet.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit 34ce110ad247a979ff084aeb157632f7b918bfc2 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 2 lines to infinigen/assets/shelves/triangle_shelf.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit dd191be0b76fcd7d89dc78c7303f2c4b8d5113d9 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 2 lines to infinigen/assets/shelves/triangle_shelf.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 5a83521268033ec429ffb602eacb6228ba614663 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 340 lines to infinigen/assets/shelves/triangle_shelf.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 349792226fdbc18192ce20105d4157119b77272a +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 526 lines to infinigen/assets/shelves/triangle_shelf.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit 5d8cfa5c8b79e23da3069153e05c039a0869df4d +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 2 lines to infinigen/assets/shelves/simple_desk.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit eb7a35d058bba82496ea4279711b47241ad3560b +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 11 lines to infinigen/assets/shelves/simple_desk.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 426d5616410ee2debb5f755ae3ea227e39b245b0 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 43 lines to infinigen/assets/shelves/simple_desk.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 927ccead91e369b8d8b898cec3335f0ed227db9a +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 211 lines to infinigen/assets/shelves/simple_desk.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit de585591a0a25d94f4fcd33305b3a88e0eb3b6a9 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 154 lines to infinigen/assets/shelves/countertop.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit d380d10d68483cd2f6f18bbd73611d287571e8f9 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 2 lines to infinigen/assets/shelves/utils.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 92786223272dca3624d78db73fc085e30338ce2d +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 7 lines to infinigen/assets/shelves/utils.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d4608181ffc6d32bcd000cc37b6716b94d5b5b19 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 52 lines to infinigen/assets/shelves/utils.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit 7bd931a05212199cf33b3febbf4b0c44871f111d +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 2 lines to infinigen/assets/shelves/cabinet.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 784999d4ce19cbe68f3219156bb5b5c06f2d4ef8 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 264 lines to infinigen/assets/shelves/cabinet.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 2d3cd29f4c5df2915a6e0d58357f0531a2bd0209 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 737 lines to infinigen/assets/shelves/cabinet.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit b8416ef5fd478a78c17d3f4ba5267b9db0d2da38 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 1 lines to infinigen/assets/shelves/kitchen_space.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit c37d776faa5b6c34a1a62aec0f103b8d30215341 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 1 lines to infinigen/assets/shelves/kitchen_space.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 32e51aa4e39729b894318f63fc78759754525116 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 2 lines to infinigen/assets/shelves/kitchen_space.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit e160187fe604413e3832a5b3a90612563a2786cc +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 5 lines to infinigen/assets/shelves/kitchen_space.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit 21fe61b9328ede5c760bdce63e96bb9c5e96af4f +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 6 lines to infinigen/assets/shelves/kitchen_space.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 13f9825b0a65293f5494151a2011856dcdbff28b +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 68 lines to infinigen/assets/shelves/kitchen_space.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 9404ff0db2554c8e83f9f1531b912a9f6a1e7ef8 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 137 lines to infinigen/assets/shelves/kitchen_space.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit d33d7dc817b892a52c8aaa4aed6c890b003b7aae +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 1 lines to infinigen/assets/shelves/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 3c99cc40045179181c93a675c84d9598ef74b704 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 1 lines to infinigen/assets/shelves/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 089236016e23143ef0203da5084441a19a6879b7 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 8 lines to infinigen/assets/shelves/__init__.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit 4fcf6d2b11ba4bf754d5bdcbf0f168457b50359f +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 1 lines to infinigen/assets/shelves/large_shelf.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 051d5676fcbc3d18491bae10975674913a7a7977 +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 1 lines to infinigen/assets/shelves/large_shelf.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 3827bdf2cb56f77cb9231d1a35e8e2225ef2b29d +Author: pvl-bot +Date: Sun Jun 16 23:16:27 2024 -0700 + + Add 2 lines to infinigen/assets/shelves/large_shelf.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit db936bbe8740e8639ed913de8791e568f7670ffb +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 612 lines to infinigen/assets/shelves/large_shelf.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit b065b73beb885bda17926a45be88a1cfed855266 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 1 lines to infinigen/assets/shelves/simple_bookcase.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ba6dc0c4458fb68ee2c641bf774426165e08f25f +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 2 lines to infinigen/assets/shelves/simple_bookcase.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit e1fab8bbeaa6e15eaa83e32f2f0ef017db1ab6a9 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 159 lines to infinigen/assets/shelves/simple_bookcase.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 0f92df4aaccf410323c45b22fd89c24a7d7019de +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 356 lines to infinigen/assets/shelves/simple_bookcase.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit dfe9857289f6a1714c6291b9ab584e7785eef8a4 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 1 lines to infinigen/assets/shelves/drawers.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 25f3196595889e639620068731645520c741895d +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 1 lines to infinigen/assets/shelves/drawers.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 37370c336faf3ab069927173000e7bb3925027a5 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 4 lines to infinigen/assets/shelves/drawers.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit baeebb9fe2c85dae745a3e44b16bfd7b712176c7 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 410 lines to infinigen/assets/shelves/drawers.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit 90269abe640d3956f2a01b8ab742b37042269655 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 2 lines to infinigen/assets/shelves/cell_shelf.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 08824a214e568656d16cddb704b9cf8dd1bb0845 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 3 lines to infinigen/assets/shelves/cell_shelf.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit af3b2672c245d8f8e364e765fe417a824c9e5f4a +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 9 lines to infinigen/assets/shelves/cell_shelf.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit f04b47fb35ac82e1a2ab2bc94abe40843e777df1 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 36 lines to infinigen/assets/shelves/cell_shelf.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit afc2fbc10eb643bc29fc880ef56a98cffbc88ffd +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 850 lines to infinigen/assets/shelves/cell_shelf.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit c82980a2394e23e9e49c93776f27e64967ee4a15 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 6 lines to infinigen/assets/bathroom/toilet.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 10beed29137fe07be80d492be288e6c79a632ab5 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 286 lines to infinigen/assets/bathroom/toilet.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 25e5980e2c04f6281c86c3f4bf7026bc8dcfa711 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 11 lines to infinigen/assets/bathroom/hardware.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit a17feca0bbf006a8542ebf7b7d038e8f5d3659c7 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 110 lines to infinigen/assets/bathroom/hardware.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit ad6bfec63e6e5ee2b301230df1f5c9f11d5f761f +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 13 lines to infinigen/assets/bathroom/bathtub.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit d49a0a4ff75e9d1bc668e355279867e9675b56c0 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 40 lines to infinigen/assets/bathroom/bathtub.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 8013bd9f2114b3651c6510c5292a6b8eaaa6be9b +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 236 lines to infinigen/assets/bathroom/bathtub.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 279e32494c2478cb90217a94ea605890bb59ea98 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 8 lines to infinigen/assets/bathroom/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit debc696443bab1780457f9bddbe11281b8278002 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 5 lines to infinigen/assets/bathroom/bathroom_sink.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 227a01d1219eaf2c6fe3d874296c7bc855f79db3 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 138 lines to infinigen/assets/bathroom/bathroom_sink.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 49ddc761ef592c24afa073110929264624adb507 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 6 lines to infinigen/assets/clothes/shirt.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 268a5a1143fdfff604f9f4805fc3641697a22318 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 65 lines to infinigen/assets/clothes/shirt.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit f2cff82c97098b5b5ad97d9cdb719bb526ab0ee1 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 9 lines to infinigen/assets/clothes/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit c44d205f50af4d2a6b59e554930f1f9909206c8b +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 6 lines to infinigen/assets/clothes/towel.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 0bd622a3b3d22f8ad306a46f95cf5eb516bad5a4 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 117 lines to infinigen/assets/clothes/towel.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 1da9534a168e4d9cbeb7b890eb1bf2dede4f134c +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 6 lines to infinigen/assets/clothes/blanket.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 4cbf5b737505da2094fa487bbb132395b06740cc +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 73 lines to infinigen/assets/clothes/blanket.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 358952c780386d3e781d8d8a181d984472f9258b +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 6 lines to infinigen/assets/clothes/pants.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 71be8d430897865bc395745e86fddee4844b6469 +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 60 lines to infinigen/assets/clothes/pants.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 04f9c87338fc11cf1e51faf34086894d3f4cf01a +Author: pvl-bot +Date: Sun Jun 16 23:16:26 2024 -0700 + + Add 2 lines to infinigen/assets/materials/woods/wood_old.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 29fe4d06acf7a2a939df8ee43f58613afa989a7c +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 3 lines to infinigen/assets/materials/woods/wood_old.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 2f2d8de76790e0cd3680d4a9f3571825586b2ed2 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 21 lines to infinigen/assets/materials/woods/wood_old.py. Contributed as part of Infinigen-Indoors by Mingzhe Wang. + +commit e32838a35a025ab3cf247396ea093f04261be5d8 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 50 lines to infinigen/assets/materials/woods/wood_old.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 270216a50b244df40a91790bfd101ef705065868 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 15 lines to infinigen/assets/materials/woods/wood_tile.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 69c9c5cbfb2a45e8ebc23d0a9db15c18b7b3aae7 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 12 lines to infinigen/assets/materials/woods/hexagon_wood_tile.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit ed6fb7f2182f299215b79cbf56fd9e9559a14cde +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 18 lines to infinigen/assets/materials/woods/composite_wood_tile.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 1b8d1fa021ea40e8e66e5c0ebc70f89827afc08f +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 22 lines to infinigen/assets/materials/woods/non_wood_tile.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 5fdad3f7345bff430745b5636addf9eb74609473 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 14 lines to infinigen/assets/materials/woods/square_wood_tile.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 87cb2917af93261415e1287b41f56d4114138079 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 6 lines to infinigen/assets/materials/woods/wood.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 182bc877b764dc7cf01219841e5c02a4ee1998ea +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 7 lines to infinigen/assets/materials/woods/wood.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 62d5b7c3c606c680d0354d8c6634142e9d96fc44 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 133 lines to infinigen/assets/materials/woods/wood.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 8814ffb16eaa9388e2b94ba25848803e484270fa +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 12 lines to infinigen/assets/materials/woods/staggered_wood_tile.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 86dbcb903bcb1e89949575f36ad7d3aacae7bae8 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 14 lines to infinigen/assets/materials/woods/crossed_wood_tile.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 230bf146b4172180487ad2f7b9cb2e50a71c2977 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 3 lines to infinigen/assets/materials/woods/tiled_wood.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit f1ec5bf90e94173b0ff4968f6595917efeae255e +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 11 lines to infinigen/assets/materials/woods/tiled_wood.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit dd0493e9c617a1454769d11c42e7546f1f2b9711 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 58 lines to infinigen/assets/materials/woods/tiled_wood.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 6a5e880802f69cf527aa0408b4ed7959f270fc5b +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 116 lines to infinigen/assets/materials/woods/tiled_wood.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 9109359d3becc8acaddbb635e0f5a279b0cdccf5 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 63 lines to infinigen/assets/materials/stone_and_concrete/concrete.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 25bc986e937c6dc04fe1448cd6c7bc6d4093fc2a +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 179 lines to infinigen/assets/materials/stone_and_concrete/concrete.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 6b8a4f2172bcbaeb5d86d030cadf587940996bc0 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 20 lines to infinigen/assets/materials/metal/brushed_metal.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit e3d2916af692425917bcf36b290d1fef9ebef25c +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 67 lines to infinigen/assets/materials/metal/brushed_metal.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 434a7339f13889e9a700164f7a469dee7b5e7313 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 20 lines to infinigen/assets/materials/metal/grained_and_polished_metal.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 9776d5acb7109f62b18fe624f76215b305947f6c +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 59 lines to infinigen/assets/materials/metal/grained_and_polished_metal.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 49651d254f283c736ac078d74b57004ce5374269 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 56 lines to infinigen/assets/materials/metal/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit cd97d6110b0c621d6b8654eefe3762483d2add75 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 26 lines to infinigen/assets/materials/metal/hammered_metal.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 99d5fbf2d48830bc5f81feae3e3d86bde2a1c4c7 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 55 lines to infinigen/assets/materials/metal/hammered_metal.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit fa2a7bd0676d76068a85a7143d16379db24cb4b7 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 33 lines to infinigen/assets/materials/metal/metal_basic.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 4a0e6b1601bba78408cee8f9ed122fda0a4310ff +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 19 lines to infinigen/assets/materials/metal/galvanized_metal.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit dc1cfcdaf9cb5fba5ab15962259364cc488cd1ba +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 49 lines to infinigen/assets/materials/metal/galvanized_metal.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 35a7a0d73b252ada8051b81b273122b4ec1bfc39 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 1 lines to infinigen/assets/materials/plastics/plastic_rough.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 08b4e3f3792d80185d9553b327cecb79a5c52bff +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 13 lines to infinigen/assets/materials/plastics/plastic_rough.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 01212dcf30bdd702a07563056f65cfd3b5cb0f37 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 69 lines to infinigen/assets/materials/plastics/plastic_rough.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 7028289d869e51f2b5b401a9e094bbc55f550dd9 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 2 lines to infinigen/assets/materials/plastics/plastic_translucent.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 8998f3f6ae9a438e01ef41b9815d683cf557f4a9 +Author: pvl-bot +Date: Sun Jun 16 23:16:25 2024 -0700 + + Add 7 lines to infinigen/assets/materials/plastics/plastic_translucent.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 422618540da609c99c90b5ec936834d812d2d07a +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 35 lines to infinigen/assets/materials/plastics/plastic_translucent.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 25f92e17c68283fa4ebd148ae969bb240e189b53 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 4 lines to infinigen/assets/materials/leather_and_fabrics/coarse_knit_fabric.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 8d76a819c133e746c39b54cb2d37d12b9bcf86de +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 266 lines to infinigen/assets/materials/leather_and_fabrics/coarse_knit_fabric.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 6ab9c9405173b17a62ee63c7ac1b13b5ab0439bc +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 8 lines to infinigen/assets/materials/leather_and_fabrics/leather.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 0536a0f7b08c398cffe0ecef394e39fc7f479c51 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 28 lines to infinigen/assets/materials/leather_and_fabrics/leather.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit a72d93218ed3a1e92f48c79f370145a3e2024e6d +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 74 lines to infinigen/assets/materials/leather_and_fabrics/leather.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit de22aa971664c751581e37eaf12089bc07e1f638 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 4 lines to infinigen/assets/materials/leather_and_fabrics/general_fabric.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ecf69bb1f2934d38c8dfa8bb66c6c17d5b540b11 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 41 lines to infinigen/assets/materials/leather_and_fabrics/general_fabric.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 044649819bcbde3c61687c1106cdcbd841388bc8 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 125 lines to infinigen/assets/materials/leather_and_fabrics/general_fabric.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 94bd36e7ad9555aa74e6e48bad8bd6e80b97b623 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 4 lines to infinigen/assets/materials/leather_and_fabrics/lined_fabric.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit b6ea7077be962e2677df5ff100dadbbc1e5d62b9 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 163 lines to infinigen/assets/materials/leather_and_fabrics/lined_fabric.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 1f17a053a81109337d59962a958c9cc39c934d62 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 2 lines to infinigen/assets/materials/leather_and_fabrics/__init__.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 718e4825af397b0f20ecc5363fadad9b760fdbdc +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 20 lines to infinigen/assets/materials/leather_and_fabrics/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 9f636a2bc1a163b5a92f74673d8efa1e108fb7e1 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 4 lines to infinigen/assets/materials/leather_and_fabrics/velvet.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 7e447c1d87fff59bacb4faecd168acc55521f409 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 82 lines to infinigen/assets/materials/leather_and_fabrics/velvet.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit c95602c75ec9045e331ee2b3a4bd959c63f91969 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 4 lines to infinigen/assets/materials/leather_and_fabrics/sofa_fabric.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 9cfad3da986577c10f7e2ee06bfec6bb669a387f +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 36 lines to infinigen/assets/materials/leather_and_fabrics/sofa_fabric.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 66c7d007676ce0b50e9060613de3121662d89c17 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 4 lines to infinigen/assets/materials/leather_and_fabrics/fine_knit_fabric.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 9bfe404beab82aab070178a3f79d057e9aa87062 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 150 lines to infinigen/assets/materials/leather_and_fabrics/fine_knit_fabric.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 554fc1ce2d8956f305df18d8bd27a9a0cae43c6d +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 11 lines to infinigen/assets/materials/wear_tear/procedural_scratch.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit c905341ad3aca7b66ab817256775fdb2cb38dc67 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 14 lines to infinigen/assets/materials/wear_tear/procedural_scratch.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 1e3f0d9b2d3f057559bedfb7e3077b9f7453ff5f +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 145 lines to infinigen/assets/materials/wear_tear/procedural_scratch.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 728397b670eb020cd3322beb7ed13525af8b2d55 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 11 lines to infinigen/assets/materials/wear_tear/procedural_edge_wear.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d6b4188a0558f548fc68e1f15f5660f08b3ddbae +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 265 lines to infinigen/assets/materials/wear_tear/procedural_edge_wear.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 44c1d4afbf120f2882ee8c9ece9cddecf849298b +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 53 lines to infinigen/assets/materials/marble_voronoi.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit 7e4792a75401cefa966046d139592178c11fec35 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 1 lines to infinigen/assets/materials/table_marble.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit b619525c8e481ec3034ef8265c65eb927680fb43 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 7 lines to infinigen/assets/materials/table_marble.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 4a0ff94693627c26f9eeecd73c7090ede6e458a9 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 150 lines to infinigen/assets/materials/table_marble.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 785feb8460bdb80476de0eccaebcd2970026cb02 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 64 lines to infinigen/assets/materials/brick.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 669baf98f5942c0f9b6a8f754ef7d55e111cda99 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 10 lines to infinigen/assets/materials/fabrics.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 4d1e999a114e3b51fec2dfd7fa1b20f045426e90 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 37 lines to infinigen/assets/materials/invisible_to_camera.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit aa98111c4223a788a8bb0450aefe483a1dc43ba9 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 5 lines to infinigen/assets/materials/black_plastic.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 9b07936f45b5653eafb07442a1bbafedc2f6e9f8 +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 17 lines to infinigen/assets/materials/black_plastic.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 7b9da237a6ec2a27b05268a313ea8e0c2ffcec3d +Author: pvl-bot +Date: Sun Jun 16 23:16:24 2024 -0700 + + Add 5 lines to infinigen/assets/materials/table_materials.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 368c5ea783509e9fa789654abe75627a4aba3524 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 53 lines to infinigen/assets/materials/table_materials.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit bc9fbbb58d9bd1e8504daa21c34f65a12efcfaba +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 105 lines to infinigen/assets/materials/table_materials.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 45503a50ced952138b604ef3e52dd245343b1702 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 3 lines to infinigen/assets/materials/plaster.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 02bee5beb4c026b817ec6a1ba66b08fd2ba5db10 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 6 lines to infinigen/assets/materials/plaster.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 9948be401b60810749aa7720186a9552f368cb70 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 50 lines to infinigen/assets/materials/plaster.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit d73a7f4143e59190561dc75aaacd076f38344dbd +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 5 lines to infinigen/assets/materials/microwave_shaders.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit f2f91c74084a7a626d79da55de67279637cafedf +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 22 lines to infinigen/assets/materials/microwave_shaders.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit ca81a3410a4ea78a4e7f0f70fd3e21febece52f4 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 21 lines to infinigen/assets/materials/glass.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit fe7ea88ee5d89542ed68203818f8ce0ac29008ed +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 27 lines to infinigen/assets/materials/glass.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 25de7cf093bcefa6e47b001a1d95bb0c564214ba +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 12 lines to infinigen/assets/materials/text_no_barcode.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 2c448bd7600ee5f9325237e0105890cde4f15f0a +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 1 lines to infinigen/assets/materials/art.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 161ad8dbf1fac7abe2fbd7cc9af8dea752fed9ca +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 99 lines to infinigen/assets/materials/art.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit d05b25800d93ce04ac9b6f8a72b4fc7c778ebf2d +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 1 lines to infinigen/assets/materials/ceiling_light_shaders.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 34ea11ac19760c874bb3c0dad730e28f5bdc60e4 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 5 lines to infinigen/assets/materials/ceiling_light_shaders.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 29adf5c572746c0f17c270c5964025386675c08d +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 39 lines to infinigen/assets/materials/ceiling_light_shaders.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 2bf4f659948e09831b3625229f592a02ccce93fe +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 8 lines to infinigen/assets/materials/rug.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit ffbc4298fec243eb63aec2779075f0ba193904fd +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 42 lines to infinigen/assets/materials/rug.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 36b4f6a2a84e5fe60ad21cb808fa50908a3c6116 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 27 lines to infinigen/assets/materials/text.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit daf6aa10469f3010be72e2f52051b352e4ab8133 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 35 lines to infinigen/assets/materials/text.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit 79fa245b06c84f260a3fd8e427a38414a2b09670 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 247 lines to infinigen/assets/materials/text.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit abea932da4a82b93122c454301986119a5acff1d +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 1 lines to infinigen/assets/materials/marble.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 4ea0527e45aa8ce28b1643776dd7f4a7cdde13ef +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 9 lines to infinigen/assets/materials/marble.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 3cac6eaf59ecfda77ffdc0bcb2a72e950db8e586 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 1 lines to infinigen/assets/materials/ceramic.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 88f1565d033d51c821eb14d7deb44013d3ec4757 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 43 lines to infinigen/assets/materials/ceramic.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 626505ff8fdadd7f0f392f2d11c4a106c8e1e85c +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 3 lines to infinigen/assets/materials/common.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit baa9da34a355c6256aa18c8fd682a047304d4e52 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 65 lines to infinigen/assets/materials/common.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 623a2e8a3c7d74ef8b96c78fb1075a3565ec5fef +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 1 lines to infinigen/assets/materials/vase_shaders.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 90571bcc82435659db0261f1213e1fae0edb0370 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 36 lines to infinigen/assets/materials/vase_shaders.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit e77d9aeeb5338974bf10fac68ef51154f62fc543 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 5 lines to infinigen/assets/materials/shelf_shaders.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit a683eeb235412d161222ace548610522eccddf31 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 7 lines to infinigen/assets/materials/shelf_shaders.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit f09f240257e0806006129709a80a0fc5df1546b7 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 16 lines to infinigen/assets/materials/shelf_shaders.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 163fa12dd8df4f253a68657c333146618302d92d +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 275 lines to infinigen/assets/materials/shelf_shaders.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit f0ac5fe836f35d8f197c02eb8f6cbfe43f3354d6 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 2 lines to infinigen/assets/materials/plastic.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit cb5f1e2485ab82f1dbead62c4e83ca95b60c6491 +Author: pvl-bot +Date: Sun Jun 16 23:16:23 2024 -0700 + + Add 22 lines to infinigen/assets/materials/plastic.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 662ed46607923119413dfff18928792e5fad305c +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 5 lines to infinigen/assets/materials/oven_shaders.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 155c1e63c33675d672ff29e77f9ec01983c1a13f +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 23 lines to infinigen/assets/materials/oven_shaders.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit e1c0fbaaa06ef8207a02bf7e5007f562ca5a1ed5 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 5 lines to infinigen/assets/materials/lamp_shaders.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 157452f95a2e2bbb08e99500e3e065b895dafa02 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 54 lines to infinigen/assets/materials/lamp_shaders.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 7ea3d7ba135246749cd4c95f603be04b3fcd68fb +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 58 lines to infinigen/assets/materials/marble_regular.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit b66bab6b1aaa7f6be8eab42b73da79bf612f5d63 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 5 lines to infinigen/assets/materials/beverage_fridge_shaders.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit 06c60da8843900b93dbd23e7e2e662b9b3a42a19 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 41 lines to infinigen/assets/materials/beverage_fridge_shaders.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 01c4f5a3f8046326141fcc1d3836ed93cea3ea04 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 4 lines to infinigen/assets/materials/glass_volume.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit acb9c4694f7d8c11a584cdc08266373822021003 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 27 lines to infinigen/assets/materials/glass_volume.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit f08a1c209574bc569ef841d235169fb436057f4c +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 5 lines to infinigen/assets/materials/dishwasher_shaders.py. Contributed as part of Infinigen-Indoors by Hongyu Wen. + +commit bd90bc19f48df70535c4196aed26cb38c15b6f47 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 80 lines to infinigen/assets/materials/dishwasher_shaders.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit dcfa1c29bb05278f3ab7d86a501c5cce07e3c8f2 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 176 lines to infinigen/assets/materials/bumpy_rubber_floor.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit c93dbdab8efb5b6138c917f7b4e532e55bd91913 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 1 lines to infinigen/assets/materials/hardwood_floor.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit e45420e702f79fdda176e932f569fb150ee4d093 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 1 lines to infinigen/assets/materials/hardwood_floor.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 7b2acadf7bb95301811034ad68684ac23bff1ef8 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 44 lines to infinigen/assets/materials/hardwood_floor.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 23ef5ddc787e100803fe4a52269d9e32fd74802d +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 18 lines to infinigen/assets/materials/mirror.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 74ea6a9f3f62f438ec9f4745d08a2124ed797126 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 5 lines to infinigen/assets/materials/tile.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 3dc64c65e87964af1abce0b32cfdb3d15fe9eeeb +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 341 lines to infinigen/assets/materials/tile.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 6334b54b59d4f17a5a14b5c6648e53fb3f65f52f +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 36 lines to infinigen/assets/tables/legs/straight.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 34eea1a7bf88503c3784bd3f35baa5264bf916c6 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 74 lines to infinigen/assets/tables/legs/square.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 07696a08c31117c138d90da8e2e2a28df77ed92e +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 217 lines to infinigen/assets/tables/legs/wheeled.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 7bd75d733ebd353b5f14b0bf7768a31172b5e9e0 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 34 lines to infinigen/assets/tables/legs/single_stand.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit b79a3df48e5787298cf047280195fab9b2308a10 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 33 lines to infinigen/assets/tables/strechers.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 6bedc524d60a0939de4fabcdcd7f5b95a4529cbd +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 1 lines to infinigen/assets/tables/cocktail_table.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit e94b3c4fdc0f06ef65acef1072d58b0f8e97af0f +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 30 lines to infinigen/assets/tables/cocktail_table.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 1de873bebd4176144d09d8f49cbc665000fb0432 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 35 lines to infinigen/assets/tables/cocktail_table.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 0f331061e48d1f7d39e8d38e71a63c472ccc80eb +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 37 lines to infinigen/assets/tables/cocktail_table.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 6e23bae05216fb4cad0db15070a32afb3aea5439 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 166 lines to infinigen/assets/tables/cocktail_table.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit dbc798cbca7c2f15248c2b1ae44d1c3c435461a0 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 1 lines to infinigen/assets/tables/dining_table.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit da3d71a574794dc9d2dddd46d2cd3fa20b39dc85 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 41 lines to infinigen/assets/tables/dining_table.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 0859b8e211653dbb3902922aa300a3d2fbd4eefa +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 44 lines to infinigen/assets/tables/dining_table.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d7ce755847f31a2b105991f833cca2402131c05e +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 88 lines to infinigen/assets/tables/dining_table.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit df7ec4d336dc36c67b005f640a1471420dea82ff +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 138 lines to infinigen/assets/tables/dining_table.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 456ec73ebc83cceab93e050bdb668d3428167148 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 1 lines to infinigen/assets/tables/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 6ff2e6edc6a702c37adfbb679e197b1173190b52 +Author: pvl-bot +Date: Sun Jun 16 23:16:22 2024 -0700 + + Add 6 lines to infinigen/assets/tables/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 39e2d65e49c7cbdd8ed87781e69ba9c30e6b43e1 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 298 lines to infinigen/assets/tables/lofting.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 30056c11bb0a78d907c8c81d9b5815c6c5cfd866 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 3 lines to infinigen/assets/tables/table_top.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 00ae17f4b184adba788947ef34f5c8a81300d10f +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 4 lines to infinigen/assets/tables/table_top.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit fedf45dc993f1847ecca50a8be5c24700c162500 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 167 lines to infinigen/assets/tables/table_top.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 31eae18d55fb37104355b808019c00352f143c77 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 514 lines to infinigen/assets/tables/table_utils.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 41ba5074f3d76dcd64b8672273a483eee3696e63 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 12 lines to infinigen/assets/tableware/lid.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 8f3eccf631ae70d9d4735e562d63c19a584c8622 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 111 lines to infinigen/assets/tableware/lid.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 58eae8af3a1db681e4e1bf8e6270ab8f2c7888e3 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 100 lines to infinigen/assets/tableware/pot.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit bed052a83d7bd49dc60bc3f7a98c3dbe2c63ed7c +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 2 lines to infinigen/assets/tableware/base.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit e00ef1a5a34b07fa0348b15ac0c4af0c9585d63c +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 13 lines to infinigen/assets/tableware/base.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 3d510db7b67a9f12aa1d5a9e19522d2879d01037 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 101 lines to infinigen/assets/tableware/base.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 28756d3082c93daf6c5a0fbf7b017a68cfa451e2 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 4 lines to infinigen/assets/tableware/wineglass.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit dfa8aba194405348662dfc8394c758883815fe37 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 46 lines to infinigen/assets/tableware/wineglass.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 5fc7345dbba81c554b2264734d23931ea99cf76f +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 6 lines to infinigen/assets/tableware/plant_container.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 81213a80d9a8837109a1dc3720addefeceb9cc7a +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 7 lines to infinigen/assets/tableware/plant_container.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 3e1e76a974e3ef26c4594ae069f90985dadb5403 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 18 lines to infinigen/assets/tableware/plant_container.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 232931b04c7726d350687d1f76a69fb1dbc1b742 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 98 lines to infinigen/assets/tableware/plant_container.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit a2449e6096bd0c972a37fc03d4e821747889c223 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 15 lines to infinigen/assets/tableware/bottle.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 568fa794ed7e643471cca4438bea4e87d0f863f5 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 131 lines to infinigen/assets/tableware/bottle.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit c3043e33a28c198f876ecb3ee14eb12200c1347b +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 3 lines to infinigen/assets/tableware/pan.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 28b0e537b98740d1e3878b67df2a61bb4b30eaae +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 3 lines to infinigen/assets/tableware/pan.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 5d531b840e1334d281f9df20241f0a3e2cf803bb +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 4 lines to infinigen/assets/tableware/pan.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 758022df9c287a500eaa44920b9f2072ebdc6db5 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 119 lines to infinigen/assets/tableware/pan.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 33d22a0d831ff9ec9df361c48a868db72832280c +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 1 lines to infinigen/assets/tableware/bowl.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 1c39257116f03a9a56fb858f3dfd9cc94b486089 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 1 lines to infinigen/assets/tableware/bowl.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 931d458ceec9a61392e04facd4ed9cf233c49001 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 49 lines to infinigen/assets/tableware/bowl.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 6e775b4b281d33ecbc964ce76a0225dad8ab1ad9 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 14 lines to infinigen/assets/tableware/can.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 25af5f99bedbe39c02b648062c3355cea388f307 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 83 lines to infinigen/assets/tableware/can.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 25d2c99251caa9ee977030ddeecbb6eba43db6e9 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 1 lines to infinigen/assets/tableware/spoon.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit c344270973d7f372206e396cca6f6aa45a3085ee +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 57 lines to infinigen/assets/tableware/spoon.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 1f218a8f0bd3dd3d0d0273c5a43ed93f9a970b70 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 22 lines to infinigen/assets/tableware/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 5e633172711e09a604f965b8bea807dff992b37a +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 8 lines to infinigen/assets/tableware/food_box.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 8653d8223c18cf82bb157188f2e747fdbe97a889 +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 29 lines to infinigen/assets/tableware/food_box.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 617715fd88dc95d612af8e10219094f2273a5a9b +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 1 lines to infinigen/assets/tableware/fork.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit c8ac06ba5530bf23319d807fc7b133f7f77772ed +Author: pvl-bot +Date: Sun Jun 16 23:16:21 2024 -0700 + + Add 88 lines to infinigen/assets/tableware/fork.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 2cb27e1efe6753bf560dddf9ce36812bb6f7bfd3 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 1 lines to infinigen/assets/tableware/food_bag.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit fa14d39a4c2f9f1a59eb11ae3948356b7e5ca868 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 5 lines to infinigen/assets/tableware/food_bag.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 7407655e2b3695e7baba18c345a50e6230236f39 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 77 lines to infinigen/assets/tableware/food_bag.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 46099b6335b9b81c890c82ae5d5b7d3dc7f15605 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 13 lines to infinigen/assets/tableware/cup.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit c541075ff1ad6c2e1c6579611491edf008f7f25d +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 123 lines to infinigen/assets/tableware/cup.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 2e2cc4c568700a98ccd69ead8abb0d2be3146fa3 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 13 lines to infinigen/assets/tableware/jar.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 6a04e800b79aa022968b5e3186f3ed51fbc00117 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 70 lines to infinigen/assets/tableware/jar.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 493f5e2fc34c7b8674c7305e71e584cf43a6709a +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 44 lines to infinigen/assets/tableware/plate.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit c25d30e9ae2a02e73a6cfa1adbddae95124bcd4e +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 1 lines to infinigen/assets/tableware/knife.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit c52c2fcb150e4f7d61b8ec577984417e4cfb52ef +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 94 lines to infinigen/assets/tableware/knife.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit cf2c2e7a3f869328193a5029089792e2d1ea3705 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 81 lines to infinigen/assets/tableware/chopsticks.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 0a568c05b94dd55c64aa5fbb9aae83dde506dca0 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 2 lines to infinigen/assets/tableware/fruit_container.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit ca3873993033a3cf1f3a548f2c51df8a178b31e2 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 2 lines to infinigen/assets/tableware/fruit_container.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 95ae48a23b24e9d20363b5a6257c4a6da7083970 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 67 lines to infinigen/assets/tableware/fruit_container.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 5457c0e1bfa25776823cb9a88f07bcc8b259cdac +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 1 lines to infinigen/assets/material_assignments.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit 95e480eaf59f540bcb3e92577fed83a869e06ed1 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 1 lines to infinigen/assets/material_assignments.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit b8698a60415eb2e9a4322ad670c8a65bdeff7295 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 15 lines to infinigen/assets/material_assignments.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 1020d08c520be75759994d1659c374a788cfd0bf +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 119 lines to infinigen/assets/material_assignments.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 477b144ccfe5b3674084dd4aebc92c47b4025f9c +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 456 lines to infinigen/assets/material_assignments.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit 55cd1691f43e40ebf55ce9fbf642951be316ab38 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 1 lines to infinigen/assets/color_fits.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 0457e88065ef011d6ebf4902cd6a1c9fe0cc3cc9 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 4 lines to infinigen/assets/color_fits.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit e13509e7a79b0332c9f9c31b1b325411c5c0cdbd +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 93 lines to infinigen/assets/color_fits.py. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit ac8f7da7f668944a2b90703785f856de9085ebd6 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 173 lines to infinigen/core/placement/path_finding.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit 27b21b60b3ccea2c833654aa6af960488d147a25 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 53 lines to infinigen/core/nodes/shader_utils.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 3663b5bdc766a8c8d95cc45423492318c5fd517d +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 79 lines to infinigen/core/util/bevelling.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit 3d167d62fb02d4dd5981f12c1db7784bef21a83b +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 182 lines to infinigen/core/constraints/constraint_language/expression.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ff7845bbdca992eea0b83afcbf761fdfb18b7e52 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 2 lines to infinigen/core/constraints/constraint_language/geometry.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 3d6ed7dfa5ebc37da1b89dea1d376ecca4787d87 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 35 lines to infinigen/core/constraints/constraint_language/geometry.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 4fdc87a90e46154b5c1b01aad0da4cb8b71ba6c8 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 55 lines to infinigen/core/constraints/constraint_language/geometry.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d958e95c7c9282e12eec8851240d0ab7c74e0be2 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 3 lines to infinigen/core/constraints/constraint_language/set_reasoning.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit ca9ac168061b3f11478a181252ad9ac4ab3f13bb +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 80 lines to infinigen/core/constraints/constraint_language/set_reasoning.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit c18ac5cd6489eed7e649644c9b6029954f1dbe04 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 32 lines to infinigen/core/constraints/constraint_language/result.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 0caf21a9433956c4b1d91b446ac4e55aae5ddbe5 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 3 lines to infinigen/core/constraints/constraint_language/types.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit ac681e51fef6068a7b1d6d1fe48215d237a724f8 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 42 lines to infinigen/core/constraints/constraint_language/types.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 4d7bf5ccfb12919b4ccf098b41633ab669ba2070 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 2 lines to infinigen/core/constraints/constraint_language/__init__.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 0b6840508f5df4c62a2e86d8888861e16c938bb6 +Author: pvl-bot +Date: Sun Jun 16 23:16:20 2024 -0700 + + Add 11 lines to infinigen/core/constraints/constraint_language/__init__.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit dbc84ef087c31f0d6b6c91a58839434b5c67b6f0 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 54 lines to infinigen/core/constraints/constraint_language/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 1d21848fd15d1a299ad9923bfc85e3303b2b6845 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 1 lines to infinigen/core/constraints/constraint_language/relations.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 456641d7bbc095eb65dc21b55e91e8fa1fd44f38 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 3 lines to infinigen/core/constraints/constraint_language/relations.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit f34257b82395078bd6065a9997a40e6c48aac08d +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 10 lines to infinigen/core/constraints/constraint_language/relations.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 3ef3738a8dcee658ea17a6592707f87cd0449933 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 37 lines to infinigen/core/constraints/constraint_language/relations.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit c918a1a7c576922e591fd31c0fb271cdd9079740 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 363 lines to infinigen/core/constraints/constraint_language/relations.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 26d01a01e3fed1ffb429ab658c99a2bd8dd438f4 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 4 lines to infinigen/core/constraints/constraint_language/util.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit dae03a8635a91fea9455b4000be91e50869e1e70 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 46 lines to infinigen/core/constraints/constraint_language/util.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d201928001b14fb1b31f2c3d028eae9410d627c8 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 354 lines to infinigen/core/constraints/constraint_language/util.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit bc6c0f62123d840ae3bf021e71a634a3f27dc1a4 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 1 lines to infinigen/core/constraints/constraint_language/gather.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 7ddb8bf1fc02f6b98f5ac343c2628e20cc7d6822 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 66 lines to infinigen/core/constraints/constraint_language/gather.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 9b1cf7e422b7d5f0a8764b6406f6f7a386fdc75c +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 1 lines to infinigen/core/constraints/example_solver/geometry/parse_scene.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 61f37934754a4ead8ba877dd4f6ebf2c49e96ed0 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 3 lines to infinigen/core/constraints/example_solver/geometry/parse_scene.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 32df7740ccac5613f22f73875a24dc9ba613c074 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 7 lines to infinigen/core/constraints/example_solver/geometry/parse_scene.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit f35fc07c4dbad5c5e265f41415abdde273ebf685 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 68 lines to infinigen/core/constraints/example_solver/geometry/parse_scene.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 1ab422dbfb911a09e42e94c1893bc2a7f9313a41 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 7 lines to infinigen/core/constraints/example_solver/geometry/dof.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit bfbdf35a4c0dae51df44cf0a7a1d8fc79a46f759 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 10 lines to infinigen/core/constraints/example_solver/geometry/dof.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 88ea5301262d0994d8d81c556e3e2a9103f1bb6d +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 150 lines to infinigen/core/constraints/example_solver/geometry/dof.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ef8d79f4978cee56e20c9be9ceda3be7a5d97f79 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 259 lines to infinigen/core/constraints/example_solver/geometry/dof.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 916620ccace0ff797e9f0c9f617ec323e5ed8f68 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 8 lines to infinigen/core/constraints/example_solver/geometry/planes.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 6158f85e7cf94639f49817cc5ea6f90a060c46b2 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 27 lines to infinigen/core/constraints/example_solver/geometry/planes.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 52d7c9285526874fb414fca98720e9044a954c1d +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 268 lines to infinigen/core/constraints/example_solver/geometry/planes.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 583cfa63dfd002a3657f4e5afe80e96714a85d46 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 55 lines to infinigen/core/constraints/example_solver/geometry/validity.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 0e426d6ac5c64e4f08863610bf2b14c7a77a2916 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 67 lines to infinigen/core/constraints/example_solver/geometry/validity.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 2d568a7216a3689492671c6d29add4fc001e1959 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 4 lines to infinigen/core/constraints/example_solver/geometry/stability.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 87c3cf5d47d14aa50f656f2a7e4c93fe2a88eda4 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 88 lines to infinigen/core/constraints/example_solver/geometry/stability.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit af67ce66529d5ca35b1d25acfca6e78467222660 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 266 lines to infinigen/core/constraints/example_solver/geometry/stability.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit c2a3b3fd7f012d4b36022b7f6a0fdbb8f0078fa0 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 1 lines to infinigen/core/constraints/example_solver/greedy/all_substitutions.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 9ecb38199f63d824bf3ad2cc9ce0a6911941b85d +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 175 lines to infinigen/core/constraints/example_solver/greedy/all_substitutions.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 2b9f4e90f48147831f268120155a4b03341ec3bf +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 3 lines to infinigen/core/constraints/example_solver/greedy/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit b4dd499c70e776cb005df06afa6cced7ac0d29c9 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 269 lines to infinigen/core/constraints/example_solver/greedy/constraint_partition.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit b48db72ebc9cbadc3e13496dba9259128e1d17cd +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 113 lines to infinigen/core/constraints/example_solver/greedy/active_for_stage.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 51db9cf3a26e6f09eaee5a939d2bf64bdb0d1a39 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 6 lines to infinigen/core/constraints/example_solver/room/scorer.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 10ba901f8f74f2af858b25caff34c6c589b624d2 +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 25 lines to infinigen/core/constraints/example_solver/room/scorer.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ddea8ba03e3a830cee88a409a97a9878d928514a +Author: pvl-bot +Date: Sun Jun 16 23:16:19 2024 -0700 + + Add 250 lines to infinigen/core/constraints/example_solver/room/scorer.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit bf0061463fe46732be27224c0482df7bee02da9c +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 4 lines to infinigen/core/constraints/example_solver/room/contour.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit dd5145fb9a01b8dea38608ee3425e7912d751008 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 128 lines to infinigen/core/constraints/example_solver/room/contour.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 4fcfbfaa6789cb6280975241a092e2a6197f457a +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 4 lines to infinigen/core/constraints/example_solver/room/segment.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit e1fef368f783b8ee3562f13f18c275a13c71bcf6 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 6 lines to infinigen/core/constraints/example_solver/room/segment.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 16a4b09351109d95b38b61e826ece1e648e18c04 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 134 lines to infinigen/core/constraints/example_solver/room/segment.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 0530bce30cf4bd41415c6c00df1fbd5951462dae +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 3 lines to infinigen/core/constraints/example_solver/room/utils.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 16825ed562b6b4b35725105a83633889ee644245 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 7 lines to infinigen/core/constraints/example_solver/room/utils.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit ace8689ac2bf5a3bb168a970b312d79acb3a529a +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 139 lines to infinigen/core/constraints/example_solver/room/utils.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit af2325601a5e42db7a19324126af72d5ef4d4df0 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 80 lines to infinigen/core/constraints/example_solver/room/types.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 0a2b8a3dfcf4b847aaca0369d22ce704f28f2fc2 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 5 lines to infinigen/core/constraints/example_solver/room/decorate.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit c5b65d8aadc8427388cf6e74eb643ceebe606004 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 11 lines to infinigen/core/constraints/example_solver/room/decorate.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit 865e1a546bb959012bc30d39636b33c81112ba58 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 24 lines to infinigen/core/constraints/example_solver/room/decorate.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit ad83cb791cee28e44591e48109bfce537e5ba0b9 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 114 lines to infinigen/core/constraints/example_solver/room/decorate.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ed4ba693dfaf0bff54824d67878b52f5f91b09c4 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 206 lines to infinigen/core/constraints/example_solver/room/decorate.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit c3f9a0e5a1b26c39b5ac418bd0e2c30aa848f449 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 6 lines to infinigen/core/constraints/example_solver/room/__init__.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 25367eaf2e1069da9033dd41558fb58360470f61 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 11 lines to infinigen/core/constraints/example_solver/room/constants.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 543db110eea4a715a36ed4f949cdc7f9b7475581 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 15 lines to infinigen/core/constraints/example_solver/room/constants.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 583dd74e4fd7fa9a6fc630fb769123523e6c09bf +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 74 lines to infinigen/core/constraints/example_solver/room/constants.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 3fda728ff4c24f5b1e823cc7a6346a13a328f2bf +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 2 lines to infinigen/core/constraints/example_solver/room/graph.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 39bed180c090fdaca6a1a779e646f975d9f758b0 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 165 lines to infinigen/core/constraints/example_solver/room/graph.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 3e5ff0ccd2f2e1d419d548eb57db8a130000dbac +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 6 lines to infinigen/core/constraints/example_solver/room/blueprint.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 2f59ac5283a9b15d19af511c1f8faeef33a41ddf +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 9 lines to infinigen/core/constraints/example_solver/room/blueprint.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 63ad9b392d05fe6bfb2e62f2a1980bf4d0ef92ec +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 29 lines to infinigen/core/constraints/example_solver/room/blueprint.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 63eb43c822d0a34e45e53ac0fc995177cc6d81ef +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 174 lines to infinigen/core/constraints/example_solver/room/blueprint.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit da6e3f76c131b27a6635f72acaa5a2976776e7df +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 6 lines to infinigen/core/constraints/example_solver/room/solver.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 8d1fd1a4966375fdb05377b181a24cc24d80cfa2 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 6 lines to infinigen/core/constraints/example_solver/room/solver.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 6d74e65184ca2dda03605030f24d7fd9ebfb1774 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 273 lines to infinigen/core/constraints/example_solver/room/solver.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 97c20302917aee0f88879a3bd8f0e51f979e289d +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 16 lines to infinigen/core/constraints/example_solver/room/configs.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d455f742702d4eabe271fdd5d3a70ad8deb86d68 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 116 lines to infinigen/core/constraints/example_solver/room/configs.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 8632374545f5d688b6442d3649a4415df0912008 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 4 lines to infinigen/core/constraints/example_solver/room/solidifier.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 4ca8eaeef25c3858ae055c50ba98d849ed22b2f6 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 12 lines to infinigen/core/constraints/example_solver/room/solidifier.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit ebf4d17d872ed6fc76318a160459abd51b9501fc +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 99 lines to infinigen/core/constraints/example_solver/room/solidifier.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 9f7e37e7cf85a981818dbc5d19296c4542357cfc +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 321 lines to infinigen/core/constraints/example_solver/room/solidifier.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit d81ace5d0dc717e43a4b4dc8b51f51928cb75616 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 52 lines to infinigen/core/constraints/example_solver/moves/deletion.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 8e75f73d36c28eccc706eedb081f5e448005d231 +Author: pvl-bot +Date: Sun Jun 16 23:16:18 2024 -0700 + + Add 6 lines to infinigen/core/constraints/example_solver/moves/pose.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 3808681f221f3d2ee91dd76f6f7037c9718cc792 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 116 lines to infinigen/core/constraints/example_solver/moves/pose.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit b138eed715cfafeadae881e23cda345fb4afdf47 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 5 lines to infinigen/core/constraints/example_solver/moves/addition.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 7dcaea581871a50a0794aa969e7329ba5a7d8e81 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 15 lines to infinigen/core/constraints/example_solver/moves/addition.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 0873d0169cfc9738cccd5db487e9e45043f0a0d8 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 168 lines to infinigen/core/constraints/example_solver/moves/addition.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 25fbdff205a763aa8cf6b4fc8d59d76f43871066 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 6 lines to infinigen/core/constraints/example_solver/moves/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 1761c4421d95fd6a3015399c1571686e7894b6ce +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 62 lines to infinigen/core/constraints/example_solver/moves/swap.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 5e533523032ace58b07df14e3e3044f3a6fab335 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 4 lines to infinigen/core/constraints/example_solver/moves/reassignment.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 97ba36b8a1384a3f4e9c72e82993373de0873e22 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 106 lines to infinigen/core/constraints/example_solver/moves/reassignment.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 0f011dcc7187162c8904ca95e3346726674aa566 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 37 lines to infinigen/core/constraints/example_solver/moves/moves.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 593bfbb6dc2db247efb7a896f1c70efd878a2dc1 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 6 lines to infinigen/core/constraints/example_solver/populate.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit a782b61f7527bf10243d93bb91920fc7cc76c340 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 114 lines to infinigen/core/constraints/example_solver/populate.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit b347420d27ab4d491d0bf5fc73471196371e3fd3 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 4 lines to infinigen/core/constraints/example_solver/solve.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit ef9be11101b2ff415f74cc2d59c3945ec09e4425 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 6 lines to infinigen/core/constraints/example_solver/solve.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 1ff89f29a71008c90bd83f1ae240d22986f878a1 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 8 lines to infinigen/core/constraints/example_solver/solve.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit 31ae234624d32ef8eca1f89270dadda3f1e6d95f +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 8 lines to infinigen/core/constraints/example_solver/solve.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 557c0b3cb021a49a31dac7ad6206901fb4b5ad5e +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 216 lines to infinigen/core/constraints/example_solver/solve.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 973168125e36524d08f3b3698b089fd3e0897165 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 148 lines to infinigen/core/constraints/example_solver/propose_continous.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit c7814a710cfb43fc44067d757ad7aa2529f1c8c2 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 5 lines to infinigen/core/constraints/example_solver/propose_discrete.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 69ef24df083a866401e5882984da9ce2d405dbaa +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 12 lines to infinigen/core/constraints/example_solver/propose_discrete.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 39eb45fb980ff9ce23b8b8233a1a9aa77b9b8c99 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 333 lines to infinigen/core/constraints/example_solver/propose_discrete.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 022a462ba8423da64ecdb31509a7f0693a43ac4d +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 2 lines to infinigen/core/constraints/example_solver/state_def.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 2b2a2a8908ad5be22cab890f9725ab99d651580b +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 7 lines to infinigen/core/constraints/example_solver/state_def.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 50d9ffce021d424607d38a0a6be61efab5b7e58c +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 9 lines to infinigen/core/constraints/example_solver/state_def.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 1f7e3022a0b786f6a31d5d7c38d033efbef5b578 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 17 lines to infinigen/core/constraints/example_solver/state_def.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 1d17d97b8b356e2681746efd06c934c33967fd79 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 172 lines to infinigen/core/constraints/example_solver/state_def.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit c95abae83707ef96f7a4eb5a0714d10bf7fc937b +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 3 lines to infinigen/core/constraints/example_solver/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 677077fdeda5df24773f72cbee2c240282e70ee6 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 151 lines to infinigen/core/constraints/example_solver/propose_relations.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit f4fbb450d29abf9ae6df0d82fac5ea41275cf443 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 7 lines to infinigen/core/constraints/example_solver/annealing.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 7362439725681f36134812843fada28cd96e9945 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 365 lines to infinigen/core/constraints/example_solver/annealing.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit e001ff8e3c5c63b4f7c513eecb9378b4d085cb40 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 66 lines to infinigen/core/constraints/reasoning/domain_substitute.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit df4f36249cda10ba79743c4d0a31ee22136cb1f4 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 21 lines to infinigen/core/constraints/reasoning/constraint_constancy.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 2365e12080add0ee8abe934b1ac36586236a64c6 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 22 lines to infinigen/core/constraints/reasoning/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 61eed6d6efcf19058ad0e9b870472c7050a1e6c6 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 1 lines to infinigen/core/constraints/reasoning/constraint_bounding.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit b14e66eb75e032ce514a126b6014983dda41a80a +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 35 lines to infinigen/core/constraints/reasoning/constraint_bounding.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 3419d1bf0431941075dc9f00b37674c9aa576963 +Author: pvl-bot +Date: Sun Jun 16 23:16:17 2024 -0700 + + Add 174 lines to infinigen/core/constraints/reasoning/constraint_bounding.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 2d1acf44a3b263f7d77a63a904a0d4d9d6faaf8d +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 1 lines to infinigen/core/constraints/reasoning/constraint_domain.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 0aa2c390433233d3e71f349eff4bef08945bbd89 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 74 lines to infinigen/core/constraints/reasoning/constraint_domain.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit cd88dd267a99b53daef2dd345756378754d87c4b +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 416 lines to infinigen/core/constraints/reasoning/domain.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 4098d2de731f4e49dc475b620efbca10063d186e +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 64 lines to infinigen/core/constraints/reasoning/expr_equal.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 8769347e49682e70da29f51bde06408193555952 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 3 lines to infinigen/core/constraints/evaluator/node_impl/impl_bindings.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit a733a20254dfadb7d1a28cda189b6520245cb46c +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 42 lines to infinigen/core/constraints/evaluator/node_impl/impl_bindings.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 21401795ce7981746fc11ed844be21a3adf7e1b5 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 52 lines to infinigen/core/constraints/evaluator/node_impl/impl_bindings.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit ad7d9faf1c58defa8edd2f0be30ebead50c51095 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 277 lines to infinigen/core/constraints/evaluator/node_impl/impl_bindings.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 9b8e3674ca870e62322a669c6487a0a3851a005f +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 1 lines to infinigen/core/constraints/evaluator/node_impl/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit dac544843a5c7206e05a80a9a46538b86ca39881 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 9 lines to infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 33666d75b7b0da783702f12c7a34f0bd347bba96 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 170 lines to infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit e92b795126b13f3d909a529b1f5519eb7894e289 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 993 lines to infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 8ae175b7bd2e0e53c39df347e2f051e4c7eec7d2 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 1 lines to infinigen/core/constraints/evaluator/node_impl/symmetry.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit e6ec3d3cef708147e1161cff12e0829b2f9944a4 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 262 lines to infinigen/core/constraints/evaluator/node_impl/symmetry.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 5b26f6c7497a7258a7cf15ab63f909047a48f9f9 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 5 lines to infinigen/core/constraints/evaluator/evaluate.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 09e2990ed80fd498db440415359e933702f2404d +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 26 lines to infinigen/core/constraints/evaluator/evaluate.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 9bb4d4bb9633d8795fc0c5ea059fdb5bf19d4c20 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 224 lines to infinigen/core/constraints/evaluator/evaluate.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 08afd13cf72f6512988e496bdfea27417c552458 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 231 lines to infinigen/core/constraints/evaluator/indoor_util.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 3ab967494e9146aa2f90c0ad51c85a6bdd8461e7 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 115 lines to infinigen/core/constraints/evaluator/eval_memo.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 128ed562848dd7f178d032df9a445ce6a13ae858 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 10 lines to infinigen/core/constraints/evaluator/__init__.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 6d50d8daf81b530eef2037c0f7468644b71778d6 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 1 lines to infinigen/core/constraints/evaluator/domain_contains.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 6512c8d39b0d76f7f6ea136d971d638305f3c262 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 59 lines to infinigen/core/constraints/evaluator/domain_contains.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ad642910695461d192beaaa47a6ce04ed3a6fa08 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 126 lines to infinigen/core/constraints/checks.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 2dc5fec21b78d1288568649f82f4e52d69fefa82 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 3 lines to infinigen/core/constraints/usage_lookup.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit dd7dd7b90e96a16f835d37f71ab924f2e637e9f1 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 45 lines to infinigen/core/constraints/usage_lookup.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 7d8a4772287b04e352f8a9da16f09a6831a36ddb +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 9 lines to infinigen/core/tagging.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 92d4eb9d92908b51ba472572bcd5900ddbd94558 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 19 lines to infinigen/core/tagging.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 9023e340b9fb925c4c2ee2c7c30dfbd20e2ce03f +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 25 lines to infinigen/core/tagging.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 9842170aeb3590081273d34a484c3778d5434636 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 30 lines to infinigen/core/tagging.py. Contributed as part of Infinigen-Indoors by Lahav Lipson. + +commit cb923fe808235484edfb263e6329b5f1a6dfb5d4 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 345 lines to infinigen/core/tagging.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ef8e50d19d46ebb108e367069ea5d25a07509218 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 3 lines to infinigen/core/tags.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit f69c57cf10cb89db286bf531484852c40f8394be +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 7 lines to infinigen/core/tags.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit dd9c78879a011f8d260e9462c2c0ad2fb5ed1bec +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 80 lines to infinigen/core/tags.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit a9c72789e0e44f13929c7b64a667c6c549f4be31 +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 215 lines to infinigen/core/tags.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d4331267447675006a16589e668fb53540784c3a +Author: pvl-bot +Date: Sun Jun 16 23:16:16 2024 -0700 + + Add 1 lines to infinigen/tools/results/visualize_planar_graph.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 6c3a0045a1d697244c987ab51d9f1fa2dcb16330 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 70 lines to infinigen/tools/results/visualize_planar_graph.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 0a4ef0e03a609e4b4b56c306126f0bc2fc899b3c +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 12 lines to infinigen/tools/config/demo_config.yaml. Contributed as part of Infinigen-Indoors by David Yan. + +commit e823bcbd04b5579744253bcbc05bb5ccbd34e78c +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 37 lines to infinigen/tools/perceptual/create_submission.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 3b360a665f80a6e66b14c798d15b41ed83bfe6b1 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 411 lines to infinigen/tools/perceptual/analysis.ipynb. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit b47966ce5cc1ec88232b9b566048c488fa834823 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 34 lines to infinigen/tools/perceptual/perceptual_extract.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 3262add86904d8f21f31365c54055ff3839b11fd +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 12 lines to infinigen/tools/perceptual/rename.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 65a5a561b822f224e79b03d1fb76bf63450bc310 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 13 lines to infinigen/tools/perceptual/rename.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit dc15981b2a1cea10bcea46baf540675008996ccf +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 187 lines to infinigen/tools/perceptual/create_pairs.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 4b94c4073e6493683f2a8760fd503b15ba38a355 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 124 lines to infinigen/tools/convert_displacement.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 757024b09ce3cd182b64b8adcec9da02beffb638 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 32 lines to infinigen/tools/isaac_sim.py. Contributed as part of Infinigen-Indoors by Beining Han. + +commit 36b10aa2d5f157746a58eab317d0f04ef5cc242e +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 139 lines to infinigen/tools/isaac_sim.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 9bed07285779baa685d0446a0ecbf4f003e85ce9 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 83 lines to infinigen/tools/indoor_profile.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 2b09762b5cadff70822cf8d226d600c991e9c276 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 63 lines to scripts/eevee_render.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 618739d0000fb680f8ac74b3d2009d6e8ddb0de5 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 11 lines to scripts/rebuttal.sh. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 2705f90c1b22e3b4ad340c18f4c52a3434f5b1d4 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 72 lines to scripts/rebuttal.sh. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 4852bbac5f3d23fa41b9afd852c796ff93b58f8e +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 8 lines to scripts/indoor.sh. Contributed as part of Infinigen-Indoors by Beining Han. + +commit 3696c69adefd7551b4dc05ef618e39e0976b5132 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 36 lines to scripts/rebuttal_retry_render.sh. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 62a1a4fec2ff9feb7ad64781a7e6cae330b2386d +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 2 lines to docs/HelloRoom.md. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 95533f59846161e86190fd2ebf81c70e0736d870 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 125 lines to docs/HelloRoom.md. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 3160f6188c5cbe8b5d339207a0ca92c699da2de6 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 1 lines to docs/ExportingToSimulators.md. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 699f3ae821fd3687068ea11833ad9cd5c68d8959 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 32 lines to docs/ExportingToSimulators.md. Contributed as part of Infinigen-Indoors by David Yan. + +commit 9f486f85ff5bd58c20d51c068c4382ae0e19986b +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 1 lines to tests/assets/list_indoor_materials.txt. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit 6e20cbe65894225c5767da3a080e38df01a8d3bd +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 1 lines to tests/assets/list_indoor_materials.txt. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit 05cd77cffd7678711516111f8b2f887693cb2e47 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 1 lines to tests/assets/list_indoor_materials.txt. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit a1bfe6d288def89442a667957e0dce7c08c6fe51 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 3 lines to tests/assets/list_indoor_materials.txt. Contributed as part of Infinigen-Indoors by Meenal Parakh. + +commit ac5cb276f32fc01a36bdfb9201165da86c359f6a +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 12 lines to tests/assets/list_indoor_materials.txt. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 36ee4ea749d40aa56ceb2b81743e61e32297ba14 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 17 lines to tests/assets/list_indoor_materials.txt. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 877e6688541fe6932124ff8d621dfe26f69dfaf2 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 7 lines to tests/assets/list_displaced_materials.txt. Contributed as part of Infinigen-Indoors by David Yan. + +commit fdf533d6bb65cbaece0a6b813193965b4a17205e +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 7 lines to tests/assets/test_materials_basic.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 125472b563eb95b76a7ccdc06539cc3c1d0e40ec +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 40 lines to tests/assets/test_materials_basic.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 8da48f0f045bef1a9f85c1d9ed9ef9eafc8376c8 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 1 lines to tests/assets/list_indoor_meshes.txt. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit b15a70034bf2688c53ed38085e7dbc0b8ae49640 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 1 lines to tests/assets/list_indoor_meshes.txt. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 2082175925a9365625e5bbd089446456a6043d05 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 1 lines to tests/assets/list_indoor_meshes.txt. Contributed as part of Infinigen-Indoors by Beining Han. + +commit 6db1fbcd3f0b658dbbfb81d2a512805b428f982f +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 3 lines to tests/assets/list_indoor_meshes.txt. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit e811301b630d04ab5779b31691dd63e8787e7fbe +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 28 lines to tests/assets/list_indoor_meshes.txt. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit b8e0d257ae54e6a6dd12aa0c5e358a0aa42597f1 +Author: pvl-bot +Date: Sun Jun 16 23:16:15 2024 -0700 + + Add 66 lines to tests/assets/list_indoor_meshes.txt. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit aecacaa0f695c4f5dde9b4932c2f7af9a306c4bf +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 1 lines to tests/assets/test_meshes_basic.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 38db10ef300d9dd30344fca1c972038a59cdc10e +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 89 lines to tests/assets/test_meshes_basic.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit a6c9bfe47d85fb65fac8ea2ef02befb3fbaa6610 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 4 lines to tests/assets/test_placeholders.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 94f7d2b0044462d5091eb8aaada511a9ecb2f482 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 7 lines to tests/assets/test_placeholders.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 8d051a01c0446760ffd9d84a0b36ef8411307972 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 57 lines to tests/assets/test_placeholders.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 9bbb94aedab2b9d80a99575b380eee26a1b9468f +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 21 lines to tests/solver/test_greedy_stages.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 48d39929f807b7b9fe9161ff57a5c904defedc28 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 286 lines to tests/solver/test_greedy_stages.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit cd6d04fc548b5507328c506220d999f181a74e19 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 44 lines to tests/solver/test_state_def.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 0e2fe0f9f6feead7ee9fb60669a5fe2d2b1fd30d +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 120 lines to tests/solver/test_greedy_substitutions.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 202e3c39073f21064b02c12f2353815f766d98b2 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 471 lines to tests/solver/test_greedy_partition.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 852cfa452f0e4a8b25e7756cdc0ea408176618b0 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 5 lines to tests/solver/test_asset_surfaces.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 5f032e1af206c1c3ae9cc5518e865f42838ea8b0 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 56 lines to tests/solver/test_asset_surfaces.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit c1f6ced9ef5d8853a248e45526985f7c926f05d7 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 83 lines to tests/solver/test_constraint_evaluator.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit e0b4e342488190622fc1ad5bbfeaa4ff73cc52a1 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 218 lines to tests/solver/test_constraint_evaluator.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit f080965f5e1af641872fb2bde4d7229d7b50bb1f +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 679 lines to tests/solver/test_constraint_evaluator.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 6baf8e5fb4f0683e9c6ef185163336db9d050785 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 6 lines to tests/solver/test_stable_against.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 662dbf04c3915cdfec063ae05aa5b9f3ecd10cdc +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 74 lines to tests/solver/test_stable_against.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ebc6fdcca9cbd9b7173dd3d3f7f10421f81247ec +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 84 lines to tests/solver/test_stable_against.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 22b6f554ff6cb02ac63a55d131dea5842b51a3ed +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 29 lines to tests/constraints/test_constraint_domain.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 71b77e1fb2848a82ccc6befafbb107f28471d5c3 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 170 lines to tests/constraints/test_constraint_domain.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 8e1ea377cc33439e2394a36f68170c1807b29e3e +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 13 lines to tests/constraints/test_constraint_relations.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 2c13da8ddfa26450038aefe01ddbc82095df6d4f +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 83 lines to tests/constraints/test_constraint_relations.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 8e3e746291e9d206be336e80b392a4b94831d242 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 77 lines to tests/constraints/test_reldom.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 0c723cdc2bc2c2deba9a01c21f22697c7153fc85 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 18 lines to tests/constraints/test_tagset_operations.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 35b393f202154e59710ed4f8ebf778c2229e2e01 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 25 lines to tests/constraints/test_tagset_operations.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 7dc61ce5765e74391129836829bf99c4526e8e2e +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 15 lines to tests/constraints/test_tags.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 33b8f9d33f4af56a0059a0ef4897ab69c38306fa +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 43 lines to tests/constraints/test_constraint_language.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 992a1a7c2d3ed371fdd5681c7391868f6aa2b843 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 10 lines to tests/constraints/test_constraint_bounding.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 49df4a176209f3ebe004adbffae8f632684381f4 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 27 lines to tests/constraints/test_constraint_bounding.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit ee2950ffc77b28dfeb3ee25be16ae76459203314 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 154 lines to tests/constraints/test_constraint_bounding.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 9b03e4165f17be3e0bfa1675ead1980c9157aaaa +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 15 lines to tests/core/test_tagging.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 5318cf79dda4173a7c982720bab2993fc1692173 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 82 lines to tests/core/test_tagging.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d01a5d270c151af34ba440f7341b0f770fcea207 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 39 lines to tests/core/test_gins.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit e6dd3c610de11316ac90e72c487692893f5a5a15 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 28 lines to tests/tools/test_export.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 843d90397d39b2e494500b8327d8c5c4b7dd2a7c +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 52 lines to tests/tools/test_export.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit aed7c77b255edc60065effa0cf9eb08cbb17f022 +Author: pvl-bot +Date: Sun Jun 16 23:16:14 2024 -0700 + + Add 7 lines to tests/list_displaced_materials.txt. Contributed as part of Infinigen-Indoors by David Yan. + +commit 7a93f7205e08c2dbbaf7bd6d7f4270fb43e3e5de +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 35 lines to tests/material_balls.txt. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 13f280963796bb7207651997d1f53acff9fa2d49 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 3 lines to infinigen_examples/configs_indoor/disable/no_objects.gin. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 428c51532435454b2c5c154cc7f11cd91d1d395e +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 2 lines to infinigen_examples/configs_indoor/disable/no_details.gin. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d2fb7a324f5187906cbc9c1a064dad866cca5345 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 7 lines to infinigen_examples/configs_indoor/disable/no_details.gin. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 88bf8ec0ea810ba2d36ef6e85a94c1996841a8a1 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 1 lines to infinigen_examples/configs_indoor/studio.gin. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit dbd9d60f7a2b4e70fe41ae940c6087565479fb3a +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 7 lines to infinigen_examples/configs_indoor/multistory.gin. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 82d9f85836625a41e6daf71a0e06a77f5149fade +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 7 lines to infinigen_examples/configs_indoor/multistory.gin. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit d0531a71f35682af71fe08839ac2e98af444e4f5 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 7 lines to infinigen_examples/configs_indoor/overhead.gin. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit b8d11d80341482b5feb857933df993ccbdb8a34d +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 8 lines to infinigen_examples/configs_indoor/overhead.gin. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 8ff32f1b8f2bb865db6eab51f3ba9967af487227 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 19 lines to infinigen_examples/configs_indoor/fast_solve.gin. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 82ceea367391f619e680eebfeae5b32870d506bd +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 4 lines to infinigen_examples/configs_indoor/topview.gin. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 8d9ba331e5c2054f874a9129988070bc8280220e +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 4 lines to infinigen_examples/configs_indoor/topview.gin. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 3d403c4644bcf455ea157ae494a4b18869031b80 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 2 lines to infinigen_examples/configs_indoor/export_upload.gin. Contributed as part of Infinigen-Indoors by David Yan. + +commit 7b89026662f8161f5da8c99d6d58bc8b32d5ea0b +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 2 lines to infinigen_examples/configs_indoor/singleroom.gin. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d9be42ac856eb1f0ae977cdddcb8021191aab744 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 1 lines to infinigen_examples/configs_indoor/base.gin. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit 478badcddee79412359dd81d166a1ba7afa3c5d1 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 6 lines to infinigen_examples/configs_indoor/base.gin. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit 2717fe91df5be267d07fd2efe689364c8982a33a +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 58 lines to infinigen_examples/configs_indoor/base.gin. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit bf64297829c4da6e7f86c6ac7911a58ef07560a3 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 1 lines to infinigen_examples/util/constraint_util.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 737265cf42087b551f2133e7a6a14cc36a751c11 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 62 lines to infinigen_examples/util/constraint_util.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit ccb851d7cf43fa769ce8cc33ab9b2687f6b122c9 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 2 lines to infinigen_examples/util/generate_indoors_util.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit a1c4834c601099c572b5ccbaec69b4605e13f2e2 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 5 lines to infinigen_examples/util/generate_indoors_util.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit f4ba54c94235d9aa715a491cec9dd2d4ebd6a0ab +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 258 lines to infinigen_examples/util/generate_indoors_util.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 3a7e3a7750a01cb4db3409e9c4ba624c3525e419 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 1 lines to infinigen_examples/util/test_utils.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit c6cdfa71852761ddbcd30332c7af12258b165ddb +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 2 lines to infinigen_examples/util/test_utils.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit 16711191bc293f451ad0561a37877340ea2bc7d2 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 57 lines to infinigen_examples/util/test_utils.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d01430919464dd048c3cae264ef7a30da737c8b4 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 1 lines to infinigen_examples/indoor_constraint_examples.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit f053a032b24963a46e69561caaebb8c0516f2de0 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 1 lines to infinigen_examples/indoor_constraint_examples.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit a7f5c07e33c48f3894fb7e6b7eed96ef2b5a45b4 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 2 lines to infinigen_examples/indoor_constraint_examples.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit b03147cd6fff0d0e76f4cd82b4963be3728f15d5 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 2 lines to infinigen_examples/indoor_constraint_examples.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit b97b816b14df8ed6ea73cb087b52166373d9332f +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 19 lines to infinigen_examples/indoor_constraint_examples.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit 6014f883ac56ba254ad39909db0f0cd1a2a6e1ff +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 865 lines to infinigen_examples/indoor_constraint_examples.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 6c789c103d9cff2491973cbafd0dd5ad5df91322 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 203 lines to infinigen_examples/generate_material_balls.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit e46952295de3b90d5aa0aaf58452c4fd17ce5a39 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 6 lines to infinigen_examples/indoor_asset_semantics.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 3bd77582713236871d15fcfcaf3f880308f39c82 +Author: pvl-bot +Date: Sun Jun 16 23:16:13 2024 -0700 + + Add 332 lines to infinigen_examples/indoor_asset_semantics.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit e46aefc5bee3d3979609803ca30430e2ee9bf3f0 +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add 45 lines to infinigen_examples/asset_parameters.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit f4eeb4bfe1272599af4e16c1c3483a4aaae7a3de +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add 1 lines to infinigen_examples/generate_indoors.py. Contributed as part of Infinigen-Indoors by Stamatis Alexandropoulos. + +commit 9bca4587dff99a094a4c16047fb18207ebdc3dbe +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add 1 lines to infinigen_examples/generate_indoors.py. Contributed as part of Infinigen-Indoors by Yiming Zuo. + +commit 24b000109867afe807c832393bccd5c5a5902182 +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add 1 lines to infinigen_examples/generate_indoors.py. Contributed as part of Infinigen-Indoors by David Yan. + +commit 56b88a113f79a0e2091304375f857a3c3bdbf93b +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add 3 lines to infinigen_examples/generate_indoors.py. Contributed as part of Infinigen-Indoors by Karhan Kaan Kayan. + +commit 9bb58746a6391a0104b0012e2c80096da51b87eb +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add 12 lines to infinigen_examples/generate_indoors.py. Contributed as part of Infinigen-Indoors by Pvl Bot. + +commit ba87ded72811420f12918238a7684481d091c2e0 +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add 14 lines to infinigen_examples/generate_indoors.py. Contributed as part of Infinigen-Indoors by Zeyu Ma. + +commit 1b5ffe310d7ff6cc2bfbde6306aa7c3bd7834a5d +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add 38 lines to infinigen_examples/generate_indoors.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit d33eb1b151fb23562266b4cbe8a9ce6a41262896 +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add 356 lines to infinigen_examples/generate_indoors.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit 22b9acfbfa7d47c985ee1027428c4e81f690f3c2 +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add 1 lines to infinigen_examples/generate_asset_parameters.py. Contributed as part of Infinigen-Indoors by Alexander Raistrick. + +commit d1268dfd23aec66495f90bd11a72e3846ff8b0f9 +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add 328 lines to infinigen_examples/generate_asset_parameters.py. Contributed as part of Infinigen-Indoors by Lingjie Mei. + +commit 356dc9c98404f24bb06501c33a974f6422e27b95 +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Improvements to exporting.py, contributed by David Yan as part of Infinigen Indoors + +commit b3fa5e9471c29b0cdef00206aa9863a6bf4223e4 +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Changes to existing files contributed as part of Infinigen-Indoors. Contributed by all authors - we were unable to create correct per-author commits for edits to existing files. + +commit 001797f5419dc02fe6e39a10e7e496fe907b49f3 +Author: pvl-bot +Date: Sun Jun 16 23:16:12 2024 -0700 + + Add files that were renamed/deleted as part of Infinigen Indoors + +commit f98b9575c73c322f1cbcc2dd48d26bbe8d6239f6 +Author: Alexander Raistrick +Date: Sun May 26 13:16:44 2024 -0400 + + Fix typos in export documentation + +commit f5bcba8de47623da9b715348cf95822445e0e9a7 +Merge: 830891c3a 857c9be95 +Author: Alex Raistrick +Date: Sun May 26 12:29:33 2024 -0400 + + Merge pull request #245 from princeton-vl/rc_1.3.3 + + Merge v1.3.1 - v1.3.3 + +commit 857c9be957fa14a40a75371a2aa5b64aecedc21f +Author: Alexander Raistrick +Date: Sat May 25 19:01:59 2024 -0400 + + Fix unit-test failures for empty materials + +commit e0a6e254c11a920e078867a1b488e4e68102d8d3 +Author: Alexander Raistrick +Date: Sat May 25 17:29:35 2024 -0400 + + v1.3.3 - Camera bugfix, pass all tests + +commit 03f603d030f3f8571808b96180514149c11a64fa +Author: Alexander Raistrick +Date: Sat May 25 17:08:27 2024 -0400 + + Add purge_empty_materials to every geonodes apply to fix emptymat tests + +commit 113230a056826d07e8de2a24f7087975ef85c48d +Author: Alexander Raistrick +Date: Sat May 25 17:08:07 2024 -0400 + + Add pytest timeout, reorganize tests/test_meshes_basic.txt to pass + +commit 7e3d6b31321a5f651a63eb615b442e7423b0eb03 +Author: Alexander Raistrick +Date: Tue Apr 23 13:49:06 2024 -0400 + + revert fps change + +commit dfc2b2ab1a8df700575f6d518685932c26ce396a +Author: Alexander Raistrick +Date: Tue Mar 5 13:47:31 2024 -0500 + + Add METAL to devices list + +commit 7d821161cfa6aac2e8c1ad317784b5e38bdb410e +Author: Zeyu Ma <31351547+mazeyu@users.noreply.github.com> +Date: Tue Mar 5 11:00:04 2024 -0500 + + decrease sss radius + +commit 7538de1b97846fd3eab729f213b79b8768e13a42 +Author: Zeyu Ma <31351547+mazeyu@users.noreply.github.com> +Date: Tue Mar 5 10:57:38 2024 -0500 + + multi cam fix + + * fix + + * fix placement + +commit 3078b0cfa02c2bd35877ba9aaec300b8125fe1c9 +Author: Zeyu Ma <31351547+mazeyu@users.noreply.github.com> +Date: Sun Feb 18 14:21:06 2024 -0500 + + refactor camera selection syntax and prevent water in all frames + +commit 3c96b3b99c6e00413a3a125804c18b2e455d63f6 +Author: Alexander Raistrick +Date: Sun May 5 14:58:33 2024 -0400 + + Bugfix snakes & manage_jobs (v1.3.2) + + * Move glowingrocks to rocks to prevent circular/far-reaching imports + + * Update OcMesher + + * Fix white materials on snakes, add unit test for empty materials on any asseet + + * Fix denoising always on + + * Add slurm_partition to manage_jobs + + * Update formats_all, make release.py work with just a folder of videos + +commit 35506e8821830bd34af04eaef44a28f9f6932d8b +Author: Alex Raistrick +Date: Mon Feb 12 12:45:37 2024 -0500 + + Smbclient commandline utility + + * Add commandline bindings to smbclient.py + + * Add mapfunc for slurm launch of smb_client, WIP fix ls with glob at end + + * Fix recursive glob ls and download + + * Add exclude flag to eliminate downloading blends + + * Add verbose, ratelimit + +commit dd5d560cf0495b51b5991d9a8788d6a5ee4e7e7f +Author: DavidYan-1 +Date: Sun Feb 4 15:37:34 2024 -0500 + + Texture-baked exporting (princeton-vl/infinigen_internal/#103) + + * Full Scene Exporter + + * Regex Tweaks for Integration Testing + + * Refactor and optimize export + + * Move exporting README to docs/ folder and add more caveats + + * Path handling tweaks for exporter + + * Move export to infinigen.tools.export + + * Tweak docs + + * Add slurm scheduling to generate_individual_assets + + * Tweak generate_individual_assets + + * Small Refactor + + * Glass Export and other features + + Glass, Individual object, .obj vertex col export and fixes UV overrwrite + + * Add --export option to generate_individual_assets, use a slurm job array per factory not for the whole set + + * Add docs on generating & exporting individual assets + + * Export Optimizations and Bugfixes + + * Remove Unused Args + + * Typo / import fixes + + * Add --export option to generate_individual_assets, use a slurm job array per factory not for the whole set + + * Fix kwargs + + --------- + + Co-authored-by: Alexander Raistrick + +commit e4a42d7cf9084c271f908070c559395747ec27fb +Author: Alex Raistrick +Date: Wed Feb 7 09:12:11 2024 -0500 + + Render improvements (v1.3.1) (princeton_vl/infinigen_internal/#107) + + * Attempt to fix white snakes and volume_bounces=0 bugs + + * Make videos 4sec not 8, tweak slurm_1h, tweak terrain resolution, speed up cameras for video + + * Fix --use_existing for scenes started with --specific_seed + + * Increase opengl time, decrease render sample quality + + * Noisify camera motions + + * Add overhead.gin + + * Tweak video length and cam seped + + * Drop fps to 16 + + * Reorganize cycles configuration, overhaul enable_gpu to be more robust and only ever use one device type + + * Bump version to 1.3.1, remove scripts/launch + + * Tweak ratios, fix typo + + * Fix noshortrender typo, rename conf to noisy_video + + * Changelog + +commit 830891c3ac988f6be02ded26388b1572a5463de3 +Merge: 18be26c9b 80ac24c68 +Author: Alex Raistrick +Date: Wed Apr 24 22:34:04 2024 -0400 + + Merge pull request #122 from princeton-vl/rc_1.2.6 + + Bugfix v1.2.6 - Fix duplicate configs, CUDA_VISIBLE_DEVICES & more + +commit 80ac24c681ace3f811d1eec7363f53f4d3725ae3 +Author: Alexander Raistrick +Date: Wed Apr 24 13:22:09 2024 -0400 + + Increment version & changelog for v1.2.6 + +commit 4216ac371f3caab32666a0e3cc79f75ab0afe00f +Author: Alexander Raistrick +Date: Wed Apr 24 15:03:31 2024 -0400 + + Unify other creature interfaces + +commit 069ad8b12f932d44619928d4d54c08a85c5f9235 +Author: Alexander Raistrick +Date: Wed Apr 24 13:07:40 2024 -0400 + + Attempt to fix dynamic hair for #215, raise NotImplementedError for dynamic hair for now + +commit e7b3ef04df314b62e94047fa30321dbd48c054aa +Author: Alexander Raistrick +Date: Wed Apr 24 11:52:41 2024 -0400 + + Fix submitit emulator improperly following CUDA_VISIBLE_DEVICES (#212) + +commit 7177237a64ad00bddb32fd81e516ef7ef37d3d29 +Author: Alexander Raistrick +Date: Wed Apr 24 11:22:05 2024 -0400 + + Add trycatch for flowvis install + +commit 43244c9e26c6498182a1128cdf8f45115a467bba +Author: Alexander Raistrick +Date: Wed Apr 24 00:19:44 2024 -0400 + + Avoid duplicated configs by skipping sample_scene_spec when --configs specifies an option: + +commit f54f536420a759e56da3b4a11af9ed10806c4d1e +Author: Alexander Raistrick +Date: Tue Apr 23 23:34:27 2024 -0400 + + Implement mutually exclusive folders for scene_types to prevent common error case + +commit 18be26c9b4a7b375442d23569b737d8e2169e372 +Author: Zeyu Ma <31351547+mazeyu@users.noreply.github.com> +Date: Tue Apr 2 11:16:34 2024 -0700 + + Bugfix v1.2.5 - Terrain bugfix for multi-task command; Terrain.bounds and Terrain.populated_bounds parameter + + * fix reinitializing terrain + + * add bounds and populated_bounds for terrain + +commit 5132903cd68704367d1c44c841e5163158e0f33d +Author: Alex Raistrick +Date: Sat Mar 9 16:08:10 2024 -0500 + + Update Installation.md + +commit 331b4e5e8b3d84f8a31f395c8a344df20844195e +Author: Alexander Raistrick +Date: Tue Mar 5 15:24:17 2024 -0500 + + Hotfix v1.2.4: Fix TreeFactory(season='winter'), fix join_objects ignoring empty meshes + +commit 66a449399f2f38d63e4b9aad689c02a4479f14df +Author: Zhangir Azerbayev <59542043+zhangir-azerbayev@users.noreply.github.com> +Date: Wed Feb 28 15:51:32 2024 -0500 + + Update Installation.md (#192) + +commit e8687f4ab5e809be28778fe42e26d26b414cca6a +Author: Alex Raistrick +Date: Mon Jan 15 13:44:40 2024 -0500 + + Update Installation.md + +commit 40a261506252f17107ff7398d787a3feb13a53d1 +Author: Alex Raistrick +Date: Fri Dec 29 09:49:57 2023 -0500 + + Hotfix v1.2.3 + + * Fix misplaced opengl path, tweak installation + + * Tweak launch scripts + + * Fix underspecified child python paths + + * Fix cleanup except_crashed + + * Fix underspecified child python paths + + * Remove cd worldgen typo + + * Fix leftover objects in tree generation + + * Change version to 1.2.3 not 1.2.0.3 + +commit 0c622a1788235e2270a73262463383ff1fade70a +Merge: 48be1cde4 e23073612 +Author: Alex Raistrick +Date: Sun Dec 17 15:24:59 2023 -0500 + + Merge pull request #184 from princeton-vl/hotfix_v1.2.0.2 + + Fix hello world crash + +commit e23073612e4d953d0c3a6f3639262e566a69ab9a +Author: Alexander Raistrick +Date: Sun Dec 17 13:45:58 2023 -0500 + + Fix hello world crash + +commit 48be1cde4f44e79b237fdf0170fbf28084f80704 +Merge: 873fedd61 ff086b6cb +Author: Alex Raistrick +Date: Sat Dec 16 13:27:44 2023 -0500 + + Merge pull request #182 from princeton-vl/hotfix_v1.2.0.1 + + Hotfix helloworld, data download, smooth shading + +commit ff086b6cb6c19b99bf01f1ddf53a0c156df4b720 +Author: Alexander Raistrick +Date: Sat Dec 16 12:36:17 2023 -0500 + + Hotfix helloworld, data download, smooth shading + +commit 873fedd6108e356c701112c8e67f300e4fe69d02 +Author: Zeyu Ma <31351547+mazeyu@users.noreply.github.com> +Date: Thu Dec 14 23:40:11 2023 -0500 + + Update .gitmodules + +commit fcd7fc265e5a7c25b56e431e4ed57c3b5320fb42 +Author: Zeyu Ma +Date: Tue Dec 12 14:35:23 2023 -0500 + + Integrate OcMesher + +commit e4765d06e4794e45e166066be9a12fe188155285 +Author: pvl-bot +Date: Tue Dec 12 14:22:19 2023 -0500 + + Fix typos + +commit a31950f9ada0345fbf7812e78ad0f5598d170ff1 +Author: Alexander Raistrick +Date: Tue Dec 5 13:26:52 2023 -0500 + + Remove build/publish gh actions for now + +commit 998ba6bf657cd99710a8a17d1e2cf57c60b9e9a8 +Author: Alexander Raistrick +Date: Wed Nov 29 17:16:20 2023 -0500 + + Fix snakes, import crash, CI failures + + * Fix snake material, logging bug, fix debug crash + + * Add trycatches to prevent marching_cubes crash when not installed + + * Fix linting + + * Update changelog + +commit 71f254a7b31d2abc4ff02388a65d9aca5ca068e7 +Merge: e9313a618 04d8e573f +Author: Alexander Raistrick +Date: Tue Nov 28 15:48:14 2023 -0500 + + Merge branch 'main' into rc_1.1.1 + +commit 04d8e573f68b95981cc206ce858126bec0c5a57d +Author: pvl-bot <136786582+pvl-bot@users.noreply.github.com> +Date: Fri Nov 24 13:29:53 2023 -0500 + + Update dependencies, loosen version reqs, remove unused dependencies + +commit e9313a618220bfe02bc2b90f863143c64ac67687 +Author: pvl-bot +Date: Wed Oct 25 21:23:34 2023 -0400 + + Transpiler v2.6.5 - revert Ignore Reroutes to fix incorrect linkage bug + +commit 24242098961519c0678ff3757d4a9736358c4778 +Author: Alexander Raistrick +Date: Mon Oct 23 21:23:57 2023 -0400 + + Add infinigen.launch_blender helper, remove $BLENDER, fix test, update docs + +commit 2f025c5ff055b2538080fbceba36fb76e7da5a91 +Author: Alexander Raistrick +Date: Mon Oct 16 23:45:03 2023 -0400 + + WIP revised install instructions + +commit 091d6ec5ba61cab9db97851ccd92f3fdce005dd7 +Merge: ef75fe7fa a8ba86a39 +Author: Alexander Raistrick +Date: Mon Oct 16 17:01:07 2023 -0400 + + Merge branch 'main' into rc_1.1.1 + +commit a8ba86a394f8757586b4fb15252a3282e56a91de +Merge: 0f208a804 359f08e7c +Author: Alex Raistrick +Date: Mon Oct 16 15:58:16 2023 -0400 + + Merge pull request #87 from princeton-vl/rc_1.0.4 + + v1.0.4 - Pregenerated download tools, ground truth updates, render throughput improvements + +commit 359f08e7cfdf6caa3acf84bbb1bdaf6fbfe9a91d +Author: Lahav Lipson +Date: Mon Oct 16 15:44:15 2023 -0400 + + Update docs and download_pregenerated_data.py (princeton-vl/infinigen_internal/#90) + + * Update docs and download_pregenerated_data.py + + * Update GroundTruthAnnotations.md + + * Update GroundTruthAnnotations.md + + * Update GroundTruthAnnotations.md + + * Update GroundTruthAnnotations.md + + * Update GroundTruthAnnotations.md + + * Update GroundTruthAnnotations.md + + * Update GroundTruthAnnotations.md + + * Fix hello_world example + + --------- + + Co-authored-by: Alexander Raistrick + +commit a646f3513d5512911fb5488537a8a724b57b79f3 +Author: Alexander Raistrick +Date: Sun Oct 15 18:54:25 2023 -0400 + + Make download script interactive, revise data docs + +commit 02cd5b6045b22ec772706257d0722d2f6b13b57f +Author: Lahav Lipson +Date: Sun Oct 15 17:35:19 2023 -0400 + + Refactor bounding_boxes_3d.py segmentation_lookup.py (#89) + +commit 79244928803532393f7f572c6f03540c0d9d1138 +Author: Alexander Raistrick +Date: Sun Oct 15 14:35:00 2023 -0400 + + Debug render pipeline getting stuck due to backlog limiters + +commit ef75fe7fa1d9155f365e0ac8344d6fe35d38f9b0 +Author: Alexander Raistrick +Date: Sat Oct 14 13:07:00 2023 -0400 + + Fix pip install with cuda, build.yml, terrain test, mark which tests are ci + +commit 6780f819cc2ba25e9298148ce35d0aa9ec02195c +Merge: f7889fb3c 3664df8bf +Author: pvl-bot +Date: Sun Oct 15 00:09:21 2023 -0400 + + Merge remote-tracking branch 'origin/fan' into rc_1.1.1 + +commit f7889fb3c95d5ec915b929b75a332a6e539e8087 +Author: Alexander Raistrick +Date: Sat Oct 14 23:50:54 2023 -0400 + + Fix GeneratingIndividualAssets + +commit b3d9d6f14c9d617d75a397dea6c96f95934adc46 +Author: lahavlipson +Date: Sat Oct 14 22:10:52 2023 -0400 + + Optical flow / ground truth fixes and refactor + + * Refactor. + + * Scaling no longer needed for optical_flow_warp.py + + * Update compile_opengl.sh + + * Refactor optical_flow_warp.py + +commit ced0e871a740785bb0c366607b215244b836d6c0 +Merge: 9310e7b7e d2246f493 +Author: Alexander Raistrick +Date: Sat Oct 14 22:38:49 2023 -0400 + + Merge remote-tracking branch 'origin/rc_1.0.4' into rc_1.1.1 + +commit d2246f493679fa973271155179b826c5956dfec7 +Author: pvl-bot +Date: Sat Oct 14 21:28:29 2023 -0400 + + Update CHANGELOG, add copyright comments + +commit b2d4e47ecf325d5fd2346b8041b3ebc966046071 +Author: lahavlipson +Date: Sat Oct 14 21:10:14 2023 -0400 + + Infer buffer_size = 2 x image_size + +commit 90b87310787b99f4bde004e0b8cd045442a762ff +Author: lahavlipson +Date: Sat Oct 14 20:53:10 2023 -0400 + + Update flow scale. + +commit 93c0bef3280fccca042ad5cfe75301a32385abda +Author: Alexander Raistrick +Date: Sat Oct 14 18:36:08 2023 -0400 + + Create dataset download tool, pregenerated dataset docs + +commit f1a27a93b858e9dee32b389e0694b98ce50f9485 +Author: Alexander Raistrick +Date: Fri Oct 13 15:55:12 2023 -0400 + + Bugfix tag segmentation, render throughput, upload cleanup, crashing on symlinks + + * Revert monocular video fineterrain/populate ordering + + * Fix jobs getting stuck due to max_stuck_at_numdone settings + + * Fix upload cleanup + + * Fix crashing on symlinks + + Bugfix tag segmentation, render throughput, upload cleanup, crashing on symlinks + + * Revert monocular video fineterrain/populate ordering + + * Fix jobs getting stuck due to max_stuck_at_numdone settings + + * Fix upload cleanup + + * Fix crashing on symlinks + +commit b7b431f90d106a11154a7d9d27b8835505e97e2c +Author: lahavlipson +Date: Fri Oct 13 19:08:02 2023 -0400 + + optical_flow_warp.py bug fix. + +commit 75994298ccc60e24aae838ac16b51a15172bde42 +Author: Alexander Raistrick +Date: Fri Oct 13 18:05:30 2023 -0400 + + Allow RCLONE prefix via ENVVAR, allow more general prefixes + +commit f94eebda675cdc8748319fa7265e881d13c38de8 +Author: lahavlipson +Date: Thu Oct 12 20:36:38 2023 -0400 + + Refactor. + +commit 6a49d6fe586175537dc98459f0d470b82731579e +Author: Alexander Raistrick +Date: Fri Oct 13 17:14:04 2023 -0400 + + Standardize image suffix format across the whole repo + +commit 98acdb8e1e147bda9fc295460ef3db695ad1751a +Author: Lahav Lipson +Date: Thu Oct 12 15:12:05 2023 -0400 + + Ground truth scripts update oct5 (princeton-vl/infingen_internal/#86) + + * Bug fix when rendering instance ids. Remove indents in Objects.json. Save instance_ids to Objects.json. + + * Update optical_flow_warp.py + + * Update depth_to_normals.py and requirements.txt + + * Update depth_to_normals.py + + * Update rigid_warp.png + + * Update instructions. + + * Update segmentation and bounding box scripts to work w/o tags. + + * Update comments. Use get_frame_path. + +commit fdef6f268bd47324f561d23ecdd8c48e48c84f4a +Author: Alexander Raistrick +Date: Wed Oct 11 16:06:16 2023 -0400 + + Implement retar for release + +commit 3e855cba2d6d91639a459793fdc81d818d3f7e26 +Author: Alexander Raistrick +Date: Sat Oct 7 18:53:27 2023 -0400 + + Foolproof interpolation specification, handle mask and compressed npz cases + +commit b69b87d1701b95a080a3318903fc56d999cdd1fc +Author: Alexander Raistrick +Date: Wed Oct 4 15:33:14 2023 -0400 + + Implement frames folder reformat, update torch dataset, make dataset mostly standalone + +commit a188fe18dfb21e75e25be837e0e9c612cc180e29 +Author: Alexander Raistrick +Date: Sun Sep 17 13:41:04 2023 -0400 + + Data release toolkit + + * Implement format fixer + * torch dataset example + + * Tar-by-tar version of data toolkit, + * resize groundtruth, + * optimize jsons, + * fill missing poses with dummy dicts + +commit 9310e7b7e440bdc84b5671a65baceb0f7db068d9 +Author: Alexander Raistrick +Date: Wed Oct 11 14:28:00 2023 -0400 + + Update landlab and numpy, fix bnurbs import when using minimal install + +commit 0306641b6899a825f698a29649bc9aa874c7e937 +Author: Zeyu Ma +Date: Tue Oct 10 03:23:08 2023 -0400 + + terrain tests + +commit 67d9b17ae9b72024a684e4e84210474c5a5cad39 +Author: Alexander Raistrick +Date: Mon Oct 9 17:39:07 2023 -0400 + + Add INFINIGEN_MINIMAL_INSTALL flag to disable ALL compiling when doing interactive blender install + +commit 464a3f7a66c42f123481c9b6c9d53c679f9f994c +Author: Alexander Raistrick +Date: Fri Sep 1 15:22:53 2023 -0400 + + Update docs, fix runtime issues in pip installation + + * Update docs, add json to packaging, fix .soil file loading + + * Update docs to remove all references to old installation strategy + + * Move tools back to infinigen/tools, update all python invocations to be -m calls for compatibility with pip-only installs + + * Dont require wandb, dont even import it unless explicitly enabled + + * Fix erroneous gin logging, disable non-warning logging for all child packages, make all code use a non-root logger + + * Make fluid installation a fully separate post-install step, and dont attempt to init the module unless it will be used + + * Import ordering fix + + * Add cibuildwheel config, fix python -m build crash + + * Fix infinite runtime on hello world blendergt + + * Install script for interactive blender install + + * Move color util to fix circular import + + Fix rebase typos, fix torch_dataset imports, fix interactive_blender install + +commit fd3592344ef4f0f9b8d3cdd4feb10d2588190263 +Author: David Yan +Date: Wed Aug 30 23:13:33 2023 -0400 + + ast rendering fix + +commit 593876afb822da756b42fd56618233e08826dcd8 +Author: David Yan +Date: Wed Aug 30 22:21:29 2023 -0400 + + bpy module multiprocessing fix + +commit 6b59ddae188de20cff361802489f24eb9a460ae2 +Author: DavidYan-1 +Date: Mon Aug 28 15:44:03 2023 -0400 + + Update docker for pip-installed bpy + + * docker fixes + + * wsl docker fix + + * remove opengl compilation from docker-run + + * docker editable install + + * dockerfile type + +commit cba91ca596c46cd8f78a693b1b2650ff2b35cf1e +Author: Alexander Raistrick +Date: Mon Aug 21 12:31:47 2023 -0400 + + Working pip installation + + * Update test import mechanism to use better specified paths + + * Move nonessential tools out of the infinigen package dir + + * Dont build terrain etc during tests + + * temp commit txt package data + + * Fix tests and runtime errors when running as a package + + * Refactor config loading, fix relative paths in config, html, json pallette + + * Move examples to infinigen_examples, test execute_tasks, make all config imports relative + + * Add manifest.in, simplify pyproject.toml + + * Convert surface registry to relative importlib style + + Remove old docs, single-source the package version + + Fix misc test warnings, disable egregiously slow single asset tests + + * Final painstaking fix for pyproject.toml to include all compiled files & data files + +commit eaaa8162a1647442e511bc2e1a35f1a2c061d8e5 +Author: DavidYan-1 +Date: Mon Aug 21 15:09:36 2023 -0400 + + Makefile switch back to rm -rf + +commit c367b6bdf5106a9eb4820123ea6d9201f8719586 +Author: pvl-bot +Date: Sat Aug 19 15:47:39 2023 -0400 + + Docker build & editor config from "Various docker fixes (#22)" + + Co-authored-by: datashaman + +commit 887cd553df405e37ed82ff19c95a207eef13f5d8 +Author: Alex Raistrick +Date: Fri Aug 18 12:24:53 2023 -0400 + + Fix everything that didnt pass tests / checks + + * Fix non-compiling code found by linting + + * Fix non fatal linting errors + +commit 0228fde529258a2b366450d75ae2db78f133ef58 +Author: Alex Raistrick +Date: Thu Aug 17 17:44:34 2023 -0400 + + Unit tests + + * Set up pytest + + * asset tests + + * material tests + + * hello_world tests + + * Iterate github workflows + + * Update tests + + * fix gh actions + + * Remove installation checks for now + + * Ignore dependencies folder + + * Test improvements + + * Fix unit test commit pyproject toml + +commit c4a14d6f89fa40a00d5e2f575946c0d851eba0c5 +Author: Alex Raistrick +Date: Mon Aug 14 15:17:47 2023 -0400 + + Create setup.py and configs, minimize dependencies + + * Disable blending.py, make pallette requirements optional + + * Move marching cubes into toplevel setup.py, remove all python command invocations from CMake + + * Add pyproject.toml, convert bnurbs cythonize to toplevel setup.py + + * Leave terrain/opengl as independent install scripts + + * Move version to a txt file + + * Install flip fluids into pip bpy's addons folder + + * Reduce commits via better __init__.pys + + * Use pip install -e . in setup.py, update infinigen_gpl pointer + + * Move remainder of install.sh to setup.py, move version to __init__.__version__, add build commands to Makefile + + * Only run build_deps in the right steps, add options to disable terrain etc + + * Move scripts to examples + + * Tweak setup.py + + * add tabulate req + +commit f1c3e24dd8dd51b21d07b8a8c806279a04fdf58a +Author: pvl-bot +Date: Sat Aug 12 17:47:33 2023 -0400 + + Fix all imports and paths + +commit 6265e9a6bef5a98c16f41be7f551f0339cfceec9 +Author: pvl-bot +Date: Sat Aug 12 15:04:22 2023 -0400 + + Reorganize the entire repo (breaks imports) + +commit 3c767644952044b96bbe4415e91d50f8ab696854 +Author: David Yan +Date: Sat Aug 12 14:40:47 2023 -0400 + + Update to 3.6, fix installation, fix all existing asset code + + * working install.sh + + * assorted 3.6 compatibility fixes + + * fixed transfer attribute and added kernerlizer nodes.mix code + + * group input value fixes + index -> name specifiy + + * rename asset_grid and fixed blender path + + * bird fixes + revert geometrynodes.py transfer_attr + + * tiger, snake, transfer_attr compatibility fixes + + * chameleon and sculpt transfer attribute updates + + * Coconut Tree Fixes + + * Tree Fix + + * Assorted Fixes + + * Fish, Bird, Flowering plant fixes + + * Dragonfly, Chameleon fixes + making more assetfactories discoverable + + * Ivy, Lichen, Treeflower Fixes + +commit 1100f52b49f474a602bfd2b14b9f4729d44691e6 +Author: Alexander Raistrick +Date: Sat Aug 12 14:38:50 2023 -0400 + + Initial buggy 3.5 fixes + + * pip-based install script + + * Update docs and manage_datagen_jobs to use conda python + + * Copy over 3.5 nodegroup interface fixes + + * Fix duplicate Value nodegroup input kwargs + + * Implement compatibility mapping functions to catch old code using no-longer-support blender nodes + + * Handle both commandline formats in argparse + + * Update transfer attributes to ignore hidden attrs + + * Fix all Msample default_value interfaces in mingzhe materials, fix remaining StoreNamed + + * patched butil and mesh tools + + * blender_internal_attr + +commit e393b667ab89de5498ff2ce374b8756a18e9ada0 +Author: lahavlipson +Date: Tue Sep 5 10:49:33 2023 -0400 + + Fix opengl not writing ground truth for second stereo image + + * GT bug fixes sep4 (princeton-vl/infinigen_internal/#81) + + * Misc bug fixes. + + * More bug fixes. + + ---------------- + + Co-authored-by: Alexander Raistrick + +commit dd9569b8329da70596cb7576301c1942e936a630 +Author: Alexander Raistrick +Date: Sun Sep 3 16:59:56 2023 -0400 + + manage_datagen_jobs refactor and new features + + * splt into many files + + * add max_queued_tasks + + * add cleanup except_logs + + * add finalize_tasks list, move upload + + * Aggressively cancel jobs when siblings crash + +commit b26f5654804870d9c22d09a237daa9a517edc498 +Author: Alexander Raistrick +Date: Sun Sep 17 13:41:04 2023 -0400 + + Data release toolkit + + * Implement format fixer + * torch dataset example + + * Tar-by-tar version of data toolkit, + * resize groundtruth, + * optimize jsons, + * fill missing poses with dummy dicts + +commit 4489715eaab60a3fbebfad512b046df3c3e7967e +Author: pvl-bot +Date: Wed Oct 4 09:50:15 2023 -0400 + + v1.0.4 - Rendering tools improvements, ground truth optimization + +commit 420664cc448ab93b8c887877a64be4581368dafd +Author: Alexander Raistrick +Date: Sat Sep 30 17:43:46 2023 -0400 + + Upload checking, fix opengl default resolution + + * Enforce that user tells upload what to do with every single file, no missing or accidentally omitted files are possible + + * Fix upload fine, disable camera placeholder to bring back forests, Refine cleanup, make camviews mandatory, opengl default to 720p + +commit 6907ac787061d3cdf5e89a2b6ce1e5b123e331ea +Author: DavidYan-1 +Date: Mon Oct 2 16:03:29 2023 -0400 + + test fixes + remove pytest from integration testing script + +commit 39bf2bffd1db2a54a0c3b62d959eba1f00bc5c38 +Author: Alexander Raistrick +Date: Sun Sep 17 23:22:00 2023 -0400 + + Manage datagen jobs refactor and latency improvements + + * Refactor and reorder upload, do metadata/thumbnail last as a sign of completion + + * Add max_stuck_at_step limiter, refactor state tracking, bring back jobs.log, cleanup/refactor some parts + + * Add command upload, add slurm_cpuheavy, tweak other configs, remove fineterrain by default + +commit 9694693de5ebca754cf8ce9a8840806b79cf8686 +Author: lahavlipson +Date: Tue Sep 5 10:49:33 2023 -0400 + + Fix opengl not writing ground truth for second stereo image + + * GT bug fixes sep4 (princeton-vl/infinigen_internal/#81) + + * Misc bug fixes. + + * More bug fixes. + + ---------------- + + Co-authored-by: Alexander Raistrick + +commit 03e27a735b1445cc16a50b55c8c3ecf32e0c4a1f +Author: Lahav Lipson +Date: Sun Sep 3 19:26:05 2023 -0400 + + Opengl updates sep3 (princeton-vl/infinigen_internal/#80) + + * Save view size and camera parameters to single npz file. + + * Ignore CURVES objects, for now. + + * Remove unused code. + +commit 3648e8571f2f762dbea5982919f6936518531154 +Author: Lahav Lipson +Date: Sun Sep 3 19:25:36 2023 -0400 + + Reduce storage costs & make segmentation masks easier/faster to use (#77) + + * Add compress_masks.py + + * Call compress_masks.py in opengl_uuid.sh + + * Teensy fix. + + * Teensy fix. + +commit 6a1e2190999f77e217feee70da03943fe5875f44 +Author: Alexander Raistrick +Date: Sun Sep 3 16:59:56 2023 -0400 + + manage_datagen_jobs refactor and new features + + * splt into many files + + * add max_queued_tasks + + * add cleanup except_logs + + * add finalize_tasks list, move upload + + * Aggressively cancel jobs when siblings crash + + * Fix queueing + + * Bugfixes + +commit 32e11751eb1cd68f1b0e3de50cd3a96c95930186 +Author: lahavlipson +Date: Fri Aug 25 18:13:56 2023 -0400 + + Fix missing range-check bug. + +commit fab93f4e819084b54b150105f1b80f369e1f7c7a +Author: Alexander Raistrick +Date: Thu Aug 24 16:20:34 2023 -0400 + + Visual & Pipeline config improvements + + * Fix upload_util + + * Tune visual configs, move ocean to experimental + + * Deduplicate slurm_account settings, allow random choice of accounts + + * Print banned nodes to verify they are working on startup + + * Add commit hash and resolve paths in run_pipeline.sh + + * Tune caustics and glowing rocks chance/strength + + * Move rain to experimental due to no motion blur + +commit 51c73075b7ead5ff2a8effeb62fdb3f494beb88f +Author: Lahav Lipson +Date: Thu Aug 24 16:17:09 2023 -0400 + + 96 bit instance ids (princeton-vl/infinigen_internal/#71) + + * Update exporting.py to save 3 32-bit ints for instances. + + * Save instance ids as HxWx3 array. + + * Update instance segmentation visualization. + +commit 774346e211935b0e84eeb1228ddf5ad979e75082 +Author: lahavlipson +Date: Sun Jul 9 00:57:58 2023 -0400 + + Only load vertex indices for current frame. + + * Save mesh bugfix - untested + + * Fix compilation error. + + * Bug fix. + +commit 0f208a8044c38a797f3383d7c7f7f7425154f25b +Author: Kaiyu Yang +Date: Mon Sep 4 08:06:38 2023 -1000 + + Update ImplementingAssets.md (#142) + +commit 6919bfbbb3342041504ff1ef986b036857e27783 +Author: pvl-bot <136786582+pvl-bot@users.noreply.github.com> +Date: Mon Aug 28 16:56:11 2023 -0400 + + Hotfix highpoly terrain mesh not shown in fine.blend (#139) + + * optimize_terrain_diskusage_flag + + * no redundant glb saving + + * Disable optimimze_terrain_diskusage unless using high_quality_terrain + + --------- + + Co-authored-by: Zeyu Ma + +commit 3664df8bf5b7ff45f3911be29bd56358689fd44e +Author: Lingjie Mei +Date: Fri Aug 25 14:16:19 2023 -0400 + + Make deformed trees work again. + +commit 0ab7cd7d2507115f3228aafc06126a4d4332a9e7 +Author: Lingjie Mei +Date: Thu Aug 24 22:01:15 2023 -0400 + + Cherry-pick from e2a7 + +commit b166f66f32d789481daff0d4a01e2fd2b9e57906 +Author: Lingjie Mei +Date: Thu Aug 24 21:01:28 2023 -0400 + + Cherry-pick from e2a7 + +commit d38346baeff7b30140c1b253ca4349125837f74d +Author: Lingjie Mei +Date: Thu Aug 24 21:01:10 2023 -0400 + + Cherry-pick from 986d3 + +commit 7e2975b239d6da2ad6af05457df19db06bdf755a +Author: Lingjie Mei +Date: Thu Aug 24 20:58:55 2023 -0400 + + Cherry pick from b63c9b + +commit e5d15b76c7a46a17f03bd2809d073f0e8ea03eca +Author: Lingjie Mei +Date: Thu Aug 24 20:53:29 2023 -0400 + + Cherry pick from 14e12f + +commit f26a82e0ae5fc0b08a7d2d0cff6b9830e41d2d8f +Author: pvl-bot +Date: Wed Aug 16 11:32:02 2023 -0400 + + Hotfix flip_fluid loading, update github templates + +commit 4ae5d20c410da9ced0424e3ec69e5a5701f30ae4 +Author: pvl-bot +Date: Tue Aug 15 18:41:53 2023 -0400 + + Hotfix opencv version #130 + +commit a1edc13ce3639384cf7afd9d45b7ec6060cfee2c +Merge: b387b5a60 366836bca +Author: pvl-bot <136786582+pvl-bot@users.noreply.github.com> +Date: Tue Aug 15 17:05:46 2023 -0400 + + Merge pull request #132 from princeton-vl/develop + + v1.0.3 - Fluid code release, implementing assets documentation, render tool improvements, integration tests + +commit 366836bca07917cc312b9a7c3b3b4e5818a1ef8c +Author: pvl-bot +Date: Mon Aug 14 13:34:26 2023 -0400 + + v1.0.3 - Fluid code release, implementing assets documentation, render tool improvements, integration tests + +commit a124321d82f0e9917baa35c9a291d4dd37c6dae6 +Author: Karhan Kaan Kayan +Date: Tue Aug 8 12:19:47 2023 -0400 + + Fluid documentation (princeton-vl/infinigen_internal/#61) + + * Add hello world scene + + * Config tweaks + + * fix the camera bug and water not simulating + + * res fix + + --------- + + Co-authored-by: pvl-bot + +commit c534cd87da56afe1a00d07a30595c17186082b95 +Author: Alex Raistrick +Date: Tue Aug 8 11:13:28 2023 -0400 + + Fluid Refactor (princeton-vl/infinigen_internal/#60) + + * Acknowledge FLIP-Fluids + + * Deduplicate configs + + * Remove fluid-specific logic from camera funcs, move to scene_type_fluidsim + + * Move river invocation from core.py to compose_scene run_stage calls + + * Remove FLIP caustics from release + + * Move fire scenecomp and Cached class wrappers under fluid/ + + * Change on_the_fly to use run_stage + + * Move installation to tools/install, make FLIP installation optional and update GeneratingFluidSimulations.md + + * Deduplicate river configs, add example commands + + * Only unhide assets needed for fire sim, and unhide once done + + * Move enable parent cols to butil contextmanager + + * Remove unnecessary --blender_path + + * Typofixes + + * Fix accidentally deleted config fields in new river configs + + * Fix finding placeholders for river calls + + * Fix impl typo + + * Cleanup camera selection varname, unused kwargs + + * Catch modulenotfound errors + +commit 18152ea60abab61fa226a67180be5249c9469922 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/tools/submit_asset_cache.py + +commit f295b204bd0124ddfc11355029ab55e98910f28d +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/tools/scripts/render_video_fire.sh + +commit d870745a74329f443aa66cf7c0942e41ea17f697 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/tools/scripts/render_river_video.sh + +commit 3f5bc04819280bdf9c8580f4a57057d4736b7021 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/tools/pipeline_configs/slurm_fluid.gin + +commit 70bd6f97dde1ee0b15c14d4453bd92e62f53d6a5 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/tools/pipeline_configs/monocular_video_river.gin + +commit 419baf3623de30d1220944d04f0ff7751eddb6ea +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/surfaces/templates/waterfall_material.py + +commit f3634a288ba7559554194a238ea1ff18aa5bb895 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/surfaces/templates/smoke_material.py + +commit bf3d97e03ed3aaa1966584d15d477afc92153009 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/surfaces/templates/river_water.py + +commit 77c36a5e93a4e79eefb7b7bf9327795a10759aa3 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/surfaces/templates/new_whitewater.py + +commit d684e5693a72f34a174c2362c1253f2addd8725d +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/surfaces/templates/blackbody_shader.py + +commit 8108cd9d7fcab8c6830379f64098cb99dc03eaf7 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/unit_tests.py + +commit 4ce86c1592b922db31da3637df491112ad84f3a6 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/run_tests.py + +commit 701defdda8ee1905d105e92b5f1ee8a6af109e5a +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/run_asset_cache.py + +commit f5c2a739afb2337e02d35cd7cf3e37c266e6db3f +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/liquid_particle_material.py + +commit 9107c1873b41f27df0f424fe8a8b5c5c44c5671c +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/generate.py + +commit a129c88170d062c639192395fb2d36386419c15f +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/fluid.py + +commit af0a1d6930b05afb0d65a89f4680a95685e248ad +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/flip_init.py + +commit d2b67ae694a7a3c7b8b878f8e810a51103100848 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/flip_fluid.py + +commit a33529ee7f2247d0efa1495e69f5f15b06c3dbfb +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/duplication_geomod.py + +commit 993087f4684e8f7e72fd9c5cc85a5806d2f79132 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/bounding_box.py + +commit 26022c1d6726bf2ef09ae4f1a75ed305e0d405d9 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/asset_cache.py + +commit 9fac0fbcae1cd1483cf6cf735bea38bf51509b6b +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/fluid/__init__.py + +commit 612527eb14b91f363d28841f362faa26616fd3f0 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/config/use_on_the_fly_fire.gin + +commit 85d1f789a17b9dd1b3ab834ce4c716bdd04af595 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/config/use_cached_fire.gin + +commit 891e0f57204a42cea612e04fd7487653e6fdfbcb +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:09 2023 -0400 + + Add fluid code-release changes in worldgen/config/trailer_river.gin + +commit c17a58aedd17586158a524f5388d4230a923b5b7 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/config/scene_types/tilted_river.gin + +commit e80503a64566d8b888aab242ee45928a9910d233 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/config/scene_types/simulated_river.gin + +commit 5ee57e4905b76fa1f639cb49a3a9b24f16b81438 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/util/blender.py + +commit 6487c2a2742abfdb877aa832f855245ffb6f7e04 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/tools/util/cleanup.py + +commit ca83ec8c4ca3174dc8150909b20150ed2b61a6d4 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/tools/asset_grid.py + +commit 7115b597e4ce1f92348a32795660bdc5cec9350a +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/terrain/core.py + +commit eddd10571f893099777f3cfdcfff1b6a58843312 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/surfaces/templates/spider_plant.py + +commit 2b9481d5fdcb23d860ea2996c2339aea8be50cc5 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/surfaces/templates/snake_plant.py + +commit a98fdd9bf52fc14eb2c51540906a2c1540aabb84 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/rendering/render.py + +commit 7b7c845a152262d59dca63d2337227c34c95c963 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/placement/placement.py + +commit e687f0b8edce3007acfc956d0320a4ff483b2fe8 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/placement/camera.py + +commit 3e685b6cf20f55d57e858b5c011faccbe6d67c9f +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/placement/animation_policy.py + +commit 3388c5d532504b663bcd54fdf076875088b5c907 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/generate.py + +commit 4903651982349fdfe841c41d16f15472386553a1 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/core.py + +commit f94a2d90349131fa858bce93c72655fdbedc0d18 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/config/scene_types/desert.gin + +commit 8141784a3e977820e0dde22b108d50aefc8017df +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/config/scene_types/arctic.gin + +commit 521da2ed314d95631b5e2d4f1f902600ce85e59d +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/config/base.gin + +commit bcddd46c8330ea4fdda80b563e55796959624144 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/assets/trees/generate.py + +commit d8a17ef130abd790f0bca36334db93fe88d9c256 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/assets/trees/__init__.py + +commit b3133b4504a844d4fad43ac754057cac789e206b +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/assets/small_plants/spider_plant.py + +commit 27c70b91874b74bc66123c7bdad4772f80b35c5f +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/assets/small_plants/snake_plant.py + +commit 630a951ef43f1fa34e5979e2d46fc87158921d33 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/assets/creatures/genomes/carnivore.py + +commit b47c24f778eeabb7c1c3d80e92acd1bfda916a3f +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/assets/creatures/__init__.py + +commit f167d5a594b1d855d82641e5a01af965f378ae73 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/assets/cactus/generate.py + +commit 2cf1a545fa717826e08b37d0aa9d0b9e0f8077b1 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/assets/cactus/__init__.py + +commit fc1d9b2c5433f4bc7334e606648a25ad0ca7e4d0 +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in worldgen/assets/boulder.py + +commit a2c9960d0d33660892616c27644cac0be19961da +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in process_mesh/blender_object.hpp + +commit 91b0edaa6e93dd54cf2aa968d0d839dbd69c13bb +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in process_mesh/blender_object.cpp + +commit bfa2ab7f82b3a08b128a07f164b02b26b03f127d +Author: Karhan Kaan Kaan +Date: Mon Aug 7 10:54:08 2023 -0400 + + Add fluid code-release changes in install.sh + +commit bd69644a3b322f3af941a0aeed74022f14f33340 +Author: Alex Raistrick +Date: Tue Aug 8 17:30:28 2023 -0400 + + Implementing Assets Documentation (princeton-vl/infinigen_internal/#64) + + * Documentation draft + + * Make terrain optional, remove terrain dependence from camera logic + + * Fix config typos + + * Improve intro and blender UI setup + + * Added transpiler tutorial + + * Finish minimal implementing assets docs + + * Downsize images, add new doc to README + + * Fix image hyperlinks + + * Add testing script addition + + * Fix non-terrain version + +commit 6e17b5c798cc2f71cc35b53e6f48a9ba439038e6 +Author: Alex Raistrick +Date: Tue Aug 1 17:53:31 2023 -0400 + + Rendering improvements (princeton-vl/infinigen_internal/#39) + + * Tweak render_video_final + + * Remove random config choosing from core.py + + * Create tools/pipeline_configs/base.gin, move scenetype distribution configs into it + + * Create noshortrender config to test on IONIC + + * Implement slurm niceness override, add it to render_video_final.sh + + * Only include camera 0 in parse_video output + + * Read slurm partition from ENVVAR by default + + * Fix config postprocess + + * Fix slurm envvar + + * Typo fixes + + * Use roundrobin by default + + * Rendering tweaks + + * Change trailer.gin to video.gin with 720p res + + * Fix niceness + + * Set exclude_nodes list via envvar, move niceness configs into slurm.gin + + * Create render_video_720p.sh, start off experimental.gin but more needs adding + + * Add dryrun options + + * Fix --override vs --overrides + + * Move legacy task.Fine + + * Retool upload func + + * Add slurm_1h and stereo config + + * Rendering & typo fixes + + * Update render script and slurm.gin mem amounts + + * Fix excluded gpus + + * Add queues stats to wandb, add pandas to requirements.txt + + * Fix num_concurrent reset 24h later + + * Dont keep working on scenes which have had a fatal crash + + * Add new timeout message to error parsing + + * Fix overly nested upload dirs + + * Add thread limit to local jobs + +commit 887eb82f099ba48a59f894b8eaa50ce52fbbb7da +Author: pvl-bot +Date: Sat Aug 5 15:18:52 2023 -0400 + + Docker overhaul (#65) + + Co-authored-by: datashaman + Co-authored-by: David Yan + +commit a27e62059b2fa17f88f7a073dae5218f98d79143 +Author: pvl-bot +Date: Sat Aug 5 15:11:54 2023 -0400 + + Remove docker code attributed to pvl-bot, to be recommitted with github + co-authorship + +commit 62350548c029b2aa8cfe9a67fd9a8da29c8799ed +Author: David Yan +Date: Tue Aug 1 17:55:53 2023 -0400 + + Profiling & Testing (princeton-vl/infinigen_internal/#28) + + * basic framework + + * step times + + * multiple file fix + + * multiple file fix + + * more detailed logs + + * asset stat reporting + + * memory stats + + * fixed brightness test + + * improved formatting + + * noise estimation using PSNR + + * grayscale working + + * single image noise estimation + + * asset step memory accuracy + + * switched to opengl_gt + + * fixed inaccurate mem diffs + + * output formatting + + * increased sampling and changed noise estimation method + + * blender opengl gt combined config + + * fixed opengl+blender config + + * fixed timedeltas > 1 day breaking + + * further ground truth testing + + * aggregate tag segmentation stats + + * obj segmentation stats + + * cleaned up gt comparison (not working still) and reordered config + + * named tables + + * more table titles + readability + + * removed old copyright + + * deleted extraneous file + + * copyright + + * made internal representation of stage times more flexible + + * Delete logs.log + + * save data csv + + * object and instance counting + + * general testing usability improvements + +commit b387b5a6002b2fd2898e78a664b5754ca01e7081 +Merge: 6d0d34a11 a3a5b57ff +Author: pvl-bot <136786582+pvl-bot@users.noreply.github.com> +Date: Fri Jul 28 18:22:29 2023 -0400 + + Merge pull request #115 from princeton-vl/develop + + v1.0.2 - New documentation, plant improvements, disk and reproducibility improvements + +commit a3a5b57ffee494121a50514a8e331d4c2a42dbd5 +Author: araistrick +Date: Fri Jul 21 01:14:26 2023 -0400 + + v1.0.2 - New documentation, plant improvements, disk and reproducibility improvements + +commit 587fe9828306a8ea1ae1eacdf786700a4c87f1a3 +Author: Alex Raistrick +Date: Fri Jul 21 03:01:02 2023 -0400 + + Refactored & Expanded Documentation (#22) + + * Move existing docs + Installation and HelloWorld into docs/ folder + + * Search entire config/ and pipeline_config/ dirs, allow specifying with .gin prefix + + * Organize worldgen/tools/pipeline_configs + + * Organize worldgen/configs + + * Initial commandline documentation + + * generate.py config tweaks + + * Add help strings to all manage_datagen_jobs args + + * CommandlineOptions documentation + + * Reorganize + + * Fix typos + +commit ee36d886b346b5f26ee265051ba743f16f481766 +Author: Alex Raistrick +Date: Wed Jul 19 19:17:07 2023 -0400 + + Copy in terrain onthefly diskusage improvements, update infinigen_gpl to add .gitignore + + Co-authored-by: Zeyu Ma + +commit 0ff02d6fc3d65048e5ce4e320cdcc0200d6efc01 +Author: Zeyu Ma <31351547+mazeyu@users.noreply.github.com> +Date: Thu Jul 20 07:16:47 2023 +0800 + + Reproducibility of asset placement + + * mesh_to_sdf as included code + + * replace mix with explicit formula + + * use relative path of mesh_to_sdf + + * comment out opengl + + * add pyrender req which is prereq of mesh_to_sdf + + * uncommenting back + + * use float in mix + + * trimesh force version + + * face ordering + + Add MIT license for mesh_to_sdf + +commit 18685454f5c411cd2eda6247e1a35a08e4afe75c +Author: Beining Han +Date: Wed Jul 19 13:35:17 2023 -0400 + + Add spider plant and snake plant + +commit d42a77b4126696ec8d3dda004057039892cc45ba +Author: zuoym15 +Date: Wed Jul 19 13:18:45 2023 -0400 + + move over tree branching code + + * move over tree branching code + + * performance bug fix + +commit ccee42c14a7bb3b084019565c92c00639d8c1530 +Author: araistrick +Date: Thu Jul 13 12:04:59 2023 -0400 + + Typo-fixes by tms-gvd (princeton-vl/infinigen#76) + + Co-authored-by: tms-gvd + +commit dbb6d1c63a37712a71a606ddd18609546a8790e7 +Author: araistrick +Date: Mon Jul 10 04:24:41 2023 -0400 + + Refactor rigging, fix IKs, smooth before remesh, fix running, add end trim (Fixes princeton-vl/infinigen#89) + + Copy in import blend devscript + +commit a28da5ad71eda6d616e47c27ed3242b49750ed7c +Author: araistrick +Date: Sun Jul 9 02:10:11 2023 -0400 + + Hide culled placeholders to minimize effects of white cube bug (princeton-vl/infinigen#86) + +commit 209c803f752484fc8a2310645e2448364371f0cf +Author: araistrick +Date: Sun Jul 9 01:55:50 2023 -0400 + + Clarify crashed.txt (princeton-vl/infinigen#95) + +commit 6d0d34a115ed5f9e453fa010c1f4a9038e9dc5c3 +Author: lahavlipson +Date: Mon Jul 3 15:43:58 2023 -0400 + + Revert "Update requirements.txt" + + This reverts commit 941880a4a383ffb15bdc7b288fb1fa3b5f7f6ec1. + +commit aa22d4d1d4cb30de12f6aac453e6f49b87b787db +Author: Lahav Lipson +Date: Mon Jul 3 01:22:06 2023 -0400 + + Gt utilities (#83) + + * Update GT utilities. + + * Overhaul GT visualization for blender's built-in GT + + * Refactor. + + * Flip camera pose axes to be consistent with computer vision. + + * Save camera parameters during render step, not during GT. + + * Fix bug with blender's built-in segmentation masks. + + * Flatten object data json. Remove redundant information. + + * Make built-in GT metadata and OpenGL metadata consistent. + + * Misc. + + * Update GroundTruthAnnotations.md + + * Update GroundTruthAnnotations.md + + * Update GroundTruthAnnotations.md + + * Update README.md + + * Update requirements.txt + + * Update GroundTruthAnnotations.md + + * Update GroundTruthAnnotations.md + + * Update GroundTruthAnnotations.md + +commit 97b8a415f8b4733c4fc0ec6bdd93dfd4aa4c4a44 +Author: Lahav Lipson +Date: Sat Jul 1 00:37:36 2023 -0400 + + Remove old files, update file headers. (#82) + +commit e0b75a8b4359003ee98bdf742c03247e158c0024 +Author: pvl-bot +Date: Fri Jun 30 03:53:40 2023 -0400 + + Infinigen v1.0.1 - BSD-3 license, expanded ground-truth docs, show line-credits, miscellaneous fixes + +commit 3040c22ba751ac28d8acca3972d846f269971ffe +Author: Zeyu Ma +Date: Wed Jun 28 22:39:39 2023 -0400 + + Code separation + +commit 1ebed6765fd185c7757766bdd40a856f3f57fbe5 +Author: pvl-bot +Date: Wed Jun 28 18:26:21 2023 -0700 + + Add acknowledgements + +commit aeb7fd0556b7aa2c34e17d53a9b26741156904a2 +Author: pvl-bot +Date: Sun Jun 18 00:46:48 2023 -0400 + + Switch to BSD 3-Clause License + +commit e52bf9f0045dae127d403132a8ea8a2900aa2a9a +Author: Soney Mathew +Date: Thu Jun 22 05:54:30 2023 +1000 + + Update README.md (#2) + + Blender documentation says it's `-noaudio` instead of `--noaudio` (Tested in MacOS Ventura 13.3.1) + +commit e059b8cf310c5d94c34deb0679f13f9da3291606 +Author: Jordan Hubbard +Date: Tue Jun 20 21:24:59 2023 -0700 + + Change submodule paths. + +commit 313a05d780d4dd4f358ec1a4c462caa8268e2594 +Author: Pvl-bot +Date: Fri Jun 30 03:11:45 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/land_process/erosion.py, fix SurfaceTypes as final commit + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e08a6184b09a17d326cc64d51259173e527598a2 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:45 2023 -0400 + + Add 66 lines last edited by Zeyu Ma in worldgen/terrain/land_process/erosion.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 56329e9f3a058bafd27c136cf596fefa1850e0f7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:45 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/land_process/snowfall.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 83151be13cca1c77428c9adcf7616cc4079a02b8 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:45 2023 -0400 + + Add 73 lines last edited by Zeyu Ma in worldgen/terrain/land_process/snowfall.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4cc24cce7554ea3ad4212cc06197c2c7e13f4d22 +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 13 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/meshing/cube_spherical_mesher.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2a21f2d3569beeb00e3de6d75f6ec2071951621b +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 1240 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/meshing/cube_spherical_mesher.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0ddfffb512c7675c64bdc679678782a35dcb95a4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 14 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/meshing/frontview_spherical_mesher.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4707b1c20d8e7fb574f2404b7e9034ed5c622ba9 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 958 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/meshing/frontview_spherical_mesher.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d1c09900b0dadada0328749b12b6143ae765c18b +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 14 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/meshing/visibility_test.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9abbce21271db762a66be81a22bdb14f7c081c46 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 729 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/meshing/visibility_test.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9a8ec0fb964c3a6544e94c7d3ce71090067ed1d7 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 7 lines last edited by Lahav Lipson in worldgen/terrain/source/cpu/meshing/utils.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b47a3811069f8abea32db6cae584856b8f5d7ba7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 13 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/meshing/utils.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c21bd16dac9cf7b67ddfde98a0a162b4a3895451 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 274 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/meshing/utils.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cea5de37431e445f4f2fdc2e95e75784cba7317a +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 12 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/meshing/uniform_mesher.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b38a82b4ccf4fdf45bac85db661515bda519c7f1 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 310 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/meshing/uniform_mesher.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 40c8dea09dc35dbb4de73958ffc561bb1bb649ec +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/elements/mountains.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 56a12c342b8523f6a86b372ea1109523a1805736 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 16 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/elements/mountains.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 06cbc998082ca92b962eca2b5a85e338c047cf50 +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/elements/ground.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 25cddd8a2b273f470ab8de2e721280f8a6b3147d +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 21 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/elements/ground.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cb401d0e9d1f1438dab5aa305a9e007c92cb94e2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/elements/warped_rocks.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f27d0dca660d3516f9d2e58ab85946b37a218863 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 21 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/elements/warped_rocks.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dc0c3adf1e0a4ed5d16d15bf6f9f5168c47174d2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/elements/atmosphere.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 73c750c1816ba0524d01b7dcb63be6c8497dcebf +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 17 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/elements/atmosphere.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6821006fc83806e39251694bad9c6c19d0eb37cf +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/elements/waterbody.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3be2bd470c4f47e20ec3d96645102006eb67e42a +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 19 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/elements/waterbody.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 946c4a9e411297a0ce1471aeab017e544405f3d3 +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/elements/voronoi_rocks.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7dffb8b4688ef14d0107fd5b84bf5e0355362fa3 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 21 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/elements/voronoi_rocks.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 188697887a228ea66d5750114a9cb827f75e5339 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/terrain/source/cpu/elements/landtiles.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit db368388b5d16488045028f1a6e082cadae40103 +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/elements/landtiles.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4cba457063c3cd8bbbd2daa5c2e8c982c0a982f1 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 20 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/elements/landtiles.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 76e261bdb94169aac159a6f4e0a5b312ccdaf9f8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/elements/header.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d7fa201a6c9fe53d34dcc2d8cc6903466e73be6f +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 22 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/elements/header.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a9089cc6278d5a6e9c33bea66c1935d9b02719a0 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 5 lines last edited by Lahav Lipson in worldgen/terrain/source/cpu/elements/upsidedown_mountains.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6de8d76e7b953c3bd61fe9c2daa71ef6516de826 +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/elements/upsidedown_mountains.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 501fd9f085f5a5ddee03a6601dd7427f1aafc9ca +Author: Zeyu Ma +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 12 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/elements/upsidedown_mountains.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f9bd2e742b066121ba679c79321724f2b0e4d368 +Author: Pvl-bot +Date: Fri Jun 30 03:11:44 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/elements/core.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 003aee9267b4655462ee861e656fc545d3eb6969 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 41 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/elements/core.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5199a2384c3c0d9337ca247896b93098531dd9b4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 28 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/ice.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 369fb806f6f2924864a11621081bcf26f3b19449 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 29 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/cobble_stone.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 937752205c60d041230c2abb468ce0c6de1fe1ff +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 30 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/stone.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 50a4ed6ecaaed4a61c22ed98740db8b9ea45c8e8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 29 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/sandstone.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 436b443751fce727a585d01209e1257fd61953fa +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 29 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/sand.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 33ff252a4fe5196f03bc6bdf317f401ce791eb81 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 27 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/header.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 40365f0c6490369e7ea59f66ebdfd744fef47013 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 29 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/dirt.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7b1da1c7f848e7ec15a78d82868102624a6f44b1 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 29 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/soil.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ec0e1b753212309f8a824c4640e8d87c58dc5cb0 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 28 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/mud.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dc6d2011cc54d37ad55ee2b194165ca72060d0e7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 27 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/snow.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 53bc800b7f3654f1c510e10cf5f4b07c12b85ffa +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 30 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/chunkyrock.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1e842865fa7dddc06e2aacba8f00fe78d70b9f87 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 29 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/mountain.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0d759200f27e0b8072fcc4bb93ea32c6300a46e4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 28 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/surfaces/cracked_ground.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 86237c404769cbaff4c36e96f9fd291341348082 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 161 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/soil/rockgravelpebblessand.soil + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 05fb751ddb5dc49c61c836168255a58f9baa4932 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 63 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/soil/default.soil + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9f395cd3332cb94a40d489e9bdf3fb868fd60e91 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 60 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/soil/sand.soil + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5c4808672fcf1c6a699d99512456a5f48f2f455b +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 118 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/soil/rockgravelpebbles.soil + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 44cd7b9090c6d05ceade24602bb01b5b527d35da +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 108 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/soil/rocksand.soil + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 245370e0bd9546556d3665f91d09f5b4bf405422 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 114 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/particle/particle.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f90c35d48af8c56ff3470dbc8bc705ee403e7de7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 156 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/particle/wind.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1eef059900f6be5f89847d4af43478b5f7e6aaf6 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 385 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/particle/water.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8faa28325cdaec81c0eb6b4e9b2b5eb0d43be3ff +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 84 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/include/distribution.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6cc1edd468079585ab0de8f1bc4d6a44696686b9 +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 2586 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/include/FastNoiseLite.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 237997aa1ede3e3a95afeca06dd95ce74128ca8b +Author: Pvl-bot +Date: Fri Jun 30 03:11:43 2023 -0400 + + Add 360 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/include/vertexpool.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8c6dc05fc2e8b2e8af80f29faf08c3a13678a4c6 +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 247 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/io.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6e093438c2b6ea9b0910510f96934918b11196c3 +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 122 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/surface.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 272c4958492945d6d1fe798877a905006758786e +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 627 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/layermap.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4ca6cc2e51c3a83d93e5220a0c587f14042c5473 +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 79 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/soil_machine/SoilMachine.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit caab2aac7a4ffda7e8b9d223aaffc59d64931ba2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/terrain/source/cpu/utils/FastNoiseLite.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 72ee992a0b1b1d792e046f2d3a2b286dc0a9763b +Author: Zeyu Ma +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 21 lines last edited by Zeyu Ma in worldgen/terrain/source/cpu/utils/FastNoiseLite.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 464ddf83726a026930eb91b8b746159d9e0832ac +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/elements/atmosphere.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f9e5114f247364dd9b701376aec0a480128328e0 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 39 lines last edited by Zeyu Ma in worldgen/terrain/source/cuda/elements/atmosphere.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9b5f4a57a59d4d716923958136177ed35924a962 +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/elements/ground.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3fd179b7acc87988ba75396908d3f998c993f8a9 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 47 lines last edited by Zeyu Ma in worldgen/terrain/source/cuda/elements/ground.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cbb2f994cbda949af95f03622a2506a589624fc1 +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/elements/upsidedown_mountains.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1596141a911ab2bb592ce6da75168016c3afc605 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 12 lines last edited by Lahav Lipson in worldgen/terrain/source/cuda/elements/upsidedown_mountains.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0d7985a4e18e6729246b25360ea432a920e80bb2 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 31 lines last edited by Zeyu Ma in worldgen/terrain/source/cuda/elements/upsidedown_mountains.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b80bc4085034c2b9a17275a443df6b5e1a97f736 +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/elements/core.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dd603c9b6e678603089fcb519af06e97b10dbc27 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 54 lines last edited by Zeyu Ma in worldgen/terrain/source/cuda/elements/core.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c871c3cf4cf14620876b47b1c8f1177dab8c87b4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/elements/voronoi_rocks.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5f5f1f4e8c80fff433e7e3dac98a53e18b4f83fa +Author: Zeyu Ma +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 54 lines last edited by Zeyu Ma in worldgen/terrain/source/cuda/elements/voronoi_rocks.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4e9343756cf3fe0469dd0b256a39d6901396088e +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/elements/waterbody.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit abf703e63e345150d1176e9b152cdf3e6c6c1c0d +Author: Zeyu Ma +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 50 lines last edited by Zeyu Ma in worldgen/terrain/source/cuda/elements/waterbody.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2282d9f18673239d2440b87376b26b949ead7d0a +Author: Lahav Lipson +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/terrain/source/cuda/elements/landtiles.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1dd4d713dd7d3ce04025a337054c1b02ac0c7264 +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/elements/landtiles.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0d19659d5381bb7b28b58dc5c1056c1ca694eda2 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 49 lines last edited by Zeyu Ma in worldgen/terrain/source/cuda/elements/landtiles.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4eea05fe37a778b1e8b95c09d20280fdf23a28cc +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 10 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/elements/header.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ffd50b367e3fdc83a4006e86de4115a240ba3d7d +Author: Zeyu Ma +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 22 lines last edited by Zeyu Ma in worldgen/terrain/source/cuda/elements/header.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a0fbbf7e7d494f3db734aa5fa7a3e7a86d5a39a6 +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 10 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/elements/warped_rocks.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d423c2cb8cc94e397ec7cd42440bd974f9b866f2 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 50 lines last edited by Zeyu Ma in worldgen/terrain/source/cuda/elements/warped_rocks.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c1c39bf80c8aee2ceee76085c0736ec8f80f3029 +Author: Pvl-bot +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/elements/mountains.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 729397bdc13ef52a0c79feb62084d95d48e29856 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:42 2023 -0400 + + Add 36 lines last edited by Zeyu Ma in worldgen/terrain/source/cuda/elements/mountains.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b73e2318b9b37e5a4781201b5ceaf98f29b7fdac +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 64 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/chunkyrock.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2d3f11fcbadecfc4081a2dc10154e4f3a0c6883b +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 58 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/sand.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 74cc178655a18a4e8f6437e9940d5116fe232559 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 55 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/ice.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 24ddf1df072af0ca39903122fcebd09a80c9d655 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 64 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/stone.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e68462f69a2901cf3bf9b3d04da591588bb31ce3 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 54 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/cracked_ground.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 08d6a43f354f2ab35a9f1a25ce15f4db82a2e082 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 59 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/sandstone.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 55c1cb90a89725bf7e2b2e3ea4dc1747ff6bb927 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 59 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/cobble_stone.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 59f7c2472a3ba189d6869e748358cdc5b5106efc +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 53 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/mud.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 97e6f45b47b1dbcb51f868f5a9df18dabad137d3 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 38 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/header.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 45cf420485c891f85970881868cd3b1a0d2cf192 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 50 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/snow.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6dd21da6250bd04356cda8678ba4e60453650867 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 59 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/dirt.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 89e8ed2ae95cecc1687b35c1a254e91f03f5095d +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 59 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/soil.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 92aaa2da89d795f03df9f2e3a68080579784a796 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 59 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/surfaces/mountain.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 155e2ddafbf513140a30a871705c430f5d7056a3 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 10 lines last edited by Pvl-bot in worldgen/terrain/source/cuda/utils/FastNoiseLite.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 926bafa3ee1e6dd3ae16d0a7fcdf1153112c47ba +Author: Zeyu Ma +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 42 lines last edited by Zeyu Ma in worldgen/terrain/source/cuda/utils/FastNoiseLite.cu + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 339047ad282ddce279e792985e5c1250802eff07 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 165 lines last edited by Pvl-bot in worldgen/terrain/source/common/nodes/node_shader_map_range.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f6a9c916e165655a3a156c7674280547bdd7c283 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 89 lines last edited by Pvl-bot in worldgen/terrain/source/common/nodes/node_shader_tex_noise.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 502cb910f2363a32e8c13dd7be78a23cae913c7a +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 92 lines last edited by Pvl-bot in worldgen/terrain/source/common/nodes/node_shader_tex_wave.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit baa93b3ae059a66222f637731d10ded2f0743100 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 290 lines last edited by Pvl-bot in worldgen/terrain/source/common/nodes/node_texture_valToRgb.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d6ebfabfd22022865d3b5042d86b72c083eb7e5f +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 32 lines last edited by Pvl-bot in worldgen/terrain/source/common/nodes/node_shader_sepcomb_xyz.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d2b4d841b1418245b761103c287d3b3096ee4526 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 190 lines last edited by Pvl-bot in worldgen/terrain/source/common/nodes/node_shader_tex_musgrave.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 15dab862fe5facaa9b5a5d8d888c21d1c36bf032 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 58 lines last edited by Pvl-bot in worldgen/terrain/source/common/nodes/node_shader_vector_math.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 68a265c6d8b721e388d4300d92419eff6afec1f7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 278 lines last edited by Pvl-bot in worldgen/terrain/source/common/nodes/node_texture_math.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5b6d47fb015d92f660e64b0d5ddc66ca56c60995 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 32 lines last edited by Pvl-bot in worldgen/terrain/source/common/nodes/node_shader_mix_rgb.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ff6a1cd4afbed04abf01240156bc1da5ae616547 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 437 lines last edited by Pvl-bot in worldgen/terrain/source/common/nodes/node_shader_tex_voronoi.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b42021ef16240da77c54e34f41e22b3b4717ffaf +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 59 lines last edited by Pvl-bot in worldgen/terrain/source/common/nodes/node_float_curve.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 236b7cbee28dd8aca08a9730d61a6f8f669e5821 +Author: Pvl-bot +Date: Fri Jun 30 03:11:41 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/common/elements/atmosphere.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f5f509d0611eff0e89a89c3adf27abb308aba4f2 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 33 lines last edited by Zeyu Ma in worldgen/terrain/source/common/elements/atmosphere.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b7690e68d124e8d37bdbeca17adb7d2144c13548 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/common/elements/ground.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 633a44b5e18ed10e390113e520fff7869d72158c +Author: Zeyu Ma +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 65 lines last edited by Zeyu Ma in worldgen/terrain/source/common/elements/ground.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 64bd3ad4b468ecf2c2749f68544be6bc516286f7 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 4 lines last edited by Lahav Lipson in worldgen/terrain/source/common/elements/landtiles.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a3be16231109394430bd0231ec776a0234f235ff +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/common/elements/landtiles.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cbfcaaf8f8e77ffec114e00d1e1dfa36f02de1c6 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 187 lines last edited by Zeyu Ma in worldgen/terrain/source/common/elements/landtiles.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3b9cd2f52a9aa0c33dc3661d87ee3b3b32f25d74 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/common/elements/mountains.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit da1dbba68d740e396a00c088abbcecff63481055 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 55 lines last edited by Zeyu Ma in worldgen/terrain/source/common/elements/mountains.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bc2fe2b781e56ba3f1a1f91f52b03863e74e0e10 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/common/elements/waterbody.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cd49f7079ac1b5d519053f0bd8d9e983a9acfb88 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 35 lines last edited by Zeyu Ma in worldgen/terrain/source/common/elements/waterbody.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cf9a114ff3c2aa5f8e34df237a13746eb4ad0ce3 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/terrain/source/common/elements/voronoi_rocks.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 39f69a73ffb39782d30b1615353dad574dcddddc +Author: Zeyu Ma +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 154 lines last edited by Zeyu Ma in worldgen/terrain/source/common/elements/voronoi_rocks.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 35aaba8cdb981101fc06384827833b024d0550c4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/common/elements/caves.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 04fe96eef38a20836cc19822d66bb3244357bc09 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 96 lines last edited by Zeyu Ma in worldgen/terrain/source/common/elements/caves.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 31c91c81d3ddfcc597342b93af62d346f045337d +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/common/elements/upsidedown_mountains.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 175cf0f103c4a03aa79bea09d6853f2d4d2022e3 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 7 lines last edited by Lahav Lipson in worldgen/terrain/source/common/elements/upsidedown_mountains.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5fd91e8ae624bdcfdc1c76192bb15ce8646e2ed8 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 63 lines last edited by Zeyu Ma in worldgen/terrain/source/common/elements/upsidedown_mountains.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e5f708c702b4021080356b9883712cd3952dd2e9 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/common/elements/warped_rocks.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0d7d9c94b8cbf00ca80b277b93d0788f1074a04a +Author: Zeyu Ma +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 56 lines last edited by Zeyu Ma in worldgen/terrain/source/common/elements/warped_rocks.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cffd09694da75c829ab67e4b76fdf9f02f48d1d5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 189 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/chunkyrock.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7591e81f00e227a0517fadddd4ebc2e411072447 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 63 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/ice.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2cb748c507f8f47140facac762903aae6a3c2b6f +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 614 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/sandstone.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5949feaa2a32b25bfa4b19a452638fa2e5d20376 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 339 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/mud.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2d4631b73d45e8b23b678e60b4c7a88ad82f34d3 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 185 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/cracked_ground.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2020f0c2600c34cdda00c5ebdc8c460885b1aa50 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 1296 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/mountain.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4cfafaa3970a8cb1c1639a65bfd844aab9b6c8b4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:40 2023 -0400 + + Add 90 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/snow.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d7f88ca8668a49965c68ff53b55593e6cf0f830b +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 376 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/stone.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fcaf59c7c1cdd34ac19c106729125b87e5b68150 +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 399 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/sand.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1fd009838a0ac43954e94da7b12afbc6cc3fd380 +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 306 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/dirt.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d71858dc7d5fe4d971edb29a73e2f8bd522ca6ab +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 309 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/soil.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5dabf8f9e52152a444edb1166aba83cdef87f023 +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 152 lines last edited by Pvl-bot in worldgen/terrain/source/common/surfaces/cobble_stone.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b01daf75db77e3e16d105469d69ff5e24e886405 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 829 lines last edited by Zeyu Ma in worldgen/terrain/source/common/utils/nodes_util.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a77fbafb1f8260d604b3f3b8b113bbab28365074 +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 585 lines last edited by Pvl-bot in worldgen/terrain/source/common/utils/FastNoiseLite.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 33e8e7a621d5b0a315a4cae76f83f8bc88c30cbe +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/common/utils/vectors.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 52af8720e8cfa833a480fda090989b73e8ab6dbd +Author: Zeyu Ma +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 189 lines last edited by Zeyu Ma in worldgen/terrain/source/common/utils/vectors.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1946c2afd30ce28a7703579f7cb234d057645388 +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 2498 lines last edited by Pvl-bot in worldgen/terrain/source/common/utils/blender_noise.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 56a7e030f0118ac0b2490ffac7ddbd5e67e3b9d8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 5 lines last edited by Pvl-bot in worldgen/terrain/source/common/utils/smooth_bool_ops.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 085e45db314f87c278ce244497cd50613c8be70d +Author: Zeyu Ma +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 9 lines last edited by Zeyu Ma in worldgen/terrain/source/common/utils/smooth_bool_ops.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 463d6d70b3f6d99b91b7eb2f677f5f80b74dcbd2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/source/common/utils/elements_util.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ed0edd28e8795083e41f85d2a1d8ab8236c4f31f +Author: Zeyu Ma +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 167 lines last edited by Zeyu Ma in worldgen/terrain/source/common/utils/elements_util.h + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5503a285c437cf819f7d9f5b406a4e02c0cd0ab0 +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 2 lines last edited by Pvl-bot in worldgen/terrain/assets/caves/cfg.txt + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ad26966f4914b10ba53342462713bf6d6e0ef5e8 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 32 lines last edited by Lahav Lipson in worldgen/terrain/assets/caves/cfg.txt + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5909a7a6c0820410ab4a347cf81d495f5f40e668 +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/assets/caves/geometry_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit af6ca6179b9ba38eff220c0ab126b7bb8d64858d +Author: Zeyu Ma +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 30 lines last edited by Zeyu Ma in worldgen/terrain/assets/caves/geometry_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ceb8aae3edf812fcb3dcf1072e4d55af448fcc5b +Author: Zeyu Ma +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/terrain/assets/caves/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a7d7141a83eee19d72427716759d2bf31a4ba645 +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/assets/caves/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7c6fa86c44b8b35b92806a8a6d52a7c4a1babf43 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/terrain/assets/caves/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e03401b786fd8a8a6db1808a7b7ac40af8149506 +Author: Pvl-bot +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/assets/caves/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a70048d6e89af95df577a4adb6d6bfd1cf079b06 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:39 2023 -0400 + + Add 104 lines last edited by Lahav Lipson in worldgen/terrain/assets/caves/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d5f0d1b88e6da0beb8053124b22e4c8b06e17096 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 113 lines last edited by Zeyu Ma in worldgen/terrain/assets/caves/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cd7ff5a567af667c8b7c3e0e4c4a25165f511c24 +Author: Pvl-bot +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/assets/caves/pcfg.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 066cdfba8d01c0bcf8ff16e7ad126543aab5f135 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 11 lines last edited by Zeyu Ma in worldgen/terrain/assets/caves/pcfg.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 98525d810439e8a063ed25b89bbcd8591c42d9a8 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 47 lines last edited by Lahav Lipson in worldgen/terrain/assets/caves/pcfg.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 397bd5530a4fb4be94bf9125f7cd7c90537d7a03 +Author: Pvl-bot +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/assets/landtiles/custom.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c220ce6da281667233787857566bd74b48b09d3c +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 195 lines last edited by Zeyu Ma in worldgen/terrain/assets/landtiles/custom.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c46f9f677d742ed199564662780ffc9913406fff +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/terrain/assets/landtiles/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fe998030be082ae2df5632563f1ca116d0971083 +Author: Pvl-bot +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/assets/landtiles/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit da9784861325ab948f687b9ec73eb82711259341 +Author: Pvl-bot +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/assets/landtiles/ant_landscape.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e2de8cfe4274d72f5aed3aefb359df49716efdcf +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 64 lines last edited by Zeyu Ma in worldgen/terrain/assets/landtiles/ant_landscape.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ef4abe6c355c9f61df886348732814112f6d5fdb +Author: Pvl-bot +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/assets/landtiles/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 32dc7898ae60f0fab244dcac39b6065c034a654a +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 137 lines last edited by Zeyu Ma in worldgen/terrain/assets/landtiles/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2aca7e39a72e0944d78b8fd956a321efafd92597 +Author: Pvl-bot +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/assets/upsidedown_mountains.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f149e6865869385309f9136a88edb9aca9cd1e93 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 128 lines last edited by Zeyu Ma in worldgen/terrain/assets/upsidedown_mountains.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5dbfb9cd1ca123e3a7ec29af3f59560d692ee813 +Author: Pvl-bot +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/assets/ocean.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f58de275dec6cb66488f2233c5560eab3374f395 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 90 lines last edited by Zeyu Ma in worldgen/terrain/assets/ocean.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 18aa7286bdac54d96aca7c59d0132b90cc381412 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/terrain/elements/warped_rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 82df81a81ba1673e4acc974c2720118f7b140e2b +Author: Pvl-bot +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/elements/warped_rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3a85eb77ccf8ac81d26619cfda62911639670ad2 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 43 lines last edited by Zeyu Ma in worldgen/terrain/elements/warped_rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e7dcb0590764a47d85957510df5d7ebf6a41c2e6 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/terrain/elements/waterbody.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c045a41b921b077a28270a7186578375f96fa48c +Author: Pvl-bot +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/elements/waterbody.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5686d3320b13e47eb68f4d0b2cc08383cb6ec4a0 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 37 lines last edited by Zeyu Ma in worldgen/terrain/elements/waterbody.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 87f50e89f6376dcd496bc68fbe6490541e480863 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 3 lines last edited by Lahav Lipson in worldgen/terrain/elements/voronoi_rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 245a069e86f175305dacae906c93a80f27909bed +Author: Pvl-bot +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/elements/voronoi_rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 62a3601780807f6c69e4a454a8b4901a12422ee2 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 75 lines last edited by Zeyu Ma in worldgen/terrain/elements/voronoi_rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 148a8c47d241443d5e4c21e20ed5f7b498f63122 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/terrain/elements/upsidedown_mountains.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2eb5d3742b589846bc2f7371dd182178e8cc3e34 +Author: Pvl-bot +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/elements/upsidedown_mountains.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c8e6486f6b1cd5b8d14d2b966b19bf7177d69cf9 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:38 2023 -0400 + + Add 74 lines last edited by Zeyu Ma in worldgen/terrain/elements/upsidedown_mountains.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a82bee772e769b04e5329eb1a23cfbe335c0cf99 +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/elements/landtiles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9904aae6247d7f7b36d4fe7bf6a92ffdbfba2976 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Lahav Lipson in worldgen/terrain/elements/landtiles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7a0e8810f5f19cf13e0070948c609ad0478d24db +Author: Zeyu Ma +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 197 lines last edited by Zeyu Ma in worldgen/terrain/elements/landtiles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b906721695d83abe498c1f4b868b93e6d375edcb +Author: Lahav Lipson +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/terrain/elements/ground.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1970501acbc38d12a59ff1c7ecd69f9c8ba02e0a +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/elements/ground.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit eed0ce1a975dbba39d2551c290b670eaf42a974c +Author: Zeyu Ma +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 55 lines last edited by Zeyu Ma in worldgen/terrain/elements/ground.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2b37eaec949520fbcac37e28fffa8cb1f4d84f07 +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/elements/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a47e4bfd4a7558d284efb4cca98cb49a9217cd87 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 94 lines last edited by Zeyu Ma in worldgen/terrain/elements/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a3a96cc43e10cec80ae558a0e6bcb42d927ee6e6 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/terrain/elements/mountains.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ce626ea179d5c281b4fb8453c91aad10ad72dfe6 +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/elements/mountains.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9ec0b8ba4f80f1ec4b9700a5863705c927e48f7c +Author: Zeyu Ma +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 54 lines last edited by Zeyu Ma in worldgen/terrain/elements/mountains.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 10fcb2ea95548b0e86d9b352b264094ce0861d3d +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/elements/caves.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e654b23e528a6223bb1a004aa327a0989896d7d5 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 81 lines last edited by Zeyu Ma in worldgen/terrain/elements/caves.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0dbe126a05b2472768da60bbd79fe8feac9a4d73 +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/elements/atmosphere.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2789b1150032c98e6f308b7345d0af9164dc3d18 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 29 lines last edited by Zeyu Ma in worldgen/terrain/elements/atmosphere.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6b6f3902b21d6c42410b5ff0fa80ce27ca1d282d +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 303 lines last edited by Pvl-bot in worldgen/terrain/mesher/_marching_cubes_lewiner.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8d7276722e5b2903d68054d3333c873c07b30dc2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/mesher/cube_spherical_mesher.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit deaf37362c33b80a4554e813a221fb0b3078da54 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 221 lines last edited by Zeyu Ma in worldgen/terrain/mesher/cube_spherical_mesher.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 658f2915ad5103a2c99fac1f8996e39768f31217 +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/mesher/frontview_spherical_mesher.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7cea012be68bb384ec51b175a65b8af904942bba +Author: Zeyu Ma +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 230 lines last edited by Zeyu Ma in worldgen/terrain/mesher/frontview_spherical_mesher.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1179436a1a0d6f39459670af25f3a3c7cc2470fa +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/mesher/spherical_mesher.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit efee975ee0325dc661fd9a539a37efdd6dee0bda +Author: Zeyu Ma +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 159 lines last edited by Zeyu Ma in worldgen/terrain/mesher/spherical_mesher.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b01fd68abdd8ee470026217cf582bf9c5e4fa1e5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/mesher/uniform_mesher.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dbdd9e83ff4614d324aa975ed65ad5f94338e9df +Author: Zeyu Ma +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 154 lines last edited by Zeyu Ma in worldgen/terrain/mesher/uniform_mesher.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e90711721cee0f493ca1308fcfd3f0d3bbc57901 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 2 lines last edited by Zeyu Ma in worldgen/terrain/mesher/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c4e4e22831094f7fa6256653008d19e7e08bcfd2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:37 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/mesher/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e752f111b1c9f08400e7d3a50f0a9210843a82b0 +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 533 lines last edited by Pvl-bot in worldgen/terrain/mesher/_marching_cubes_lewiner_luts.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 925928586bbe9e22ff80ead4c125bfd25766020f +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/terrain/surface_kernel/kernelizer.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a223624a858b5bc9ceb3a033ed13c095d87feec7 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 330 lines last edited by Zeyu Ma in worldgen/terrain/surface_kernel/kernelizer.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4efdd49ef27e2895645ea5ca945b5d6f4eeeb6b0 +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/surface_kernel/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 02c0cdb1579e441ce826b4b7e7f9aac5de2e8a40 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 104 lines last edited by Zeyu Ma in worldgen/terrain/surface_kernel/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 697cd6a5b31739eb688ded342ba99083434e327d +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 1460 lines last edited by Pvl-bot in worldgen/terrain/marching_cubes/_marching_cubes_lewiner_cy.pyx + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9451c899d0e32f339311dc624a9dc5f380035fdf +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/terrain/utils/logging.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ff96bc4ea57c13d27fd0a3f7e0a556ce1d976557 +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/utils/logging.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0bf4a35c1471ffeb7727e2bfed0b61aad65a5760 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 24 lines last edited by Zeyu Ma in worldgen/terrain/utils/logging.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6ddc4d66a325bbf292efb639fe19dff6baab2547 +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/utils/ctype_util.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 75bdaca30979d267e9016a9feb796b3ef0168b65 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 22 lines last edited by Zeyu Ma in worldgen/terrain/utils/ctype_util.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 074fec5a44e7c16cf8f58c48e1a5e28bfb6e60d8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/utils/image_processing.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a187f56bac34492050aec2ae7cbbcf299fe1fe5d +Author: Zeyu Ma +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 69 lines last edited by Zeyu Ma in worldgen/terrain/utils/image_processing.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4e9448cb137841f81d40e7f9c92d3dadabaf3c5c +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/utils/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit de0ea76dc9044be143620b623ce4376355bdca4c +Author: Zeyu Ma +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 17 lines last edited by Zeyu Ma in worldgen/terrain/utils/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7ac59976ad3e99d982612bf2b56820191f5c230c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/terrain/utils/mesh.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3e39289b52c0f3bd7caefc2c77ce59f2d5747ad0 +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/utils/mesh.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 46679a09de0b50ceb0963188944226bf7eabf244 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 16 lines last edited by Lahav Lipson in worldgen/terrain/utils/mesh.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4bee70645bea16cfaf421811b6b160902d18a8e7 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 302 lines last edited by Zeyu Ma in worldgen/terrain/utils/mesh.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b809edd7070ce61c92bc50a31fb0ac840f8a104d +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/utils/camera.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 78b60bc0c8eb0e2dc086264fe04dee8481be0fed +Author: Zeyu Ma +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 71 lines last edited by Zeyu Ma in worldgen/terrain/utils/camera.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1b1c8f405bb5eb41c3cacdbe4bbc7b7586ad5ec5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/utils/random.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d817eacf5d7323e52a6712306120479c899535ff +Author: Zeyu Ma +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 47 lines last edited by Zeyu Ma in worldgen/terrain/utils/random.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 583dd159742bccfefcfce05e9ca035501b12de53 +Author: Pvl-bot +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/utils/kernelizer_util.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f2986d5004265d9816f3710c6c12f5b7862e37dd +Author: Zeyu Ma +Date: Fri Jun 30 03:11:36 2023 -0400 + + Add 283 lines last edited by Zeyu Ma in worldgen/terrain/utils/kernelizer_util.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 06732fbabfdac9135c1b261573c4f1f2a34711f0 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/terrain/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ffaf7ae18a5a757554a233e74f0078742b7eab20 +Author: Pvl-bot +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 83fbc38b21a289cc8c020882b3873fcd0ecb11c6 +Author: Pvl-bot +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/terrain/setup.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ffdca236d98d94675bee9d12624b25dbb7126288 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 29 lines last edited by Zeyu Ma in worldgen/terrain/setup.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e4527748bdb39f5da991062f99592e3cfc7669a4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/terrain/install_terrain.sh + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 117a5775adc9056ff410126e47dfc655e62d1b93 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 124 lines last edited by Zeyu Ma in worldgen/terrain/install_terrain.sh + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 118d889c100359b226a7447eafadb6bf3cab1418 +Author: Pvl-bot +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ed02f2ca51e2aad9f9ce383642fc622bc026a0bc +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 9 lines last edited by Alexander Raistrick in worldgen/terrain/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit efe1ee2bcb899359be9753e333cb07669b74ddb7 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 456 lines last edited by Zeyu Ma in worldgen/terrain/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 42dfeab2c3d8ce8cf948409a786a20e0d6e61f3c +Author: Pvl-bot +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/terrain/scene.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1c3caccc175b6035dddfa45085bb422e782337d2 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 100 lines last edited by Zeyu Ma in worldgen/terrain/scene.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 63386fc3333b2d126c88dfa0d30adfbb939c2abe +Author: Zeyu Ma +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/util/logging.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 012a8e9549fdc178ef54b7fafbff7012163d358e +Author: Hei Law +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 3 lines last edited by Hei Law in worldgen/util/logging.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 839d5350a126b821a1834f738c713d0a5908eb67 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 4 lines last edited by Lahav Lipson in worldgen/util/logging.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 46c0ea7bc2bc0c655febf0a6e51bb2a958cc6db2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/util/logging.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 21d8b9c7c45c004e70c56f9f6d4a7ef7740871f6 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 56 lines last edited by Alexander Raistrick in worldgen/util/logging.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 485d67bf71418bcf6b84f5d7d877227cbab4806c +Author: Zeyu Ma +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 6 lines last edited by Zeyu Ma in worldgen/util/math.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit db832935d38cf9e633f567c1d3e9afc7e2a07c85 +Author: Pvl-bot +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/util/math.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3b13a4228c0febc2cc37af86c4ab2340555f3877 +Author: Jia Deng +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 44 lines last edited by Jia Deng in worldgen/util/math.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6e02cb8505fe154dc706534e7469373adeea196c +Author: Lingjie Mei +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 52 lines last edited by Lingjie Mei in worldgen/util/math.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 908ea180068a6204f3dc13151847caa8bc328640 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 267 lines last edited by Alexander Raistrick in worldgen/util/math.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d33c4aed5aa4e63f3c5ce5688726750bf00af072 +Author: Yihan Wang +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 1 lines last edited by Yihan Wang in worldgen/util/exporting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3387103219d28ac10da49b585262525c9f71db18 +Author: Pvl-bot +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/util/exporting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dc7a834e6a6707c577fa4312f864888ecbcde2bd +Author: Lahav Lipson +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 250 lines last edited by Lahav Lipson in worldgen/util/exporting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 61fcf50d3134f9d22e9e1c62f75c85bed755dc7c +Author: Pvl-bot +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/util/blender.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d00d862971805fcc66043c69ecec72a27ceff2bf +Author: Lahav Lipson +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 7 lines last edited by Lahav Lipson in worldgen/util/blender.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 71d750b18ff6207fbfa5e1a055588239a90f7176 +Author: Hei Law +Date: Fri Jun 30 03:11:35 2023 -0400 + + Add 10 lines last edited by Hei Law in worldgen/util/blender.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 73ebc49214c52725faae5e62a8d66b7330c2c273 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 78 lines last edited by Zeyu Ma in worldgen/util/blender.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e36706d04f7315ed8c71293bdc23c9680a7a12bf +Author: Lingjie Mei +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 84 lines last edited by Lingjie Mei in worldgen/util/blender.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cedf84ffd431bd56c0422d4964c645870f48f569 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 553 lines last edited by Alexander Raistrick in worldgen/util/blender.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit edbdee1c56915247227fdb1f45f0413f8c70606b +Author: Pvl-bot +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/util/pipeline.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cfe923609e0358d3e94ff5bb0578cdcb13170de3 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 67 lines last edited by Alexander Raistrick in worldgen/util/pipeline.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f6deec15fd555fa4948ce00d8987bba29a32c908 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/util/organization.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8a5819bc7935e607d8f3facc87ffdd14f0cc7707 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/util/organization.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ef4b0109ffb30d1eff7f3ed7bff63391eed235a5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/util/organization.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b1c6b86028c3c9373953fcf34b80cdfb1e058312 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 103 lines last edited by Zeyu Ma in worldgen/util/organization.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5c88e81bc425ae41b69c9ab2accba3d1eb7ef704 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 175 lines last edited by Lahav Lipson in worldgen/util/camera.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0f1cd7c27afe1c40135b3773b85b84bec7cc608a +Author: Lingjie Mei +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 1 lines last edited by Lingjie Mei in worldgen/util/random.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 36d3bd1b856af1f97fe3d80fdb8365f51a72926a +Author: Pvl-bot +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/util/random.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1bc8e3097c5d2322590ccb5e8d0aa553a318b55a +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 59 lines last edited by Alexander Raistrick in worldgen/util/random.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4482ea1e124e29af7055eb25c5117615fa7e73c1 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 134 lines last edited by Zeyu Ma in worldgen/util/random.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 40f54d04f4ee7153de1fd7ce97d716675d165dc7 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/placement/placement.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9addba12191b7491f6625bf327429912c7ab26e4 +Author: Karhan Kayan +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 4 lines last edited by Karhan Kayan in worldgen/placement/placement.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 70890ea719a72686a870d3b8f4bf262767235869 +Author: Pvl-bot +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/placement/placement.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b928dc6010d30bc6c15747502a610084550d957b +Author: Zeyu Ma +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 9 lines last edited by Zeyu Ma in worldgen/placement/placement.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit aa111820262e700b218baf3a20b9113434d70168 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 17 lines last edited by Lingjie Mei in worldgen/placement/placement.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dc2b197c51c7c3b5046c8ec42bbf5a3f27ddebb3 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 218 lines last edited by Alexander Raistrick in worldgen/placement/placement.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2cdaff645022d47008f4cde8dec1dc44d3441f3e +Author: Hei Law +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 1 lines last edited by Hei Law in worldgen/placement/factory.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 792c0428da3a0ffa35c7e7515e74b39c933ee5b5 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 5 lines last edited by Lahav Lipson in worldgen/placement/factory.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4725bb330bf9dbbb6290ef135d806e877f141c0a +Author: Lingjie Mei +Date: Fri Jun 30 03:11:34 2023 -0400 + + Add 7 lines last edited by Lingjie Mei in worldgen/placement/factory.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ca08588329fb15f13c42befddd68b70adec3cf59 +Author: Pvl-bot +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/placement/factory.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a41cd76bbead9802f8b7764897357d442ed26290 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 146 lines last edited by Alexander Raistrick in worldgen/placement/factory.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b82018bb812f887ba9d22a7a513e2a76dccdf228 +Author: Pvl-bot +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/placement/animation_policy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5b87800c9c9d11a9b2feefd6e4fb92b75e2926d9 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 32 lines last edited by Zeyu Ma in worldgen/placement/animation_policy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e977a20cd5004d0850a73e800b44a13299b94418 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 444 lines last edited by Alexander Raistrick in worldgen/placement/animation_policy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9243b65884d9e25347629d9b1e11adac9d7bac19 +Author: Pvl-bot +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/placement/instance_scatter.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b7cf1454c112188b131453590bf01919b29623b3 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 12 lines last edited by Lahav Lipson in worldgen/placement/instance_scatter.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 867e6ea2fd400e3d4416fb91bb0bd285f8e66d9c +Author: Lingjie Mei +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 35 lines last edited by Lingjie Mei in worldgen/placement/instance_scatter.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7d37d055539cdbbfdb91f50d687bbe6e0a845e61 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 169 lines last edited by Alexander Raistrick in worldgen/placement/instance_scatter.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 53783ffe6884f472186f20c629aed9c913abd1a6 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 3 lines last edited by Zeyu Ma in worldgen/placement/particles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 36c5a8aeb8d22716c37b2fb767b37c61d0e3d224 +Author: Pvl-bot +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/placement/particles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0857ebfe739496198467dfd82bcdb1dff53c8472 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 18 lines last edited by Lingjie Mei in worldgen/placement/particles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 57aa91c3de91726209722c65e3f76c91f9f84f49 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 208 lines last edited by Alexander Raistrick in worldgen/placement/particles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4107232a4579514b6c502af3f576f21a45491d42 +Author: Pvl-bot +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/placement/density.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fdb9d66f2cd2feeb97289905a5d8113f048c0c51 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 8 lines last edited by Lahav Lipson in worldgen/placement/density.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 10f4c392a2469bab06ee89e990e465182629aa9c +Author: Lingjie Mei +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 8 lines last edited by Lingjie Mei in worldgen/placement/density.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2153c9e08391a886d0f62707b3393062de2439ed +Author: Zeyu Ma +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 25 lines last edited by Zeyu Ma in worldgen/placement/density.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f625ea0e99b4b94c665e3092e4cf01102f36ffa8 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 32 lines last edited by Alexander Raistrick in worldgen/placement/density.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a34b388cd196dcf53fc4cd9d63477a6cca75f229 +Author: Pvl-bot +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/placement/camera.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6827237a6d35bcd3dc04023f17fecf06afb2bd4c +Author: Hei Law +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 20 lines last edited by Hei Law in worldgen/placement/camera.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9a90db1a9b11d5fb59cdb26748eba95ace99c412 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 38 lines last edited by Lingjie Mei in worldgen/placement/camera.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e79b1f08b062557304a40e18d081be543cd4a366 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 89 lines last edited by Lahav Lipson in worldgen/placement/camera.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a87dd9dd313e6afb6cbce291048481cb1631aa73 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 92 lines last edited by Zeyu Ma in worldgen/placement/camera.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 23aab7f49dc8cdfb5779bc438bfadc80a48f6895 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 288 lines last edited by Alexander Raistrick in worldgen/placement/camera.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6988922bf8f4c0cba386e9770ede022b888feb0a +Author: Zeyu Ma +Date: Fri Jun 30 03:11:33 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/placement/detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bfbcffc4d4f3c9b3133f93b0ce5a9dc070b08be2 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/placement/detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 02bb2d21b84e2325f95de075d65fdc0b8f171a11 +Author: Pvl-bot +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/placement/detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 222d9c1fab7608bcf17ebe071e78e6c8870073d8 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 64 lines last edited by Lingjie Mei in worldgen/placement/detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4b72f3a4998779b59f6f3f0cefbf368d262d388a +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 99 lines last edited by Alexander Raistrick in worldgen/placement/detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f0b8a294a27a82e9f77b2be5770411bc56bdc6f0 +Author: Pvl-bot +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/placement/split_in_view.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 82c342392565d7f1737ff7d3c9e19a0675528903 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 115 lines last edited by Alexander Raistrick in worldgen/placement/split_in_view.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6d9da80880f95fc0ed6365ecc0e1ce725ba04a61 +Author: Pvl-bot +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/results/aggregate_job_stats.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 35a0f09e5f97c8a45b2c176dfcad2b583742ab53 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 95 lines last edited by Lahav Lipson in worldgen/tools/results/aggregate_job_stats.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8676d57777c3c4bbf834e7308c8596a37b9613ee +Author: Pvl-bot +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/results/resource_stats.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d2f8d568da167478ea6077dd71d539621b7a73d6 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 117 lines last edited by Lahav Lipson in worldgen/tools/results/resource_stats.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8892d082d732f50dc09404a13f715dcc35924cc2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/results/job_stats.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fddf01eafb2302d2c519cf4ede579dbbf9ba6665 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 99 lines last edited by Lahav Lipson in worldgen/tools/results/job_stats.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit eb3127fe990079687a2572150804f1fe3038d899 +Author: Pvl-bot +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/results/scatter_figure.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 49e3cc0dcd6588d77ba6b49e550af03b46ed8cab +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 10 lines last edited by Alexander Raistrick in worldgen/tools/results/scatter_figure.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 92af756e27ca7d059730d7e23617d62294a4d035 +Author: Hongyu Wen +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 170 lines last edited by Hongyu Wen in worldgen/tools/results/scatter_figure.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit de67aa7d9db88ae0455da5b85dcbbf3e676e6c37 +Author: Pvl-bot +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/results/make_grid_figure.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3ca2c27b14b2f216a345a48b95a2ea4cd8ab3fed +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 79 lines last edited by Alexander Raistrick in worldgen/tools/results/make_grid_figure.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9498fc5f10bef531cc42ae8192345d53435284d4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/tools/results/parse_videos.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0103dd8bfa036961e06a83cb637d3c34cdb948a3 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 47 lines last edited by Alexander Raistrick in worldgen/tools/results/parse_videos.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9b8ebdd6d0ff94d1dcaf6342794fd69b8f8acb81 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/tools/results/parse_times.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cfabaa0e5382f37a3e3dd21db3ea656c7d630e20 +Author: Pvl-bot +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/results/parse_times.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 79acaca0fe22868c148f7b139aabdb93d7feb701 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 53 lines last edited by Lahav Lipson in worldgen/tools/results/parse_times.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8212edf7ba8c650617483342277cbef525b3bd0a +Author: Pvl-bot +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/results/strip_alpha_background.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7a5ef91f137828efb45b31a1ea0458d12063cb40 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:32 2023 -0400 + + Add 35 lines last edited by Lingjie Mei in worldgen/tools/results/strip_alpha_background.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6758b0a8aa2e37ab02b26f30c927ee1c7306f50c +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/tools/util/smb_client.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 670263fa5626ac943e41232ae0e033323861d207 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 31 lines last edited by Lahav Lipson in worldgen/tools/util/smb_client.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 94d33bcfc2bb0f468e7d83728f6be9a258f44e7f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 40 lines last edited by Alexander Raistrick in worldgen/tools/util/smb_client.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 978c2060b01741bacbea1d7677e34309fd55cccd +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/tools/util/submitit_emulator.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ad1a2cef2e995aaa6ce7584ebbdae1c74f11eeca +Author: David Yan +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 13 lines last edited by David Yan in worldgen/tools/util/submitit_emulator.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bed7df582a34fc21ad31bb5bbf45e570338c4e9b +Author: Lahav Lipson +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 48 lines last edited by Lahav Lipson in worldgen/tools/util/submitit_emulator.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 00182ebfe08685c4f032d47ae5a9a08c4147477d +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 205 lines last edited by Alexander Raistrick in worldgen/tools/util/submitit_emulator.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ebe043ff1affb0125f208cdf8046aa17fc184ae2 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/tools/util/cleanup.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f0d078c7189acf2031d15e43e5f8b53a39b387a1 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 2 lines last edited by Zeyu Ma in worldgen/tools/util/cleanup.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6227474d3e683453f0fe763abf823c4d495af2f9 +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/util/cleanup.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4fce49c6f76d6a1e5829dcab4c33cfff8fd4ad2c +Author: Lahav Lipson +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 26 lines last edited by Lahav Lipson in worldgen/tools/util/cleanup.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 310bd59de24a6a5b929b922c58e6f00b6f481393 +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/util/google_drive_client.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 25c6636d75cde3d067455f39683c08fd9bdf76e8 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 17 lines last edited by Lahav Lipson in worldgen/tools/util/google_drive_client.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8780d296a177e91f12d430d7afaeff7d44c21b33 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/tools/util/show_gpu_table.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ee6e23b85481c92b55a899c90ab785c95874aea5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/util/show_gpu_table.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 82cf3acaccda4a67b013d3f8ce81fbcff59bdec3 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 58 lines last edited by Lahav Lipson in worldgen/tools/util/show_gpu_table.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e03f4ee23839c5b2ccb1886cf90139536a3b1f31 +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/util/upload_util.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit da47341be52fa9269168df2b18e03917b2d9ea71 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 114 lines last edited by Alexander Raistrick in worldgen/tools/util/upload_util.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d4204525783da9944de8b324abf6d55dea32000c +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 3 lines last edited by Pvl-bot in worldgen/tools/ground_truth/bounding_boxes_3d.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fb1c6062e6854da6e2b1897ff9e948a32fca1dac +Author: Lahav Lipson +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 128 lines last edited by Lahav Lipson in worldgen/tools/ground_truth/bounding_boxes_3d.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bf2e16cf84ea1767fe2db5aa899043cea6537814 +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 3 lines last edited by Pvl-bot in worldgen/tools/ground_truth/rigid_warp.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5b5820c110a64179c3ee4471e2584de19379ce75 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 71 lines last edited by Lahav Lipson in worldgen/tools/ground_truth/rigid_warp.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 14356c73528dbd25dba55cf1765b0d20aad231b4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 1 lines last edited by Pvl-bot in worldgen/tools/ground_truth/segmentation_lookup.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7bd87435b3d3f351264cc26964fe97f89c19652d +Author: Lahav Lipson +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 112 lines last edited by Lahav Lipson in worldgen/tools/ground_truth/segmentation_lookup.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b6339eb7e74a287d9f942ff110d5e36396b2ef1e +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 3 lines last edited by Pvl-bot in worldgen/tools/ground_truth/optical_flow_warp.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b4969f7d0d6fbfe4b84668934de01f8b7a24ce49 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 47 lines last edited by Lahav Lipson in worldgen/tools/ground_truth/optical_flow_warp.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7047e2ba8c5e2e25bafd8f53e58bc8bbdd3c1e61 +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/tools/export/export.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cd713d252d8d5bc99373f9386866c5c86e81db22 +Author: David Yan +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 298 lines last edited by David Yan in worldgen/tools/export/export.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f52a24db5021621de0dad157af6737d7d2d1568f +Author: Pvl-bot +Date: Fri Jun 30 03:11:31 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/dev/params_parser.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9eaf36ec6d0674095af00377d974119c5c22a449 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 43 lines last edited by Zeyu Ma in worldgen/tools/dev/params_parser.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5267ee392fc6acc14c402a7e756d4bcac9a96c0e +Author: Pvl-bot +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/dev/landtile_viewer.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9c03b35df3cfde7422288acdb681a9857f99aeaa +Author: Zeyu Ma +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 34 lines last edited by Zeyu Ma in worldgen/tools/dev/landtile_viewer.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b63d362e30570d4a5c675ba7d47ff537c630cdf4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/palette/palette.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1e5d6b7e6490175774557f5d1f49eb866dfe20db +Author: Lingjie Mei +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 12 lines last edited by Lingjie Mei in worldgen/tools/palette/palette.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d1df11c61f005f64584e618fdcb719d62ecb2412 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 99 lines last edited by Zeyu Ma in worldgen/tools/palette/palette.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a363493017d476d8f550a7a75bf839dd411ea721 +Author: Pvl-bot +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 1 lines last edited by Pvl-bot in worldgen/tools/pipeline_configs/local_256GB.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2722260c66efa202ca74c954125239a2c0813d4f +Author: Lahav Lipson +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 5 lines last edited by Lahav Lipson in worldgen/tools/pipeline_configs/local_256GB.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f38f050b7e7b991112dc70b36bcc1f1d4d6539e8 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 35 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/local_256GB.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6977eab608a3cfdb46ad724d96f29a34be40e16e +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 6 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/local_16GB.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 37005f39db0103e8978e6da2d171e56851ebfb07 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 4 lines last edited by Zeyu Ma in worldgen/tools/pipeline_configs/cuda_terrain.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6eb9fcb89bb62684144b2e2559f94eb159831b30 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/stereo_video.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fda5805ac8684eeb2ae851647fa7f528e0f59db6 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 5 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/local_128GB.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 26fb0a355b8746136af96fbc07c0fc3ccc9fd4fa +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 3 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/asset_demo.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c3ce2900eff969e39e074407efa5dc69d9ee41b6 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/tools/pipeline_configs/stereo.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 718eb794a7288c1954b9c6b0e5114209a9ce5be4 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 10 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/stereo.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b6670a890627c407b96178ae62b68cecc4b7fcd4 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 5 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/local_64GB.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1b55b251cca8b02c58085649023533f30f9828d7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 1 lines last edited by Pvl-bot in worldgen/tools/pipeline_configs/monocular.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 45a8d71ea618286cea9bf2b70a62dd2208ca4bbb +Author: Lahav Lipson +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 8 lines last edited by Lahav Lipson in worldgen/tools/pipeline_configs/monocular.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f2ca8ada5a5d4b6ef6dbe00bc3433cd43555a7fa +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 8 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/monocular.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6901c26a95dabbe5c262f019aa0629c6f146eb95 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/tools/pipeline_configs/blender_gt.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7655583efc525fefecf5e326dc69458e28dafea1 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 3 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/blender_gt.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7254ce423eb9ebc13c9277d265ab55be18f2e53f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/monocular_flow.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b005f8b93e7e17a5ba682ac99c645d1dd1756c96 +Author: Pvl-bot +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 1 lines last edited by Pvl-bot in worldgen/tools/pipeline_configs/slurm.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1246ea48a4a670612808029109ae3313e0507c29 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 28 lines last edited by Lahav Lipson in worldgen/tools/pipeline_configs/slurm.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 73fbe7c44af1b2b4193fe4a0268ac321e45e89f9 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 58 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/slurm.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f84978fecdf46041a66ef24f9649f3e249171f2f +Author: Pvl-bot +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 1 lines last edited by Pvl-bot in worldgen/tools/pipeline_configs/stereo_1h_jobs.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit db91f830c86eaaf97f370abdfad01ec604dd96b7 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 2 lines last edited by Zeyu Ma in worldgen/tools/pipeline_configs/stereo_1h_jobs.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 633614af2f0d3673002442254964621cf92660c5 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 11 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/stereo_1h_jobs.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3e7de65403621102303402d84a2af89413a96712 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/tools/pipeline_configs/opengl_gt.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5b3879ee532a6b98924857a6d0c8b46f8aa8bd32 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/opengl_gt.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fa8189908a59a007b2f664e96f5d4fc3e1ee346a +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 17 lines last edited by Alexander Raistrick in worldgen/tools/pipeline_configs/monocular_video.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b347c94a3ced3313023ff91e4adcdf5ea0816898 +Author: Pvl-bot +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 2 lines last edited by Pvl-bot in worldgen/tools/compile_opengl.sh + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4d9d863fa804367782efa435b6886d0135e608fb +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 10 lines last edited by Alexander Raistrick in worldgen/tools/compile_opengl.sh + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cd1a01c327668961df34fed88a12b019a755cb2f +Author: Yihan Wang +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/tools/asset_grid.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c05865f99ceb29f2f2a088fa5e95d86945a29f9a +Author: Pvl-bot +Date: Fri Jun 30 03:11:30 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/tools/asset_grid.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 85d63a82a9eece6ab070ad0d1eaedb7d3639ae0b +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 118 lines last edited by Alexander Raistrick in worldgen/tools/asset_grid.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8cd15f0f5bd16dac9b313737ff08ccb964160f92 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 204 lines last edited by Lingjie Mei in worldgen/tools/asset_grid.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f96cd6186ef353a841dc0ffa8eddf548e9d8af3f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/tools/generate_terrain_assets.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c108731140abe296846a0bd2b6989da921c1dbea +Author: Pvl-bot +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/generate_terrain_assets.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1a6004970fc585412f492ec2ac2d12511a5c5c29 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 76 lines last edited by Zeyu Ma in worldgen/tools/generate_terrain_assets.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 85b90a55e816bd19f2f33b037d6a82446295325d +Author: Pvl-bot +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/cancel_jobs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a55d2ae7f0210e51653f9c494e49f06549142f9f +Author: Lahav Lipson +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 22 lines last edited by Lahav Lipson in worldgen/tools/cancel_jobs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 809fa9e8ee14c5836981c9b702601b86617e328d +Author: Lahav Lipson +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 23 lines last edited by Lahav Lipson in worldgen/tools/template.html + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 733da481d61a73799405cba6fa6f8b2ab934db10 +Author: Hei Law +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 37 lines last edited by Hei Law in worldgen/tools/template.html + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c5a03edf6c52d7c4bd535ec06eef81787d2e4aef +Author: Pvl-bot +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/tools/kernelize_surfaces.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1305f23f35c2388d7dc4deca2808bf6a5ad01ce2 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 30 lines last edited by Zeyu Ma in worldgen/tools/kernelize_surfaces.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 25f237ab3d162d4cf88e40c8b9a8534e664ec721 +Author: Pvl-bot +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 5 lines last edited by Pvl-bot in worldgen/tools/summarize.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit de86cd7466eceea4ebe7f8198482e05e0f04d510 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 151 lines last edited by Lahav Lipson in worldgen/tools/summarize.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c737f78639f7a6a0134f9603023400aa463431e7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/tools/manage_datagen_jobs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 97f5bdcb7ab58d3ed669eb6f5eed289a607b0f1b +Author: Zeyu Ma +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 34 lines last edited by Zeyu Ma in worldgen/tools/manage_datagen_jobs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 84fbb516c6136cee72dbb65c5f01109a9be596e0 +Author: Hei Law +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 67 lines last edited by Hei Law in worldgen/tools/manage_datagen_jobs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1327421eec2e8ed6d9be6bc1142275410fd18c8e +Author: Lahav Lipson +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 212 lines last edited by Lahav Lipson in worldgen/tools/manage_datagen_jobs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1413fd14f328c843f73e19661c923c2afc46f5db +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 719 lines last edited by Alexander Raistrick in worldgen/tools/manage_datagen_jobs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 41ce5efcc16b7e95e9b1bd330d1ad4e234eb349a +Author: Zeyu Ma +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/assets/grassland/grass_tuft.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a6367dcee3d7fa1461434b966a017d7734938607 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 1 lines last edited by Yiming Zuo in worldgen/assets/grassland/grass_tuft.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 828739a7f06bde25102962087a49ea44fc9d1e1b +Author: Yihan Wang +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 3 lines last edited by Yihan Wang in worldgen/assets/grassland/grass_tuft.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f8fafdd121a45b34dc08c7fa097313f6ce30d51d +Author: Pvl-bot +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/grassland/grass_tuft.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit add953855b65019cbed6c00a1310040d6e0641e2 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 88 lines last edited by Alexander Raistrick in worldgen/assets/grassland/grass_tuft.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ff4c7ab90be54e90df9266286711cc20ca2b9375 +Author: Pvl-bot +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/grassland/dandelion.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 712811a3c6fbbcf572423c41bea6ca5214227f16 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 11 lines last edited by Yiming Zuo in worldgen/assets/grassland/dandelion.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d1aa204da9ed93f71d0e3a99572c91c78359c5dc +Author: Yihan Wang +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 17 lines last edited by Yihan Wang in worldgen/assets/grassland/dandelion.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bd246c44612d31a5a073bfbff7192c4a238b4410 +Author: Beining Han +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 636 lines last edited by Beining Han in worldgen/assets/grassland/dandelion.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 57acf128c6b1e9d287bd01d7b6bfe7d9d2b193cd +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/grassland/flowerplant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7bb69c857227dfbf66f6c6442c731aaf17c69e97 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 1 lines last edited by Lingjie Mei in worldgen/assets/grassland/flowerplant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bb2a5c61e220dcac04f081f1311ba1778f5ba09d +Author: Lahav Lipson +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/grassland/flowerplant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2073684656f6a7801bf8b20fcf317ceefeadb645 +Author: Pvl-bot +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/grassland/flowerplant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 091332d8dcff3e3c9472c43238f99f9c9fd34501 +Author: Yihan Wang +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 23 lines last edited by Yihan Wang in worldgen/assets/grassland/flowerplant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 144978fa943144734c42a0386f5b034614649b86 +Author: Beining Han +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 578 lines last edited by Beining Han in worldgen/assets/grassland/flowerplant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 59adba075dd6c0cf41ca2bdf81d55f23a2a01456 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/grassland/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 96c7d57ce224df624fd1d7c6f9349263f1c48a52 +Author: Beining Han +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 1 lines last edited by Beining Han in worldgen/assets/grassland/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 21b90d6d5203163e8455a54dbe096561865ab448 +Author: Pvl-bot +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/grassland/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 55987196f3c41271d99923de018ba1896acd89cb +Author: Pvl-bot +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/surfaces/coconuthairy_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f05e8ccabd61417ff7656c7bd552d6600bec9a9c +Author: Yiming Zuo +Date: Fri Jun 30 03:11:29 2023 -0400 + + Add 80 lines last edited by Yiming Zuo in worldgen/assets/fruits/surfaces/coconuthairy_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e10f35a199be71126a912e2207c458f35d2a74be +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/surfaces/blackberry_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 85a762235d8fbf7a95f65044c44d97080550fb90 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 107 lines last edited by Yiming Zuo in worldgen/assets/fruits/surfaces/blackberry_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 54b57ced9e69da6a65b084df4c373afcd6da480d +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/surfaces/durian_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 143adc3d8129058ed8f88fc5499a4e3bc9ff36d4 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 110 lines last edited by Yiming Zuo in worldgen/assets/fruits/surfaces/durian_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 354fbaef665951117680bddfd77e788eb7678167 +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/surfaces/surface_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 88d40e6b151640a53ed44da426a6ae8738a34900 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 50 lines last edited by Yiming Zuo in worldgen/assets/fruits/surfaces/surface_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d7188b16e740fb10699bfe15cfda96109a5a89cb +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/surfaces/apple_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ecd0738a7b338a60f24793f57d8292fe3a28ef63 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 85 lines last edited by Yiming Zuo in worldgen/assets/fruits/surfaces/apple_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0c2e13ecf8a5ed27f47f028e0975aec9fa60c33c +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/surfaces/starfruit_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 789d38e7c8191649916f1f8ee96cae1f75717a45 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 76 lines last edited by Yiming Zuo in worldgen/assets/fruits/surfaces/starfruit_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 56bb6cb18fdea93c3fcc4c0973bc9d5a5ed549a9 +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/surfaces/strawberry_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ef536d3c0b83c3354e9d6abc7e9673d76b34ad59 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 96 lines last edited by Yiming Zuo in worldgen/assets/fruits/surfaces/strawberry_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 926da6b1bbf98cebd6bb37d87a1d79892d67f85c +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/surfaces/coconutgreen_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cc2bcdcce487c9e4cadbf98f2a2d0567d2038d40 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 93 lines last edited by Yiming Zuo in worldgen/assets/fruits/surfaces/coconutgreen_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9dd49b82f4c618cb7e0ec17011ab7452f1270656 +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/surfaces/pineapple_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 83c40183fdfb0dc8f920f3547b056d2299da9e64 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 174 lines last edited by Yiming Zuo in worldgen/assets/fruits/surfaces/pineapple_surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dce4a7271d06275ae92315a38783146f07428aab +Author: Lahav Lipson +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/assets/fruits/compositional_fruit.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ebb4f2d5748bf6959d785e0777399c2941b1bd27 +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/compositional_fruit.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 796df833c2f17e3c21b26aff9b21ed9d4d5383b3 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 61 lines last edited by Yiming Zuo in worldgen/assets/fruits/compositional_fruit.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dbbfb420c03e0586eb9dd7cb3bd82af509307b03 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/assets/fruits/apple.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1792c3ae3dbfd2b98be0993731b9db30972c5f94 +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/apple.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e6a18486386c20c683c22e736ef26657f5c05860 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 92 lines last edited by Yiming Zuo in worldgen/assets/fruits/apple.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b9e960c9392b2f1368dea76a31b855aff04412a4 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/assets/fruits/strawberry.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b0d54be1d52590d2c3dc17e0b91c70604c30d022 +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/strawberry.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cc5cf22a982167d0932f1851a88f9e988cb9c8ef +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 95 lines last edited by Yiming Zuo in worldgen/assets/fruits/strawberry.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 81fb223ea7a93ba2b43bab7bcc02f1276bab8368 +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/stem_lib.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 29988239c302a2df95f36fd3a03ae8c92b633d5e +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 617 lines last edited by Yiming Zuo in worldgen/assets/fruits/stem_lib.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d78bc6b2da1b8f7a2a28b55a6cad1fb8cfcba11d +Author: Lahav Lipson +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/fruits/coconutgreen.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b3d5199277dbbc68aea57b25b60e2734079ef028 +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/coconutgreen.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b41d0bc8ab60335da71c05ae4e541cd23208c1b1 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 100 lines last edited by Yiming Zuo in worldgen/assets/fruits/coconutgreen.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 64a47ed0b0a1528e6e6e4cb97798fccef1e41693 +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/cross_section_lib.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2761b2043f61865997b484ef93f671302fd194a6 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 251 lines last edited by Yiming Zuo in worldgen/assets/fruits/cross_section_lib.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 68a799e8f9ea4528cb6d6affa8b953528a6d0b6f +Author: Lahav Lipson +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/fruits/general_fruit.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c308a98a8d398638e03002eed9740d2433ee8c83 +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/general_fruit.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e0fa15e74ff68710635eabc1d78c3227598d5d7e +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 167 lines last edited by Yiming Zuo in worldgen/assets/fruits/general_fruit.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d2cdf77edef61f136f73c9b942f8ba9bb00f9b87 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/assets/fruits/starfruit.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 930803251ec0ec00cf2e4103912bf965923afa57 +Author: Pvl-bot +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/starfruit.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2fce798df6aa40980b7813a5b92106c020831009 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 85 lines last edited by Yiming Zuo in worldgen/assets/fruits/starfruit.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6efbcb3cafcf4eaba9730960ef71ba6999afab4e +Author: Lahav Lipson +Date: Fri Jun 30 03:11:28 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/assets/fruits/durian.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a9223b901fa0b4dd288341c489886ec4e8dd8388 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/assets/fruits/durian.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6b2d08340b40744089e6120290cdd4731d6f24c5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/durian.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ec52cedb338f51d9625b3e8c79125764c619c4d8 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 92 lines last edited by Yiming Zuo in worldgen/assets/fruits/durian.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c47928124f932564136750aecdbedc0f389e6fb7 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/assets/fruits/blackberry.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bb77306aa20424e6f1c201577a3612203140ec7c +Author: Pvl-bot +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/blackberry.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b9cd9c0d6ea3ddfbc70d722b47eecb46e8ab0fdb +Author: Yiming Zuo +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 74 lines last edited by Yiming Zuo in worldgen/assets/fruits/blackberry.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b4fd1a69b1b67cbdd27344ee3c91013b8105a3e1 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 9 lines last edited by Lahav Lipson in worldgen/assets/fruits/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3f0ea4da02f6f5ca1de6ae420a51888c33d8ec6c +Author: Pvl-bot +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/fruit_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0e26afc719509198ec25a9ac239c4d987ab36a2d +Author: Yiming Zuo +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 690 lines last edited by Yiming Zuo in worldgen/assets/fruits/fruit_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a7193831a907b9e99605f64e768e16e8f40a3c30 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/assets/fruits/pineapple.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e29aca0f3a1dac6c1f858332378d96b567b371e9 +Author: Pvl-bot +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/pineapple.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ed7708823e60dba77264fd8d6d1e0c7c53247218 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 107 lines last edited by Yiming Zuo in worldgen/assets/fruits/pineapple.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2db6cae024b6961cdd16b302b0610b7892e71e48 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/assets/fruits/coconuthairy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 15f1215d9c5a4715e4b94f1aa635b86d8533149f +Author: Pvl-bot +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/coconuthairy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b97b5231595d2adae686ba10d10734ff5baddba1 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 72 lines last edited by Yiming Zuo in worldgen/assets/fruits/coconuthairy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d541f3af606a07f033dc6d61f54f65bf7d50f11d +Author: Pvl-bot +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/fruits/seed_lib.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 64df98d039152ea22dfc7be40a1bd4eec0ec25c8 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 59 lines last edited by Yiming Zuo in worldgen/assets/fruits/seed_lib.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e724e581221d3ff9cf150a024df95762fd49d2ab +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/assets/monocot/growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3c0da77a618867de1b1870b5ae75665ecd58b3a4 +Author: Yihan Wang +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 3 lines last edited by Yihan Wang in worldgen/assets/monocot/growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 63cce9574e3ca21dd83062fbe0dd5bf64d89b415 +Author: Pvl-bot +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/monocot/growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1ba7b24aabb153cf56268c4a420a67c0667d0221 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 205 lines last edited by Lingjie Mei in worldgen/assets/monocot/growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8450419fad8e4dd8817de1c0f480aa7dd26ac2bf +Author: Pvl-bot +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/monocot/grasses.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit af3cd6323c01e9e5362f17dec6f6f9ceb21693bb +Author: Yihan Wang +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 9 lines last edited by Yihan Wang in worldgen/assets/monocot/grasses.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 40bb71254302061337277493c354c3acd9849d9e +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 9 lines last edited by Alexander Raistrick in worldgen/assets/monocot/grasses.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6ecf0e37dc61cff2eb1ba4fdaac0db5983dbaa8f +Author: Lingjie Mei +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 230 lines last edited by Lingjie Mei in worldgen/assets/monocot/grasses.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 95ead79fca144948b6b969e1caf36d50eb68f0bd +Author: Yihan Wang +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 3 lines last edited by Yihan Wang in worldgen/assets/monocot/banana.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6c6398a72af0d0b755f2a015024a5e0cfcda878a +Author: Pvl-bot +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/monocot/banana.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cb79f5963cef9a6b737d63c4cdef2ba1a76012d4 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 142 lines last edited by Lingjie Mei in worldgen/assets/monocot/banana.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 93f5018ff8a860c8c22783ae476db9738f0243cc +Author: Yihan Wang +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 1 lines last edited by Yihan Wang in worldgen/assets/monocot/tussock.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d27e31e92e5f54115fd326e63c17801865ea76bc +Author: Pvl-bot +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 5 lines last edited by Pvl-bot in worldgen/assets/monocot/tussock.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8fd0ad437e32695dae8205803ac9f79149597f28 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 35 lines last edited by Lingjie Mei in worldgen/assets/monocot/tussock.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c71b7a99cb2b23516412b21bed90254e3a023163 +Author: Yihan Wang +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/monocot/pinecone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit edd205084b2965341d53d5b294459aa3ac529384 +Author: Pvl-bot +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/monocot/pinecone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e733ee4e9a08d847ea9e9e419bc84b6f43b34247 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 71 lines last edited by Lingjie Mei in worldgen/assets/monocot/pinecone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5225ca0c83fe9bf3a31b45111ad09f8b501bd317 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:27 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/monocot/agave.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d9a4ed3566d81085994f951fe82937b8d49508d8 +Author: Yihan Wang +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/monocot/agave.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6b9de7516790066ba137970f71b45966451172ad +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/monocot/agave.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 358d1897e7e29c2ce2e014803e0132936a68834c +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 61 lines last edited by Lingjie Mei in worldgen/assets/monocot/agave.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5e210a4bbaee48f72945611fb005ddfa6e26a30e +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/monocot/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 120ab6094f31b89095e9a003147779afbc6ae691 +Author: Yihan Wang +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 5 lines last edited by Yihan Wang in worldgen/assets/monocot/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ee97a0163e460c4836c319f6a82b486f5a827fdc +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/monocot/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ac47111069623844137010981b2aa0ee624b8919 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 46 lines last edited by Lingjie Mei in worldgen/assets/monocot/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 823b2b3cb5ffe1f4ab7ff80ec62ea050b60853cd +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 9 lines last edited by Lingjie Mei in worldgen/assets/monocot/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 22a8889823a4ccfb722f8d364f4172027e8ce655 +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/monocot/veratrum.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4a9520c4a5e3c821043bcc33335f836cf2ac0f9c +Author: Yihan Wang +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Yihan Wang in worldgen/assets/monocot/veratrum.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 202aa5ef533e4fcc49e51874b8e6fc45e2b735d5 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 128 lines last edited by Lingjie Mei in worldgen/assets/monocot/veratrum.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 086f2b3fe6e89ebc93605065c472e830c1970488 +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/monocot/kelp.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 77cf40d1ed21799c81bfd023746739b0df69951a +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 105 lines last edited by Lingjie Mei in worldgen/assets/monocot/kelp.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit eb0a5777adae2fbb14c6a85b4ee66aade8c3c1b4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/base.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b8443658824e220f4704d0914309b0dcf0b2cff8 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 20 lines last edited by Lingjie Mei in worldgen/assets/corals/base.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7baafb8809b00a613907c6b3f36b76afc5b82b5f +Author: Zeyu Ma +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/assets/corals/tentacles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b836855c893889df12e733e9d57ef6cd8dd76400 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/corals/tentacles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit aeb1e5abaa3b3ce63a10a50c288fbe54d7989103 +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/tentacles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0df25b19304f9b25f8eb6f8d063079f4000b90b5 +Author: Yihan Wang +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 9 lines last edited by Yihan Wang in worldgen/assets/corals/tentacles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bd5d82978059c8c2e2131998d1ec4d6278136922 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 98 lines last edited by Lingjie Mei in worldgen/assets/corals/tentacles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2430ef77cc089464e1db93be37a60fac0ee2340a +Author: Yihan Wang +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 4 lines last edited by Yihan Wang in worldgen/assets/corals/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 99d0edc25a856aa8a1eca03f3858137f2dd3f46d +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 87ef8b6293076cbda62cb10bb36de64692d20257 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 18 lines last edited by Alexander Raistrick in worldgen/assets/corals/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f487484b64de38ca04ea8bedc143072cec1bc0db +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 154 lines last edited by Lingjie Mei in worldgen/assets/corals/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d366eb2995295a1b8fcdd8d84d194921c0549e25 +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f0ca98d8599378d01d9bce66d280a08dc81f1022 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 12 lines last edited by Lingjie Mei in worldgen/assets/corals/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ef526f03892433c06296bf94d5e5c0a826729813 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/corals/elkhorn.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f8a161adf5d4faca7cabe4b4b8d2ae398482d3ca +Author: Yihan Wang +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/corals/elkhorn.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b9feefd48601e7f2fccc1bd36841ea327878f8e1 +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/elkhorn.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4f1affdb87a5ac91c1ec8c7d7050b4a8b508ba39 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 136 lines last edited by Lingjie Mei in worldgen/assets/corals/elkhorn.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1c99e21c5edb613d8db4b3e3f79cf8f023e0ec77 +Author: Yihan Wang +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/corals/diff_growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1a3befb5c73e5cc0337c7d2d592310520f7e6c6b +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/diff_growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f4ca02e7dbc2c4f41a14a40b4e92782cf7fdb033 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 107 lines last edited by Lingjie Mei in worldgen/assets/corals/diff_growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f4890828f821f03c7278ee69b08f58f75ac7951b +Author: Yihan Wang +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/corals/laplacian.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fe9718b723bfb85cb6d595c666e37438f7c772ae +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/laplacian.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d4e9d38485761a33da40d58aab73965ca35b3b21 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 23 lines last edited by Lingjie Mei in worldgen/assets/corals/laplacian.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0bdd6ba6bb04a7c23febd97d8fb7b9349502251d +Author: Yihan Wang +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/corals/reaction_diffusion.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0862d438705880bb0f3849216343a4f48837e9c8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/reaction_diffusion.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a3c6b991fb1cdfb77be56b85494c72a505e3c8f5 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 74 lines last edited by Lingjie Mei in worldgen/assets/corals/reaction_diffusion.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c29c069f271cbf4c180eb10aa0761fb619f1602d +Author: Yihan Wang +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/corals/star.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8fc60f2386c23441a542fd1c662d5d05b57ec137 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:26 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/assets/corals/star.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 85a5fca6bae065e4a84375208d9aa9f4380ed5ae +Author: Pvl-bot +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/star.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fb0707a3de2a0dfb0665ab545c0f4e7b6cce73d5 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 116 lines last edited by Lingjie Mei in worldgen/assets/corals/star.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fa5f252d4118e1e32d8cd3a89c42afe8dd019b0f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/corals/fan.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f3e7f10e24a07ae89ac2ec5396532f6ce4ad179f +Author: Yihan Wang +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/corals/fan.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2d938f99942cdbf13033839f217ee6649dd3d26f +Author: Pvl-bot +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/fan.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c76673033539398d1cfa0e4608ac0888dace150a +Author: Lingjie Mei +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 83 lines last edited by Lingjie Mei in worldgen/assets/corals/fan.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 566cebe2ac0e5e887698f67cd7d821ea48faf482 +Author: Yihan Wang +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/corals/tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9ab68eb97050c2e4331efc00beab0bb69c857438 +Author: Pvl-bot +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ca13fbedd3fb48e34c419586c035746558ac86a5 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 149 lines last edited by Lingjie Mei in worldgen/assets/corals/tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 17a9578dc8ebc7aeb5bf5839faef4e435ce93c0e +Author: Yihan Wang +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/corals/tube.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8b8d932042f6b7edab062aad294b964a9e470ee8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/corals/tube.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ff3c13348e33fbff34b78be45d758bc1179b4168 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 71 lines last edited by Lingjie Mei in worldgen/assets/corals/tube.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7a527cdd371526df7c27d56679c66323e8c91684 +Author: Pvl-bot +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/trees/utils/materials.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9daf24f322ecf3fd60bb4a1307c21504fbb22796 +Author: Alejandro Newell +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 245 lines last edited by Alejandro Newell in worldgen/assets/trees/utils/materials.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9f2bdadc567a759b47273e7cd6521c003b9bf05d +Author: Pvl-bot +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/trees/utils/mesh.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6b829e64c7e6adfb3e536f2e49a6848ac7a57640 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 8 lines last edited by Alexander Raistrick in worldgen/assets/trees/utils/mesh.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ac8757b18025da709521532ecf50132d4fd459f5 +Author: Alejandro Newell +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 300 lines last edited by Alejandro Newell in worldgen/assets/trees/utils/mesh.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bade7cf689411121aa696fb2530998782147b17e +Author: Lingjie Mei +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 4 lines last edited by Lingjie Mei in worldgen/assets/trees/utils/geometrynodes.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e2c20350f4dbfeae06714220494a6bd173345d71 +Author: Pvl-bot +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/trees/utils/geometrynodes.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 55b9c5ff3977effbc6dfe3d71c943d1af9ee15b6 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 57 lines last edited by Yiming Zuo in worldgen/assets/trees/utils/geometrynodes.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f00b5fbc51fc93edb899ef9e4b3261a132b0c157 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 269 lines last edited by Alexander Raistrick in worldgen/assets/trees/utils/geometrynodes.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5c6df950470eab81b09b6a40981ef9cf16fcdaac +Author: Alejandro Newell +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 369 lines last edited by Alejandro Newell in worldgen/assets/trees/utils/geometrynodes.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9fe6ae855715f4a5c3fd6e9e6d2c2ad9a5438774 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 5 lines last edited by Alexander Raistrick in worldgen/assets/trees/utils/helper.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8ab8b5300b28de0bc0eed2c290222ee6ff62307b +Author: Pvl-bot +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/trees/utils/helper.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 703cb96178177bd16ebd4117ad943b41d293ee15 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 9 lines last edited by Yiming Zuo in worldgen/assets/trees/utils/helper.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9e53dc0e91a11050d1357485858f68cad2eae363 +Author: Alejandro Newell +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 222 lines last edited by Alejandro Newell in worldgen/assets/trees/utils/helper.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 00bf0651331d6d483892c47ce3852b0fe668ba56 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 2 lines last edited by Lingjie Mei in worldgen/assets/trees/treeconfigs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit efd6b249c92b5fcb9501795afdc7b48a7abda688 +Author: Pvl-bot +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/trees/treeconfigs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d8bca28c0e9bbc9a6756dc2817ec12ad35aa6878 +Author: Alejandro Newell +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 30 lines last edited by Alejandro Newell in worldgen/assets/trees/treeconfigs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f6282d2febb6cf983470a9fc79aa37e718c4a04f +Author: Yiming Zuo +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 49 lines last edited by Yiming Zuo in worldgen/assets/trees/treeconfigs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1441bd8bef9322c13fc483deb8bac29330d23924 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 689 lines last edited by Alexander Raistrick in worldgen/assets/trees/treeconfigs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b6575781ec1ea500d58d378e2f932c6eb3b3c91d +Author: Lahav Lipson +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/assets/trees/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e537899fb0d48064965f3963439e5972cfef418d +Author: Zeyu Ma +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 2 lines last edited by Zeyu Ma in worldgen/assets/trees/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cf6941fdfccf8293c460ff2af43ba16eefe30f83 +Author: Yihan Wang +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 3 lines last edited by Yihan Wang in worldgen/assets/trees/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 75f5468cfb5a74290dea849fc62c8683644c5760 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 4 lines last edited by Lingjie Mei in worldgen/assets/trees/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d3dced0f42f68e68a609cfa5d3b69b5b1ae81df8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/trees/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 94181957782bd80918392d26c5dbf3e5d41e9755 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 93 lines last edited by Yiming Zuo in worldgen/assets/trees/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cfa19eb7b517414d3f240a2cc0fb42c2aab5e951 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 305 lines last edited by Alexander Raistrick in worldgen/assets/trees/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5f328d5cd1c26fe5ac080d762c9c5bec2432a2a1 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/trees/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit db42163d78ba6d4b49e2893a1ae4db045abcd59c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:25 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/assets/trees/tree_flower.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2e2a05cb6ef675db56f929b9335212a788fa401e +Author: Lahav Lipson +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 4 lines last edited by Lahav Lipson in worldgen/assets/trees/tree_flower.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 70c978a4085655831b520a824a30f7007c1b4f3a +Author: Pvl-bot +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 9 lines last edited by Pvl-bot in worldgen/assets/trees/tree_flower.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ecd22645a2b51b853903c090a6709394c2fdd745 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 586 lines last edited by Yiming Zuo in worldgen/assets/trees/tree_flower.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0c81542b7656ee3ed5da98f2e3e5b6183f578d10 +Author: Pvl-bot +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/trees/tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9cc8ff5c6732993bab9f35747d0f1382b6999541 +Author: Alejandro Newell +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 38 lines last edited by Alejandro Newell in worldgen/assets/trees/tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 11de6e2cd47d26896599acac7f340ec4cf660bc0 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 70 lines last edited by Yiming Zuo in worldgen/assets/trees/tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 959e843b3577e3c6a952375587b98bb7773c61b2 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 90 lines last edited by Lingjie Mei in worldgen/assets/trees/tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a7e92135279d9b826b8620c2aba5308319086e2c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 261 lines last edited by Alexander Raistrick in worldgen/assets/trees/tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8d06e44d82b896667c5902a84d44930a45ef1372 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/leaves/leaf_maple.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit eb2450e1f8c30e5adffc1b92cc8172627355529f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/assets/leaves/leaf_maple.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f509eefc2fce39a5b5d8837c5c9b99b645060c42 +Author: Pvl-bot +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/leaves/leaf_maple.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 83be410d3226955955b30ba9bc1e6a12f1f85750 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 785 lines last edited by Yiming Zuo in worldgen/assets/leaves/leaf_maple.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6b870767699992bd9d81505d48ad5627004ee4a2 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/leaves/leaf_v2.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0b2800d95ac4294827fd17327a0a9edb4d5489e8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/leaves/leaf_v2.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 95d4b1a693c882ae5b7a4b0cedf9698cbe3e1f94 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 106 lines last edited by Alexander Raistrick in worldgen/assets/leaves/leaf_v2.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4beb78c60849056129d351fa7bb5fc009e57ec38 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 889 lines last edited by Yiming Zuo in worldgen/assets/leaves/leaf_v2.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e5f4333de1e97d260004150059aca3766d4389bc +Author: Lahav Lipson +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/leaves/leaf_ginko.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7c62042a5f96113480b770786b722d30279de834 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/assets/leaves/leaf_ginko.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 07759e758ba7ff372ec5ff817d31ea9f149a50f8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/leaves/leaf_ginko.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9d54216f03feadf3bd2707b53669136c8b5d39f4 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 516 lines last edited by Yiming Zuo in worldgen/assets/leaves/leaf_ginko.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2d42179ee86631ba3ef12620353d8ea00d327609 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/leaves/leaf_pine.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 53775f547f8d5a7c125406e4051725480977f7fe +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/assets/leaves/leaf_pine.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c834eb8e19a35f9c4f0f4fbb3f9de36acc5136a7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/leaves/leaf_pine.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 80a50165775715263089ac58518b7bfc77e57250 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 365 lines last edited by Yiming Zuo in worldgen/assets/leaves/leaf_pine.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 092ab6eae584f65323567aceb06a89ac50e4fdd4 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/leaves/leaf_broadleaf.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c0a92750738a51bc6861ea6dae47e238de138092 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/assets/leaves/leaf_broadleaf.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 00f76c40917f5840d5894ef2076f0895e0742518 +Author: Pvl-bot +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/leaves/leaf_broadleaf.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8bdb8cef3b5a5a2cb8345d4e386a7bad8fb36ea2 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 752 lines last edited by Yiming Zuo in worldgen/assets/leaves/leaf_broadleaf.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 063e727a43555a571372001889ef8acbbf8d76e1 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/leaves/leaf.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d8c6f642e800438ed389d7619ff97537ef058e24 +Author: Pvl-bot +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/leaves/leaf.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 169ceb117294cf67cc3c4ff2f84f63c347a11c61 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 7 lines last edited by Yiming Zuo in worldgen/assets/leaves/leaf.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f844741042f4dcc906a52a1c4220300b4bba9972 +Author: Alejandro Newell +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 28 lines last edited by Alejandro Newell in worldgen/assets/leaves/leaf.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d4965f724400334de747020b8eba9c059a1e1fa4 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 112 lines last edited by Alexander Raistrick in worldgen/assets/leaves/leaf.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6903ec714fd8fc031ceddfd218f540f97d699f7d +Author: Pvl-bot +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/leaves/leaf_wrapped.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3becd13d6ccf35151c8da6f3f7478169c4ef89fa +Author: Yiming Zuo +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 178 lines last edited by Yiming Zuo in worldgen/assets/leaves/leaf_wrapped.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 17f7c957c63ab9b1393199d6b7b9050e3ea861cd +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/mushroom/growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 727c35e9fc45532ee60121accbff61b312d260d2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:24 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/mushroom/growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 628ea3bea8d61c35915a07aefd8cee4d0810f2fa +Author: Lingjie Mei +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 57 lines last edited by Lingjie Mei in worldgen/assets/mushroom/growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5b8a6558f206f603e1c9897e1f0c09a86ecf675b +Author: Yihan Wang +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 4 lines last edited by Yihan Wang in worldgen/assets/mushroom/stem.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 098f54bcc4cf25cf20a0762609413cb052880271 +Author: Pvl-bot +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/mushroom/stem.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a6dfa53320e6d2734ecf45dd4ff4a225d894214b +Author: Lingjie Mei +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 119 lines last edited by Lingjie Mei in worldgen/assets/mushroom/stem.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b91266da784f58b88fa2792e4ba59816b1f89219 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/mushroom/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit aa9c7fbf0349dc19a975489ed2d0ab5b3e26e0bb +Author: Yihan Wang +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/mushroom/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 189277bc8cb9c4da8575347d0a845b0fc90d40d3 +Author: Pvl-bot +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/mushroom/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit afd7e139c0af446b2d472e090516db68114c5d6f +Author: Lingjie Mei +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 100 lines last edited by Lingjie Mei in worldgen/assets/mushroom/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2ca5fe2b541ab4a28e820f71aad347ee21d524e1 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 2 lines last edited by Lingjie Mei in worldgen/assets/mushroom/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b08d0e131808144826d233b8e25080758d9d714f +Author: Yihan Wang +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/mushroom/cap.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ed0aa4aa0848b973c645e8b09cf918f627ccdfa3 +Author: Pvl-bot +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/mushroom/cap.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ccf05b0a57018601f1f1984a0b7526ae317e13e8 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 374 lines last edited by Lingjie Mei in worldgen/assets/mushroom/cap.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5f046ad73147ac927a2386f9de26b4e565e3d992 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/small_plants/num_leaf_grass.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 93076e4582a389a4df0188b7bbe224082ae97e0b +Author: Lahav Lipson +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 4 lines last edited by Lahav Lipson in worldgen/assets/small_plants/num_leaf_grass.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 65ffa2d1efe8159efe8bfdb7b2556e71971d435a +Author: Pvl-bot +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/small_plants/num_leaf_grass.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 441fd40d6bc5b35de5edd973be22e7ccd46a00f8 +Author: Beining Han +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 182 lines last edited by Beining Han in worldgen/assets/small_plants/num_leaf_grass.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit be8ff695511f66afd7ad9b5071a04393abf1f5d5 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/small_plants/fern.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3f545fd6ed4da4b40f8d000097646942459b6c8b +Author: Pvl-bot +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/small_plants/fern.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2c1f5175e95531dbd7fe1bbc4ebd493e715d2878 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 19 lines last edited by Alexander Raistrick in worldgen/assets/small_plants/fern.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6e587d6806307089f5a828bb1d9ca3aeec516239 +Author: Beining Han +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 734 lines last edited by Beining Han in worldgen/assets/small_plants/fern.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 698475e2ddc6aa8cc52bcd5b55091c78e67c8132 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/small_plants/leaf_heart.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f9044e8758bccc5ad6fd4e81973f9fd8210d0b25 +Author: Pvl-bot +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/small_plants/leaf_heart.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 287afddaae4a857a72a78126f8afdcc99dc0862c +Author: Beining Han +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 73 lines last edited by Beining Han in worldgen/assets/small_plants/leaf_heart.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 171a275e8618531b4f62d4d96811cf9a1027ad30 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/small_plants/leaf_general.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6e01879e0f3cebff8a396ae9e592550d1dff8176 +Author: Pvl-bot +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 5 lines last edited by Pvl-bot in worldgen/assets/small_plants/leaf_general.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9db4cdd150ac4b642e399084232c1a786341f512 +Author: Beining Han +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 85 lines last edited by Beining Han in worldgen/assets/small_plants/leaf_general.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 20bc971e661c675e47af0d292d136902e40841c4 +Author: Beining Han +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 2 lines last edited by Beining Han in worldgen/assets/small_plants/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fbcc9c53a42049a5ec336770917283ef6266306e +Author: Pvl-bot +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/small_plants/succulent.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit aa8b13fe9e267da51e0e6680c5cd9019c6900a63 +Author: Yihan Wang +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 6 lines last edited by Yihan Wang in worldgen/assets/small_plants/succulent.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cb72caadd84a356163aa7510cd61fe5c5fd793e0 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 13 lines last edited by Alexander Raistrick in worldgen/assets/small_plants/succulent.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6aa8c2daa272e46b49bf114b4bd258b41148cd71 +Author: Beining Han +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 505 lines last edited by Beining Han in worldgen/assets/small_plants/succulent.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7698c3bcc01c0bd7a48d33ef6afaf04bb810472a +Author: Pvl-bot +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/cactus/base.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 00b915be7c9bd164e32fdc650307ff5d29512d5b +Author: Lingjie Mei +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 21 lines last edited by Lingjie Mei in worldgen/assets/cactus/base.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 87972fa868b4de5112d0c224247b4cfc13d340d7 +Author: Yihan Wang +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/cactus/pricky_pear.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 59a43b84cba35935fff59737603cb75b25b1ce7c +Author: Pvl-bot +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/cactus/pricky_pear.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 56a9f61b205b6f22c16816108f4909758f5dc491 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 62 lines last edited by Lingjie Mei in worldgen/assets/cactus/pricky_pear.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3819ebb818860da4e5f4a351ab56612a5e439c0e +Author: Yihan Wang +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/cactus/columnar.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 618ee22b2fdd02f4ae478d8ce8af3b5f15e9e694 +Author: Pvl-bot +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/cactus/columnar.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 03a5d4df8963eab55c93ae8f2de753ab1e11611c +Author: Lingjie Mei +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 92 lines last edited by Lingjie Mei in worldgen/assets/cactus/columnar.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d72e5d11cd6534d6d5101400a51349b53754185b +Author: Zeyu Ma +Date: Fri Jun 30 03:11:23 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/assets/cactus/spike.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2e2b237d8923f5643de7ac031dbc719f95a9c3f8 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 5 lines last edited by Alexander Raistrick in worldgen/assets/cactus/spike.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0f3e353825c2c5f51f2396a8ebc3e2bb193878df +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/cactus/spike.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e6137b646c1920fafa2d11cc12de6b327a116cc0 +Author: Yihan Wang +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 8 lines last edited by Yihan Wang in worldgen/assets/cactus/spike.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 57b005ca95e0ecb762482c9768b32a9842c11464 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 98 lines last edited by Lingjie Mei in worldgen/assets/cactus/spike.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b0e7661bcf3dbc8d7a2328f29296d87eb2a42591 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/assets/cactus/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 519981998e3fafe3d522f16b59e9f72037bde1b9 +Author: Yihan Wang +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 3 lines last edited by Yihan Wang in worldgen/assets/cactus/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4ef74e04f14ac44a9a5c54ca1e981eed3ab243f4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/cactus/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 490bb59675540dec37a91537c0b33cfd0107f074 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 89 lines last edited by Lingjie Mei in worldgen/assets/cactus/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 576967bfe4ce5c69173a0bbbea2e129f36b827c2 +Author: Yihan Wang +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/cactus/kalidium.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 21d3051fca8a5abdabfcee47d0f2635255d600cb +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/assets/cactus/kalidium.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 01e2463493ed24a452e3177dd32616b3356df030 +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/cactus/kalidium.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c885f549a671ff6e621c5f43586dd0ea29303be0 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 88 lines last edited by Lingjie Mei in worldgen/assets/cactus/kalidium.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit eb0bf9868af187766d6a96caa3cd62435ad8e8d0 +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/cactus/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 54e39135e5eb2bd097eb7ac4cd6f955bd0481790 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Lingjie Mei in worldgen/assets/cactus/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d912c86ff8a55981ef98f3357dbf19838b20b4ef +Author: Yihan Wang +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/cactus/globular.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 971478d3c08cac97032be994469be298167cb192 +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/cactus/globular.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f3851726070fe284b6117cd35e7ddccfe0837d8c +Author: Lingjie Mei +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 48 lines last edited by Lingjie Mei in worldgen/assets/cactus/globular.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 06dbbd2078ed71fc9dc67239819a0a0c76f0517d +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/assembled/dragonfly.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6181e03c79d4d87e33b5d7b4c4478afef091990f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 71 lines last edited by Alexander Raistrick in worldgen/assets/insects/assembled/dragonfly.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a1bdc3a0998c5dcfa55d3c65f2df49f87f4f9bb4 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 240 lines last edited by Yiming Zuo in worldgen/assets/insects/assembled/dragonfly.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 90dbe93030eb09933fdb53fc6493b56e18885060 +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/parts/head/dragonfly_head.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 13bf7f13e7ba59f2cfe18b0dd153db30c126f84b +Author: Yiming Zuo +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 178 lines last edited by Yiming Zuo in worldgen/assets/insects/parts/head/dragonfly_head.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1058edcaebf78104df4d3809f19faf49a708db34 +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/parts/eye/dragonfly_eye.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0fdff51fee89d9bc61bfa25651aceb8ab2f50c2e +Author: Yiming Zuo +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 69 lines last edited by Yiming Zuo in worldgen/assets/insects/parts/eye/dragonfly_eye.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ba1d44d79a1af333841b7775e3764e11f28949ef +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/parts/tail/dragonfly_tail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b37c8bf2dd0c362ef936d90ff32d24840f2cb0ce +Author: Yiming Zuo +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 378 lines last edited by Yiming Zuo in worldgen/assets/insects/parts/tail/dragonfly_tail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 483c6b3f3c2a689bed5d0f7a57278915f218c498 +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/parts/antenna/dragonfly_antenna.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f66b56f2f8233704e876e478fdb01126343f9d9d +Author: Yiming Zuo +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 28 lines last edited by Yiming Zuo in worldgen/assets/insects/parts/antenna/dragonfly_antenna.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2c62e5defa7cb971950e4b4dc4b29f518961c802 +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/parts/leg/dragonfly_leg.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bc83b2525be9cd506c99eddc80a60044b07ade98 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 186 lines last edited by Yiming Zuo in worldgen/assets/insects/parts/leg/dragonfly_leg.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 17229a7b0e38e5deb25137f477ec2c028e887b31 +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/parts/wing/dragonfly_wing.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7e7589377c99c9250f0a7f3c654964445368a1ff +Author: Yiming Zuo +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 280 lines last edited by Yiming Zuo in worldgen/assets/insects/parts/wing/dragonfly_wing.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d270995586b5738b668270963ff31b1ed1a1f8fd +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/parts/body/dragonfly_body.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 142be2c07aa8801bd35e801ab3a5a58af5753875 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 225 lines last edited by Yiming Zuo in worldgen/assets/insects/parts/body/dragonfly_body.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 98f15744b153d2b936c50d0b6370cd4f0cb723a4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/parts/mouth/dragonfly_mouth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c3b4ca49c47cfe21fca758d3612d472208e3acac +Author: Yiming Zuo +Date: Fri Jun 30 03:11:22 2023 -0400 + + Add 67 lines last edited by Yiming Zuo in worldgen/assets/insects/parts/mouth/dragonfly_mouth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0e360496bea19f6068a0a7929b86c59878196d4b +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/parts/hair/principled_hair.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f06ed88664d00b92201f9926621287fdaae00030 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 31 lines last edited by Yiming Zuo in worldgen/assets/insects/parts/hair/principled_hair.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5b421d7003cd24120b15b2d8c46af96e56ff6a14 +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/utils/geom_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9adcc051fedf041a24373ab66afaa9149fe7e438 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 829 lines last edited by Yiming Zuo in worldgen/assets/insects/utils/geom_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3307333ebababe72e0c831c4e36bbebc95bfe0ef +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/insects/utils/shader_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d3f0e62ded603d45bf2fcc48bcb9817979b0eb25 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 87 lines last edited by Yiming Zuo in worldgen/assets/insects/utils/shader_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 72df0dac62a14e4ad3a2f4e3b540526df792d246 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/assets/utils/object.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5401431fd877b8f3e9c1216cbb0bfe44dee4a6cc +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/utils/object.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1b5690c3ba983531e6edcf324c162e822e75ecdf +Author: Lingjie Mei +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 101 lines last edited by Lingjie Mei in worldgen/assets/utils/object.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1be69f22de9620d7c33c01057fbbea96959ca4d7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/utils/decorate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2a593a19a53206d2fde2387fe7b8bd314c59f890 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Alexander Raistrick in worldgen/assets/utils/decorate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8c755190927477707ef818660590d37e9d77c169 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 239 lines last edited by Lingjie Mei in worldgen/assets/utils/decorate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 510f1e10c7140e01353c8a724807db2e6f3d62e4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/utils/physics.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e1710621e9e83011216e0904ee4ae15a41343b91 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 43 lines last edited by Lingjie Mei in worldgen/assets/utils/physics.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 20b4476deffa1453f08947f8f3a74f16e53cf49d +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 3 lines last edited by Alexander Raistrick in worldgen/assets/utils/shortest_path.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4687f89070c567a43b09423b4b40f7fe3797073a +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/assets/utils/shortest_path.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 737b74b13f1c0b070a8091aa8c6b7edf86d4f9a6 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 35 lines last edited by Lingjie Mei in worldgen/assets/utils/shortest_path.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 060bea59a3b3675b45456a5b0f7ef1efc9ddc5e0 +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 5 lines last edited by Pvl-bot in worldgen/assets/utils/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a528c48eff7b635b72b2306a9b8e0c0b25ba5188 +Author: Yihan Wang +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 4 lines last edited by Yihan Wang in worldgen/assets/utils/tag.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d3f30fe79ad264829c4368e2e6c1f6e8dc80e8d2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/utils/tag.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 57bc35f03c061a326ba41cae34d6d8d66895e386 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 139 lines last edited by Lahav Lipson in worldgen/assets/utils/tag.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e1a534dee34dbcc79b8bd0b27b58599f45015493 +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/utils/mesh.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cad33f9ff0eae1fcca1b3a21b92a4ed95a066ed1 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 65 lines last edited by Lingjie Mei in worldgen/assets/utils/mesh.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8631a9751422a7591b2c67165b921d8e24bb4d17 +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/utils/diff_growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e7f7fe49711e0aaf6168f7ac8b0a82527dc10d79 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 81 lines last edited by Lingjie Mei in worldgen/assets/utils/diff_growth.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6065753ccfab7e5d78183315f414c96fa3c22a63 +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/utils/nodegroup.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 38412a0a124ed44753ef3eacd3ecc8a649e89c05 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 84 lines last edited by Lingjie Mei in worldgen/assets/utils/nodegroup.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7a701974073fee802fb517822098b6b9a3a98bc1 +Author: Pvl-bot +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/utils/draw.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 65eec1620f00f97dc69f15de477ec0d68628bf59 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:21 2023 -0400 + + Add 155 lines last edited by Lingjie Mei in worldgen/assets/utils/draw.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c32013a55f83661facb3facc468c665e737ceb17 +Author: Pvl-bot +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/utils/laplacian.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b14a1c568fb02c44b9a58a8bcedbc2fa40baac9e +Author: Lingjie Mei +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 96 lines last edited by Lingjie Mei in worldgen/assets/utils/laplacian.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 707f157d34e1c3c59f0721460e679bcff37b8986 +Author: Pvl-bot +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/utils/reaction_diffusion.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2a99ee22bf76c8dfbfc00e6e1aa9ef3199f84378 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 69 lines last edited by Lingjie Mei in worldgen/assets/utils/reaction_diffusion.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0d344597523f46e00368723d8a7bd7a581ddd1ab +Author: Pvl-bot +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/utils/misc.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit aced7028a1303a9316845c3e954ba5604ab7e547 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 52 lines last edited by Lingjie Mei in worldgen/assets/utils/misc.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ff4ed5a64b00ee08d17a13a8a1be527f97c74557 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/deformed_trees/base.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d0538413c1ac05ce1d8a1a0c2e81a627a69eab1c +Author: Pvl-bot +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/deformed_trees/base.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f73ef6b01250c56799e602f9694d35a866b0adfb +Author: Lingjie Mei +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 54 lines last edited by Lingjie Mei in worldgen/assets/deformed_trees/base.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 05c4fad39bd00ea8032f13cd8232902c746be79c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/deformed_trees/hollow.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 13a812e4224d4c6ce3841835ee5cfac3ee1a785e +Author: Yihan Wang +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/deformed_trees/hollow.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit eebca09139b393a3cb5298a8d79924c985028dd6 +Author: Pvl-bot +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/deformed_trees/hollow.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 85fb21d48326745d696bb386b2714b9491118277 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 68 lines last edited by Lingjie Mei in worldgen/assets/deformed_trees/hollow.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c78d553503bfd20989ab5fb97d245284231f96df +Author: Yihan Wang +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 1 lines last edited by Yihan Wang in worldgen/assets/deformed_trees/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c0b701697da307653db0d04f0d5acc18637683df +Author: Pvl-bot +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/deformed_trees/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f5478819e4f2d64af778fde85c74b9f2f141a237 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 18 lines last edited by Lingjie Mei in worldgen/assets/deformed_trees/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5838146649aaa36a618f12c55f12786659126725 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 5 lines last edited by Lingjie Mei in worldgen/assets/deformed_trees/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 937adca4fe3f79142315143889da4b84966e02d0 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/deformed_trees/fallen.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fc3961b0120d74039ef4f3458d73e37ec0945b79 +Author: Yihan Wang +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/deformed_trees/fallen.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 04c0a997f324c0a4d3a1f5a02ec4bd9ea2b57e39 +Author: Pvl-bot +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/deformed_trees/fallen.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b54f153677ab77883c62e27b3b61ce02b7113715 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 76 lines last edited by Lingjie Mei in worldgen/assets/deformed_trees/fallen.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 187bd18fe0537ed550ebe452cdb4c41239f78e53 +Author: Yihan Wang +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/deformed_trees/truncated.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit faad74edef4054fd62adf72c611ba2aea0e85d79 +Author: Pvl-bot +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/deformed_trees/truncated.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d3d40dcce054193c20eb08bdb4b44e23c71e5610 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 35 lines last edited by Lingjie Mei in worldgen/assets/deformed_trees/truncated.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c991e0848f739c7875084c315806a9c97ac8df79 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/deformed_trees/rotten.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2cabd7f412274922aff7515000818ee85391221f +Author: Yihan Wang +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/deformed_trees/rotten.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ed6cd70e7ef5666a5bb761d04fbb6f533b66b7c2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/deformed_trees/rotten.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5823a9e8ca1bc3cef7ae93e6f44ea8b67f75ea5a +Author: Lingjie Mei +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 81 lines last edited by Lingjie Mei in worldgen/assets/deformed_trees/rotten.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit df779c3cce33bb74dd911a99b06395a5de07287a +Author: Pvl-bot +Date: Fri Jun 30 03:11:20 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/util/creature_parser.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bfe0436df208d4f89f76f7961c88ea6b82d3c90d +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 135 lines last edited by Alexander Raistrick in worldgen/assets/creatures/util/creature_parser.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 18fce8601a024d39fcb6a29f3cd6b10c941ffc3c +Author: Karhan Kayan +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 4 lines last edited by Karhan Kayan in worldgen/assets/creatures/util/geonode_part.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e666c6814e592c698677a7c4d1831546efc656d8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/util/geonode_part.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7ef45ed84dd6f79aedf1a5de7934a5f7002a187f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 98 lines last edited by Alexander Raistrick in worldgen/assets/creatures/util/geonode_part.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 97646d353737cd34114a5fa32d48f8f3e7589bf6 +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/util/join_smoothing.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 63b793d36d4e36c8d7e3756f434a362c3038d9c7 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 217 lines last edited by Alexander Raistrick in worldgen/assets/creatures/util/join_smoothing.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a71a6331ad69dc659075abf06f6fd4412ce63978 +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/util/tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e8fd58873deb24f7a8ed493ecb8a7eb471d7b1a3 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 62 lines last edited by Alexander Raistrick in worldgen/assets/creatures/util/tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a22533f323ad7b53b308e6a621dcda6209d5f424 +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/util/part_util.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c2b138966cbc46e3b870272f928e11547bcb01c3 +Author: Hongyu Wen +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 43 lines last edited by Hongyu Wen in worldgen/assets/creatures/util/part_util.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7472ff532d6d6d42f4dfdf5b2014f4ca48b127c0 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 169 lines last edited by Alexander Raistrick in worldgen/assets/creatures/util/part_util.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 74c5e265676e620f94c440aca5a847d1a7d950f7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/crustacean/antenna.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit af1dfdf39d2c5297d2c474c7710f001d3ad1580a +Author: Lingjie Mei +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 52 lines last edited by Lingjie Mei in worldgen/assets/creatures/parts/crustacean/antenna.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6449159f7f35a479690faa870c7f7a83341b7d39 +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/crustacean/fin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8170a0134bde00f4fb0e4a34dc766009e425f59b +Author: Lingjie Mei +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 29 lines last edited by Lingjie Mei in worldgen/assets/creatures/parts/crustacean/fin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 67a12ce06e6c4314526718660deebf6fbb3d61eb +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/crustacean/leg.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5ad9f97f11f4a689311abe74fee1a34cd2252723 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 78 lines last edited by Lingjie Mei in worldgen/assets/creatures/parts/crustacean/leg.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8735b715042fbd973075d7992eaf2828ce88faf4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/crustacean/eye.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cb9350f3e12111a0e8a3de5ddf20c42b10c8179c +Author: Lingjie Mei +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 33 lines last edited by Lingjie Mei in worldgen/assets/creatures/parts/crustacean/eye.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0b1476aead15787ab55e79bde8e93d59bb4f280f +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/crustacean/tail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 40304e10c3b4eb7b90a1271dd55af4ae1c5651cf +Author: Lingjie Mei +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 82 lines last edited by Lingjie Mei in worldgen/assets/creatures/parts/crustacean/tail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a24a50efa21a0af1d9f245dd5d7f6948328e6c32 +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/crustacean/body.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 85f58288e621af812bcdbcdd58b8df3f45a68e64 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 271 lines last edited by Lingjie Mei in worldgen/assets/creatures/parts/crustacean/body.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7ba8722f71ed0a521c967547c07ed8583a68c826 +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/crustacean/claw.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 34955ea00f3ba0d5b1581b8f2501892eb24a2354 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 166 lines last edited by Lingjie Mei in worldgen/assets/creatures/parts/crustacean/claw.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 79b45f9c23991ff5a3dd6bacf157ca172940a60c +Author: Pvl-bot +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/utils/draw.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4c5f4fb376b7b96e008036c8c25ee8100b2b03ba +Author: Lingjie Mei +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 56 lines last edited by Lingjie Mei in worldgen/assets/creatures/parts/utils/draw.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9672da989fb1ce89718d2ff86f69880ebf0dd809 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:19 2023 -0400 + + Add 3 lines last edited by Lahav Lipson in worldgen/assets/creatures/parts/fin_old.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d8ba795f7406232ad0126686c4d7765d6f5c299b +Author: Pvl-bot +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/fin_old.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 983e237798bdab9702b484636de3a2a58fdfd62e +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 105 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/fin_old.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 49ebf25061994944110dfbda08ecb12045445dcd +Author: Yihan Wang +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/creatures/parts/ridged_fin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit faa6619b19bf3afa6cfaeb6c08fb091587d72f59 +Author: Pvl-bot +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/ridged_fin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b7ff89d5a57bb283ab911957f9a97d561a7ae86e +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 193 lines last edited by Mingzhe Wang in worldgen/assets/creatures/parts/ridged_fin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bbff5f927415b8029c3fbdfbae6f2c755ceb7c1f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 275 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/ridged_fin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7c286ca38486722766cab9e2fbb34d6621693161 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/creatures/parts/foot.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 77cf4868bf846df45e26e8d7b6888070bec39a13 +Author: Pvl-bot +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/foot.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1b9b91a5108366c095090c4762c906bd27168580 +Author: Beining Han +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 127 lines last edited by Beining Han in worldgen/assets/creatures/parts/foot.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 34d6fa5de7a137632e9bdd06b7b902cee576a4cf +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 159 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/foot.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b60d2d6ec7e16b4dd6468f451f8663140d0a278b +Author: Pvl-bot +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/eye_new.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6b21294f55c3060253984063da01384e390cda93 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 2425 lines last edited by Mingzhe Wang in worldgen/assets/creatures/parts/eye_new.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 96f78c135b08bf1f158abcce8613a3ab0d9dbd1f +Author: Karhan Kayan +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 2 lines last edited by Karhan Kayan in worldgen/assets/creatures/parts/head_detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fc10e38b3d9dfc74c696dc475bb7b45eb7153d4d +Author: Pvl-bot +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/head_detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cce5f0e7f2d055c652bd0723a1e078cc2a75df72 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 6 lines last edited by Lahav Lipson in worldgen/assets/creatures/parts/head_detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 724a60d242b0dc1e271c51bcc1eb38d142e0a425 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 189 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/head_detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit accf12cbadd0332f1cedde7a5e58d3b762609f02 +Author: Pvl-bot +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/head.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 22a9ad6181148caf18e511a83206713161af5ec0 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 6 lines last edited by Lahav Lipson in worldgen/assets/creatures/parts/head.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 41f90d29b964741008240dbc787b3f4206a9d722 +Author: Beining Han +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 14 lines last edited by Beining Han in worldgen/assets/creatures/parts/head.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f6395e08f8bdc07c517583a94ff1959a74589227 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 597 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/head.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 21e885144f94872f30ddbcae47231c087cd554d6 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:18 2023 -0400 + + Add 5 lines last edited by Lahav Lipson in worldgen/assets/creatures/parts/leg.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b15bc841f202f4f9309b77c3f2e873a8c9492a7f +Author: Pvl-bot +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/leg.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5490afa859ed5a4a93f43a5823e001c858cf7e4a +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 263 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/leg.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8d65e47e8e06e26489850acfbd6961ca9a78ef1f +Author: Lahav Lipson +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 3 lines last edited by Lahav Lipson in worldgen/assets/creatures/parts/wings.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7a01c9f0c208ea6c41498b07eba259bce06f3b85 +Author: Pvl-bot +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/wings.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b4943cb5e7ecab70388fe866597f239d79b6a18a +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 301 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/wings.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1847dd9ca523d267493f87da2a6c38002b0d9ce8 +Author: Beining Han +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 359 lines last edited by Beining Han in worldgen/assets/creatures/parts/wings.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b4e3f77200e1327b3172f5c4a090b97616c9239f +Author: Lahav Lipson +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 3 lines last edited by Lahav Lipson in worldgen/assets/creatures/parts/eye.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit af1f2543a1e97bf31bb5790ef4a2fe807fbb0224 +Author: Pvl-bot +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/eye.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9283f2d36414c5a94211b92b920230102d0ecbfa +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 6 lines last edited by Mingzhe Wang in worldgen/assets/creatures/parts/eye.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f651188d061f06b386db5dbf92be5b765a9886d9 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 153 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/eye.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3280342de4602422f00095f857647c8711f97a38 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/creatures/parts/tail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2a390f83c026e2eb6f40b1decc5b26e79ae17538 +Author: Pvl-bot +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/tail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a01ae86f567a34b7243a4f841adff26e039f398e +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 47 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/tail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 780beca5b505ac70a52fa10f2aa0e1054d847fb5 +Author: Hongyu Wen +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 1 lines last edited by Hongyu Wen in worldgen/assets/creatures/parts/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 49e28a4d95a4ffc7cb79c472d7a3a2675fae575e +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2173db844a8ff4bb082221c4223b07a79a5c35e0 +Author: Pvl-bot +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/chameleon.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 01e97ac0313d1c501c4dc2d914ac290da503aab3 +Author: Hongyu Wen +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 1592 lines last edited by Hongyu Wen in worldgen/assets/creatures/parts/chameleon.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d4456b504acf69e9c2903a69b0206ca21a77e5d5 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 4 lines last edited by Lahav Lipson in worldgen/assets/creatures/parts/hoof.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 184ddca5d357d37ce9e934820d27fc947dd2a095 +Author: Pvl-bot +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/hoof.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ac45fbc25665ea2f4d0e454fc490a38e07bda557 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 21 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/hoof.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4fb3b14cedcd691836bdcacb8be0077744db375e +Author: Hongyu Wen +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 177 lines last edited by Hongyu Wen in worldgen/assets/creatures/parts/hoof.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ab74d593a0d9d9f3b3bbfa81538e2c9f50cd0b1c +Author: Lahav Lipson +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 4 lines last edited by Lahav Lipson in worldgen/assets/creatures/parts/body.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 23ffb961a957cb4d9dd8cedc72ae9bc754df02f8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/body.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 220abbcc91f97cc14c112d4e54eb3bcb96f50114 +Author: Beining Han +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 14 lines last edited by Beining Han in worldgen/assets/creatures/parts/body.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5e94c9368bf736a84d7754dc5fdd7f781891381b +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:17 2023 -0400 + + Add 199 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/body.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 71e19b570e967b549a93195dd3c7bf8a6539a45a +Author: Hongyu Wen +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 368 lines last edited by Hongyu Wen in worldgen/assets/creatures/parts/beak.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e1c34beb283aff36576821650a83b1a282d93ae5 +Author: Yihan Wang +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/creatures/parts/horn.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f82afae24e139b74b62f20691b468ea8188b542f +Author: Pvl-bot +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/horn.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f692b4d67d65e46d8c4027441bb2aca9e40ab63b +Author: Hongyu Wen +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 90 lines last edited by Hongyu Wen in worldgen/assets/creatures/parts/horn.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8783dc34b56a8a5584d5091ac0a1b53f63d9e4bb +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 175 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/horn.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 814803614f4507fd480b6ddd83969a6c08a50530 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/reptile_detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit eb64ca465a7b6eff0d9853dc66f0bc7582a18780 +Author: Pvl-bot +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/reptile_detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 59f6bbf54e5e9fb2e60d2b6e5a55045a8558c8ef +Author: Hongyu Wen +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 1386 lines last edited by Hongyu Wen in worldgen/assets/creatures/parts/reptile_detail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 27456f3c02880d1471a5a67097e8ddbd74e40232 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 3 lines last edited by Lahav Lipson in worldgen/assets/creatures/parts/generic_nurbs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d803963f6925a1df879187f5f86a9e00cb6e27e3 +Author: Pvl-bot +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/parts/generic_nurbs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2f56825bf85cbe3187d00cc73cb37a9d8ad8d2d9 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 155 lines last edited by Alexander Raistrick in worldgen/assets/creatures/parts/generic_nurbs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 990f031486170b964981bce11db5e362a4420a0c +Author: Lahav Lipson +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 3 lines last edited by Lahav Lipson in worldgen/assets/creatures/genomes/carnivore.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5ce5e20c404a37536f19d4317f24dcbea18f6b93 +Author: Pvl-bot +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/genomes/carnivore.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e350c0b1c1d21796c0ca9582d07a14f3e5173120 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 214 lines last edited by Alexander Raistrick in worldgen/assets/creatures/genomes/carnivore.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0a96bf67949087bbe45f29fcea2387af4d6849f8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/assets/creatures/genomes/reptile.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9544466aed78499a38fbb86f07b92e2147365a04 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 126 lines last edited by Alexander Raistrick in worldgen/assets/creatures/genomes/reptile.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 266ef52c97ed1b4270325ddc00fcf3a09d39a0a2 +Author: Hongyu Wen +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 410 lines last edited by Hongyu Wen in worldgen/assets/creatures/genomes/reptile.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 69dd9eaf7b2b2bdfb3748f747c5e8822a7633cc1 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/creatures/genomes/herbivore.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e3f0e29ece4835464bc5e4c512a3c57d4e68701b +Author: Pvl-bot +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/genomes/herbivore.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ba271b8b06376f45b828853b18a09ddf84507cf5 +Author: Hongyu Wen +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 8 lines last edited by Hongyu Wen in worldgen/assets/creatures/genomes/herbivore.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0f2178e5f0e654f08baeb935bf2a3dcc7eaa8e7c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 219 lines last edited by Alexander Raistrick in worldgen/assets/creatures/genomes/herbivore.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a778e8aeac7ab38bf95eaebd85b3a9abd869c092 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/assets/creatures/genomes/beetle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f6133be082f9d057e67f0f4ab4fcfa02316e19b5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/genomes/beetle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5c2922899ae6fb48e5d709bf9c455a0dc96d4500 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 193 lines last edited by Alexander Raistrick in worldgen/assets/creatures/genomes/beetle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4e5f7959236bc96435ffe75c5ffef9f764c91559 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:16 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/creatures/genomes/bird.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9ab62f63fe519cd51c2ae1ca302bbb3cf16e925b +Author: Hongyu Wen +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 2 lines last edited by Hongyu Wen in worldgen/assets/creatures/genomes/bird.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 160e451fbf43423d452f9c67ea2771f2c543d38c +Author: Zeyu Ma +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 4 lines last edited by Zeyu Ma in worldgen/assets/creatures/genomes/bird.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c210e18356612770a4f193fab0724bbd2291f0e4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/assets/creatures/genomes/bird.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 92dcb39ab83f6017a1e1a501f014b11d271fbd3a +Author: Beining Han +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 100 lines last edited by Beining Han in worldgen/assets/creatures/genomes/bird.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 30cefb8c88c4d208ad066a96c356309d064ec4bf +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 221 lines last edited by Alexander Raistrick in worldgen/assets/creatures/genomes/bird.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 75fd21564a589447c43035b838130a6fa00c852e +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/assets/creatures/genomes/fish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f0d582c14803cdb87a60f1845dd6cd19c5256cf8 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 31 lines last edited by Mingzhe Wang in worldgen/assets/creatures/genomes/fish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 36d58f373d3256a3fd4b2004f63664b553a0933d +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 289 lines last edited by Alexander Raistrick in worldgen/assets/creatures/genomes/fish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 69fb6d85e4c2c333e45c26140f824300d4b27d07 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 4 lines last edited by Zeyu Ma in worldgen/assets/creatures/genomes/crustacean.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bbe1fec3619ee7462162c0857d88fc0242bfad51 +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/genomes/crustacean.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a0f510fb557acfe471866895485d1ae059b17861 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 7 lines last edited by Alexander Raistrick in worldgen/assets/creatures/genomes/crustacean.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6e944b5177b9a1a28f337922c9c43b3b5ce14296 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 302 lines last edited by Lingjie Mei in worldgen/assets/creatures/genomes/crustacean.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 77ff1a7324ea33ad68069f109a47378650b5f354 +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/tools/dev_script_save_nurbs_handles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 232f96dc30719dde69a5e0959c1bd1d834210c25 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 37 lines last edited by Alexander Raistrick in worldgen/assets/creatures/tools/dev_script_save_nurbs_handles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 777eef102df53be31e7aeafce685ca20c15048a0 +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/animation/driver_wiggle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0ee1defacf98977bed1d5dc0d99ce5846c7ac804 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 51 lines last edited by Alexander Raistrick in worldgen/assets/creatures/animation/driver_wiggle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cb961960e4dea162e324483d8caabdfbd5da9d7f +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/animation/curve_slither.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 116797cd802970951bdce3a5f3ca0dd62adcde6d +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 208 lines last edited by Alexander Raistrick in worldgen/assets/creatures/animation/curve_slither.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f82f3dce8834597deadbfaf2ac64984767326df2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/animation/idle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2119a9bfff11dcff2401b914d4c641b5d77215e0 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 147 lines last edited by Alexander Raistrick in worldgen/assets/creatures/animation/idle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 85acc7cab889620bdbf267c9310cd99bdd3605db +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/animation/run_cycle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 819d8249e40e0daac502a0665400edde593bad3a +Author: Hongyu Wen +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 142 lines last edited by Hongyu Wen in worldgen/assets/creatures/animation/run_cycle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 337fa9b0679f3f1ba984779642f1e52ce1dfccf2 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 185 lines last edited by Alexander Raistrick in worldgen/assets/creatures/animation/run_cycle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7cc5299abf9d9e23f501dce3d06382e641af8a2f +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/animation/driver_repeated.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 581b3570e3ae34d7f8e10a5bfb92c751b09662a7 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 41 lines last edited by Lingjie Mei in worldgen/assets/creatures/animation/driver_repeated.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cae6979bd8a30edda68702745d8aa97b1a9d9163 +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/nodegroups/math.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7015e0fe8eada1ca631ca8a18bc1dffa6be3d8a0 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 197 lines last edited by Alexander Raistrick in worldgen/assets/creatures/nodegroups/math.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit afbdf4fad5af6952ad7d1ef1ae126fb5f149a517 +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/nodegroups/curve.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 18660518145487759a126605d34f361e67b2e11b +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 364 lines last edited by Alexander Raistrick in worldgen/assets/creatures/nodegroups/curve.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 388590e7445d3b0e5df176f4e96f4378a3bee7ed +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/nodegroups/geometry.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bf5f977b989b51f8ab1067cd6bb501747c027daa +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 131 lines last edited by Alexander Raistrick in worldgen/assets/creatures/nodegroups/geometry.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 568cfb7f988abe73c033b8cc09884a5260eff572 +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/nodegroups/attach.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 703c735d941edcb4fc2490cf2240bcc51aab95d9 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 228 lines last edited by Alexander Raistrick in worldgen/assets/creatures/nodegroups/attach.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 871f3aacf36c45fc86892a36f1c96df0ad15a771 +Author: Pvl-bot +Date: Fri Jun 30 03:11:15 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/nodegroups/shader.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a032cb3f81478f0090d466506dff6fdc1b3a9ed4 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 180 lines last edited by Alexander Raistrick in worldgen/assets/creatures/nodegroups/shader.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e6b76009f64e8d8fc2a4e3acc13cbb744ad912a1 +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/nodegroups/hair.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e9b0436aea4e60bcefa5ff1f9862da5316172c78 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 288 lines last edited by Alexander Raistrick in worldgen/assets/creatures/nodegroups/hair.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e0ba75a6943c7df0a362dec5213c7ffc8456a2c1 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/creatures/nodegroups/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8af483da9847d95fcead545a1319bca7dbc5e76c +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/nodegroups/sculpt_v1.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 368cc8949cce9057a2129f6d1153b08ef46b4e9e +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 245 lines last edited by Alexander Raistrick in worldgen/assets/creatures/nodegroups/sculpt_v1.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 44bc141c1de1838487ded6e0e8cca3322908afee +Author: Jia Deng +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 22 lines last edited by Jia Deng in worldgen/assets/creatures/geometry/cpp_utils/setup_macos_as.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7a7b4fb76b3038c69cb8e0df68bb14c610d04aa4 +Author: Jia Deng +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 21 lines last edited by Jia Deng in worldgen/assets/creatures/geometry/cpp_utils/setup_linux.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 557539bdf82442a6faac87033b9a38213ea46b5d +Author: Jia Deng +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 23 lines last edited by Jia Deng in worldgen/assets/creatures/geometry/cpp_utils/setup_macos.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5914c4f00fcae496a63610ca566bf6004891796a +Author: Jia Deng +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 160 lines last edited by Jia Deng in worldgen/assets/creatures/geometry/cpp_utils/bnurbs.pyx + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e537184f424b9d01c2a951430dfbd45de48a16bf +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/geometry/skin_ops.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 59ed8729a1d8648ec48e7c73f4e6186e7f33dffa +Author: Jia Deng +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 23 lines last edited by Jia Deng in worldgen/assets/creatures/geometry/skin_ops.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6e3cbde52f70c2b2558308aadd2fd9b47077fb06 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 94 lines last edited by Alexander Raistrick in worldgen/assets/creatures/geometry/skin_ops.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 169f01789a7dbd1c4c20077c1af494aa26b2d069 +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/geometry/metaballs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 301adc8425a2f0746b149686ebfc1e950757067b +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 158 lines last edited by Alexander Raistrick in worldgen/assets/creatures/geometry/metaballs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit efc371d8981c7f6b015a4400f510445ef0c24e97 +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/geometry/curve.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2c026530b525138045304ab987b96c5d1f46ab9c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 92 lines last edited by Alexander Raistrick in worldgen/assets/creatures/geometry/curve.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d5c741da7a58a901898d5fd54061bc58b74560f8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/geometry/nurbs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e2eb3ba64054190a075a81ea06e84aa6e6393794 +Author: Jia Deng +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 150 lines last edited by Jia Deng in worldgen/assets/creatures/geometry/nurbs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c55cb40494796974f70849f7e3d85ebed94d2982 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 169 lines last edited by Alexander Raistrick in worldgen/assets/creatures/geometry/nurbs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2268b4a61e91a17172ad9b1c6b77baa869b29346 +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/geometry/blending.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 16c915daf17cea85ff23f184d6b0dd1a85a06628 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 9 lines last edited by Alexander Raistrick in worldgen/assets/creatures/geometry/blending.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f2fd7d327ec6cb8404f6e38c0be09389c9af83fe +Author: Jia Deng +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 1164 lines last edited by Jia Deng in worldgen/assets/creatures/geometry/blending.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 331f7f1834bc88764414391fc99fce1d91a9f834 +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/geometry/lofting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit afd348d17c155169c762a60fd55ceb02fc3717db +Author: Jia Deng +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 15 lines last edited by Jia Deng in worldgen/assets/creatures/geometry/lofting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9caa4ff3a44f4ca47e0e3e77fadb24cc47446959 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 103 lines last edited by Alexander Raistrick in worldgen/assets/creatures/geometry/lofting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 044a12bed86558a18fa4d9ee12ef535266ce4053 +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/boid_swarm.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e8e58ed6a1dd8e19eb9e721cb6d20bef0bf516f3 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 92 lines last edited by Alexander Raistrick in worldgen/assets/creatures/boid_swarm.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ac52cfc3d11c0227d8377a41a6f0d675df8a192f +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/creature_util.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 06ff1028f196d5f20912834e70fc93659b5e70f2 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 94 lines last edited by Alexander Raistrick in worldgen/assets/creatures/creature_util.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit eb5d704fe9265a15602eb3a0deb8b491c338c136 +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/cloth_sim.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 85cb3d77ffc877d2937d540a4324c7f3e9f04109 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 115 lines last edited by Alexander Raistrick in worldgen/assets/creatures/cloth_sim.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 082c42b987bc579c9cf01cd5f1cf9dde1536f19b +Author: Pvl-bot +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ec7d68d58aa3ad5dc456e3366e9cd46c307bf147 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:14 2023 -0400 + + Add 209 lines last edited by Alexander Raistrick in worldgen/assets/creatures/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 161dfb5339f456e76d2c5f77e6ba99f30a689539 +Author: Pvl-bot +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/creature.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 404284b843aba05611447a667bd56da14f96e092 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 48 lines last edited by Lingjie Mei in worldgen/assets/creatures/creature.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8d0be183ff0467d069df20e5bb0a1c7c8f05fe13 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 267 lines last edited by Alexander Raistrick in worldgen/assets/creatures/creature.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 690d466826debc49846b135b9afa7fad95e68a89 +Author: Pvl-bot +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/rigging.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4f6a3ac595d2ce55bdf68a7768c357d8cf38cab5 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 51 lines last edited by Lingjie Mei in worldgen/assets/creatures/rigging.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ea7ed03dcc92829a1f030e93624c6b885f1de288 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 372 lines last edited by Alexander Raistrick in worldgen/assets/creatures/rigging.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b72bdefabdcfe7dedb5afdbea04af0e3790abb2a +Author: Pvl-bot +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/hair.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9c7a09150584b31213ebd88dc04d917faac986c5 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 389 lines last edited by Alexander Raistrick in worldgen/assets/creatures/hair.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3a62209640e5ffb70794b9d080ed7c34cb78fe1b +Author: Beining Han +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 1 lines last edited by Beining Han in worldgen/assets/creatures/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6c72d20085c5ab94acd05c0fec6f2918d5bcd105 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 2 lines last edited by Lingjie Mei in worldgen/assets/creatures/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5c7a681239b1c156c40a1f89d9ddf7aa2665a55d +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/assets/creatures/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 09db66bc87a9b38570e7379e8e578b9d46c610c2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/creatures/genome.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 39a23ea9cce9dfae0517a3345fef2583c253e1b0 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 36 lines last edited by Lingjie Mei in worldgen/assets/creatures/genome.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 36ff7176ad82237bc93897cc56bb24cefecc5815 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 145 lines last edited by Alexander Raistrick in worldgen/assets/creatures/genome.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 978defa0e5dce600a062f6d93aa702829271b837 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 2 lines last edited by Zeyu Ma in worldgen/assets/cloud/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c380df557175b214b7d9a523fec6f7c07e7e1194 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 2 lines last edited by Lahav Lipson in worldgen/assets/cloud/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 55d8921640ae8844518ea57c9ac7ec2ec34681c2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/cloud/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e40f58b92f7ee40ff130ad2b3f31712d604bb228 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 12 lines last edited by Alexander Raistrick in worldgen/assets/cloud/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 286858019f00028b40dd4ffb1bdad0d8f3ae0da0 +Author: Hei Law +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 45 lines last edited by Hei Law in worldgen/assets/cloud/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 50d50854e64f75c233000b39bdc7dd0d19b96e13 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 84 lines last edited by Lingjie Mei in worldgen/assets/cloud/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b2e6ce898dad23c5bcf10067bc59044dde30344e +Author: Lingjie Mei +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 2 lines last edited by Lingjie Mei in worldgen/assets/cloud/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 834b615b65e67d87105418fb4affcf2b4aa7e11d +Author: Pvl-bot +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/cloud/cloud.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 75a46cc286ea4ef145c3ae804c5788cc6c960c6d +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 11 lines last edited by Alexander Raistrick in worldgen/assets/cloud/cloud.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9bb7184098039091520953c52efeb5b1b3480b17 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 124 lines last edited by Lingjie Mei in worldgen/assets/cloud/cloud.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 74f4a8d8044dba507002e204f973f45a37306d0f +Author: Hei Law +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 422 lines last edited by Hei Law in worldgen/assets/cloud/cloud.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 56cb68aee9dd7348eef1ce2f0c262e6359db6034 +Author: Pvl-bot +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/cloud/node.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5fdcee6e9c0a6dfeb9f3ea6988724cde84d8a46f +Author: Hei Law +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 805 lines last edited by Hei Law in worldgen/assets/cloud/node.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9ceab46f4271a471f9934cc12cda72abf2a5923e +Author: Pvl-bot +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/tropic_plants/tropic_plant_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cfe8bc57976ca0417bb3ece5b6f1c5a891ce48c0 +Author: Beining Han +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 821 lines last edited by Beining Han in worldgen/assets/tropic_plants/tropic_plant_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 23d148e270c7fb0ca6414fed1d1dd5ba36251f70 +Author: Pvl-bot +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/tropic_plants/palm_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 04a85ee681d09be8d6fa73283962d629212df43c +Author: Zeyu Ma +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 7 lines last edited by Zeyu Ma in worldgen/assets/tropic_plants/palm_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2a3edef8ddffb42862ba6a5682eb69d3ba8549bd +Author: Beining Han +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 980 lines last edited by Beining Han in worldgen/assets/tropic_plants/palm_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bab60719f1add3290c20b7697c8ad491e9406d6a +Author: Pvl-bot +Date: Fri Jun 30 03:11:13 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/tropic_plants/coconut_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 32b39c8dc8a44464eaf3c5980bac3d1a17424dcd +Author: Beining Han +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 1288 lines last edited by Beining Han in worldgen/assets/tropic_plants/coconut_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0f3713921e013ccc7267a71ce5cc75102e149dcf +Author: Beining Han +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 5 lines last edited by Beining Han in worldgen/assets/tropic_plants/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b3181b2d715416dcd3e5fb49efebcd5fe73fa0c7 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/assets/tropic_plants/leaf_palm_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 64a135f766b4a4cd1388a78d0c8e9bda5827fa1f +Author: Yihan Wang +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/tropic_plants/leaf_palm_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 49b6b485ff450f1ca41c6d5ba87d41deb43913ef +Author: Pvl-bot +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/tropic_plants/leaf_palm_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c01224a7896e2758beb5e3fe90b19045546735fd +Author: Beining Han +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 625 lines last edited by Beining Han in worldgen/assets/tropic_plants/leaf_palm_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 89417899e8b97928c94fed27025441cd1610374d +Author: Zeyu Ma +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/assets/tropic_plants/leaf_palm_plant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 05c0c7556950f5019c4012c6733df98452a6fe2b +Author: Pvl-bot +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/tropic_plants/leaf_palm_plant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 97802868a06df24a169da14ff98cdc94be0a46d5 +Author: Beining Han +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 545 lines last edited by Beining Han in worldgen/assets/tropic_plants/leaf_palm_plant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a7fe96eda0bb3f74d1c16351a5887782e3bd91b6 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 5 lines last edited by Lahav Lipson in worldgen/assets/tropic_plants/leaf_banana_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 401eb16d7d62599864690b5b974c998294a7a370 +Author: Pvl-bot +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/tropic_plants/leaf_banana_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d11516020d320d64feb7d946b8577f280a5f8b4d +Author: Beining Han +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 666 lines last edited by Beining Han in worldgen/assets/tropic_plants/leaf_banana_tree.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cf0418549daac9a88fd60af8f1a8240de1d25301 +Author: Pvl-bot +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/mollusk/base.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f15a2aa0a1f9300572fdf2e4296f41362921a15d +Author: Lingjie Mei +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 18 lines last edited by Lingjie Mei in worldgen/assets/mollusk/base.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8a777baea85f658586f1926d67e1e7b3a837108f +Author: Pvl-bot +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/mollusk/snail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1843e4a7f529b683fedd868a4d246654e254671a +Author: Yihan Wang +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 15 lines last edited by Yihan Wang in worldgen/assets/mollusk/snail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 54b408ab5cc98e1f269a3c4c67af50fc9e378d1b +Author: Lingjie Mei +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 147 lines last edited by Lingjie Mei in worldgen/assets/mollusk/snail.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fb20f0c5970d4e61b05ae15c5257a5ac8d97fe57 +Author: Yihan Wang +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/mollusk/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9accb8dc8a14bd9d72e064dc67f21fb8f494a7b4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/mollusk/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 56259dda65db0ab27e51908508de3ea356fdf139 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 126 lines last edited by Lingjie Mei in worldgen/assets/mollusk/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 28a8571a9c7d267d611f5891dd256650b1cb69f0 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 4 lines last edited by Lingjie Mei in worldgen/assets/mollusk/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 74e6d9df883bd5d1ef2420006788fe35b50aee29 +Author: Pvl-bot +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/mollusk/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 04c68164077caff196c34cc6b5e259351fcc42bc +Author: Yihan Wang +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 4 lines last edited by Yihan Wang in worldgen/assets/mollusk/shell.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d2812ef948ac6f675cfae47a9bbf70d473d6f582 +Author: Pvl-bot +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/mollusk/shell.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 27277176c84588c86a9a4071cd533642d9bef483 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 150 lines last edited by Lingjie Mei in worldgen/assets/mollusk/shell.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a207c09286adcaba04cef9457a085d31bfa3ae69 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 4 lines last edited by Lahav Lipson in worldgen/assets/flower.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6676198b95a3411843f67d61684931662a520964 +Author: Pvl-bot +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/assets/flower.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0a5a7e3a915bbfebbd94edbbfda39c97ef6cd725 +Author: Alejandro Newell +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 46 lines last edited by Alejandro Newell in worldgen/assets/flower.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cdf57e70cf310d187cb5c351365b3b55e62c00b4 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 540 lines last edited by Alexander Raistrick in worldgen/assets/flower.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9241d064f307aff8f96a30e33da4d0f1c00acd38 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/assets/blender_rock.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 15d024aaa0b25d61fcb48f6d9ddd28265a1a44be +Author: Yihan Wang +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/blender_rock.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b417fb9737dfa74b2625d0892fee3344b0ce8700 +Author: Pvl-bot +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/blender_rock.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 401373d7c6c62a0d4a8750446b4cae76318c0adf +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 46 lines last edited by Alexander Raistrick in worldgen/assets/blender_rock.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 60d4aead9f4c43b7a5edf610603a5346c7a11667 +Author: Yihan Wang +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 1 lines last edited by Yihan Wang in worldgen/assets/boulder.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e4bb6f33c99ee020911bb29319d89c3fc5da03b3 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 2 lines last edited by Zeyu Ma in worldgen/assets/boulder.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 897d18d1fbfb2c61b0ebc36fc86c2500937c1676 +Author: Pvl-bot +Date: Fri Jun 30 03:11:12 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/assets/boulder.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 490287b3dc833205fdfe02f80be6a3d43f554b95 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 64 lines last edited by Alexander Raistrick in worldgen/assets/boulder.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit da820c53201c1acc6b19fde70c76c85c25f8f9c3 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 70 lines last edited by Lingjie Mei in worldgen/assets/boulder.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 238be4f5fd2053321f71e9860d2d1ac831d985e3 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/assets/particles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f4c74d360c32ac66326e22356b6eabdcb49f8b88 +Author: Yihan Wang +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 4 lines last edited by Yihan Wang in worldgen/assets/particles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f41ea6ee3a3d6c5b6fff911b93f51abcae6056ca +Author: Pvl-bot +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/particles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c52291c9c7c91412b63bca4e5b6869918857e053 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 147 lines last edited by Alexander Raistrick in worldgen/assets/particles.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d65ba87682d13e1daba081a2c9a388ce0b790bd6 +Author: Yihan Wang +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 3 lines last edited by Yihan Wang in worldgen/assets/jellyfish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dbada6344b61f599f3e36d23189405c7ffa057b2 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 6 lines last edited by Mingzhe Wang in worldgen/assets/jellyfish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 954e537235103dd240707214be799b3c888a3604 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 6 lines last edited by Alexander Raistrick in worldgen/assets/jellyfish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 50e0dc44cd5af8cfa8e32ab45280fec21b10f3ba +Author: Pvl-bot +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/assets/jellyfish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3c53977c2232d9e3041206ad6f8c9226b1042a40 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 246 lines last edited by Lingjie Mei in worldgen/assets/jellyfish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 30832a3202a97d943aaf9ddf2415bbaeb43bb47c +Author: Yihan Wang +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/glowing_rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c0fbccfbcfb74ef8f8296daa2aff283337dca999 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 4 lines last edited by Lahav Lipson in worldgen/assets/glowing_rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7ae6c94f23b6348624539480b807c1ee2b4e2040 +Author: Pvl-bot +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/glowing_rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 146592dcf44658985dc43494ffbc1bd726ca5959 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 7 lines last edited by Lingjie Mei in worldgen/assets/glowing_rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 65b51de615a5f001e70d1e68820eba0fa15ac571 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 56 lines last edited by Alexander Raistrick in worldgen/assets/glowing_rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 453c764c173af77979ccb2ed4a4d5ae7b1d26d9e +Author: Yihan Wang +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/seaweed.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 86718b778acfbd1576a7d8d85f08bded5b44cc6c +Author: Pvl-bot +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/assets/seaweed.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d7e11adec31eeea003c207d9093a8743922e1b83 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 119 lines last edited by Lingjie Mei in worldgen/assets/seaweed.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d37ebdd331e49557a675b6e30e032009ea7746dd +Author: Yihan Wang +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/urchin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7ad739f2824fd16266e63215b43c880751aa8515 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/assets/urchin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d19a71c9019590d6dc7e5c3c8f60624b3500d242 +Author: Pvl-bot +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/urchin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b6b15bacd18b735424b0e91bab698e31ac6e48ee +Author: Lingjie Mei +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 116 lines last edited by Lingjie Mei in worldgen/assets/urchin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1921142675561436c0b11fdb8bacb0f9c292f4e5 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/assets/pile.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a70265d34d50a16f3c6d1e055bfd4788bf3c38aa +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/assets/pile.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4ece2cddfc0c7392655ceb26d0088cddbfe885e4 +Author: Yihan Wang +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/assets/pile.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 969bc1f4ed6d983fcea44fe963f2cc48ab35d49a +Author: Pvl-bot +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/pile.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 373d0942ef1cbeb948d0da8a0db2ecaf9ca4d040 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 86 lines last edited by Lingjie Mei in worldgen/assets/pile.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit caef62cdf63b01ed56e8ab4d07b3babedbb7a146 +Author: Pvl-bot +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/assets/caustics_lamp.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7a635d1774a442d3e14895914e790d796325a42f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 58 lines last edited by Alexander Raistrick in worldgen/assets/caustics_lamp.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 504aa4e0e5bb59ee526852ee8d652732a1792cd4 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 60 lines last edited by Lingjie Mei in worldgen/assets/caustics_lamp.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c48977d817594af5ad1525bfa8ea478f733969f4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 165 lines last edited by Pvl-bot in worldgen/lighting/kole_clouds.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b95dd1c0a66cc9377308be6b94d49f8786d5b1ca +Author: Hei Law +Date: Fri Jun 30 03:11:11 2023 -0400 + + Add 1 lines last edited by Hei Law in worldgen/lighting/lighting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 45fedf8d83b90e5fc38bf8fe8ec70df7c010ee2f +Author: Kaiyu Yang +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 2 lines last edited by Kaiyu Yang in worldgen/lighting/lighting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3db7f17af3cddc3745d2f417e610bd9e06dcc3b2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/lighting/lighting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a165c6de82c21ad0973d3b1768d18a3114cafc47 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 11 lines last edited by Lingjie Mei in worldgen/lighting/lighting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9c39b8311238cc1a795699fb454f948cc0507440 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 23 lines last edited by Alexander Raistrick in worldgen/lighting/lighting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 877a1e87f5bc97c32c9e9ec5ed8cc71a68548e00 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 38 lines last edited by Zeyu Ma in worldgen/lighting/lighting.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c95c814dc9c55945f0265955a9c8f6725b556256 +Author: Pvl-bot +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/nodes/nodegroups/transfer_attributes.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5b358c623b1946b7faa20d66987bb7089ad36974 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 15 lines last edited by Yiming Zuo in worldgen/nodes/nodegroups/transfer_attributes.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8352d7a7f803f22205f565fe32a8914673f36e81 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 24 lines last edited by Lingjie Mei in worldgen/nodes/nodegroups/transfer_attributes.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8c4b1ee16913d56bff4ff63d9a900acd2213b276 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 66 lines last edited by Alexander Raistrick in worldgen/nodes/nodegroups/transfer_attributes.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 94810cbea5e6cd65aaab1fcc7c8c10352e4641ed +Author: Hei Law +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 1 lines last edited by Hei Law in worldgen/nodes/node_transpiler/dev_script.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2b313bb5b21e920f56e56d18fcf8fdcb806ab00f +Author: Pvl-bot +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/nodes/node_transpiler/dev_script.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8fb40f4a83581826bc0cb292d598d5ca39dbb210 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 70 lines last edited by Alexander Raistrick in worldgen/nodes/node_transpiler/dev_script.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1f459be67af302484d91a9e52ea667ec457e1262 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 16 lines last edited by Alexander Raistrick in worldgen/nodes/node_transpiler/changelog.md + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ac19dc2c6a6bfe779014df26b58d422e39ee777b +Author: Pvl-bot +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/nodes/node_transpiler/transpiler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 696cbda1f206fb33b78178dd687723212992a545 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 16 lines last edited by Lingjie Mei in worldgen/nodes/node_transpiler/transpiler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c61f21920767262cb7c7547b1b6479a26327e191 +Author: Alejandro Newell +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 28 lines last edited by Alejandro Newell in worldgen/nodes/node_transpiler/transpiler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c24f705e25fab67bc1a2e50f5b5e2be456f6f71e +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 680 lines last edited by Alexander Raistrick in worldgen/nodes/node_transpiler/transpiler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 284a5f9eaa94e4f85f40e83291dcdb4955b363d9 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 1 lines last edited by Yiming Zuo in worldgen/nodes/node_wrangler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ac5f29f40e491101f5e479061b5e6ab9a0103eb9 +Author: Alejandro Newell +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 5 lines last edited by Alejandro Newell in worldgen/nodes/node_wrangler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a1ff763f9650bdeb1c0452e26fea4be8dedc3f56 +Author: Jia Deng +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 5 lines last edited by Jia Deng in worldgen/nodes/node_wrangler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 628cbe7df33b1871db4abb44b65404add0ac37e5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 10 lines last edited by Pvl-bot in worldgen/nodes/node_wrangler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a42f50b78412a463e7c405a98fa31d7b82945f66 +Author: Karhan Kayan +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 18 lines last edited by Karhan Kayan in worldgen/nodes/node_wrangler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fcfaf14b59a48c88dc180c59aaf6f0e4aad3f11b +Author: Lahav Lipson +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 20 lines last edited by Lahav Lipson in worldgen/nodes/node_wrangler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d37161315d63157c0f8c3214894a0efa64c6e831 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 32 lines last edited by Zeyu Ma in worldgen/nodes/node_wrangler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 91928425db2b4cc9ebd80fdc875adb580c7cffce +Author: Lingjie Mei +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 199 lines last edited by Lingjie Mei in worldgen/nodes/node_wrangler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c78f3b27bf09f5b3724339dcab812135a1dea794 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 245 lines last edited by Alexander Raistrick in worldgen/nodes/node_wrangler.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5c4220eb794dc7a410c389101755ab09d5e72f1b +Author: Yihan Wang +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 1 lines last edited by Yihan Wang in worldgen/nodes/color.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a6bc2e273a022605c2176b4e717c01d213b5e19a +Author: Lingjie Mei +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 4 lines last edited by Lingjie Mei in worldgen/nodes/color.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 228e72df12fa7faaae2af8adb569b9cedc61cd13 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 5 lines last edited by Zeyu Ma in worldgen/nodes/color.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6be930ca73c798b818a8e9de85814e24bfb9cbc7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/nodes/color.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f0b45e1cf22e75428f91b3d0be371c0904f7f0aa +Author: Lahav Lipson +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 10 lines last edited by Lahav Lipson in worldgen/nodes/color.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 72f85d48b7862b5b7a47615efce0e1ab3d764fe3 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 23 lines last edited by Yiming Zuo in worldgen/nodes/color.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 576c3bc5bf7e854ed85876e034aa63a982efaf13 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 104 lines last edited by Alexander Raistrick in worldgen/nodes/color.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 92e45a535cdc7ae24ed43fc3fb99b97b464930ad +Author: Pvl-bot +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/nodes/node_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 862dec69837a310bffb673e1fdcf9bbcff0f0a2d +Author: Lingjie Mei +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 22 lines last edited by Lingjie Mei in worldgen/nodes/node_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cdc0aafd27530257a23fe54059fa127a98b588cb +Author: Lahav Lipson +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 23 lines last edited by Lahav Lipson in worldgen/nodes/node_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 33454fcaaee8a4baf6e1c0c0a2a208e45b00826c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 54 lines last edited by Alexander Raistrick in worldgen/nodes/node_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a7f2e94b9f011e0a0d4879e20974d569de47d26c +Author: Hongyu Wen +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 2 lines last edited by Hongyu Wen in worldgen/nodes/node_info.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3156f4bf088704d2799a14d9aeb62d47c9907c68 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 4 lines last edited by Lahav Lipson in worldgen/nodes/node_info.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6b068b507f37d07df870f7d1f84140067ca7d684 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 4 lines last edited by Mingzhe Wang in worldgen/nodes/node_info.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dbf09aafdd3981e624dfe4c95c934df6a6bb3a77 +Author: Beining Han +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 4 lines last edited by Beining Han in worldgen/nodes/node_info.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8877decbbb979041a9a8cab8908db7fce7e7fbe8 +Author: Hei Law +Date: Fri Jun 30 03:11:10 2023 -0400 + + Add 5 lines last edited by Hei Law in worldgen/nodes/node_info.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e38f9f4458d19989da837348dbd5b18784aee242 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Zeyu Ma in worldgen/nodes/node_info.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5b2f51be6928c2e86244a0c1aebcb22e5c123d14 +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/nodes/node_info.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 75f22acae9dde135fe079a318371c247c257fcda +Author: Yiming Zuo +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 15 lines last edited by Yiming Zuo in worldgen/nodes/node_info.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b2d85e129e8dc238ee2bb188fd682ec71609e955 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 63 lines last edited by Lingjie Mei in worldgen/nodes/node_info.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 49b032c41d966753ff8cc972cb62087f64256c58 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 266 lines last edited by Alexander Raistrick in worldgen/nodes/node_info.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ae41925cabd74e1cc0969bf1552d080c52436321 +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 42 lines last edited by Pvl-bot in worldgen/rendering/auto_exposure.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a5b1a676d9770711544c3d11aed3d3ef8a6a5668 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 51 lines last edited by Lahav Lipson in worldgen/rendering/resample.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 58407782cd0408e87f7beff0f64a8ea2161a5757 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/rendering/post_render.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 08e685e3f215cd12afb5d963dda6b3d0404ef498 +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/rendering/post_render.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1b5ee0c11f7cdba0f678c02be12b301ce3d7f30d +Author: Lahav Lipson +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 74 lines last edited by Lahav Lipson in worldgen/rendering/post_render.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 33b233b16c215b9bc326fba56b6bac134b50b207 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 14 lines last edited by Zeyu Ma in worldgen/rendering/render.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 65984cfa57cbc3ff3bc06d23928ace33e6bcac92 +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 15 lines last edited by Pvl-bot in worldgen/rendering/render.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f0f3a8cf33d0ad299d8efb8ee71a53dba4e084f1 +Author: Hei Law +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 38 lines last edited by Hei Law in worldgen/rendering/render.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 67333b50b04b81e582c170dc43cafaca220e2ae2 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 89 lines last edited by Alexander Raistrick in worldgen/rendering/render.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cd2a39b7cc589eb8a46771c08480cceb14b9eec6 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 208 lines last edited by Lahav Lipson in worldgen/rendering/render.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 100c3c16d4b2c4d64626cff1949efcd8b922a2d2 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/utils/cluster.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 45a0915a175dd71462964244f3ac7238b9400eab +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/utils/cluster.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 045475a689f131983a8c1f982950fef2ed07dc54 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 70 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/utils/cluster.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f61d5a6a306ec4f334318b7ba02147feaf18ff75 +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/utils/selection.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fec38c344899b0490f9ed4e1b773312b9c361dec +Author: Lingjie Mei +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 23 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/utils/selection.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2c47b408217a9c68ddb2df4508d9c43737765821 +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/utils/wind.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9be175819399476352a5e6e779dc42cec590436c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 56 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/utils/wind.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9516aa9ce4dd08dbaa75d1e1e557a1ec439218b3 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 1 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dd72aeeb82266b7f79f15fdef48a5137404ac7f2 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 4 lines last edited by Lahav Lipson in worldgen/surfaces/scatters/rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2c4d71ba90bf1dd6b99ec36ee750d4a4f5f5bffc +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1ce54adcbf43be0f2278261119a17131fcea9b57 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 24 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/rocks.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9a837b85014cd0b8b57ca25b7dd80db5177b085e +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/ground_mushroom.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b6b547f7980f13c860dd9f7db1bfb61f0294599d +Author: Lingjie Mei +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 8 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/ground_mushroom.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3de4154ff5b37acaa21aed22f6f9104dab1d4ac6 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 19 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/ground_mushroom.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b7f0fb3e246e9c6b679609441dc415dd25e06b14 +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/coral_reef.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ce76a156b5070c770cf37da5b8535310ce54e0b4 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 21 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/coral_reef.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d433cafd1328dece97b963b19b1b69ce4c94294b +Author: Lingjie Mei +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 34 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/coral_reef.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fa547cca7ddd134bc9e06ea7a710dae45c6cb63f +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/slime_mold.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5b7c22e4a50601091e911cad73a0315f0d69d1e4 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 12 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/slime_mold.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d31dbd1b6f64d2ef460a5a9502d35e7d5db17278 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 39 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/slime_mold.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5c1af13e576caefff1c1018135308aa58305492b +Author: Yihan Wang +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 1 lines last edited by Yihan Wang in worldgen/surfaces/scatters/pine_needle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dc01e73a925e86f7611b3670bb34c307551da6a5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/pine_needle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9d1c8aea29d13363ad73b5b47ce25d8eb6228512 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 18 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/pine_needle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ff9f6df85452c4f42c1bfe14d6939c38dd2cd2af +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 82 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/pine_needle.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5e861cea4bd575ec18aadd87b47db2f6d7e48c15 +Author: Pvl-bot +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/seashells.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 75760ac1a896d966cc42702ffd9069ff24d47eb2 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 10 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/seashells.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2e7e882f2b9a142b643479565ba0f73db2a3c61e +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:09 2023 -0400 + + Add 19 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/seashells.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9f136a45ede8c19b9c590720bc8e8ef7dbb7b23c +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/pinecone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 324ec5359ca121d497b32b7ba285857248ece9d4 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 13 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/pinecone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cd5d316dba33265e12f6b30f0eed03ea9d440cc6 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 17 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/pinecone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0e3c3a11e8c4f55b6ba0a00ab77b323c411e9541 +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/flowerplant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9bd195553d2644ad8efa90f4760fc9dd56faf693 +Author: Beining Han +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 14 lines last edited by Beining Han in worldgen/surfaces/scatters/flowerplant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9367f349a6221cf5e4f1b82f9e5599ea11cfbaeb +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 17 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/flowerplant.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f8ad90b40bf26f70c446d40b2aa00450afb9b802 +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/monocot.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 86d40c7902fcaa9c28128d82d4293c44b75a1929 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 9 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/monocot.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bf25d99020f02efb029365a56c96564a4b11fa38 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 16 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/monocot.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 54cfb6d6d9927bb30c0ef032abd33c7b74910cd2 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/surfaces/scatters/snow_layer.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ae489aeb332e0e1b54f394fbaeade2e0978c2a62 +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/snow_layer.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 137e19e0fbe84c876028be4e5c74ad2927ca87ed +Author: Zeyu Ma +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 10 lines last edited by Zeyu Ma in worldgen/surfaces/scatters/snow_layer.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cb792d450731b109b66ef3ccb40f7827beaf74d4 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 13 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/snow_layer.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 954f1a161ae894d0820c689d51589a6e9158c7aa +Author: Yihan Wang +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/surfaces/scatters/moss.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0fbd6841ac93e3d9a95f356191920f506226540a +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 5 lines last edited by Mingzhe Wang in worldgen/surfaces/scatters/moss.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f3897770d6fdf207a6f97456c09fcbd44d783433 +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/moss.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7f6422752a8ee9294d467fcc7793757257fd59ee +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 26 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/moss.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fa64b3af9b39295e89f5920b94cd0903dcc63e5a +Author: Lingjie Mei +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 80 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/moss.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fa8227dbbb2466872dd04825ccc470e4e31e0ccf +Author: Beining Han +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 5 lines last edited by Beining Han in worldgen/surfaces/scatters/fern.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cafe5f599cb9cd41dd8059da0374404ba08a24d7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/fern.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit abd3f57677841656bc4f7248500463caa35b2646 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 17 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/fern.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 868657dad8172b916752a8a083aa170b20b222ac +Author: Yihan Wang +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/surfaces/scatters/ivy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3b220a679d9275664503cf7d907f78969602d415 +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/surfaces/scatters/ivy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 333e9a409835ed7dc1aff54c3136ddaabf890975 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 12 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/ivy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3c12b37ce43627ea37976f3c034d1215d0ec3331 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 77 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/ivy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9d480cc480f7dc90a44ab1b93e7504ae78aef4e9 +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/decorative_plants.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0a81065e9d93113bc264d44cceb2b4fc478ebace +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 32 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/decorative_plants.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a3e06749b0d45d8d13278a34e112c75d4e3c24c2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/chopped_trees.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 777e919292eba5312e9f31b142d3023f857da4a0 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 7 lines last edited by Yiming Zuo in worldgen/surfaces/scatters/chopped_trees.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 34e2362a319ab2c4318c558d7109668c575283ef +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 159 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/chopped_trees.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d4b2fb0af2e38964c4a9341651ef6927a85855a7 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 3 lines last edited by Lahav Lipson in worldgen/surfaces/scatters/grass.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 53971ebc2f0d98cc35b5998928b4fb7216b3fd2e +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/grass.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a6561bed0ccf0edb54568a38a363cd4e3b44ff7c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 42 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/grass.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6c7120724229974bf125c23ca9e8be931cbc7f13 +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/jellyfish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7675da9d7102a048e0e43aa9590bff8e1783e2ad +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 10 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/jellyfish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c1c612689ba87eb0f83aad34282ed713bbc638b0 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 19 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/jellyfish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e61124dc68d15bfbb67d687ee9a9db519d3cf2b8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/mollusk.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2b64878b777a44430c984ca4252b6de9fe1d8702 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 9 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/mollusk.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8a5ca717085fa08891519f1ed5d408a95b6472d1 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 21 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/mollusk.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3debc26397aa8ea3f390ea9bc0942197676ba39d +Author: Yihan Wang +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 2 lines last edited by Yihan Wang in worldgen/surfaces/scatters/lichen.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ab80b51d6b3ecb335a139d00b6cb762f66939ad1 +Author: Pvl-bot +Date: Fri Jun 30 03:11:08 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/lichen.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 97ae73a84be1ea985395dd6578a0a56b21a968f7 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 19 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/lichen.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 082d9afda3cc34bf7821226ebbe1b81fd35a4377 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 90 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/lichen.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f1bda4c09519c36df4ce2414d157067f5602a678 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/surfaces/scatters/ground_twigs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0b37dd3a2648476278a1a0153a93214d8a2ad860 +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/ground_twigs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4fbf52731c8a475cf33631c83f6f3512e9d360d6 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 33 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/ground_twigs.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2be81b718de7a94d0d8e7e1eac1915119bce680e +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/seaweed.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cc56eb820f432a2d0409cb153d9504ef02abbfc1 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 10 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/seaweed.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a577887772586274c9684a0534b6ae8bf5b7a921 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 15 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/seaweed.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 936530475c45f7d004b3572194215a0c3e0ca5fe +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/urchin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit be1b1900296a03d64eb6acc2fc102bb0653029ec +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 12 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/urchin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5bc5245bd27d0e939ebce04b5d7e05199096720f +Author: Lingjie Mei +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 17 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/urchin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4532bdf203d1b64455bf9d54ab4acef5d80720d4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/ground_leaves.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2bb902609d94036c4cae7c68493e1fd4c5d32add +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 18 lines last edited by Alexander Raistrick in worldgen/surfaces/scatters/ground_leaves.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 11bfcc8068c4a2b0d78a9764420389fe81860fd2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/scatters/mushroom.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d95a29b2cf66b2b247b826c88dbb68cfa3603c3f +Author: Lingjie Mei +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 86 lines last edited by Lingjie Mei in worldgen/surfaces/scatters/mushroom.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5dfeab892a9b0f8113246e4dde1557c08eae7d39 +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/twocolorz.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e1de6bc9d0b478cab950dd3d29f90ac39aa063a2 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 9 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/twocolorz.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2626efd66d831c81e0ddbc47ec92f48de6d8c5a5 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 58 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/twocolorz.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7dcb954913c20e805c5799593997ac4f70d71d98 +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/surfaces/templates/lava.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a832b811c59b8e494e1fab2ee2605f43ef37820f +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 21 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/lava.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 19130a7275d2bb3153b631839433c45f57a471b6 +Author: Ankit Goyal +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 211 lines last edited by Ankit Goyal in worldgen/surfaces/templates/lava.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5de0ce1e3adb376b56002a241fdbc24781134623 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 222 lines last edited by Zeyu Ma in worldgen/surfaces/templates/lava.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2bf5593b668deb69f66d9a50fcb5e4b5efbfb13f +Author: Lahav Lipson +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/surfaces/templates/dirt.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 84572ca988b1418c201c4e3db11c24b5b274fff5 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/dirt.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 95c450f91b5db1647d2840242a95072edfcca3c4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/dirt.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7df0a4da59d281c8dfb6f76ba7450fdfb5719ae9 +Author: Ankit Goyal +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 22 lines last edited by Ankit Goyal in worldgen/surfaces/templates/dirt.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c286e2d5f4f33db3460e7ff9f28d0ed861c637b7 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 69 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/dirt.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8714d43a0147c72f3ea900b3dd2749fd7e595a71 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 193 lines last edited by Zeyu Ma in worldgen/surfaces/templates/dirt.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6f33279f4543e5c4e1aa8670ba18854df6c52677 +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/scale.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit eb663783aee861d323ce1eb681815dc8aa449690 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 9 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/scale.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit da71467a56ce43daffdadb4c58a76aeb18cd8ab9 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 340 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/scale.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 40561175de38a7bec5e882faf781b30fb73bbd28 +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/spot_sparse_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 993046f404af7f6a2cd2a5701eff21f93ea5e9e9 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 17 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/spot_sparse_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit db7ae05407c59f260f199fae57f187ed82c948e6 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 123 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/spot_sparse_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0e8f8602df3be85d094a5925591f3541be5d9cd3 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 3 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/ice.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 62b8d858941f61f6c1118e5ef1abe1332004ef69 +Author: Ankit Goyal +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 5 lines last edited by Ankit Goyal in worldgen/surfaces/templates/ice.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c410b11f1c89a32f8123b1050044df124a825c50 +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 5 lines last edited by Pvl-bot in worldgen/surfaces/templates/ice.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 13ea07fc4ea3ecf1d48a67a7469734e5b806acee +Author: Zeyu Ma +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 24 lines last edited by Zeyu Ma in worldgen/surfaces/templates/ice.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 94f70bf246b2e06c4887315b742c6dc45d61f896 +Author: Hongyu Wen +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 78 lines last edited by Hongyu Wen in worldgen/surfaces/templates/ice.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8ba7562b155bdc69318fa23f29a56a02c3db6eb7 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 3 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/sandstone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8868ad4c51fba965fe6c328231d00fbcf542e957 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 6 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/sandstone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3ed71e51a5107ec50e498c30fbe220511f538fa2 +Author: Pvl-bot +Date: Fri Jun 30 03:11:07 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/surfaces/templates/sandstone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0823da266f77c0d267824a7892d00377352d25c7 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 105 lines last edited by Zeyu Ma in worldgen/surfaces/templates/sandstone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c8f4f0f63cc26c9602ade5b9d7e9e38cbd87cab9 +Author: Ankit Goyal +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 495 lines last edited by Ankit Goyal in worldgen/surfaces/templates/sandstone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c40941d34c7058aca12acfdcb4cd9e6f2d34c099 +Author: Pvl-bot +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/nose.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2ac07cb2577a26bb36169cc172df8dafbfd0f275 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 37 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/nose.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e53fe486f1db0f37be55d2e8780c7ba532978c91 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/surfaces/templates/two_color_spots.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3bb8a57868c9eaea39f6eb9ca1257e082051f5b9 +Author: Pvl-bot +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/two_color_spots.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 249dc2bbde40649de6a4785b88abe871d6f6306f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 12 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/two_color_spots.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 19ee97418a082358d5ced514913f59b0bbec5de7 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 105 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/two_color_spots.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 43b3ee6b5cd2fbc574ce4d7bc511b36fad6e8a14 +Author: Pvl-bot +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/atmosphere_light_haze.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 56aac172c81d12c31ebb5d809f53ee0a9fcd471e +Author: Zeyu Ma +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 8 lines last edited by Zeyu Ma in worldgen/surfaces/templates/atmosphere_light_haze.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3417c2aa9131ebd82182a185b12b72fc11813794 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 18 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/atmosphere_light_haze.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 21500bc972b5b35cf73b9071f851cdb0e9d23a35 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/mud.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1bf7a504ae12793322d6ec04a7aba7b72a698a13 +Author: Pvl-bot +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 5 lines last edited by Pvl-bot in worldgen/surfaces/templates/mud.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fc20e0bf5ee92225fa459d80a00b19d17f3e7b30 +Author: Ankit Goyal +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 7 lines last edited by Ankit Goyal in worldgen/surfaces/templates/mud.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit edb49f03b0cd77ab65db3eb7c0d96e5ba4421414 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 15 lines last edited by Zeyu Ma in worldgen/surfaces/templates/mud.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 77e3401b3ced84c4b51a137714e3b17fa8a5f3a7 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 119 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/mud.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 07b27d9bfe975292cc60443495097fe6837344a4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/eyeball.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 52ad554eaa91da7ce2f60edbbc662946c939e8c5 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 33 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/eyeball.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ed8e663b99b4f81dcf84a408fe236a15d22c4381 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 56 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/eyeball.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1f67e417f3d66a99e546e3a5d5fdd1d5abaa87cc +Author: Pvl-bot +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/chunkyrock.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0cad2be337fb4cadf91292be2915e72fa11185b5 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 7 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/chunkyrock.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c147245fe19241b43988c97bfefeee1ebf28e509 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 67 lines last edited by Zeyu Ma in worldgen/surfaces/templates/chunkyrock.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5728ad511e31ef25eeb02a6a3080b6e5253843f3 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 74 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/chunkyrock.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 742c549d36b74d674447f2ada6a95ae41563e1af +Author: Pvl-bot +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/snake_scale.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f28ced2bc3859a0bf89fedc14768cb996ae9acdb +Author: Hongyu Wen +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 272 lines last edited by Hongyu Wen in worldgen/surfaces/templates/snake_scale.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f83f1cec4c515866c2bb9c77c0fe1d02e03f1abd +Author: Pvl-bot +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/surfaces/templates/bark_birch.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7b285c9fb5e04913ae9a448ee1b64893695c49d0 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 14 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/bark_birch.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5bb02f63430703016e71623952b24834c3fc5006 +Author: Yiming Zuo +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 190 lines last edited by Yiming Zuo in worldgen/surfaces/templates/bark_birch.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit efdf2677c9c41123c872a076758ba4280454fb7b +Author: Lahav Lipson +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/surfaces/templates/stone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d60f0449b764de403b8543058323d9173b8ca374 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/stone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fce03dfa1b4cb99e59818b71789d3d64be864048 +Author: Pvl-bot +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/stone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7ed0c1f3053b80273abe477e83b660f7452d8522 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 12 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/stone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 244d6efe25d0514e6e195becff30e070d81dd895 +Author: Ankit Goyal +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 18 lines last edited by Ankit Goyal in worldgen/surfaces/templates/stone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ded02940ca08a46ff9e4ca8a0dcd03ec67359436 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 263 lines last edited by Zeyu Ma in worldgen/surfaces/templates/stone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fe4158ef4686b509c3d6005b6ce1f49d0919b314 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/cracked_ground.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c5b890bb626f6511c20b8fedfc92f3e0a64782c1 +Author: Ankit Goyal +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 5 lines last edited by Ankit Goyal in worldgen/surfaces/templates/cracked_ground.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 593a02ca5c485a72f414dee2fbd754e4967f4d8d +Author: Pvl-bot +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/cracked_ground.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 840366f9a8eb0606b5a6bebb3c089dc7b33036ba +Author: Zeyu Ma +Date: Fri Jun 30 03:11:06 2023 -0400 + + Add 18 lines last edited by Zeyu Ma in worldgen/surfaces/templates/cracked_ground.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 245302a8d663bbfc711323978f3ddb9f9cd792ad +Author: Yiming Zuo +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 166 lines last edited by Yiming Zuo in worldgen/surfaces/templates/cracked_ground.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0cd93371efd089e8c14af843a91c5ab9cbf90a9a +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/simple_brownish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5ced5fe6e09b268998565717d8d5f219ffd0bb37 +Author: Beining Han +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 27 lines last edited by Beining Han in worldgen/surfaces/templates/simple_brownish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 858d97c8a72e52e297652000e1abad40a07d56bd +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/bone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a06ab6344ed2f1d54011379b6307572728d25acf +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 20 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/bone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 85b9b0bf74988ea77f6ec7f6aa95b2d0188017fe +Author: Yihan Wang +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 60 lines last edited by Yihan Wang in worldgen/surfaces/templates/bone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 748223bf29b0841828f95ec341419241b3b6b7e8 +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/tongue.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 66a0c18ca1d974ddedd541f5d7ec3f111076771a +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 29 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/tongue.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 22406b9b177770902b224ea76e9969167822ef13 +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/soil.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2258f02d77ad425ff35f57d744e4f69cb70afd5c +Author: Zeyu Ma +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 141 lines last edited by Zeyu Ma in worldgen/surfaces/templates/soil.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 92203a38b2e23960266ee0ba88ce1156e3ece72b +Author: Ankit Goyal +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 182 lines last edited by Ankit Goyal in worldgen/surfaces/templates/soil.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a60c5268b60e9e800f6aac12ee0cdda508875e02 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/surfaces/templates/slimy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1b0af9b19ddaf011fc8ac10d89dc83f10471673f +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/slimy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ea1f5013b2ba07b276f9a7054860d5315bbd4001 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 14 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/slimy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 47c34d55910595d556dd70bc662578b4f7f20781 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 106 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/slimy.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 33c4a6b23dafcd49649540d51efd55ab25318200 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/snake_shaders.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4b88edd6b309959551c8b62c2d221a883e8b30e7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/snake_shaders.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 397201770a117f013ca3f0bc31a6207d976e7a0e +Author: Hongyu Wen +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 297 lines last edited by Hongyu Wen in worldgen/surfaces/templates/snake_shaders.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 429f92562367d14d3b0ed5401e573b428a3956bb +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/aluminumdisp2tut.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 12973dec061d29d63d767c68b2326a1f93ff4043 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/aluminumdisp2tut.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cae0380c81ed2daa2098145454e79f8d5ebfb72c +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 192 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/aluminumdisp2tut.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c2aa38b56e3249c3b98fe64707988f4264ff1e96 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/reptile_brown_circle_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 283f914b642e8d8b601ecf32e26577e63fb7dabc +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/reptile_brown_circle_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 336a46056b01c412ff7f102c362e894915b8316b +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 299 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/reptile_brown_circle_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e1a4701812225deb0d3f8304a365e818e9971c42 +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/face_size_visualizer.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bd794a3d92040ae93f13f556b50982f04f4c9464 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 41 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/face_size_visualizer.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9063d366099d7ea59448bc6f144628f7d2dc8650 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 5 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/bird.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c94bc446ac855c8b944834d72b73cdcd14a07367 +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/bird.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d8e7c2968ed0c0b0c03c55f5e2244fae1440ee56 +Author: Beining Han +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 45 lines last edited by Beining Han in worldgen/surfaces/templates/bird.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit af30523c8bbaa2f983a1423aa22898fd02d616af +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 452 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/bird.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit daf294c3a329028db79fc941e215223e87dae9f0 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/__init__.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 21288c5052b103f3df8d97238aebc456749919a0 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 3 lines last edited by Lahav Lipson in worldgen/surfaces/templates/sand.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cc2923879bda135284e6578ece95126c978e62f0 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 3 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/sand.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c83d7d421ca4057ca8f819fea714d4e3e8414735 +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/sand.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 515dff4e6868079abeabf58d3d45bf038a46633f +Author: Zeyu Ma +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 126 lines last edited by Zeyu Ma in worldgen/surfaces/templates/sand.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b11ed0fcf5101230cc173c8664013ef3597d3490 +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 5 lines last edited by Pvl-bot in worldgen/surfaces/templates/bark.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 59277ff73496b1c9fdd704d44f94a2cd1aec0afc +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 7 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/bark.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 08506e74726ee08318a3247d6be378847234fba5 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 123 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/bark.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7626580c07a4a85722ddc4bd9a95257beaabaec5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/fishfin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ccd4336df8833556a28c990fad30c6a118c3d89b +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:05 2023 -0400 + + Add 262 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/fishfin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 46fbc73a8dab688e229df7bb082ac980a78aad44 +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/three_color_spots.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d72789c9ac18f94d85d9e31b90ccfde70888c654 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Lahav Lipson in worldgen/surfaces/templates/three_color_spots.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5f6e7a46447a82ece85257bd78043f850a5f9280 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 18 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/three_color_spots.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 86393c66f85da428b5fe0a58bbb000172998642d +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 161 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/three_color_spots.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6c6c24ebf3ae52a4b87c2fb4a61b9faa2b4067c4 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/reptile_two_color_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4e79be179d48280c9fa3e2c0de80b158e5a3e0cf +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/reptile_two_color_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 78972001d77851b00d95bcf2310788b93c07ad0e +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 234 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/reptile_two_color_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a02cc9fb7ec8b77fce9507c010441f9265dce633 +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/simple_whitish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 971b77a06e38acda64c494cd0ae75c7cf0f7f69b +Author: Beining Han +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 28 lines last edited by Beining Han in worldgen/surfaces/templates/simple_whitish.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 71b9d1a3bdf2d5a00c3a12c7bf1c3acafe03a4dd +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/simple_greenery.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 74e199d97d72f29a21b19ec86664eb23cc6ba062 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 38 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/simple_greenery.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2ff5d9681c5afe16cfffa50a9425c717390e4c43 +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/giraffe_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a8c0a6953766bca0f750f116519e65b00934ac51 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 15 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/giraffe_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 76c33014a23d6d267821d31e34e1234dcb25db41 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 81 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/giraffe_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6297622c1fbff5264ae12c317140199728961720 +Author: Hongyu Wen +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 70 lines last edited by Hongyu Wen in worldgen/surfaces/templates/beak.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0193a43b3d6c0e863e08cd93958cbdaa8ac2cec0 +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/wood.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3f80c8ce4e0b229b5e786949706c5e9d2566ac46 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/wood.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e4b2ac26e109e38377804a59f193c99f080b56be +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 77 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/wood.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f55642e377dd22fc6bdc8dfde73f6d898574dfc5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/tiger_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7cd2fd5e127a043a28ec216c43097685a6302e86 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 47 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/tiger_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bc2a84190bbe4418be476e0a543b65dd4909c015 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 104 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/tiger_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 99bc849ddc856c8c1e653877481860d79efe5550 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/reptile_gray_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d7d7783347361f5b0ecc762c490af4c9be899eec +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/reptile_gray_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6f0e0fee6d8d667d59e65eec2237ff13e70402ed +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 175 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/reptile_gray_attr.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9f4c37e6bd03f81c5262dbb2f9e7ac20268362be +Author: Lahav Lipson +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 3 lines last edited by Lahav Lipson in worldgen/surfaces/templates/mountain.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a0c0388acdd8b4845ece1657542c9332d46f86d5 +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/mountain.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 215bcbce239f37a6b83658d9f1e7eebdfda95bfc +Author: Ankit Goyal +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 11 lines last edited by Ankit Goyal in worldgen/surfaces/templates/mountain.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 37ac68295b2722fc5a124048e60cafc6f8776032 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 16 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/mountain.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7fde8bf5d46fa12ac131979f599534d85c5a1d1e +Author: Zeyu Ma +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 280 lines last edited by Zeyu Ma in worldgen/surfaces/templates/mountain.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f37ac2d07b2bc4d28b9aea57037ac66f8a8f1129 +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/fishbody.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6c4c2aee94c0f0174d4ad45858506260138e7583 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 9 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/fishbody.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d058d72f82c478a67481cd0aa063ddbe13862154 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 1026 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/fishbody.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4da7a8ca09262e4b37a6a7fb41d9ab3e51bdbbfd +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/grass_blade_texture.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6de507903f46aa30f0184ca31cc0ff4779983673 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 19 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/grass_blade_texture.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit efeaf45d739da469db923c7505b357b00551c473 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 196 lines last edited by Lahav Lipson in worldgen/surfaces/templates/grass_blade_texture.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 908178dd4138fcc72f09b4590801290ea696a0ef +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 4 lines last edited by Pvl-bot in worldgen/surfaces/templates/snow.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5692341f99740a41e7c1cb18b8ac0eaefc7b0093 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 18 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/snow.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0c81bc41c66e3e2177ed9d3fc2ed5e9569422faf +Author: Zeyu Ma +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 112 lines last edited by Zeyu Ma in worldgen/surfaces/templates/snow.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0135eba9709d7ed0c757de65b4aa55969f1cae47 +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/fish_eye_shader.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9ae7d54351b333d3656719d5a783495f0ca37309 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 207 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/fish_eye_shader.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9545c617180e22f16e1afce3ac29bdcf5d9a6909 +Author: Pvl-bot +Date: Fri Jun 30 03:11:04 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/chitin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 97c40b03f7243aca18a01f781f742bb044a614cc +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 93 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/chitin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2f75fb0e44e09e08ca36ae119c652f85fdd21dea +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 125 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/chitin.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 01928f544135739e9566d6664b2f27d5b192c765 +Author: Pvl-bot +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 7 lines last edited by Pvl-bot in worldgen/surfaces/templates/bark_random.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9fcef67c55ac1a2ca7e4fcaef844982b9c5f4a29 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 23 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/bark_random.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit af0bc2a1327c4199003a20a7b8626df0051cb32a +Author: Yiming Zuo +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 519 lines last edited by Yiming Zuo in worldgen/surfaces/templates/bark_random.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 040e2dff6750c57e077743c6c928065ec706e1e6 +Author: Pvl-bot +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/horn.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3ec32ee0468d6447970588c5a7c67166c93eee63 +Author: Yihan Wang +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 78 lines last edited by Yihan Wang in worldgen/surfaces/templates/horn.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c612986c76dc68196dc0e3c26399b41517910e8b +Author: Pvl-bot +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/succulent.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d1a0dc6aa9d8a421d189d60fdb2f9b14a121c06f +Author: Beining Han +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 314 lines last edited by Beining Han in worldgen/surfaces/templates/succulent.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2dd7e80bcc27009bccf3f40414005795e10bafb3 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 2 lines last edited by Lingjie Mei in worldgen/surfaces/templates/water.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3d3423b0f0a2e2ac047d3b6df7fbea3ce5c6fd5f +Author: Pvl-bot +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/water.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1e6e0995b60347cfc3b009c9470694523696c502 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 36 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/water.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f21f836c13a4e35ac2b3c5027e4e86f31caaf0ae +Author: Zeyu Ma +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 204 lines last edited by Zeyu Ma in worldgen/surfaces/templates/water.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit eeef5630194972d9c269ee0314fdfa97b117f31f +Author: Pvl-bot +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/basic_bsdf.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1226702aed4bd2cbce0553a97374a570bfba269a +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 31 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/basic_bsdf.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7f7f25cc54096e7ae79b51c216e97f8e4f7c7858 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/surfaces/templates/cobble_stone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0e3f0ae88f8633643ecc4864dadcbb39e3ae0ae4 +Author: Ankit Goyal +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 2 lines last edited by Ankit Goyal in worldgen/surfaces/templates/cobble_stone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 460612be65d44b99f0049d4be110f0e11970b07e +Author: Pvl-bot +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/templates/cobble_stone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d9d67b502254cb77f0708c6551c9ba7749b6f97c +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 25 lines last edited by Mingzhe Wang in worldgen/surfaces/templates/cobble_stone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1413534216d53a4263d8e88a5ce5a0bab2a2caae +Author: Zeyu Ma +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 122 lines last edited by Zeyu Ma in worldgen/surfaces/templates/cobble_stone.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5769658ce26a28a0ab12aac34011eef9c6b6fba8 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/surfaces/dev_script.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1efad5e7a4e69ae1e53c51870e503375f19668f3 +Author: Pvl-bot +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/dev_script.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7e592e14b2a5f591a14405723d66a7ed3e0cd15b +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 37 lines last edited by Alexander Raistrick in worldgen/surfaces/dev_script.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 519c638ffb2b05ebd27d0cdb9236700bb3bf3390 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 5 lines last edited by Alexander Raistrick in worldgen/surfaces/surface_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e75d140c70512ca99fb3bbd05b0555dc0e1666b4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/surface_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 787864c5e5c618f597bd828843b6ddb5dc280262 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 47 lines last edited by Lingjie Mei in worldgen/surfaces/surface_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e1aa4e2a11de017e39640fb78aceb0a3218f37f8 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 124 lines last edited by Mingzhe Wang in worldgen/surfaces/surface_utils.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fc8a12578b8156ab998f31e21a4dc79db038b5a4 +Author: Pvl-bot +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 6 lines last edited by Pvl-bot in worldgen/surfaces/surface_mixing_dev_script.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3a2fc31dea210913b544d3d0aaab709ccacb072c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 12 lines last edited by Alexander Raistrick in worldgen/surfaces/surface_mixing_dev_script.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 19a7ee1060584d7f241c5fb29ae54047275198a5 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 28 lines last edited by Lahav Lipson in worldgen/surfaces/surface_mixing_dev_script.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 72092a0958836a3b84683370fe4c764da74fd211 +Author: Mingzhe Wang +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 1 lines last edited by Mingzhe Wang in worldgen/surfaces/surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 602eedebb5f2cb9a173a310e5bb171b2ba4ade0d +Author: Karhan Kayan +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 4 lines last edited by Karhan Kayan in worldgen/surfaces/surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 17221888b1acce0dee2340771a4d711163359870 +Author: Hongyu Wen +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 8 lines last edited by Hongyu Wen in worldgen/surfaces/surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9418ed9624d8961a5589431f09ba582f700dd275 +Author: Pvl-bot +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/surfaces/surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 22a3e4d3629494903b83beefa72878d0f078b218 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 16 lines last edited by Zeyu Ma in worldgen/surfaces/surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8d6e3ccad7eeed58ed576616dd6291071cee8a90 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 25 lines last edited by Lahav Lipson in worldgen/surfaces/surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9a830bd197aa891aac1a18e3c779a0e86af0090f +Author: Lingjie Mei +Date: Fri Jun 30 03:11:03 2023 -0400 + + Add 111 lines last edited by Lingjie Mei in worldgen/surfaces/surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d461618db13bc656c894329bd653d03c5bc38880 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 234 lines last edited by Alexander Raistrick in worldgen/surfaces/surface.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 61d0fba2d0d8602817ac584f1165f56aa83267fc +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 3 lines last edited by Zeyu Ma in worldgen/config/scene_types/plain.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 039b1ffc5988328766ab7a6d4371a152aa170f92 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 25 lines last edited by Alexander Raistrick in worldgen/config/scene_types/plain.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 788dfc9893d61a82856926b0684a13e1e0d6374d +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 26 lines last edited by Zeyu Ma in worldgen/config/scene_types/cave.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 945f507be7b670fa1c16bac91fd1f6a302a501a8 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 35 lines last edited by Alexander Raistrick in worldgen/config/scene_types/cave.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 631747f074cbb4246f84090a20cdf145536c3905 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 13 lines last edited by Zeyu Ma in worldgen/config/scene_types/river.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 05d26f6f3b8a146fcaccfdce4938c50068cbe081 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 28 lines last edited by Alexander Raistrick in worldgen/config/scene_types/river.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9885969f9866e0f175f104d8007cc92f41a3ee68 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 9 lines last edited by Alexander Raistrick in worldgen/config/scene_types/cliff.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a337da3df6bfe7d79b6df1ad644cf7dd4f832d3e +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 14 lines last edited by Zeyu Ma in worldgen/config/scene_types/cliff.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ec49d709edc69ed456fba6950384ac90bf4eaa69 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 2 lines last edited by Lingjie Mei in worldgen/config/scene_types/coral_reef.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e1e49c691d538d52e19f2949dafd6971aeb32d44 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 9 lines last edited by Alexander Raistrick in worldgen/config/scene_types/coral_reef.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d33be963192e5e8eb8cbe19f96d36e69bc8dc887 +Author: Pvl-bot +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 1 lines last edited by Pvl-bot in worldgen/config/scene_types/desert.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fa2800f9b13bc0d16575c1323c442b3fd710b1b3 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 1 lines last edited by Lingjie Mei in worldgen/config/scene_types/desert.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2830c81623c2354f92607839107e70b9b56943f1 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 18 lines last edited by Zeyu Ma in worldgen/config/scene_types/desert.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5a75d33a7ead9d3d3342825470bb5639de5060d5 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 25 lines last edited by Alexander Raistrick in worldgen/config/scene_types/desert.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6227818ba1e563e6ec80a926da1bb4c677b83306 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 4 lines last edited by Alexander Raistrick in worldgen/config/scene_types/snowy_mountain.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ace50ece997286cf87fcad3be54520fee3d1be47 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 31 lines last edited by Zeyu Ma in worldgen/config/scene_types/snowy_mountain.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 57b1c3e114abff81fc147ce31f553bedc44b1127 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 13 lines last edited by Alexander Raistrick in worldgen/config/scene_types/arctic.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3bde811030dc78f6ed769af01fd3b03c9416dced +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 28 lines last edited by Zeyu Ma in worldgen/config/scene_types/arctic.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit db282685ad7ae5dcc80c66bdd3a2f2b91ce5e60c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 11 lines last edited by Alexander Raistrick in worldgen/config/scene_types/coast.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit adbf76e58a503f8f75b745883026341a5f822549 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 32 lines last edited by Zeyu Ma in worldgen/config/scene_types/coast.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 29fa1c020cfa6e01d3efcbde633dc75c0d5e8a5d +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 3 lines last edited by Zeyu Ma in worldgen/config/scene_types/forest.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 27e04967cea3d6fb39f54d0f5501c7acda64d06e +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 55 lines last edited by Alexander Raistrick in worldgen/config/scene_types/forest.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c0f1b1f8a8889b60c001c6b463f8d738528306f0 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 8 lines last edited by Lingjie Mei in worldgen/config/scene_types/under_water.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 36d0faa13320b747cc89c6f95e52735df3d50715 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 13 lines last edited by Zeyu Ma in worldgen/config/scene_types/under_water.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 86c88747f903cff6b31e60807b32f34c7d0585e8 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 63 lines last edited by Alexander Raistrick in worldgen/config/scene_types/under_water.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d56d700da699e4f6b79a459dd51678c5080f7c34 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 5 lines last edited by Zeyu Ma in worldgen/config/scene_types/mountain.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 203a92b35e1de4ca94c7803aa6755004aefecaaa +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 8 lines last edited by Alexander Raistrick in worldgen/config/scene_types/mountain.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7d991ca45b30bd310de1c5210ac9f082b3dd5671 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 2 lines last edited by Lingjie Mei in worldgen/config/scene_types/kelp_forest.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 737db271662d3b143363c5b283e704d17d931ffc +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 22 lines last edited by Alexander Raistrick in worldgen/config/scene_types/kelp_forest.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 74cdc70dd13b00c8a469f444da46ae081748c192 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 7 lines last edited by Alexander Raistrick in worldgen/config/scene_types/canyon.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9766ddaa080b9d7259d46a001fb9a56fdd5dde38 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 8 lines last edited by Zeyu Ma in worldgen/config/scene_types/canyon.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit cf12a5f35d26493711fb2843779c7972022ea2d9 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:02 2023 -0400 + + Add 50 lines last edited by Zeyu Ma in worldgen/config/palette/water.json + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1f2d6c98947c54dfb13bd306c9c86a69d5be43bf +Author: Zeyu Ma +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 50 lines last edited by Zeyu Ma in worldgen/config/palette/sandstone.json + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8a857f4b2ff4427d9f3b842903ec0dda0a30c6c3 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 50 lines last edited by Zeyu Ma in worldgen/config/palette/desert.json + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6c0f48349c2af0c09071f4c3c7dd2f7eaee18ab7 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/config/simple.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1da542585359653c70ed081ae71e82d7f61a20af +Author: Pvl-bot +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 3 lines last edited by Pvl-bot in worldgen/config/simple.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3adc2dfd329ab3f1d6e789812f056010965e75a5 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/config/no_assets.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b398dae1ac9625f1f2f61c1fc37a55de0ed1fa1f +Author: Zeyu Ma +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 1 lines last edited by Zeyu Ma in worldgen/config/no_assets.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d5647b3a843f87a5d2550db37d851f69aaa5506c +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 45 lines last edited by Alexander Raistrick in worldgen/config/no_assets.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 655d87435e7e500a54e1c50bad5d2fddcc2e0ed3 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 10 lines last edited by Zeyu Ma in worldgen/config/reuse_terrain_assets.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 79785b66a09027e4dbffb17a9fdaa59ee30bad9f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in worldgen/config/high_quality_terrain.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9215f1a267408f4a8fddc8099e767b1e189a3a10 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 4 lines last edited by Zeyu Ma in worldgen/config/high_quality_terrain.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b54522163492f4457cb5067ba2496c3a2a8f380e +Author: Pvl-bot +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 1 lines last edited by Pvl-bot in worldgen/config/stereo_training.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c1bc8163ae4ae1e0d306510d65631b6287b9992d +Author: Lahav Lipson +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 1 lines last edited by Lahav Lipson in worldgen/config/stereo_training.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c9befacf759ddd9db6b8777c1521786ed55d4cc4 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 7 lines last edited by Zeyu Ma in worldgen/config/stereo_training.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 063da5f62336f2d98686a05363a352a095abc37f +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 17 lines last edited by Alexander Raistrick in worldgen/config/stereo_training.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6390ed70dc5b055754a0e5ee540aab869fa68538 +Author: Hei Law +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 1 lines last edited by Hei Law in worldgen/config/base.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0489251219c0b234e622babbfab9660a845dd316 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 32 lines last edited by Zeyu Ma in worldgen/config/base.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ba85630aecb42a3de64a2b46cfbc2c6f36e394f9 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 34 lines last edited by Lahav Lipson in worldgen/config/base.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 34964cbb63dbd4af6f0658aa6f5119a12537c0cc +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 145 lines last edited by Alexander Raistrick in worldgen/config/base.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit af684d282f645a7c7702b85849a106395fba8c97 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 9 lines last edited by Alexander Raistrick in worldgen/config/asset_demo.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2ddc977810d21f81a8e1a3cb56c38b57072511a4 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 3 lines last edited by Lingjie Mei in worldgen/config/base_surface_registry.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fafb646b1636620244837b7d1f71b9ac1d03764b +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 31 lines last edited by Alexander Raistrick in worldgen/config/base_surface_registry.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f4ca48f85f0490066f4646332c95cbbc9bcc6989 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 33 lines last edited by Zeyu Ma in worldgen/config/base_surface_registry.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 2e142d087789f5e02c1ccd19980af4bbb7751167 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 4 lines last edited by Zeyu Ma in worldgen/config/fast_terrain_assets.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3f095f0f12c5d8c34efd554a15626411ea890d73 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 3 lines last edited by Zeyu Ma in worldgen/config/monocular.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 67509281d99cd5c0ffe339508859674e9cdb3e22 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 5 lines last edited by Alexander Raistrick in worldgen/config/no_particles.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit abca0c4c5b8e106311abc9fb5466948084963fb7 +Author: Pvl-bot +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 8 lines last edited by Pvl-bot in worldgen/config/dev.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9c9f060e13f1cbcca893462893391e386a4409fe +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 8 lines last edited by Alexander Raistrick in worldgen/config/dev.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 855bc9ac0c52f274b4122af550515b1690a93b5f +Author: Pvl-bot +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 4 lines last edited by Pvl-bot in worldgen/config/no_creatures.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 08b10e8f2615df1878ce7cd5ec54a8a24ffdade6 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:01 2023 -0400 + + Add 2 lines last edited by Alexander Raistrick in worldgen/config/no_rocks.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 15b15727441cec53467c0d9b5b1bd499d5ef8991 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 20 lines last edited by Alexander Raistrick in worldgen/config/trailer.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6298ef51aca1e810abf7cd8749737698700f9e89 +Author: Pvl-bot +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 1 lines last edited by Pvl-bot in worldgen/config/natural.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5b9d22d3d6b98dd205fd652a2d90db6e5716e7da +Author: Zeyu Ma +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 2 lines last edited by Zeyu Ma in worldgen/config/natural.gin + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 613e519dcb519c090bffa24063d6a533b739c47c +Author: Pvl-bot +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 3 lines last edited by Pvl-bot in worldgen/asset_demo.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e97a625d33a0014d832cc8d1354b9447627fceea +Author: Zeyu Ma +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 26 lines last edited by Zeyu Ma in worldgen/asset_demo.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 657bce9c8ca1a0180843c79d384ca3d1418e5510 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 181 lines last edited by Alexander Raistrick in worldgen/asset_demo.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6a733761646cf0848bf72b0e3cd752704e26edc9 +Author: Hei Law +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 2 lines last edited by Hei Law in worldgen/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 18e94e177032cf3c1aab409e74ecf08bf6f37fdc +Author: Pvl-bot +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 4 lines last edited by Pvl-bot in worldgen/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit dedda5be5a670f67c61449302babd02f62f08a1b +Author: Lahav Lipson +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 11 lines last edited by Lahav Lipson in worldgen/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7a1084c19eae959d2312f964d4103e513ace7b3f +Author: Lingjie Mei +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 17 lines last edited by Lingjie Mei in worldgen/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c1c4311793cdb1a6def1d22aeb77e3e38a2a9d05 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 28 lines last edited by Zeyu Ma in worldgen/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ecdfbd13f2b06c2bd2a9e31d6b70d03e2e0dd579 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 347 lines last edited by Alexander Raistrick in worldgen/generate.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9a4fd05ae7a7951f2d2f0525953f2e19ca1171c9 +Author: Hei Law +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 3 lines last edited by Hei Law in worldgen/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9d1537514b0ec24c430437872d808fe202a56715 +Author: Pvl-bot +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 4 lines last edited by Pvl-bot in worldgen/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 58b17f6cc3e86623659f7a3ffedb474579fce608 +Author: Yihan Wang +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 4 lines last edited by Yihan Wang in worldgen/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 46bb4a6dee1bed4041130ee991cab67a71ead1c5 +Author: Lingjie Mei +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 22 lines last edited by Lingjie Mei in worldgen/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b8d90df1927b59384e6d199ffab2280a1c9f5ef9 +Author: Zeyu Ma +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 44 lines last edited by Zeyu Ma in worldgen/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 686d6284b1b0b1452c1e6dbabe4b2dd2de7a0998 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 52 lines last edited by Lahav Lipson in worldgen/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e788179213b27220067ca34d68672cf9452d4142 +Author: Alexander Raistrick +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 303 lines last edited by Alexander Raistrick in worldgen/core.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f858026d982604f494ca3fb22d2f6ac4580c4a3d +Author: Pvl-bot +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 2 lines last edited by Pvl-bot in process_mesh/glsl/spine.geom + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3341e95014a3c5bc1b4c51b7f7f48bc72178706c +Author: Lahav Lipson +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 52 lines last edited by Lahav Lipson in process_mesh/glsl/spine.geom + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5f8f2185306f6b4458f92ad15fec978c2de9df96 +Author: Pvl-bot +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 4 lines last edited by Pvl-bot in process_mesh/glsl/spine.frag + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7265b275589673f1513bfa09869d3b6c26a7ac56 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 5 lines last edited by Lahav Lipson in process_mesh/glsl/spine.frag + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d57fec664363384998510a2a5a879fd516128098 +Author: Pvl-bot +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 4 lines last edited by Pvl-bot in process_mesh/glsl/next_wings.vert + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 198d1187ce803fdb258762bee5ba8ae898ca1e92 +Author: Lahav Lipson +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 50 lines last edited by Lahav Lipson in process_mesh/glsl/next_wings.vert + + Commit made automatically to show authorship. This version of the code is not usable. + +commit b67778daa2d706ec9f4e6c1841e1aa4765bf690c +Author: Pvl-bot +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 4 lines last edited by Pvl-bot in process_mesh/glsl/hair.vert + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5e5d5d4fce9db382546316be88b23e7f63f7093c +Author: Lahav Lipson +Date: Fri Jun 30 03:11:00 2023 -0400 + + Add 32 lines last edited by Lahav Lipson in process_mesh/glsl/hair.vert + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 74cea049d5a5d8c8a34f7ceb4d6f7406bd0522bf +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 4 lines last edited by Pvl-bot in process_mesh/glsl/wings.vert + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a91a473044483be90e83c9cf6c363024915167a2 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 49 lines last edited by Lahav Lipson in process_mesh/glsl/wings.vert + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5e2d127c785d1948df149ae13cbacfdde09dd5eb +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 4 lines last edited by Pvl-bot in process_mesh/glsl/wings.frag + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 433bc23e983a24cbce091894b2bae5427ca93bd2 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 56 lines last edited by Lahav Lipson in process_mesh/glsl/wings.frag + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 10c35d608b2e2744c07c5210bac4887c906f4c32 +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 2 lines last edited by Pvl-bot in process_mesh/glsl/hair.geom + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 82c2104c4951da65d98360b4aea2717b20b2c8f0 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 100 lines last edited by Lahav Lipson in process_mesh/glsl/hair.geom + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 17e5dbf900de45f5dcd4c6cba76db2dcbc915a11 +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 2 lines last edited by Pvl-bot in process_mesh/glsl/wings.geom + + Commit made automatically to show authorship. This version of the code is not usable. + +commit f1fd1f887e8a52e97aa7ab1001859b9430987ccc +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 104 lines last edited by Lahav Lipson in process_mesh/glsl/wings.geom + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 157d46939eb30b3467ef44a515a54da23af073cd +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 4 lines last edited by Pvl-bot in process_mesh/glsl/hair.frag + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ee01661ad067fb4d1c58d052a913cc0355120f33 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 57 lines last edited by Lahav Lipson in process_mesh/glsl/hair.frag + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c0091f92d399cf66f35ac471e66cf0c0581367ca +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 14 lines last edited by Pvl-bot in process_mesh/utils.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 65bb67d8b6b27b3661e237280ebff0634bb1a6f9 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 28 lines last edited by Lahav Lipson in process_mesh/utils.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a0d6c08a5fb5f88dde2146322931c0a812f31178 +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 36 lines last edited by Pvl-bot in process_mesh/main.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1737461c9614c32418dc3a6a2380620deb132acd +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 466 lines last edited by Lahav Lipson in process_mesh/main.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 6de4841646e9918984dc3bbf5136d287e57abc6c +Author: Alexander Raistrick +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 1 lines last edited by Alexander Raistrick in process_mesh/CMakeLists.txt + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ebc5d53b491d2a435fee5541f3f12a1f0baddc43 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 63 lines last edited by Lahav Lipson in process_mesh/CMakeLists.txt + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4078562f8255760aa1b0559b5459c653ad2e9f39 +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 17 lines last edited by Pvl-bot in process_mesh/load_blender_mesh.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ed074bd1d01007e92e140e5af1cfbb93e781b478 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 90 lines last edited by Lahav Lipson in process_mesh/load_blender_mesh.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit e25bfeb0360d491ce0fd2ff74e026c1ccb21db2f +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 9 lines last edited by Pvl-bot in process_mesh/buffer_arrays.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8d6197cf3755ad019a0d08b43bd48da723d44ea5 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 117 lines last edited by Lahav Lipson in process_mesh/buffer_arrays.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit be730db2ae4b1753f5aa7760f8b11dc28bc365fc +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 10 lines last edited by Pvl-bot in process_mesh/string_tools.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 5be114b4f28a03eb0d1a39ee298e16d9f193aea9 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 31 lines last edited by Lahav Lipson in process_mesh/string_tools.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9e9058a7111f13c301c21ec8f277480a0b54cbcc +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 12 lines last edited by Pvl-bot in process_mesh/camera_view.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 028cc4f75d2a3515021c5bf7765cb3a794058d24 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 24 lines last edited by Lahav Lipson in process_mesh/camera_view.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a817f1abedff14fab35fb2b3164f3b55d501c7a9 +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 14 lines last edited by Pvl-bot in process_mesh/io.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit bff6f301f8941a02e5e6f4be91dd518f51644331 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 47 lines last edited by Lahav Lipson in process_mesh/io.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit d7ca7a0294fb19407ca230daae14c2ccc6f2ffdc +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 12 lines last edited by Pvl-bot in process_mesh/camera_view.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 79bd829cbf4cd202b5cde8010de202d4e325e9ff +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 116 lines last edited by Lahav Lipson in process_mesh/camera_view.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9620bf42cf051b6fc541f266f724f9a2c704aa91 +Author: Pvl-bot +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 6 lines last edited by Pvl-bot in process_mesh/show.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 1a318ea77d09e7184064e932e5b80c8492d45cd5 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:59 2023 -0400 + + Add 50 lines last edited by Lahav Lipson in process_mesh/show.py + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 76614a208ddd8a34684f4879a09602426389e4dc +Author: Pvl-bot +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 288 lines last edited by Pvl-bot in process_mesh/shader.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7be3e848014e4b10cb158aa54e391c90be8296f8 +Author: Pvl-bot +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 15 lines last edited by Pvl-bot in process_mesh/blender_object.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0d721679bf34c69af0e6b3ed8ec71b98f680a732 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 68 lines last edited by Lahav Lipson in process_mesh/blender_object.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 0317ee1a97004a364718866bb3f572f339cea12c +Author: Pvl-bot +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 15 lines last edited by Pvl-bot in process_mesh/utils.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 3d0e5f41c695f86d5b4248f01d4d6d5ba4f7abb6 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 152 lines last edited by Lahav Lipson in process_mesh/utils.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 8bfe62e7a2af4d06601ff0d392cfe423c98e78f9 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 11 lines last edited by Lahav Lipson in process_mesh/load_blender_mesh.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fe7356b98a39e9595a5eea3d39e2bb41c45b4244 +Author: Pvl-bot +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 24 lines last edited by Pvl-bot in process_mesh/load_blender_mesh.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit c1846b7d90912cf9fa8ccb7cc844dce5a6e733d4 +Author: Pvl-bot +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 77 lines last edited by Pvl-bot in process_mesh/shader.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ae41e261f6ca62b1436a33cdc7eb5f0d9515e542 +Author: Pvl-bot +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 9 lines last edited by Pvl-bot in process_mesh/string_tools.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 23f38b7cd4fef89ef757c84d15e33f62de35e2ab +Author: Lahav Lipson +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 9 lines last edited by Lahav Lipson in process_mesh/string_tools.hpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit ca1611bccfab7ca8612793a09735d8d1961ac6c7 +Author: Pvl-bot +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 7 lines last edited by Pvl-bot in process_mesh/buffer_arrays.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 409785366db24f3cd6bb6a55145d625885d7a3eb +Author: Lahav Lipson +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 127 lines last edited by Lahav Lipson in process_mesh/buffer_arrays.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 4ce3c92de4d366f3c1d469162a97e014cd5fd9e2 +Author: Pvl-bot +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 20 lines last edited by Pvl-bot in process_mesh/io.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 9c81b0b573afd65976da018560a9171c8cdb0879 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 64 lines last edited by Lahav Lipson in process_mesh/io.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit a9b664c47ae75815726dd7de420b27d8d5df96dd +Author: Pvl-bot +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 15 lines last edited by Pvl-bot in process_mesh/blender_object.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit 7135764600c01a5d71dc8b8de66b40b3744e4998 +Author: Lahav Lipson +Date: Fri Jun 30 03:10:58 2023 -0400 + + Add 185 lines last edited by Lahav Lipson in process_mesh/blender_object.cpp + + Commit made automatically to show authorship. This version of the code is not usable. + +commit fd99ac2314e85e2b7b5c9ebffb9107e2087b1902 +Author: Pvl-bot +Date: Fri Jun 30 03:10:58 2023 -0400 + + Initial commit + + Commit made automatically to show authorship. This version of the code is not usable. diff --git a/pyproject.toml b/pyproject.toml index fce75582e..5722067da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,12 +10,12 @@ dynamic = ["version"] description = "Infinite Photorealistic Worlds using Procedural Generation" keywords = [ - "computer vision", - "data generation", + "computer vision", + "data generation", "procedural" ] classifiers = [ - "Framework :: Blender", + "Framework :: Blender", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10" ] @@ -29,22 +29,29 @@ dependencies = [ "geomdl", "gin_config>=0.5.0", "imageio", + "ipython", + "json5", + "landlab>=2.6.0", "matplotlib", - "numpy", + "networkx", + "numpy<2", "opencv-python", + "pandas", "psutil", + "pycparser==2.22", + "pyrender", + "python-fcl", + "Rtree", "scikit-image", "scikit-learn", "scipy", + "shapely", "submitit", "tqdm", "trimesh", - "pandas", - "landlab>=2.6.0", - "pycparser", - "pyrender", - "pandas", "vnoise", + "zarr", + "networkx", ] [project.optional-dependencies] @@ -54,16 +61,25 @@ dev = [ "pytest-cov", "pytest-xdist", "pytest-timeout", - "ruff", + "pytype", + "ruff", + "isort", + "tabulate", # for integration test results +] + +vis = [ + "numba", # for ground truth visuals + "pyglet<2" # for trimesh_scene.show() ] + wandb = [ "wandb" ] [tool.setuptools] -# include-package-data is terribly named. package-data is still included if false, +# include-package-data is terribly named. package-data is still included if false, # just not the package-data setuptools would otherwise autogenerate from MANIFEST.in or version control -include-package-data = false +include-package-data = false [tool.setuptools.packages.find] include = ["infinigen*"] @@ -78,7 +94,7 @@ exclude = [ "*" = ["*.gin", "*.txt", "*.json"] -# Must be specified as paths relative to infinigen/ +# Must be specified as paths relative to infinigen/ "infinigen" = [ "terrain/**/*.soil", # extra files for SoilMachine "terrain/lib/**/*.so", # created by terrain compilation @@ -93,14 +109,26 @@ version = {attr = "infinigen.__version__"} [tool.pytest.ini_options] testpaths = "tests" junit_family = "xunit2" +markers = ["nature", "indoors", "skip_for_ci"] timeout = 240 +filterwarnings = [ + + "ignore:The value of the smallest subnormal for outputs/rebuttal_figure/living_0/logs.txt & +python -m infinigen_examples.generate_indoors --seed 1 --task coarse --output_folder outputs/rebuttal_figure/living_1 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom > outputs/rebuttal_figure/living_1/logs.txt & +python -m infinigen_examples.generate_indoors --seed 2 --task coarse --output_folder outputs/rebuttal_figure/living_2 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom > outputs/rebuttal_figure/living_2/logs.txt & +python -m infinigen_examples.generate_indoors --seed 3 --task coarse --output_folder outputs/rebuttal_figure/living_3 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom > outputs/rebuttal_figure/living_3/logs.txt & +python -m infinigen_examples.generate_indoors --seed 4 --task coarse --output_folder outputs/rebuttal_figure/living_4 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom > outputs/rebuttal_figure/living_4/logs.txt & +python -m infinigen_examples.generate_indoors --seed 5 --task coarse --output_folder outputs/rebuttal_figure/living_5 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom > outputs/rebuttal_figure/living_5/logs.txt & +python -m infinigen_examples.generate_indoors --seed 6 --task coarse --output_folder outputs/rebuttal_figure/living_6 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom > outputs/rebuttal_figure/living_6/logs.txt & +python -m infinigen_examples.generate_indoors --seed 7 --task coarse --output_folder outputs/rebuttal_figure/living_7 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom > outputs/rebuttal_figure/living_7/logs.txt & +python -m infinigen_examples.generate_indoors --seed 8 --task coarse --output_folder outputs/rebuttal_figure/living_8 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom > outputs/rebuttal_figure/living_8/logs.txt & + +wait $(jobs -ps) + +python -m infinigen_examples.generate_indoors --seed 0 --task render --input_folder outputs/rebuttal_figure/kitchen_0 --output_folder outputs/rebuttal_figure/kitchen_0 -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 1 --task render --input_folder outputs/rebuttal_figure/kitchen_1 --output_folder outputs/rebuttal_figure/kitchen_1 -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 2 --task render --input_folder outputs/rebuttal_figure/kitchen_2 --output_folder outputs/rebuttal_figure/kitchen_2 -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 3 --task render --input_folder outputs/rebuttal_figure/kitchen_3 --output_folder outputs/rebuttal_figure/kitchen_3 -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/kitchen_4 --output_folder outputs/rebuttal_figure/kitchen_4 -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 5 --task render --input_folder outputs/rebuttal_figure/kitchen_5 --output_folder outputs/rebuttal_figure/kitchen_5 -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 6 --task render --input_folder outputs/rebuttal_figure/kitchen_6 --output_folder outputs/rebuttal_figure/kitchen_6 -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 7 --task render --input_folder outputs/rebuttal_figure/kitchen_7 --output_folder outputs/rebuttal_figure/kitchen_7 -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 8 --task render --input_folder outputs/rebuttal_figure/kitchen_8 --output_folder outputs/rebuttal_figure/kitchen_8 -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom + +python -m infinigen_examples.generate_indoors --seed 0 --task render --input_folder outputs/rebuttal_figure/bathroom_0 --output_folder outputs/rebuttal_figure/bathroom_0 -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 1 --task render --input_folder outputs/rebuttal_figure/bathroom_1 --output_folder outputs/rebuttal_figure/bathroom_1 -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 2 --task render --input_folder outputs/rebuttal_figure/bathroom_2 --output_folder outputs/rebuttal_figure/bathroom_2 -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 3 --task render --input_folder outputs/rebuttal_figure/bathroom_3 --output_folder outputs/rebuttal_figure/bathroom_3 -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/bathroom_4 --output_folder outputs/rebuttal_figure/bathroom_4 -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 5 --task render --input_folder outputs/rebuttal_figure/bathroom_5 --output_folder outputs/rebuttal_figure/bathroom_5 -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 6 --task render --input_folder outputs/rebuttal_figure/bathroom_6 --output_folder outputs/rebuttal_figure/bathroom_6 -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 7 --task render --input_folder outputs/rebuttal_figure/bathroom_7 --output_folder outputs/rebuttal_figure/bathroom_7 -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 8 --task render --input_folder outputs/rebuttal_figure/bathroom_8 --output_folder outputs/rebuttal_figure/bathroom_8 -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom + +python -m infinigen_examples.generate_indoors --seed 0 --task render --input_folder outputs/rebuttal_figure/dining_0 --output_folder outputs/rebuttal_figure/dining_0 -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom & +python -m infinigen_examples.generate_indoors --seed 1 --task render --input_folder outputs/rebuttal_figure/dining_1 --output_folder outputs/rebuttal_figure/dining_1 -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom & +python -m infinigen_examples.generate_indoors --seed 2 --task render --input_folder outputs/rebuttal_figure/dining_2 --output_folder outputs/rebuttal_figure/dining_2 -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom & +python -m infinigen_examples.generate_indoors --seed 3 --task render --input_folder outputs/rebuttal_figure/dining_3 --output_folder outputs/rebuttal_figure/dining_3 -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/dining_4 --output_folder outputs/rebuttal_figure/dining_4 -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 5 --task render --input_folder outputs/rebuttal_figure/dining_5 --output_folder outputs/rebuttal_figure/dining_5 -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 6 --task render --input_folder outputs/rebuttal_figure/dining_6 --output_folder outputs/rebuttal_figure/dining_6 -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 7 --task render --input_folder outputs/rebuttal_figure/dining_7 --output_folder outputs/rebuttal_figure/dining_7 -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 8 --task render --input_folder outputs/rebuttal_figure/dining_8 --output_folder outputs/rebuttal_figure/dining_8 -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom + +python -m infinigen_examples.generate_indoors --seed 0 --task render --input_folder outputs/rebuttal_figure/living_0 --output_folder outputs/rebuttal_figure/living_0 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom & +python -m infinigen_examples.generate_indoors --seed 1 --task render --input_folder outputs/rebuttal_figure/living_1 --output_folder outputs/rebuttal_figure/living_1 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom & +python -m infinigen_examples.generate_indoors --seed 2 --task render --input_folder outputs/rebuttal_figure/living_2 --output_folder outputs/rebuttal_figure/living_2 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom & +python -m infinigen_examples.generate_indoors --seed 3 --task render --input_folder outputs/rebuttal_figure/living_3 --output_folder outputs/rebuttal_figure/living_3 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/living_4 --output_folder outputs/rebuttal_figure/living_4 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/living_4 --output_folder outputs/rebuttal_figure/living_5 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/living_4 --output_folder outputs/rebuttal_figure/living_6 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/living_4 --output_folder outputs/rebuttal_figure/living_7 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/living_4 --output_folder outputs/rebuttal_figure/living_8 -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom \ No newline at end of file diff --git a/scripts/rebuttal_retry_render.sh b/scripts/rebuttal_retry_render.sh new file mode 100644 index 000000000..25d4fcce5 --- /dev/null +++ b/scripts/rebuttal_retry_render.sh @@ -0,0 +1,36 @@ +python -m infinigen_examples.generate_indoors --seed 0 --task render --input_folder outputs/rebuttal_figure/kitchen_0 --output_folder outputs/rebuttal_figure/kitchen_0/frames -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 1 --task render --input_folder outputs/rebuttal_figure/kitchen_1 --output_folder outputs/rebuttal_figure/kitchen_1/frames -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 2 --task render --input_folder outputs/rebuttal_figure/kitchen_2 --output_folder outputs/rebuttal_figure/kitchen_2/frames -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 3 --task render --input_folder outputs/rebuttal_figure/kitchen_3 --output_folder outputs/rebuttal_figure/kitchen_3/frames -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/kitchen_4 --output_folder outputs/rebuttal_figure/kitchen_4/frames -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 5 --task render --input_folder outputs/rebuttal_figure/kitchen_5 --output_folder outputs/rebuttal_figure/kitchen_5/frames -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 6 --task render --input_folder outputs/rebuttal_figure/kitchen_6 --output_folder outputs/rebuttal_figure/kitchen_6/frames -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 7 --task render --input_folder outputs/rebuttal_figure/kitchen_7 --output_folder outputs/rebuttal_figure/kitchen_7/frames -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 8 --task render --input_folder outputs/rebuttal_figure/kitchen_8 --output_folder outputs/rebuttal_figure/kitchen_8/frames -p compose_indoors.room_tags=[\"kitchen\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 0 --task render --input_folder outputs/rebuttal_figure/bathroom_0 --output_folder outputs/rebuttal_figure/bathroom_0/frames -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 1 --task render --input_folder outputs/rebuttal_figure/bathroom_1 --output_folder outputs/rebuttal_figure/bathroom_1/frames -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 2 --task render --input_folder outputs/rebuttal_figure/bathroom_2 --output_folder outputs/rebuttal_figure/bathroom_2/frames -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 3 --task render --input_folder outputs/rebuttal_figure/bathroom_3 --output_folder outputs/rebuttal_figure/bathroom_3/frames -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/bathroom_4 --output_folder outputs/rebuttal_figure/bathroom_4/frames -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 5 --task render --input_folder outputs/rebuttal_figure/bathroom_5 --output_folder outputs/rebuttal_figure/bathroom_5/frames -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 6 --task render --input_folder outputs/rebuttal_figure/bathroom_6 --output_folder outputs/rebuttal_figure/bathroom_6/frames -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 7 --task render --input_folder outputs/rebuttal_figure/bathroom_7 --output_folder outputs/rebuttal_figure/bathroom_7/frames -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 8 --task render --input_folder outputs/rebuttal_figure/bathroom_8 --output_folder outputs/rebuttal_figure/bathroom_8/frames -p compose_indoors.room_tags=[\"bathroom\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 0 --task render --input_folder outputs/rebuttal_figure/dining_0 --output_folder outputs/rebuttal_figure/dining_0/frames -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 1 --task render --input_folder outputs/rebuttal_figure/dining_1 --output_folder outputs/rebuttal_figure/dining_1/frames -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 2 --task render --input_folder outputs/rebuttal_figure/dining_2 --output_folder outputs/rebuttal_figure/dining_2/frames -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 3 --task render --input_folder outputs/rebuttal_figure/dining_3 --output_folder outputs/rebuttal_figure/dining_3/frames -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/dining_4 --output_folder outputs/rebuttal_figure/dining_4/frames -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 5 --task render --input_folder outputs/rebuttal_figure/dining_5 --output_folder outputs/rebuttal_figure/dining_5/frames -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 6 --task render --input_folder outputs/rebuttal_figure/dining_6 --output_folder outputs/rebuttal_figure/dining_6/frames -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 7 --task render --input_folder outputs/rebuttal_figure/dining_7 --output_folder outputs/rebuttal_figure/dining_7/frames -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 8 --task render --input_folder outputs/rebuttal_figure/dining_8 --output_folder outputs/rebuttal_figure/dining_8/frames -p compose_indoors.room_tags=[\"dining-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 0 --task render --input_folder outputs/rebuttal_figure/living_0 --output_folder outputs/rebuttal_figure/living_0/frames -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 1 --task render --input_folder outputs/rebuttal_figure/living_1 --output_folder outputs/rebuttal_figure/living_1/frames -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 2 --task render --input_folder outputs/rebuttal_figure/living_2 --output_folder outputs/rebuttal_figure/living_2/frames -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 3 --task render --input_folder outputs/rebuttal_figure/living_3 --output_folder outputs/rebuttal_figure/living_3/frames -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 4 --task render --input_folder outputs/rebuttal_figure/living_4 --output_folder outputs/rebuttal_figure/living_4/frames -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 5 --task render --input_folder outputs/rebuttal_figure/living_5 --output_folder outputs/rebuttal_figure/living_5/frames -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 6 --task render --input_folder outputs/rebuttal_figure/living_6 --output_folder outputs/rebuttal_figure/living_6/frames -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 7 --task render --input_folder outputs/rebuttal_figure/living_7 --output_folder outputs/rebuttal_figure/living_7/frames -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom +python -m infinigen_examples.generate_indoors --seed 8 --task render --input_folder outputs/rebuttal_figure/living_8 --output_folder outputs/rebuttal_figure/living_8/frames -p compose_indoors.room_tags=[\"living-room\"] --configs overhead_singleroom \ No newline at end of file diff --git a/setup.py b/setup.py index 8f0557657..48feae128 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,12 @@ +# 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 + +# Acknowledgement: This file draws inspiration from https://github.com/pytorch/pytorch/blob/main/setup.py + + from pathlib import Path import subprocess import sys diff --git a/tests/assets/list_displaced_materials.txt b/tests/assets/list_displaced_materials.txt new file mode 100644 index 000000000..4444025e9 --- /dev/null +++ b/tests/assets/list_displaced_materials.txt @@ -0,0 +1,7 @@ +infinigen.assets.materials.leather_and_fabrics.fabric +infinigen.assets.materials.leather_and_fabrics.leather +infinigen.assets.materials.metal.grained_and_polished_metal +infinigen.assets.materials.metal.hammered_metal +infinigen.assets.materials.stone_and_concrete.concrete +infinigen.assets.materials.woods.tiled_wood +infinigen.assets.materials.plastics.plastic_rough diff --git a/tests/assets/list_indoor_materials.txt b/tests/assets/list_indoor_materials.txt new file mode 100644 index 000000000..49875dc23 --- /dev/null +++ b/tests/assets/list_indoor_materials.txt @@ -0,0 +1,35 @@ +infinigen.assets.materials.fabrics +infinigen.assets.materials.leather +infinigen.assets.materials.sofa_fabric +infinigen.assets.materials.coarse_knit_fabric +infinigen.assets.materials.fine_knit_fabric +infinigen.assets.materials.lined_fabric +infinigen.assets.materials.brushed_metal +infinigen.assets.materials.galvanized_metal +infinigen.assets.materials.grained_and_polished_metal +infinigen.assets.materials.hammered_metal +infinigen.assets.materials.metal_basic +infinigen.assets.materials.concrete +infinigen.assets.materials.tiled_wood +infinigen.assets.materials.art +infinigen.assets.materials.ArtRug +infinigen.assets.materials.ArtFabric +infinigen.assets.materials.brick +infinigen.assets.materials.ceramic +infinigen.assets.materials.glass +infinigen.assets.materials.hardwood_floor +infinigen.assets.materials.leather_and_fabrics.leather +infinigen.assets.materials.leather_and_fabrics.velvet +infinigen.assets.materials.marble_regular +infinigen.assets.materials.marble_voronoi +infinigen.assets.materials.metal.metal_basic +infinigen.assets.materials.mirror +infinigen.assets.materials.plaster +infinigen.assets.materials.plastic +infinigen.assets.materials.rug +infinigen.assets.materials.text +infinigen.assets.materials.tile +infinigen.assets.materials.wood +infinigen.assets.materials.wood_old +infinigen.assets.materials.tiled_wood +infinigen.assets.materials.bumpy_rubber_floor diff --git a/tests/assets/list_indoor_meshes.txt b/tests/assets/list_indoor_meshes.txt new file mode 100644 index 000000000..61f4cc886 --- /dev/null +++ b/tests/assets/list_indoor_meshes.txt @@ -0,0 +1,100 @@ +infinigen.assets.appliances.BeverageFridgeFactory +infinigen.assets.appliances.DishwasherFactory +infinigen.assets.appliances.MicrowaveFactory +infinigen.assets.appliances.OvenFactory +infinigen.assets.appliances.MonitorFactory +infinigen.assets.appliances.TVFactory + +infinigen.assets.bathroom.BathroomSinkFactory +infinigen.assets.bathroom.BathtubFactory +infinigen.assets.bathroom.HardwareFactory +infinigen.assets.bathroom.ToiletFactory + +infinigen.assets.clothes.BlanketFactory +infinigen.assets.clothes.PantsFactory +infinigen.assets.clothes.ShirtFactory +infinigen.assets.clothes.TowelFactory +infinigen.assets.decor.AquariumTankFactory + +infinigen.assets.elements.doors.GlassPanelDoorFactory +infinigen.assets.elements.doors.LiteDoorFactory +infinigen.assets.elements.doors.LouverDoorFactory +infinigen.assets.elements.doors.PanelDoorFactory +infinigen.assets.elements.staircases.CantileverStaircaseFactory +infinigen.assets.elements.staircases.CurvedStaircaseFactory +infinigen.assets.elements.staircases.LShapedStaircaseFactory +infinigen.assets.elements.staircases.SpiralStaircaseFactory +infinigen.assets.elements.staircases.StraightStaircaseFactory +infinigen.assets.elements.staircases.UShapedStaircaseFactory +infinigen.assets.elements.warehouses.RackFactory +infinigen.assets.elements.warehouses.PalletFactory +infinigen.assets.elements.RugFactory +infinigen.assets.elements.NatureShelfTrinketsFactory + +infinigen.assets.lighting.CeilingLightFactory +infinigen.assets.lighting.LampFactory +infinigen.assets.lighting.DeskLampFactory +infinigen.assets.lighting.FloorLampFactory +infinigen.assets.lighting.ceiling_classic_lamp.CeilingClassicLampFactory + +infinigen.assets.seating.chairs.BarChairFactory +infinigen.assets.seating.chairs.ChairFactory +infinigen.assets.seating.chairs.OfficeChairFactory +infinigen.assets.seating.BedFactory +infinigen.assets.seating.BedFrameFactory +infinigen.assets.seating.MattressFactory +infinigen.assets.seating.PillowFactory +infinigen.assets.seating.SofaFactory +infinigen.assets.seating.ArmChairFactory + +infinigen.assets.shelves.SingleCabinetFactory +infinigen.assets.shelves.KitchenCabinetFactory +infinigen.assets.shelves.CellShelfFactory +infinigen.assets.shelves.LargeShelfFactory +infinigen.assets.shelves.SimpleBookcaseFactory +infinigen.assets.shelves.SimpleDeskFactory +infinigen.assets.shelves.TriangleShelfFactory +infinigen.assets.shelves.KitchenSpaceFactory +infinigen.assets.shelves.KitchenIslandFactory +infinigen.assets.shelves.TVStandFactory + +infinigen.assets.table_decorations.BookColumnFactory +infinigen.assets.table_decorations.BookFactory +infinigen.assets.table_decorations.BookStackFactory +infinigen.assets.table_decorations.SinkFactory +infinigen.assets.table_decorations.TapFactory +infinigen.assets.table_decorations.VaseFactory + +infinigen.assets.tables.TableCocktailFactory +infinigen.assets.tables.TableDiningFactory + +infinigen.assets.tableware.BottleFactory +infinigen.assets.tableware.BowlFactory +infinigen.assets.tableware.CanFactory +infinigen.assets.tableware.ChopsticksFactory +infinigen.assets.tableware.CupFactory +infinigen.assets.tableware.FoodBagFactory +infinigen.assets.tableware.FoodBoxFactory +infinigen.assets.tableware.ForkFactory +infinigen.assets.tableware.FruitContainerFactory +infinigen.assets.tableware.JarFactory +infinigen.assets.tableware.KnifeFactory +infinigen.assets.tableware.LidFactory +infinigen.assets.tableware.PanFactory +infinigen.assets.tableware.PlateFactory +infinigen.assets.tableware.PotFactory +infinigen.assets.tableware.SpoonFactory +infinigen.assets.tableware.WineglassFactory +infinigen.assets.tableware.PlantContainerFactory +infinigen.assets.tableware.LargePlantContainerFactory + +infinigen.assets.wall_decorations.WallArtFactory +infinigen.assets.wall_decorations.BalloonFactory +infinigen.assets.wall_decorations.MirrorFactory + +infinigen.assets.windows.WindowFactory + +infinigen.assets.organizer.basket.BasketBaseFactory +infinigen.assets.organizer.plate_rack.PlateOnRackBaseFactory + + diff --git a/tests/test_materials_basic.txt b/tests/assets/list_nature_materials.txt similarity index 100% rename from tests/test_materials_basic.txt rename to tests/assets/list_nature_materials.txt diff --git a/tests/test_meshes_basic.txt b/tests/assets/list_nature_meshes.txt similarity index 100% rename from tests/test_meshes_basic.txt rename to tests/assets/list_nature_meshes.txt diff --git a/tests/assets/test_materials_basic.py b/tests/assets/test_materials_basic.py new file mode 100644 index 000000000..ff654c17b --- /dev/null +++ b/tests/assets/test_materials_basic.py @@ -0,0 +1,47 @@ +# 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 + +from pathlib import Path +import importlib + +import pytest +import bpy +import gin + +from infinigen.core.util import blender as butil + +from infinigen_examples.util.test_utils import (setup_gin, load_txt_list, import_item) + + +def check_material_runs(pathspec): + butil.clear_scene() + bpy.ops.mesh.primitive_ico_sphere_add(radius=.8, subdivisions=5) + asset = bpy.context.active_object + + mat = import_item(pathspec) + if type(mat) is type: + mat = mat(0) + mat.apply(asset) + + # should not crash for input LIST of objects + bpy.ops.mesh.primitive_ico_sphere_add(radius=.8, subdivisions=5) + asset2 = bpy.context.active_object + mat.apply([asset, asset2]) + + + + +@pytest.mark.nature +@pytest.mark.parametrize('pathspec', load_txt_list('tests/assets/list_nature_materials.txt')) +def test_nature_material_runs(pathspec, **kwargs): + setup_gin('infinigen_examples/configs_nature') + check_material_runs(pathspec) + + +@pytest.mark.parametrize('pathspec', load_txt_list('tests/assets/list_indoor_materials.txt')) +def test_indoor_material_runs(pathspec, **kwargs): + setup_gin('infinigen_examples/configs_indoor') + check_material_runs(pathspec) diff --git a/tests/assets/test_meshes_basic.py b/tests/assets/test_meshes_basic.py new file mode 100644 index 000000000..f6a3a2e95 --- /dev/null +++ b/tests/assets/test_meshes_basic.py @@ -0,0 +1,90 @@ +# 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 + +from pathlib import Path + +import pytest +import bpy +import gin +from math import prod + +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t + +from infinigen_examples.util.test_utils import ( + setup_gin, + import_item, + load_txt_list, +) + +def check_factory_runs(fac_class, seed1=0, seed2=0, distance_m=50): + + butil.clear_scene() + fac = fac_class(seed1) + asset = fac.spawn_asset(seed2, distance=distance_m) + + if not isinstance(asset, bpy.types.Object): + raise ValueError(f'{asset.name=} had {type(asset)=}') + + if tuple(asset.location) != (0,0,0): + raise ValueError(f'{asset.location=}') + if tuple(asset.rotation_euler) != (0,0,0): + raise ValueError(f'{asset.rotation_euler=}') + if tuple(asset.scale) != (1,1,1): + raise ValueError(f'{asset.scale=}') + + # currently, assets may have objects as '.children'. + # This will eventually be removed except well-documented special cases + for o in butil.iter_object_tree(asset): + + for i, slot in enumerate(o.material_slots): + if slot.material is None: + raise ValueError(f'In {asset.name=} {slot=} had {slot.material=}') + + for mod in asset.modifiers: + if ( + mod.type != 'NODES' + and mod.type != 'SUBSURF' + ): + # currently we allow unapplied non-modifiers for things like time-based deformation on + # seaweed etc. NODES and SUBSURF should still always be applied. + continue + raise ValueError(f'In {asset.name=} {o.name=} had unapplied modifier {mod.name=} {mod.type=} ') + + if o.type != 'MESH': + continue + + if o.data is None: + raise ValueError(f'In {asset.name=} {o.name=} had {o.data=}') + + if len(o.data.vertices) <= 2: + raise ValueError(f'{asset.name=} had {len(o.data.vertices)} vertices, usually indicates failed operation') + + if tagging.COMBINED_ATTR_NAME in o.data.attributes: + attr = o.data.attributes[tagging.COMBINED_ATTR_NAME] + if attr.domain != 'FACE': + raise ValueError(f'In {asset.name=} had {attr.domain=} for {attr.name=}. Should be FACE') + + # some objects like the older LeafFactory + #if len(o.data.polygons) < 2: + # raise ValueError(f'{asset.name=} had {len(o.data.polygons)} polygons, usually indicates failed operation') + + for attr in o.data.attributes: + if attr.name.startswith(tagging.PREFIX): + raise ValueError(f'In {asset.name}, {o.name=} had un-merged tag-system tag {attr.name=}, need to call {tagging.tag_system.relabel_obj}') + +@pytest.mark.nature +@pytest.mark.parametrize('pathspec', load_txt_list(Path(__file__).parent/'list_nature_meshes.txt')) +def test_nature_factory_runs(pathspec, **kwargs): + setup_gin('infinigen_examples/configs_nature') + fac_class = import_item(pathspec) + check_factory_runs(fac_class, **kwargs) + +@pytest.mark.parametrize('pathspec', load_txt_list(Path(__file__).parent/'list_indoor_meshes.txt')) +def test_indoor_factory_runs(pathspec, **kwargs): + setup_gin('infinigen_examples/configs_indoor') + fac_class = import_item(pathspec) + check_factory_runs(fac_class, **kwargs) diff --git a/tests/assets/test_placeholders.py b/tests/assets/test_placeholders.py new file mode 100644 index 000000000..b1b03f3e5 --- /dev/null +++ b/tests/assets/test_placeholders.py @@ -0,0 +1,68 @@ +# 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: David Yan + +from collections import OrderedDict + +import bpy +import pytest + +from infinigen.core.constraints import ( + usage_lookup, + constraint_language as cl +) + +from infinigen.core.constraints.example_solver.geometry import dof + +from infinigen_examples.indoor_asset_semantics import home_asset_usage + +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t +import numpy as np + + +def get_real_placeholder_facs(): + used_as = home_asset_usage() + usage_lookup.initialize_from_dict(used_as) + pholder_facs = usage_lookup.factories_for_usage({t.Semantics.RealPlaceholder}) + return sorted(list(pholder_facs), key=lambda x: x.__name__) + +def get_asset_facs(): + used_as = home_asset_usage() + usage_lookup.initialize_from_dict(used_as) + asset_facs = usage_lookup.factories_for_usage({t.Semantics.PlaceholderBBox}) + return sorted(list(asset_facs), key=lambda x: x.__name__) + + +@pytest.mark.skip # TODO re-enable. Too many assets fail this +@pytest.mark.parametrize('fac', get_real_placeholder_facs()) +def test_real_placeholders(fac): + butil.clear_scene() + placeholder = fac(0).spawn_placeholder(0, loc=(0,0,0), rot=(0,0,0)) + asset = fac(0).spawn_asset(0) + assert np.abs(placeholder.dimensions.x - asset.dimensions.x) <= 0.05 * np.abs(asset.dimensions.x), "X dimension of placeholder not within 5 percent of mesh" + assert np.abs(placeholder.dimensions.y - asset.dimensions.y) <= 0.05 * np.abs(asset.dimensions.y), "Y dimension of placeholder not within 5 percent of mesh" + assert np.abs(placeholder.dimensions.z - asset.dimensions.z) <= 0.05 * np.abs(asset.dimensions.z), "Z dimension of placeholder not within 5 percent of mesh" + asset_min_corner = np.array(asset.bound_box[0]) # loXloYloZ https://blender.stackexchange.com/questions/32283/what-are-all-values-in-bound-box + asset_max_corner = np.array(asset.bound_box[6]) # hiXhiYhiZ + ph_min_corner = np.array(placeholder.bound_box[0]) + ph_max_corner = np.array(placeholder.bound_box[6]) + for i in range(3): + assert asset_min_corner[i] <= ph_max_corner[i] and asset_min_corner[i] >= ph_min_corner[i], "Asset not completely contained within placeholder" + assert asset_max_corner[i] <= ph_max_corner[i] and asset_max_corner[i] >= ph_min_corner[i], "Asset not completely contained within placeholder" + + +@pytest.mark.parametrize('fac', get_asset_facs()) +def test_generated_placeholders(fac): + butil.clear_scene() + fac(0).spawn_placeholder(0, loc=(0,0,0), rot=(0,0,0)) + fac(0).spawn_asset(0) + return + + + + + + + diff --git a/tests/constraints/test_constraint_bounding.py b/tests/constraints/test_constraint_bounding.py new file mode 100644 index 000000000..d65d552c2 --- /dev/null +++ b/tests/constraints/test_constraint_bounding.py @@ -0,0 +1,191 @@ +# 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: primary author +# - David Yan: bounding for inequalities / expressions + +from itertools import chain +from functools import partial + +from pprint import pprint +import pytest +import numpy as np + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r +) +from infinigen.core import tags as t + +def test_bound_eq(): + bound1 = r.Bound(r.Domain(set())) + bound2 = r.Bound(r.Domain(set())) + assert bound1 == bound2 + +def test_constant(): + + expr = cl.constant(1) * cl.constant(2) + cl.constant(3) < cl.constant(3) + assert r.is_constant(expr) + +def test_bounds_simple(): + + furniture = cl.tagged(cl.scene(), tags={t.Semantics.Furniture}) + count = cl.count(furniture) + + bounds = r.constraint_bounds(cl.in_range(count, 1, 5)) + assert bounds == [r.Bound(r.Domain({t.Semantics.Furniture}), 1, 5)] + + lower = [r.Bound(r.Domain({t.Semantics.Furniture}), low=1)] + upper = [r.Bound(r.Domain({t.Semantics.Furniture}), high=3)] + assert r.constraint_bounds(count < 4) == upper + assert r.constraint_bounds(count > 0) == lower + assert r.constraint_bounds(4 > count) == upper + assert r.constraint_bounds(0 < count) == lower + assert r.constraint_bounds(count <= 3) == upper + assert r.constraint_bounds(count >= 1) == lower + assert r.constraint_bounds(3 >= count) == upper + assert r.constraint_bounds(1 <= count) == lower + +@pytest.mark.skip # no longer supported for timebeing +def test_bounds_compound(): + chair = cl.tagged(cl.scene(), tags={t.Semantics.Chair}) + table = cl.tagged(cl.scene(), tags={t.Semantics.Table}) + + scene_state = [(r.Domain({t.Semantics.Table}), 4)] + cons = (cl.count(chair) < cl.count(table) * 3) * (cl.count(chair) > cl.count(table)) + + bounds = r.constraint_bounds(cons, scene_state) + + assert bounds == [ + r.Bound(r.Domain({t.Semantics.Chair}), high=11), + r.Bound(r.Domain({t.Semantics.Chair}), low=5) + ] + + bounds2 = r.constraint_bounds(cl.in_range(cl.count(chair), cl.count(table), cl.count(table) * 3), scene_state) + assert bounds2 == [r.Bound(r.Domain({t.Semantics.Chair}), 4, 12)] + +def test_bounds_and(): + + tags = {t.Semantics.Furniture} + furniture = cl.tagged(cl.scene(), tags=tags) + count = cl.count(furniture) + cons = (count < 5) * (count > 1) + bounds = r.constraint_bounds(cons) + + assert bounds == [ + r.Bound(r.Domain(tags), high=4), + r.Bound(r.Domain(tags), low=2), + ] + +def test_bounds_multilevel(): + + furniture = cl.tagged(cl.scene(), tags={t.Semantics.Furniture}) + sofa = cl.tagged(furniture, tags={t.Semantics.Seating}) + cons = cl.count(sofa) <= 3 + + assert r.constraint_bounds(cons) == [ + r.Bound(r.Domain({t.Semantics.Furniture, t.Semantics.Seating}), high=3) +] + +def test_bounds_arithmetic(): + + tags = {t.Semantics.Furniture} + furniture = cl.tagged(cl.scene(), tags=tags) + count = cl.count(furniture) + cons = cl.in_range(count * 2 + 2, 2, 10) + + bounds = r.constraint_bounds(cons) + assert bounds == [r.Bound(r.Domain(tags), low=0, high=4)] + +def test_bounds_domain_AnyRelation(): + + bedrooms = cl.scene().tagged({t.Semantics.Bedroom}) + beds = cl.scene().tagged({t.Semantics.Bed}) + + all_bedrooms_beds = bedrooms.all(lambda r: + cl.related_to(beds, r, cl.SupportedBy()) + .count().in_range(1, 2) + ) + + bd = r.Domain({t.Semantics.Bedroom}) + bed_in_room = r.Domain({t.Semantics.Bed}, relations=[(cl.SupportedBy(), bd)]) + + res = r.constraint_bounds(all_bedrooms_beds) + assert res == [r.Bound(bed_in_room, low=1, high=2)] + +def test_bounds_forall(): + + rooms = cl.scene().tagged(t.Semantics.Room) + furniture = cl.scene().tagged(t.Semantics.Furniture) + small_obj = cl.scene().tagged(t.Semantics.OfficeShelfItem) + rel = cl.SupportedBy() + + c = rooms.all(lambda room: ( + furniture.related_to(room, rel).count().in_range(1, 2) * + furniture.related_to(room, rel).all(lambda stor: + small_obj.related_to(stor, rel).count().in_range(5, 10) + ) + )) + + bounds = r.constraint_bounds(c) + + furn_room = r.Domain({t.Semantics.Furniture}, relations=[(rel, r.Domain({t.Semantics.Room}))]) + item_furn_room = r.Domain({t.Semantics.OfficeShelfItem}, relations=[(rel, furn_room)]) + + assert bounds == [ + r.Bound(furn_room, 1, 2), + r.Bound(item_furn_room, 5, 10), + ] + +def test_bound_implied_rel(): + + s = cl.scene() + against = cl.StableAgainst(set(), set()) + cons = ( + s.related_to(s, cl.AnyRelation()) + .related_to(s, against) + .count().in_range(1, 3) + ) + + bounds = r.constraint_bounds(cons) + + assert bounds == [ + r.Bound( + r.Domain(set(), [(against, r.Domain())]), + low=1, high=3 + ) + ] + + cons = ( + s.related_to(s, against) + .related_to(s, cl.AnyRelation()) + .count().in_range(1, 3) + ) + + bounds = r.constraint_bounds(cons) + + assert bounds == [ + r.Bound( + r.Domain(set(), [(against, r.Domain())]), + low=1, high=3 + ) + ] + +def test_bound_implied_rel_forall(): + + s = cl.scene() + + rel = cl.Touching() + + all_dom = r.Domain() + assert all_dom.implies(all_dom) + assert r.reldom_implies((rel, all_dom), (rel, all_dom)) + + small_obj = s.tagged(t.Semantics.OfficeShelfItem).related_to(s, rel) + cons = s.all(lambda tb: small_obj.related_to(tb, rel).count().in_range(1, 3)) + + bounds = r.constraint_bounds(cons) + + assert bounds[0].domain == r.Domain({t.Semantics.OfficeShelfItem}, [(rel, r.Domain())]) \ No newline at end of file diff --git a/tests/constraints/test_constraint_domain.py b/tests/constraints/test_constraint_domain.py new file mode 100644 index 000000000..de24559e4 --- /dev/null +++ b/tests/constraints/test_constraint_domain.py @@ -0,0 +1,199 @@ +# 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 logging + +from infinigen.core.constraints import ( + reasoning as r, + constraint_language as cl +) +from infinigen.core.constraints.constraint_language import Semantics +from infinigen.core import tags as t + +from infinigen_examples.util import constraint_util as cu + +def test_domain_obj(): + + furniture = r.Domain({t.Semantics.Furniture}) + + sofas = r.Domain({t.Semantics.Furniture, t.Semantics.Seating}) + assert sofas.implies(furniture) + assert not furniture.implies(sofas) + + furniture_in_livingroom = r.Domain({t.Semantics.Furniture}, relations=[(cl.SupportedBy(), r.Domain({t.Semantics.LivingRoom}))]) + assert not furniture.implies(furniture_in_livingroom) + assert furniture_in_livingroom.implies(furniture) + + furniture_in_bathroom = r.Domain({t.Semantics.Furniture}, relations=[(cl.SupportedBy(), r.Domain({t.Semantics.Bathroom}))]) + assert not furniture_in_livingroom.implies(furniture_in_bathroom) + assert not furniture_in_bathroom.implies(furniture_in_livingroom) + +def test_domain_implies_complex(): + + against_wall = cl.StableAgainst( + child_tags={t.Subpart.Back}, + parent_tags={t.Subpart.Wall, t.Subpart.Interior}, + margin=0 + ) + + d = r.Domain( + tags={Semantics.Storage}, + relations=[ + ( + against_wall, + r.Domain(tags={Semantics.Room}, relations=[]) + ) + ] + ) + + assert d.implies(d) + assert not r.Domain({t.Semantics.Storage}).implies(d) + assert not r.Domain(set(), d.relations).implies(d) + assert d.implies(r.Domain({t.Semantics.Storage})) + assert d.implies(r.Domain(set(), d.relations)) + + # "storage related any way to any thing" is less specific + generalize_relation = r.Domain( + {t.Semantics.Storage}, + relations=[(cl.AnyRelation(), r.Domain())]) + assert d.implies(generalize_relation) + assert not generalize_relation.implies(d) + + # "storage related any way but specifically to bedroom" + # is both more and less specific so not a subset either way + different_relation = r.Domain( + {t.Semantics.Storage}, + relations=[(cl.AnyRelation(), r.Domain({t.Semantics.Room, t.Semantics.Bedroom}))] + ) + assert not d.implies(different_relation) + assert not different_relation.implies(d) + + # "storage against a bedroom wall" is more specific + against_bedroom_wall = r.Domain( + {t.Semantics.Storage}, + relations=[(against_wall, r.Domain({t.Semantics.Room, t.Semantics.Bedroom}))] + ) + assert against_bedroom_wall.implies(d) + assert not d.implies(against_bedroom_wall) + +def test_domain_var_substitute(): + + var = t.Variable('x') + start = r.Domain({t.Subpart.Interior, var}, relations=[(cl.AnyRelation(), r.Domain())]) + subfor = r.Domain({t.Semantics.Room, t.Semantics.Bedroom}, relations=[(cl.Touching(), r.Domain({t.Semantics.Furniture}))]) + + assert r.domain_tag_substitute(start, var, subfor) == r.Domain( + {t.Semantics.Room, t.Semantics.Bedroom, t.Subpart.Interior}, + relations=[ + (cl.Touching(), r.Domain({t.Semantics.Furniture})) + ] + ) + + start2 = r.Domain({t.Subpart.Interior, var}, relations=[(cl.AnyRelation(), r.Domain({t.Semantics.Lighting}))]) + assert r.domain_tag_substitute(start2, var, subfor) == r.Domain( + {t.Semantics.Room, t.Semantics.Bedroom, t.Subpart.Interior}, + relations=[ + (cl.AnyRelation(), r.Domain({t.Semantics.Lighting})), # not implied so gets kept + (cl.Touching(), r.Domain({t.Semantics.Furniture})) + ] + ) + +def test_domain_intersect_tags(): + + obj_types = {Semantics.Object, Semantics.Room, Semantics.Cutter} + obj = cl.scene()[Semantics.Object].excludes(obj_types) + room = cl.scene()[Semantics.Room].excludes(obj_types) + + ld = r.constraint_domain(obj) + assert -Semantics.Room in ld.tags + + md = r.constraint_domain(room) + assert -Semantics.Object in md.tags + + assert not ld.intersects(md) + assert not ld.intersects(md) + +def test_domain_construction_complex(): + + dom = r.Domain() + dom.add_relation(cl.AnyRelation(), r.Domain({t.Semantics.Object}, [])) + dom.add_relation(cl.StableAgainst(), r.Domain({t.Semantics.Object, t.Variable('room')}, [])) + dom.add_relation(-cl.AnyRelation(), r.Domain({t.Semantics.Room}, [])) + + assert dom.relations[0] == (cl.StableAgainst(), r.Domain({t.Semantics.Object, t.Variable('room')}, [])) + assert dom.relations[1] == (-cl.AnyRelation(), r.Domain({t.Semantics.Room}, [])) + assert len(dom.relations) == 2 + +def test_domain_construction_complex_2(): + + rd1 = (cl.AnyRelation(), r.Domain({t.Semantics.Room, t.Semantics.DiningRoom})) + rd2 = (cl.StableAgainst(), r.Domain({t.Semantics.Room})) + + assert r.reldom_intersects(rd1, rd2) + + dom = r.Domain() + dom.add_relation(*rd1) + dom.add_relation(*rd2) + + print("DOM RESULT", dom) + assert dom.relations == [ + (cl.StableAgainst(), r.Domain({t.Semantics.Room, t.Semantics.DiningRoom})) + ] + +def test_domain_satisfies(): + + a = r.Domain({t.Semantics.Object, -t.Semantics.Room}) + b = r.Domain({t.Semantics.Object, t.Semantics.Room}) + assert not b.satisfies(a) + + b = r.Domain({t.Semantics.Object, -t.Semantics.Room}) + assert b.satisfies(a) + + a.add_relation(cl.StableAgainst(), r.Domain({t.Semantics.Room})) + assert not b.satisfies(a) + + b.add_relation(cl.StableAgainst(), r.Domain({t.Semantics.Room, t.Semantics.DiningRoom})) + assert b.satisfies(a) + + a.add_relation(-cl.AnyRelation(), r.Domain({t.Semantics.Object})) + assert b.satisfies(a) + + b.add_relation(cl.StableAgainst(), r.Domain({t.Semantics.Object})) + assert not b.satisfies(a) + +def test_domain_satisfies_2(): + + res_dom = r.Domain( + {Semantics.Object, Semantics.Storage, -Semantics.Room}, [ + ( + cl.StableAgainst( + {t.Subpart.Bottom, -t.Subpart.Top, -t.Subpart.Back, -t.Subpart.Front}, + {t.Subpart.Visible, t.Subpart.SupportSurface, -t.Subpart.Ceiling, -t.Subpart.Wall} + ), + r.Domain({Semantics.DiningRoom, Semantics.Room, -Semantics.Object}, []) + ), + ( + -cl.AnyRelation(), + r.Domain({Semantics.Object, -Semantics.Room}, []) + ) + ] + ) + + filter_dom = r.Domain( + {Semantics.Object, -Semantics.Room}, + [ + ( + cl.StableAgainst( + {}, + {t.Subpart.SupportSurface, t.Subpart.Visible, -t.Subpart.Ceiling, -t.Subpart.Wall} + ), + r.Domain({Semantics.DiningRoom, Semantics.Room, -Semantics.Object}, []) + ), + (-cl.AnyRelation(), r.Domain({Semantics.Object, -Semantics.Room}, [])) + ] + ) + + assert res_dom.satisfies(filter_dom) \ No newline at end of file diff --git a/tests/constraints/test_constraint_language.py b/tests/constraints/test_constraint_language.py new file mode 100644 index 000000000..e12f1ca70 --- /dev/null +++ b/tests/constraints/test_constraint_language.py @@ -0,0 +1,43 @@ +# 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 + +from infinigen_examples import indoor_constraint_examples as ex +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r +) + +def test_residential(): + + cons = ex.home_constraints() + + assert isinstance(cons, cl.Node) + assert isinstance(repr(cons), str) + +def test_operators_simple(): + + val = cl.constant(value=1) + assert hasattr(val, '__add__') + + comp = cl.constant(1) + cl.constant(2) + assert isinstance(comp, cl.Expression) + val = comp() + assert val == 3, val + + comp = cl.constant(1) < cl.constant(2) + assert isinstance(comp, cl.Expression) + assert comp() is True + +def test_operators_cast(): + + comp = cl.constant(1) + 2 + assert isinstance(comp, cl.ScalarOperatorExpression) + assert comp() == 3 + +def test_associative_construction(): + + comp = cl.constant(1) + cl.constant(2) + cl.constant(3) + assert len(list(comp.traverse())) == 4 # 1 for additions, 3 for constants \ No newline at end of file diff --git a/tests/constraints/test_constraint_relations.py b/tests/constraints/test_constraint_relations.py new file mode 100644 index 000000000..f1ddf37ee --- /dev/null +++ b/tests/constraints/test_constraint_relations.py @@ -0,0 +1,96 @@ +# 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 + +from pprint import pprint + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r +) +from infinigen.core import tags as t +from infinigen_examples.indoor_constraint_examples import home_constraints + +def test_relation_implies_trivial(): + + assert not cl.StableAgainst(set(), set()).implies(cl.Touching()) + + sf = cl.SupportedBy({t.Subpart.SupportSurface}) + sfi = cl.SupportedBy({t.Subpart.SupportSurface, t.Subpart.Visible}) + + assert sf.implies(sf) + assert sfi.implies(sf) + assert sf.implies(cl.AnyRelation()) + assert sfi.implies(cl.AnyRelation()) + assert not cl.AnyRelation().implies(sf) + assert not cl.AnyRelation().implies(sfi) + +def require_intersects(a: cl.Relation, b: cl.Relation, truth): + assert a.intersects(b) == truth + assert b.intersects(a) == truth + +example = cl.StableAgainst( + {t.Subpart.Top, -t.Subpart.Bottom}, + {t.Semantics.Object, -t.Subpart.Top} +) + +def test_relations_intersects_unrestricted(): + + unrestricted = cl.AnyRelation() + require_intersects(example, unrestricted, True) + require_intersects(example, -unrestricted, False) + require_intersects(-example, unrestricted, True) + +def test_relation_intersects_mismatched_type(): + mismatch_type = cl.Touching(example.child_tags, example.parent_tags) + require_intersects(example, mismatch_type, False) + require_intersects(example, -mismatch_type, True) + require_intersects(-example, mismatch_type, True) + +def test_relation_intersects_superset(): + superset = cl.StableAgainst({t.Subpart.Top}, {t.Semantics.Object}) + require_intersects(example, superset, True) + require_intersects(example, -superset, True) # Top-Bot,Obj-Top AND NOT(Top,Obj) permits Top+Bot_Obj+Top + require_intersects(-example, superset, False) # Top,Obj AND NOT(Top-Bot,Obj-Top) False + +def test_relation_intersects_subset(): + subset = cl.StableAgainst( + {t.Subpart.Top, -t.Subpart.Bottom, t.Subpart.SupportSurface}, + {t.Semantics.Object, -t.Subpart.Top, -t.Subpart.Side} + ) + require_intersects(example, subset, True) + require_intersects(example, -subset, False) # Top-Bot,Obj-Top AND NOT Top-Bot+Sup,Obj-Top-Side + require_intersects(-example, subset, True) # Top-Bot+Sup,Obj-Top-Side AND NOT Top-Bot,Obj-Top + +def test_relation_intersects_intersecting(): + inter = cl.StableAgainst({t.Subpart.Top, t.Subpart.Visible}, {t.Semantics.Object, t.Semantics.Furniture}) + require_intersects(example, inter, True) + require_intersects(example, -inter, True) # Top-Bot_Obj-Top AND NOT Top+Vis_Obj+Furn. Yes, Top-Bot-Vis_Obj-Top-Furn + require_intersects(-example, inter, True) # Top+Vis_Obj+Furn AND NOT Top-Bot_Obj-Top. + +def test_relation_intersects_contradict_child(): + contradict_child = cl.StableAgainst({t.Subpart.Top, t.Subpart.Bottom}, {t.Semantics.Object}) + require_intersects(example, contradict_child, False) + require_intersects(example, -contradict_child, True) + require_intersects(-example, contradict_child, True) # Top+Bot,Obj AND NOT Top-Bot,Obj-Top = Top+Bot,Obj? + +def test_relation_intersects_contradict_parent(): + contradict_parent = cl.StableAgainst({t.Subpart.Top}, {t.Semantics.Object, t.Subpart.Top}) + require_intersects(example, contradict_parent, False) + require_intersects(example, -contradict_parent, True) + require_intersects(-example, contradict_parent, True) + +def test_relation_difference(): + + assert t.difference( + {t.Semantics.Object, -t.Subpart.Top}, + {t.Semantics.Object, t.Subpart.Bottom} + ) == {t.Semantics.Object, -t.Subpart.Top, -t.Subpart.Bottom} + + refine = cl.StableAgainst(set(), {t.Semantics.Object, t.Subpart.Bottom}) + assert example.difference(refine) == cl.StableAgainst( + {t.Subpart.Top, -t.Subpart.Bottom}, + {t.Semantics.Object, -t.Subpart.Top, -t.Subpart.Bottom} + ) diff --git a/tests/constraints/test_reldom.py b/tests/constraints/test_reldom.py new file mode 100644 index 000000000..202364f52 --- /dev/null +++ b/tests/constraints/test_reldom.py @@ -0,0 +1,77 @@ +# 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 copy +import pytest + +from pprint import pprint + +from infinigen.core import tags as t + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r, +) + +def test_reldom_compatible_floorwall(): + + room = r.Domain({t.Semantics.Room, -t.Semantics.Object}, []) + + nofloorrel = ( + -cl.StableAgainst({}, {t.Subpart.SupportSurface, t.Subpart.Visible, -t.Subpart.Ceiling, -t.Subpart.Wall}), + room + ) + + against = cl.StableAgainst( + {t.Subpart.Back, -t.Subpart.Top, -t.Subpart.Front}, + {t.Subpart.Visible, t.Subpart.Wall, -t.Subpart.SupportSurface, -t.Subpart.Ceiling} + ) + wallrel = (against, room) + + assert r.reldom_compatible(nofloorrel, wallrel) + assert r.reldom_compatible(wallrel, nofloorrel) + +def test_reldom_compatible_negation(): + + nofloorrel = ( + -cl.StableAgainst({}, {t.Subpart.SupportSurface, t.Subpart.Visible, -t.Subpart.Ceiling, -t.Subpart.Wall}), + r.Domain({t.Semantics.Room, -t.Semantics.Object}, []) + ) + + on = cl.StableAgainst( + {t.Subpart.Bottom, -t.Subpart.Front, -t.Subpart.Top, -t.Subpart.Back}, + {t.Subpart.SupportSurface, t.Subpart.Visible, -t.Subpart.Wall, -t.Subpart.Ceiling} + ) + specific_floorrel = (on, r.Domain({t.Semantics.Room, -t.Semantics.Object}, [])) + + assert r.reldom_compatible(specific_floorrel, specific_floorrel) + assert not r.reldom_compatible(nofloorrel, specific_floorrel) + assert not r.reldom_compatible(specific_floorrel, nofloorrel) + +def test_reldom_intersects(): + + onroom = ( + cl.StableAgainst( + {t.Subpart.Bottom, -t.Subpart.Front, -t.Subpart.Top, -t.Subpart.Back}, + {t.Subpart.SupportSurface, t.Subpart.Visible, -t.Subpart.Wall, -t.Subpart.Ceiling} + ), + r.Domain({t.Semantics.Room, -t.Semantics.Object}, []) + ) + + onlivingroom = ( + cl.StableAgainst( + {t.Subpart.Bottom, -t.Subpart.Front, -t.Subpart.Top, -t.Subpart.Back}, + {t.Subpart.SupportSurface, t.Subpart.Visible, -t.Subpart.Wall, -t.Subpart.Ceiling} + ), + r.Domain({t.Semantics.LivingRoom, t.Semantics.Room, -t.Semantics.Object, -t.Semantics.Bedroom, -t.Semantics.DiningRoom}, []) + ) + + assert r.reldom_intersects(onroom, onlivingroom) + +def test_reldom_negative_contradict(): + + a = (-cl.AnyRelation(), r.Domain({t.Semantics.Object, -t.Semantics.Room}, [])) + assert r.reldom_compatible(a, a) \ No newline at end of file diff --git a/tests/constraints/test_tags.py b/tests/constraints/test_tags.py new file mode 100644 index 000000000..5d11f6867 --- /dev/null +++ b/tests/constraints/test_tags.py @@ -0,0 +1,15 @@ +# 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 + +from infinigen.core import tags as t + +def test_implies(): + + assert t.implies(set(), set()) + assert t.implies({t.Subpart.Wall}, {t.Subpart.Wall}) + assert t.implies({t.Subpart.Wall}, set()) + + assert t.implies({t.Semantics.Room, t.Variable('room')}, {t.Semantics.Room}) \ No newline at end of file diff --git a/tests/constraints/test_tagset_operations.py b/tests/constraints/test_tagset_operations.py new file mode 100644 index 000000000..a35eb1a63 --- /dev/null +++ b/tests/constraints/test_tagset_operations.py @@ -0,0 +1,43 @@ +# 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 + +from infinigen.core.constraints import constraint_language as cl +from infinigen.core import tags as t + +def test_tagset_operations(): + + example = {t.Subpart.Side, -t.Subpart.Bottom} + assert t.implies(example, example) + assert not t.contradiction(example) + + superset_pos = {t.Subpart.Side} + assert not t.implies(superset_pos, example) + assert t.implies(example, superset_pos) + + superset_neg = {-t.Subpart.Bottom} + assert t.implies(example, superset_neg) + assert not t.implies(superset_neg, example) + + subset_pos = {t.Subpart.Side, t.Subpart.Front, -t.Subpart.Bottom} + assert not t.implies(example, subset_pos) + assert t.implies(subset_pos, example) + + subset_neg = [t.Subpart.Side, -t.Subpart.Bottom, -t.Subpart.Top] + assert not t.implies(example, subset_neg) + assert t.implies(subset_neg, example) + + intersect_pos = {t.Subpart.Side, t.Subpart.Front} + assert not t.implies(example, intersect_pos) + assert not t.implies(intersect_pos, example) + assert not t.contradiction(example.union(intersect_pos)) + + intersect_neg = {t.Subpart.Side, -t.Subpart.Back} + assert not t.implies(example, intersect_neg) + assert not t.implies(intersect_neg, example) + assert not t.contradiction(example.union(intersect_neg)) + + assert not t.implies({t.Subpart.Top, -t.Subpart.Bottom}, {t.Subpart.Top, t.Subpart.Bottom}) + \ No newline at end of file diff --git a/tests/test_execute_tasks.py b/tests/core/test_execute_tasks.py similarity index 60% rename from tests/test_execute_tasks.py rename to tests/core/test_execute_tasks.py index 7b3878fd9..0ddb01e9c 100644 --- a/tests/test_execute_tasks.py +++ b/tests/core/test_execute_tasks.py @@ -1,3 +1,9 @@ +# 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 + from pathlib import Path from types import SimpleNamespace import logging @@ -12,28 +18,11 @@ from infinigen.core.placement import camera from infinigen.core import init -from utils import setup_gin - -''' -@pytest.mark.order('last') -def test_noassets_noterrain(): - args = SimpleNamespace( - input_folder=None, - output_folder='/tmp/test_noassets_noterrain', - seed="0", - task='coarse', - configs=['desert.gin', 'simple.gin', 'no_assets.gin'], - overrides=['compose_scene.generate_resolution = (480, 270)'], - task_uniqname='coarse', - loglevel=logging.DEBUG - ) - generate_nature.main(args) -''' +from infinigen_examples.util.test_utils import setup_gin -@pytest.mark.ci def test_compose_cube(): - setup_gin() + setup_gin('infinigen_examples/configs_nature') def compose_cube(output_folder, scene_seed, **params): camera_rigs = camera.spawn_camera_rigs() @@ -51,4 +40,3 @@ def compose_cube(output_folder, scene_seed, **params): frame_range=[0, 100], camera_id=(0, 0) ) - diff --git a/tests/core/test_gins.py b/tests/core/test_gins.py new file mode 100644 index 000000000..8a26da057 --- /dev/null +++ b/tests/core/test_gins.py @@ -0,0 +1,39 @@ +# 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 + +from pathlib import Path +from types import SimpleNamespace +import logging +import importlib + +import pytest +import bpy +import gin + +from infinigen_examples import generate_nature +from infinigen.core import execute_tasks +from infinigen.core.placement import camera +from infinigen.core import init + +from infinigen_examples.util.test_utils import setup_gin + +nature_folder = 'infinigen_examples/configs_nature' +nature_gins = [p.name for p in (init.repo_root()/nature_folder).glob('**/*.gin')] + +@pytest.mark.parametrize('extra_gin', sorted(nature_gins)) +def test_gins_load_nature(extra_gin): + # gin must successfully load the config without crashing + # common failures are misspellings of config fields, renamed functions, etc + setup_gin(nature_folder, configs=[extra_gin]) + +indoor_folder = 'infinigen_examples/configs_indoor' +indoor_gins = [p.name for p in (init.repo_root()/indoor_folder).glob('**/*.gin')] + +@pytest.mark.parametrize('extra_gin', sorted(indoor_gins)) +def test_gins_load_indoor(extra_gin): + # gin must successfully load the config without crashing + # common failures are misspellings of config fields, renamed functions, etc + setup_gin(indoor_folder, configs=[extra_gin]) diff --git a/tests/core/test_tagging.py b/tests/core/test_tagging.py new file mode 100644 index 000000000..bfe140170 --- /dev/null +++ b/tests/core/test_tagging.py @@ -0,0 +1,97 @@ +# 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 + +from infinigen.core.util import blender as butil +from infinigen.core import tagging, surface, tags as t + +import numpy as np +import bpy + +def test_tagging_basic(): + + tagging.tag_system.clear() + butil.clear_scene() + + cube = butil.spawn_cube() + tag = t.StringTag('cubey_tag') + tag_name = tag.desc + tagging.tag_object(cube, tag) + + assert len([n for n in cube.data.attributes.keys() if n.startswith(tagging.PREFIX)]) == 0 + assert list(tagging.tag_system.tag_dict.keys()) == [tag_name] + + tagint_attr = cube.data.attributes.get(tagging.COMBINED_ATTR_NAME) + assert tagint_attr is not None + assert tagint_attr.domain == 'FACE' + + tagint_vals = surface.read_attr_data(cube, tagging.COMBINED_ATTR_NAME, domain='FACE') + + cubey_tag_int = tagging.tag_system.tag_dict.get(tag_name) + assert cubey_tag_int == 1 + + n_poly = len(cube.data.polygons) + assert len(tagint_vals) == n_poly + assert np.all(tagint_vals == cubey_tag_int) + assert tagging.tagged_face_mask(cube, tag).all() + + halftag = t.StringTag('last_half') + mask = np.arange(n_poly) >= (n_poly // 2) + tagging.tag_object(cube, halftag, mask) + + combined_half_name = tag.desc + '.' + halftag.desc + assert list(tagging.tag_system.tag_dict.keys()) == [tag_name, combined_half_name] + + assert np.all(tagging.tagged_face_mask(cube, halftag) == mask) + assert np.all(tagging.tagged_face_mask(cube, {tag, halftag}) == mask) + assert np.all(tagging.tagged_face_mask(cube, tag) == np.ones(n_poly, dtype=bool)) + + with butil.ViewportMode(cube, mode='EDIT'): + butil.select(cube) + bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + + assert tagging.tagged_face_mask(cube, halftag).sum() == 2 * mask.sum() + +def get_canonical_tag_cube(): + + cube = butil.spawn_cube() + + with butil.ViewportMode(cube, mode='EDIT'): + butil.select(cube) + bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + + tagging.tag_canonical_surfaces(cube) + return cube + +def test_tag_canonical(): + + tagging.tag_system.clear() + butil.clear_scene() + cube = get_canonical_tag_cube() + + for tag in tagging.CANONICAL_TAGS: + mask = tagging.tagged_face_mask(cube, tag) + assert mask.sum() == 2 # expect 2 triangles forming every side of the cube + + idx1, idx2 = np.where(mask)[0] + norm1 = cube.data.polygons[idx1].normal + norm2 = cube.data.polygons[idx2].normal + assert norm1 == norm2 + +def test_tag_canonical_negated(): + + tagging.tag_system.clear() + butil.clear_scene() + cube = get_canonical_tag_cube() + + assert len(cube.data.polygons) == 12 + + assert tagging.tagged_face_mask(cube, t.Subpart.Top).sum() == 2 + + all_but_top = tagging.tagged_face_mask(cube, -t.Subpart.Top) + assert all_but_top.sum() == 10 # 4*2 side triangles, 2 bottom triangles + + side = tagging.tagged_face_mask(cube, {-t.Subpart.Top, -t.Subpart.Bottom}) + assert side.sum() == 8 # 4 sides, 2 triangles \ No newline at end of file diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py deleted file mode 100644 index 90d43591f..000000000 --- a/tests/integration/conftest.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest - -def pytest_addoption(parser): - parser.addoption( "--dir",action="store") - parser.addoption( "--num",action="store") - parser.addoption( "--days",action="store") - -@pytest.fixture() -def dir(request): - return request.config.getoption("--dir") - -@pytest.fixture() -def num(request): - return request.config.getoption("--num") - -@pytest.fixture() -def days(request): - return request.config.getoption("--days") \ No newline at end of file diff --git a/tests/integration/manual_integration_check.py b/tests/integration/manual_integration_check.py index 2d36953fe..0cce0f720 100644 --- a/tests/integration/manual_integration_check.py +++ b/tests/integration/manual_integration_check.py @@ -62,14 +62,24 @@ def __str__(self): all_data = defaultdict(dict) -def sizeof_fmt(num, suffix="B"): #https://stackoverflow.com/a/1094933 +''' +The following function s attributed to Sridhar Ratnakumar from Stack Overflow at https://stackoverflow.com/a/1094933 +and is licensed under CC-BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/deed.en#ref-appropriate-credit). +David Yan used this code WITHOUT modification. +''' +def sizeof_fmt(num, suffix="B"): for unit in ("", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"): if abs(num) < 1024.0: return f"{num:3.1f} {unit}{suffix}" num /= 1024.0 return f"{num:.1f}Yi{suffix}" -def td_to_str(td): #https://stackoverflow.com/a/64662985 +''' +The following function s attributed to FObersteiner from Stack Overflow at https://stackoverflow.com/a/64662985 +and is licensed under CC-BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/deed.en#ref-appropriate-credit). +David Yan used this code WITHOUT modification. +''' +def td_to_str(td): """ convert a timedelta object td to a string in HH:MM:SS format. """ diff --git a/tests/list_displaced_materials.txt b/tests/list_displaced_materials.txt new file mode 100644 index 000000000..4444025e9 --- /dev/null +++ b/tests/list_displaced_materials.txt @@ -0,0 +1,7 @@ +infinigen.assets.materials.leather_and_fabrics.fabric +infinigen.assets.materials.leather_and_fabrics.leather +infinigen.assets.materials.metal.grained_and_polished_metal +infinigen.assets.materials.metal.hammered_metal +infinigen.assets.materials.stone_and_concrete.concrete +infinigen.assets.materials.woods.tiled_wood +infinigen.assets.materials.plastics.plastic_rough diff --git a/tests/material_balls.txt b/tests/material_balls.txt new file mode 100644 index 000000000..acbcbb941 --- /dev/null +++ b/tests/material_balls.txt @@ -0,0 +1,35 @@ +infinigen.assets.materials.brushed_metal +infinigen.assets.materials.galvanized_metal +infinigen.assets.materials.grained_and_polished_metal +infinigen.assets.materials.hammered_metal +infinigen.assets.materials.metal_basic + +infinigen.assets.materials.fabric +infinigen.assets.materials.sofa_fabric +infinigen.assets.materials.leather +infinigen.assets.materials.plastic +infinigen.assets.materials.rug + +infinigen.assets.materials.wood_new +infinigen.assets.materials.tiled_wood +infinigen.assets.materials.wood_new +infinigen.assets.materials.wood +infinigen.assets.materials.hardwood_floor + +infinigen.assets.materials.tile_hexagon +infinigen.assets.materials.tile_square +infinigen.assets.materials.ceramic +infinigen.assets.materials.glass +infinigen.assets.materials.mirror + +infinigen.assets.materials.marble_regular +infinigen.assets.materials.marble_voronoi +infinigen.assets.materials.concrete +infinigen.assets.materials.plaster +infinigen.assets.materials.brick + +infinigen.assets.materials.text_no_barcode +infinigen.assets.materials.text +infinigen.assets.materials.art +infinigen.assets.materials.ArtRug +infinigen.assets.materials.ArtFabric diff --git a/tests/solver/test_asset_surfaces.py b/tests/solver/test_asset_surfaces.py new file mode 100644 index 000000000..94b7c0c3a --- /dev/null +++ b/tests/solver/test_asset_surfaces.py @@ -0,0 +1,61 @@ +# 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 bpy +import pytest + +from infinigen.core.constraints import ( + usage_lookup, + constraint_language as cl +) + +from infinigen.core.constraints.example_solver.geometry import dof + +from infinigen_examples.indoor_constraint_examples import home_asset_usage + +from infinigen.core.util import blender as butil +from infinigen.core import tagging, tags as t + +def test_canonical_planes_real_placeholders(): + + used_as = home_asset_usage() + usage_lookup.initialize_from_dict(used_as) + + pholder_facs = usage_lookup.factories_for_usage({t.Semantics.RealPlaceholder}) + asset_facs = usage_lookup.factories_for_usage({t.Semantics.AssetAsPlaceholder}) + test_facs = pholder_facs.union(asset_facs) + + test_facs.intersection_update( + usage_lookup.factories_for_usage({t.Semantics.Storage}) + .union(usage_lookup.factories_for_usage({t.Semantics.Seating})) + ) + + for fac in test_facs: + butil.clear_scene() + + if fac in pholder_facs: + obj = fac(0).spawn_placeholder(0, loc=(0,0,0), rot=(0,0,0)) + elif fac in asset_facs: + obj = fac(0).spawn_asset(0, loc=(0,0,0), rot=(0,0,0)) + else: + raise ValueError() + + with butil.ViewportMode(obj, mode='EDIT'): + butil.select(obj) + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + + tagging.tag_canonical_surfaces(obj) + + obj_tags = tagging.union_object_tags(obj) + + for tag in [t.Subpart.Back, t.Subpart.Bottom]:#, t.Subpart.SupportSurface]: + mask = tagging.tagged_face_mask(obj, {tag}) + if mask.sum() == 0: + obj_tags = tagging.union_object_tags(obj) + raise ValueError( + f'{obj.name=} has nothing tagged for {tag=}. {obj_tags=}' + ) \ No newline at end of file diff --git a/tests/solver/test_constraint_evaluator.py b/tests/solver/test_constraint_evaluator.py new file mode 100644 index 000000000..567ac6450 --- /dev/null +++ b/tests/solver/test_constraint_evaluator.py @@ -0,0 +1,980 @@ +# 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: Karhan Kayan +from itertools import chain +from functools import partial +from time import time +import sys +import os + +import bpy +import pytest +import numpy as np +from mathutils import Vector + +from infinigen.core.constraints import ( + usage_lookup, + example_solver as solver, + constraint_language as cl +) +from infinigen.core.constraints.evaluator import evaluate +from infinigen.core.constraints.evaluator.node_impl import node_impls +from infinigen.core.constraints.example_solver.state_def import state_from_dummy_scene, State, ObjectState +from infinigen.core.util import blender as butil +from infinigen.core.constraints.example_solver.propose_discrete import lookup_generator +from infinigen.core import tagging, tags as t +import infinigen.core.constraints.example_solver.moves.addition as addition + +from infinigen.assets.tables.dining_table import TableDiningFactory +from infinigen.assets.seating.chairs import ChairFactory +from infinigen.assets.utils.bbox_from_mesh import bbox_mesh_from_hipoly + +from infinigen_examples.indoor_asset_semantics import home_asset_usage +from infinigen_examples.indoor_constraint_examples import home_constraints + +def test_home_constraints_implemented(): + butil.clear_scene() + + cons = home_constraints() + + for node in cons.traverse(): + if node.__class__ in evaluate.SPECIAL_CASE_NODES: + continue + assert node.__class__ in node_impls + +def make_chair_table(): + butil.clear_scene() + col = butil.get_collection("indoor_scene_test") + chairs = butil.get_collection("chair") + tables = butil.get_collection("table") + col.children.link(chairs) + col.children.link(tables) + + chair = butil.spawn_cube(size=2, location=(0, 0, 0), name='chair1') + butil.put_in_collection(chair, chairs) + + table = butil.spawn_cube(size=2, location=(3, 0, 0), name='table1') + butil.put_in_collection(table, tables) + + return col + +def test_parse_scene(): + butil.clear_scene() + state = state_from_dummy_scene(make_chair_table()) + + assert state.objs['chair1'].tags == {t.Semantics.Chair, t.SpecificObject('chair1')} + assert state.objs['table1'].tags == {t.Semantics.Table, t.SpecificObject('table1')} + +def test_eval_node(): + butil.clear_scene() + + state = state_from_dummy_scene(make_chair_table()) + eval = partial(evaluate.evaluate_node, state=state) + + scene = cl.scene() + assert eval(scene) == {"chair1", "table1"} + + assert eval(scene.tagged({t.Semantics.Chair})) == {'chair1'} + assert eval(scene.tagged({t.Semantics.Seating})) == set() + assert eval(scene.tagged({t.Semantics.Chair}).count()) == 1 + +def test_min_dist(): + butil.clear_scene() + + col = make_chair_table() + state = state_from_dummy_scene(col) + + constraints = [] + score_terms = [] + + scene = cl.scene() + chair = cl.tagged(scene, {t.Semantics.Chair}) + table = cl.tagged(scene, {t.Semantics.Table}) + sofa = cl.tagged(scene, {t.Semantics.Seating}) + + score_terms += [cl.distance(chair, table)] + problem = cl.Problem(constraints, score_terms) + assert np.isclose(evaluate.evaluate_problem(problem, state).loss(), 1) + + score_terms = [] + score_terms += [cl.distance(table, chair) + 1] + problem = cl.Problem(constraints, score_terms) + assert np.isclose(evaluate.evaluate_problem(problem, state).loss(), 2) + + s = butil.spawn_cube(size=2, location=(-4, 0, 0), name="sofa1") + sofas = butil.get_collection("seating") + col.children.link(sofas) + butil.put_in_collection(s, sofas) + + score_terms = [] + score_terms += [cl.distance(chair, sofa) + cl.distance(chair, table)] + problem = cl.Problem(constraints, score_terms) + state = state_from_dummy_scene(col) + assert np.isclose(evaluate.evaluate_problem(problem, state).loss(), 3) + + butil.clear_scene() + +def test_accessibility_monotonicity(): + butil.clear_scene() + scores = [] + for dist in [1,1.5,2,2.5]: + butil.clear_scene() + obj_states = {} + col = butil.get_collection("indoor_scene_test") + chairs = butil.get_collection("chair") + tables = butil.get_collection("table") + col.children.link(chairs) + col.children.link(tables) + + chair = butil.spawn_cube(size=2, location=(0, 0, 0), name='chair1') + chair.rotation_euler = (0, 0, 0.1) + butil.put_in_collection(chair, chairs) + + table = butil.spawn_cube(size=2, location=(2+dist, 0, 0), name='table1') + butil.put_in_collection(table, tables) + + obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) + obj_states[table.name] = ObjectState(table, tags= {t.Semantics.Table}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + tagging.tag_canonical_surfaces(table) + + constraints = [] + score_terms = [] + scene = cl.scene() + chair = cl.tagged(scene, {t.Semantics.Chair}) + table = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.accessibility_cost(chair, table, dist=3)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + + print("nonaccessibility scores", scores) + + assert np.all(np.diff(scores) < 0) + +def test_accessibility_side(): + butil.clear_scene() + obj_states = {} + col = butil.get_collection("indoor_scene_test") + chairs = butil.get_collection("chair") + tables = butil.get_collection("table") + col.children.link(chairs) + col.children.link(tables) + + chair = butil.spawn_cube(size=2, location=(0, 0, 0), name='chair1') + butil.put_in_collection(chair, chairs) + + table = butil.spawn_cube(size=2, location=(0, 2, 0), name='table1') + butil.put_in_collection(table, tables) + + obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) + obj_states[table.name] = ObjectState(table, tags= {t.Semantics.Table}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + tagging.tag_canonical_surfaces(table) + + constraints = [] + score_terms = [] + scene = cl.scene() + chair = cl.tagged(scene, {t.Semantics.Chair}) + table = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.accessibility_cost(chair, table)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + print("nonaccessibility scores", res) + assert np.isclose(res, 0) + +def test_accessibility_angle(): + butil.clear_scene() + scores = [] + for angle in [0, np.pi/4, np.pi/2, np.pi]: + butil.clear_scene() + obj_states = {} + + chair = butil.spawn_cube(size=2, location=(0, 0, 0), name='chair1') + + table = butil.spawn_sphere(radius = 1, location=(4*np.cos(angle), 4*np.sin(angle), 0), name='table1') + print(table.location) + + obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) + obj_states[table.name] = ObjectState(table, tags= {t.Semantics.Table}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + tagging.tag_canonical_surfaces(table) + + constraints = [] + score_terms = [] + scene = cl.scene() + chair = cl.tagged(scene, {t.Semantics.Chair}) + table = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.accessibility_cost(chair, table)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + print("nonaccessibility scores", scores) + assert scores == sorted(scores, reverse=True) + +def test_accessibility_volume(): + butil.clear_scene() + scores = [] + for volume in [1, 2, 3, 4]: + butil.clear_scene() + obj_states = {} + + chair = butil.spawn_cube(size=2, location=(0, 0, 0), name='chair1') + table = butil.spawn_sphere(radius=volume, location=(6, 0, 0), name='table1') + + obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) + obj_states[table.name] = ObjectState(table, tags= {t.Semantics.Table}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + tagging.tag_canonical_surfaces(table) + + constraints = [] + score_terms = [] + scene = cl.scene() + chair = cl.tagged(scene, {t.Semantics.Chair}) + table = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.accessibility_cost(chair, table)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + print("nonaccessibility scores", scores) + assert scores == sorted(scores) + +# def test_accessibility_speed(): +# scores = [] +# butil.clear_scene() +# obj_states = {} + +# chair = butil.spawn_cube(size=2, location=(0, 0, 0), name='chair1') +# blocking_spheres = [butil.spawn_sphere(radius=1, location=(3+i, np.random.rand(), 0), name=f'sphere{i}') for i in range(100)] + +# obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) +# for s in blocking_spheres: +# obj_states[s.name] = ObjectState(s, tags= {t.Semantics.Table}) + +# state = State(objs=obj_states) + +# tagging.tag_canonical_surfaces(chair) +# for s in blocking_spheres: +# tagging.tag_canonical_surfaces(s) + +# constraints = [] +# score_terms = [] +# scene = cl.scene() +# chair = cl.tagged(scene, {t.Semantics.Chair}) +# table = cl.tagged(scene, {t.Semantics.Table}) + +# score_terms += [cl.accessibility_cost(chair, table)] +# problem = cl.Problem(constraints, score_terms) +# s = time() +# res = evaluate.evaluate_problem(problem, state).score() +# print(time() - s) +# scores.append(res) +# print("nonaccessibility scores", scores) +# assert scores == sorted(scores) + + +def test_angle_alignment(): + butil.clear_scene() + scores = [] + for angle in np.linspace(0, np.pi/2, 5): + butil.clear_scene() + obj_states = {} + + chair = butil.spawn_cube(size=1, location=(-3, 0, 0), name='chair1') + table = butil.spawn_sphere(radius = 1, location=(0,0, 0), name='table1') + # rotate chair by angle in z direction + chair.rotation_euler = (0, 0, angle) + + obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) + obj_states[table.name] = ObjectState(table, tags= {t.Semantics.Table}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + tagging.tag_canonical_surfaces(table) + + constraints = [] + score_terms = [] + scene = cl.scene() + chair = cl.tagged(scene, {t.Semantics.Chair}) + table = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.angle_alignment_cost(chair, table)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + # state.trimesh_scene.show() + print("angle_alignment costs", scores) + assert scores == sorted(scores) + +def test_angle_alignment_multiple_objects(): + butil.clear_scene() + scores = [] + + for angle in np.linspace(0, np.pi/2, 5): + butil.clear_scene() + obj_states = {} + + chair = butil.spawn_cube(size=1, location=(-3, 0, 0), name='chair1') + table1 = butil.spawn_sphere(radius=1, location=(0, 0, 0), name='table1') + table2 = butil.spawn_sphere(radius=1, location=(3, 0, 0), name='table2') + + # Rotate chair by angle in z direction + chair.rotation_euler = (0, 0, angle) + + obj_states[chair.name] = ObjectState(chair, tags={t.Semantics.Chair}) + obj_states[table1.name] = ObjectState(table1, tags={t.Semantics.Table}) + obj_states[table2.name] = ObjectState(table2, tags={t.Semantics.Table}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + tagging.tag_canonical_surfaces(table1) + tagging.tag_canonical_surfaces(table2) + + constraints = [] + score_terms = [] + scene = cl.scene() + + chair = cl.tagged(scene, {t.Semantics.Chair}) + tables = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.angle_alignment_cost(chair, tables)] + + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + + print("angle_alignment costs (multiple objects):", scores) + assert scores == sorted(scores) + +def test_angle_alignment_multiple_objects_varying_positions(): + butil.clear_scene() + scores = [] + + for i in range(5): + butil.clear_scene() + obj_states = {} + + chair = butil.spawn_cube(size=1, location=(-3, 0, 0), name='chair') + + table_positions = [ + (0, 0, 0), + (3, 0, 0), + (0, 3, 0), + (-3, 2, 0), + (3, 3, 0) + ] + + tables = [] + for j, pos in enumerate(table_positions[:i+1], start=1): + table = butil.spawn_sphere(radius=1, location=pos, name=f'table{j}') + tables.append(table) + obj_states[table.name] = ObjectState(table, tags={t.Semantics.Table}) + + obj_states[chair.name] = ObjectState(chair, tags={t.Semantics.Chair}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + for table in tables: + tagging.tag_canonical_surfaces(table) + + constraints = [] + score_terms = [] + scene = cl.scene() + + chair_obj = cl.tagged(scene, {t.Semantics.Chair}) + table_objs = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.angle_alignment_cost(chair_obj, table_objs)] + + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + + print("angle_alignment costs (multiple objects, varying positions):", scores) + assert scores == sorted(scores) + +def test_angle_alignment_multipolygon_projection(): + butil.clear_scene() + scores = [] + + for i in range(5): + butil.clear_scene() + obj_states = {} + + chair = butil.spawn_cube(size=1, location=(-3, 0, 0), name='chair') + + # Create a complex object that may result in a multipolygon projection + table_verts = [ + (-1, -1, 0), + (1, -1, 0), + (1, 1, 0), + (-1, 1, 0), + (0, 0, 1) + ] + table_faces = [ + (0, 1, 2, 3), + (0, 1, 4), + (1, 2, 4), + (2, 3, 4), + (3, 0, 4) + ] + + table_mesh = bpy.data.meshes.new(name="TableMesh") + table_obj = bpy.data.objects.new(name="Table", object_data=table_mesh) + + scene = bpy.context.scene + scene.collection.objects.link(table_obj) + + table_mesh.from_pydata(table_verts, [], table_faces) + table_mesh.update() + + table_obj.location = (0, 0, 0) + + # Rotate the table object based on the iteration + chair.rotation_euler = (0, 0, i*np.pi/10) + + obj_states[chair.name] = ObjectState(chair, tags={t.Semantics.Chair}) + obj_states[table_obj.name] = ObjectState(table_obj, tags={t.Semantics.Table}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + tagging.tag_canonical_surfaces(table_obj) + + constraints = [] + score_terms = [] + scene = cl.scene() + + chair_obj = cl.tagged(scene, {t.Semantics.Chair}) + table_objs = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.angle_alignment_cost(chair_obj, table_objs)] + + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + + print("angle_alignment costs (multipolygon projection):", scores) + assert sorted(scores) == scores + +def test_angle_alignment_tagged(): + butil.clear_scene() + obj_states = {} + + chair = butil.spawn_cube(size=2, location=(5, 0, 0), name='chair1') + table = butil.spawn_cube(size=2, location=(0,0, 0), name='table1') + + obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) + obj_states[table.name] = ObjectState(table, tags= {t.Semantics.Table}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + tagging.tag_canonical_surfaces(table) + + table.rotation_euler[2] = np.pi/2 + + constraints = [] + score_terms = [] + scene = cl.scene() + chair = cl.tagged(scene, {t.Semantics.Chair}) + table = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.angle_alignment_cost(chair, table, others_tags={t.Subpart.Front})] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + + assert np.isclose(res, 0.5, atol=1e-3) + + butil.clear_scene() + obj_states = {} + + chair = butil.spawn_cube(size=2, location=(5, 0, 0), name='chair1') + table = butil.spawn_cube(size=2, location=(0,0, 0), name='table1') + + obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) + obj_states[table.name] = ObjectState(table, tags= {t.Semantics.Table}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + tagging.tag_canonical_surfaces(table) + + constraints = [] + score_terms = [] + scene = cl.scene() + chair = cl.tagged(scene, {t.Semantics.Chair}) + table = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.angle_alignment_cost(chair, table, others_tags={t.Subpart.Front})] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + + assert np.isclose(res, 0, atol=1e-3) + + +def test_focus_score(): + butil.clear_scene() + scores = [] + for angle in np.linspace(0, np.pi/2, 5): + butil.clear_scene() + obj_states = {} + + chair = butil.spawn_cube(size=1, location=(-3, 0, 0), name='chair1') + table = butil.spawn_sphere(radius = 1, location=(0,0, 0), name='table1') + # rotate chair by angle in z direction + chair.rotation_euler = (0, 0, angle) + + obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) + obj_states[table.name] = ObjectState(table, tags= {t.Semantics.Table}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + tagging.tag_canonical_surfaces(table) + + constraints = [] + score_terms = [] + scene = cl.scene() + chair = cl.tagged(scene, {t.Semantics.Chair}) + table = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.focus_score(chair, table)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + # state.trimesh_scene.show() + print("focus_score costs", scores) + assert scores == sorted(scores) + +@pytest.mark.skip +def test_viol_amounts(): + butil.clear_scene() + + def mk_state(n): + + butil.clear_scene() + obj_states = {} + + for i in range(n): + chair = butil.spawn_cube(size=1, location=(-3, 0, 0), name=f'chair{i}') + obj_states[chair.name] = ObjectState(chair, tags={t.Semantics.Furniture, t.Semantics.Chair}) + + return State(objs=obj_states) + + cons = cl.Problem([cl.scene().tagged(t.Semantics.Furniture).count().in_range(1, 3)], []) + assert evaluate.evaluate_problem(cons, mk_state(0))[1] == 1 + assert evaluate.evaluate_problem(cons, mk_state(1))[1] == 0 + assert evaluate.evaluate_problem(cons, mk_state(3))[1] == 0 + assert evaluate.evaluate_problem(cons, mk_state(7))[1] == 4 + + cons = cl.Problem([cl.scene().tagged(t.Semantics.Furniture).count() <= 3], []) + assert evaluate.evaluate_problem(cons, mk_state(1))[1] == 0 + assert evaluate.evaluate_problem(cons, mk_state(3))[1] == 0 + assert evaluate.evaluate_problem(cons, mk_state(4))[1] == 1 + assert evaluate.evaluate_problem(cons, mk_state(6))[1] == 3 + + cons = cl.Problem([cl.scene().tagged(t.Semantics.Furniture).count() >= 3], []) + assert evaluate.evaluate_problem(cons, mk_state(0))[1] == 3 + assert evaluate.evaluate_problem(cons, mk_state(1))[1] == 2 + assert evaluate.evaluate_problem(cons, mk_state(3))[1] == 0 + assert evaluate.evaluate_problem(cons, mk_state(4))[1] == 0 + assert evaluate.evaluate_problem(cons, mk_state(6))[1] == 0 + + cons = cl.Problem([cl.scene().tagged(t.Semantics.Furniture).count() == 3], []) + assert evaluate.evaluate_problem(cons, mk_state(0))[1] == 3 + assert evaluate.evaluate_problem(cons, mk_state(1))[1] == 2 + assert evaluate.evaluate_problem(cons, mk_state(3))[1] == 0 + assert evaluate.evaluate_problem(cons, mk_state(4))[1] == 1 + assert evaluate.evaluate_problem(cons, mk_state(6))[1] == 3 + +def test_min_dist_tagged(): + + butil.clear_scene() + obj_states = {} + + chair = butil.spawn_cube(size=2, location=(0, 0, 2), name='chair1') + table = butil.spawn_cube(size=10, location=(0,0, 0), name='table1') + + obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) + obj_states[table.name] = ObjectState(table, tags= {t.Semantics.Table}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair) + tagging.tag_canonical_surfaces(table) + + constraints = [] + score_terms = [] + scene = cl.scene() + chair = cl.tagged(scene, {t.Semantics.Chair}) + table = cl.tagged(scene, {t.Semantics.Table}) + + + + score_terms += [cl.distance(chair, table, others_tags={t.Subpart.Front})] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + assert np.isclose(res, 4) + + constraints = [] + score_terms = [] + scene = cl.scene() + # chair = cl.tagged(scene, {t.Semantics.Chair}) + # table = cl.tagged(scene, {t.Semantics.Table}) + + # butil.save_blend('table_chair.blend') + score_terms += [cl.distance(chair, table, others_tags={t.Subpart.Top})] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + assert np.isclose(res, 2) + + constraints = [] + score_terms = [] + scene = cl.scene() + # chair = cl.tagged(scene, {t.Semantics.Chair}) + # table = cl.tagged(scene, {t.Semantics.Table}) + + score_terms += [cl.distance(chair, table, others_tags={t.Subpart.Bottom})] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + assert np.isclose(res, 6) + + +def test_table(): + butil.clear_scene() + + used_as = home_asset_usage() + usage_lookup.initialize_from_dict(used_as) + + butil.clear_scene() + gen = TableDiningFactory(0) + obj = bbox_mesh_from_hipoly(gen, 0, use_pholder=False) + + # if fac in pholder_facs: + # obj = fac(0).spawn_placeholder(0, loc=(0,0,0), rot=(0,0,0)) + # elif fac in asset_facs: + # obj = fac(0).spawn_asset(0, loc=(0,0,0), rot=(0,0,0)) + # else: + # raise ValueError() + + with butil.ViewportMode(obj, mode='EDIT'): + butil.select(obj) + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.quads_convert_to_tris(quad_method='BEAUTY', ngon_method='BEAUTY') + + tagging.tag_canonical_surfaces(obj) + tagging.extract_tagged_faces(obj, {t.Subpart.Front}) + tagging.extract_tagged_faces(obj, {t.Subpart.Top}) + tagging.extract_tagged_faces(obj, {t.Subpart.Bottom}) + tagging.extract_tagged_faces(obj, {t.Subpart.Back}) + # butil.save_blend('table.blend') + + # obj_tags = tagging.union_object_tags(obj) + + # for tag in [t.Semantics.Back, t.Semantics.Bottom, t.Semantics.SupportSurface]: + # mask = tagging.tagged_face_mask(obj, {tag}) + # if mask.sum() == 0: + # obj_tags = tagging.union_object_tags(obj) + # raise ValueError( + # f'{obj.name=} has nothing tagged for {tag=}. {obj_tags=}' + # ) + +def test_reflection_asymmetry(): + + """ + create a bunch of chairs and a table. The chairs are reflected along the long part of the table. + check that the asymmetry score is 0 + """ + scores = [] + butil.clear_scene() + obj_states = {} + + table = bbox_mesh_from_hipoly(TableDiningFactory(0), 0, use_pholder=False) + obj_states[table.name] = ObjectState(table, tags= {t.Semantics.Table}) + + chairs = [] + for i in range(4): + chair = bbox_mesh_from_hipoly(ChairFactory(0), i, use_pholder=False) + obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) + chairs.append(chair) + # tagging.tag_canonical_surfaces(chair) + # tagging.extract_tagged_faces(chair, {t.Semantics.Front}) + chairs[0].location = (1, 1, 0) + chairs[1].location = (1, -1, 0) + chairs[2].location = (-1, 1, 0) + chairs[3].location = (-1, -1, 0) + + chairs[0].rotation_euler = (0, 0, -np.pi/2) + chairs[1].rotation_euler = (0, 0, np.pi/2) + chairs[2].rotation_euler = (0, 0, -np.pi/2) + chairs[3].rotation_euler = (0, 0, np.pi/2) + + bpy.context.view_layer.update() + # chairs[0].rotation_euler = (0, 0, np.pi) + + # butil.save_blend('table_chairs.blend') + + state = State(objs=obj_states) + + # tagging.tag_canonical_surfaces(table) + # for i in range(5): + # tagging.tag_canonical_surfaces(obj_states[f'chair{i}'].obj) + + constraints = [] + score_terms = [] + scene = cl.scene() + table_tagged = cl.tagged(scene, {t.Semantics.Table}) + chairs_tagged = cl.tagged(scene, {t.Semantics.Chair}) + + score_terms += [cl.reflectional_asymmetry(chairs_tagged, table_tagged)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + print(res) + assert np.isclose(res, 0, atol=1e-2) + + # assert the asymmetry increases as we gradually move one chair away from the table + chairs[0].location = (1, 2, 0) + bpy.context.view_layer.update() + score_terms = [] + score_terms += [cl.reflectional_asymmetry(chairs_tagged, table_tagged)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + + #assert the asymmetry increases if we rotate chair 0 + chairs[0].rotation_euler = (0, 0, 0) + bpy.context.view_layer.update() + score_terms = [] + score_terms += [cl.reflectional_asymmetry(chairs_tagged, table_tagged)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + + print("asymmetry scores", scores) + #assert monotonocity + assert scores == sorted(scores) + # assert it is strict + assert (scores[0] < scores[1]) and scores[1] < scores[2] + + +@pytest.mark.skip +def test_rotation_asymmetry(): + """ + create a bunch of chairs. The chairs are rotationally symmetric and then perturbed. + """ + scores = [] + butil.clear_scene() + obj_states = {} + + chairs = [] + for i in range(6): + chair = bbox_mesh_from_hipoly(ChairFactory(0), i, use_pholder=False) + obj_states[chair.name] = ObjectState(chair, tags= {t.Semantics.Chair}) + chairs.append(chair) + + circle_locations_rotations = [((2*np.cos(i*np.pi/3), 2*np.sin(i*np.pi/3), 0),i*np.pi/3) for i in range(6)] + np.random.shuffle(circle_locations_rotations) + # put the chairs in a circle + for i in range(6): + chairs[i].location = circle_locations_rotations[i][0] + chairs[i].rotation_euler = (0, 0, circle_locations_rotations[i][1]) + + + + bpy.context.view_layer.update() + + state = State(objs=obj_states) + + + constraints = [] + score_terms = [] + scene = cl.scene() + chairs_tagged = cl.tagged(scene, {t.Semantics.Chair}) + + score_terms += [cl.rotational_asymmetry(chairs_tagged)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + assert np.isclose(res,0, atol=1e-2) + + # assert the asymmetry increases as we gradually move one chair from the circle + chairs[0].location += Vector(np.random.rand(3)) + bpy.context.view_layer.update() + score_terms = [] + score_terms += [cl.rotational_asymmetry(chairs_tagged)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + + #assert the asymmetry increases if we rotate chair 0 + chairs[0].rotation_euler = (0, 0, np.random.rand(1)) + bpy.context.view_layer.update() + score_terms = [] + score_terms += [cl.rotational_asymmetry(chairs_tagged)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + + # do the same for another chair + chairs[1].location += Vector(np.random.rand(3)) + bpy.context.view_layer.update() + score_terms = [] + score_terms += [cl.rotational_asymmetry(chairs_tagged)] + problem = cl.Problem(constraints, score_terms) + res = evaluate.evaluate_problem(problem, state).loss() + scores.append(res) + + # assert monotonic + # assert scores == sorted(scores) # warning: heisenbug. disabled by araistrick + + # assert it is strict + assert (scores[0] < scores[1]) and scores[1] < scores[2] and scores[2] < scores[3] + print("asymmetry scores", scores) + + +def test_coplanarity(): + butil.clear_scene() + obj_states = {} + + chair1 = butil.spawn_cube(size=2, location=(0, 0, 0), name='chair1') + chair2 = butil.spawn_cube(size=2, location=(4, 0, 0), name='chair2') + chair3 = butil.spawn_cube(size=2, location=(8, 0, 0), name='chair3') + chair4 = butil.spawn_cube(size=2, location=(12, 0, 0), name='chair4') + + obj_states[chair1.name] = ObjectState(chair1, tags= {t.Semantics.Chair}) + obj_states[chair2.name] = ObjectState(chair2, tags= {t.Semantics.Chair}) + obj_states[chair3.name] = ObjectState(chair3, tags= {t.Semantics.Chair}) + obj_states[chair4.name] = ObjectState(chair4, tags= {t.Semantics.Chair}) + + state = State(objs=obj_states) + + tagging.tag_canonical_surfaces(chair1) + tagging.tag_canonical_surfaces(chair2) + tagging.tag_canonical_surfaces(chair3) + tagging.tag_canonical_surfaces(chair4) + + constraints = [] + score_terms = [] + scene = cl.scene() + chairs = cl.tagged(scene, {t.Semantics.Chair}) + + score_terms += [cl.coplanarity_cost(chairs)] + problem = cl.Problem(constraints, score_terms) + res1= evaluate.evaluate_problem(problem, state).loss() + # print(res1) + assert np.isclose(res1, 0, atol=1e-2) + + chair2.location = (4, 2, 0) + bpy.context.view_layer.update() + # butil.save_blend('test.blend') + + state = State(objs=obj_states) + score_terms = [] + score_terms += [cl.coplanarity_cost(chairs)] + problem = cl.Problem(constraints, score_terms) + res2 = evaluate.evaluate_problem(problem, state).loss() + # print(res2) + assert res2 > res1 + + chair3.rotation_euler = (0, 0, np.pi/6) + bpy.context.view_layer.update() + state = State(objs=obj_states) + score_terms = [] + score_terms += [cl.coplanarity_cost(chairs)] + problem = cl.Problem(constraints, score_terms) + res3 = evaluate.evaluate_problem(problem, state).loss() + assert res3 > res2 + + chair2.dimensions = (2, 2, 4) + bpy.context.view_layer.update() + state = State(objs=obj_states) + score_terms = [] + score_terms += [cl.coplanarity_cost(chairs)] + problem = cl.Problem(constraints, score_terms) + res4= evaluate.evaluate_problem(problem, state).loss() + assert res4 > res3 + + +def test_evaluate_problem_scalar_ops(): + + state = State(objs={}) + + one = cl.constant(1) + two = cl.constant(2) + three = cl.constant(3) + + e = lambda x: evaluate.evaluate_problem(cl.Problem({}, {repr(x): x}), state).loss() + + assert e(two) == 2 + assert e(one + two) == 3 + assert e(one - two) == -1 + assert e(two * three) == 6 + assert e(two / three) == 2/3 + assert e(two ** three) == 8 + + assert e(two == two) == 1 + assert e(two == one) == 0 + assert e(two >= two) == 1 + assert e(two >= three) == 0 + assert e(two > one) == 1 + assert e(two > two) == 0 + assert e(two <= two) == 1 + assert e(two <= one) == 0 + assert e(two < three) == 1 + assert e(two < two) == 0 + assert e(two != one) == 1 + assert e(two != two) == 0 + + assert e(cl.max_expr(one, two)) == 2 + assert e(cl.min_expr(one, two)) == 1 + + assert e(one.clamp_min(two)) == 2 + assert e(two.clamp_max(one)) == 1 + + assert e(-one) == -1 + assert e((-one).abs()) == 1 + +def test_evaluate_hinge(): + + state = State(objs={}) + e = lambda x: evaluate.evaluate_problem(cl.Problem({}, {repr(x): x}), state).loss() + + one = cl.constant(1) + two = cl.constant(2) + + assert e(cl.hinge(one, 0, 2)) == 0 + assert e(cl.hinge(one, 1, 2)) == 0 + assert e(cl.hinge(one, 0, 1)) == 0 + + assert e(cl.hinge(one, 2, 3)) == 1 + assert e(cl.hinge(two, 0, 1.5)) == 0.5 + +if __name__ == '__main__': + # test_min_dist() + # test_min_dist_tagged() + # test_reflection_asymmetry() + # test_accessibility_speed() + # test_coplanarity() + test_angle_alignment_multipolygon_projection() diff --git a/tests/solver/test_greedy_partition.py b/tests/solver/test_greedy_partition.py new file mode 100644 index 000000000..2518af262 --- /dev/null +++ b/tests/solver/test_greedy_partition.py @@ -0,0 +1,471 @@ +# 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 copy +import pytest + +from pprint import pprint + +from infinigen.core import tags as t + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r, + usage_lookup, + evaluator +) +from infinigen.core.constraints.example_solver import ( + state_def, + greedy, + propose_discrete, +) + +from infinigen_examples import indoor_constraint_examples as ex, generate_indoors +from infinigen_examples.util import constraint_util as cu + +from test_greedy_substitutions import make_dummy_state + +def test_partition_basecase_irrelevant(): + cons = cl.scene()[t.Semantics.TableDisplayItem].count().in_range(0, 1) + res, relevant = greedy.filter_constraints(cons, r.Domain({t.Semantics.Room}, [])) + assert not relevant + +def test_basecase_relevant(): + cons = cl.scene()[t.Semantics.Room].count().in_range(0, 1) + filter = r.Domain({t.Semantics.Room}, []) + res, relevant = greedy.filter_constraints(cons, filter) + assert relevant + +def test_partition_collapse_binop(): + + cons = ( + cl.scene()[t.Semantics.Furniture].count().in_range(0, 1) * + cl.scene()[t.Semantics.Room].count().in_range(0, 1) + ) + + assert not greedy.filter_constraints(cons.operands[0], r.Domain({t.Semantics.Room}, []))[1] + assert greedy.filter_constraints(cons.operands[1], r.Domain({t.Semantics.Room}, []))[1] + + res, relevant = greedy.filter_constraints(cons, r.Domain({t.Semantics.Room}, [])) + assert relevant + + print("RES", res) + + expect = cl.scene()[t.Semantics.Room].count().in_range(0, 1) + assert r.expr_equal(res, expect) + + +def test_partition_eliminate_irrelevant(): + + scene = cl.scene() + firstpart = scene[t.Semantics.Furniture].count().in_range(0, 1) + secondpart = scene[t.Semantics.Furniture].all(lambda f: ( + scene[t.Semantics.Chair].related_to(f, cl.AnyRelation()).count().in_range(0, 1) + )) + cons = firstpart * secondpart + + assert not greedy.filter_constraints(secondpart, r.Domain({t.Semantics.Furniture}, []))[1] + + res, relevant = greedy.filter_constraints(cons, r.Domain({t.Semantics.Furniture})) + assert relevant + assert r.expr_equal(res, firstpart) + +def test_greedy_partition_bathroom(): + usage_lookup.initialize_from_dict(ex.home_asset_usage()) + prob = ex.home_constraints() + stages = generate_indoors.default_greedy_stages() + + bath_cons = prob.constraints['bathroom'] + + on_floor = stages['on_floor'] + on_floor_any = r.domain_tag_substitute(on_floor, cu.variable_room, r.Domain()) + assert greedy.filter_constraints(bath_cons, on_floor_any)[1] + + on_bathroom = r.domain_tag_substitute(on_floor, cu.variable_room, r.Domain({t.Semantics.Bathroom})) + assert greedy.filter_constraints(bath_cons, on_bathroom)[1] + +def test_greedy_partition_multilevel(): + + usage_lookup.initialize_from_dict(ex.home_asset_usage()) + prob = ex.home_constraints() + stages = generate_indoors.default_greedy_stages() + + bathroom = cl.scene()[{t.Semantics.Room, t.Semantics.Bathroom}].excludes(cu.room_types) + storage = cl.scene()[t.Semantics.Storage] + + bath_cons_1 = storage.related_to(bathroom, cu.on_floor).count().in_range(0, 1) + + on_hallway = r.domain_tag_substitute(stages['on_floor'], cu.variable_room, r.Domain({t.Semantics.Hallway})) + assert not greedy.filter_constraints(bath_cons_1, on_hallway)[1] + + bath_cons_2 = bathroom.all(lambda r: storage.related_to(r, cu.on_floor).count().in_range(0, 1)) + assert not greedy.filter_constraints(bath_cons_2, on_hallway)[1] + + bath_cons_3 = bathroom.all(lambda r: ( + storage.related_to(r).all( + lambda s: cl.scene()[t.Semantics.Object].related_to(s).count().in_range(0, 1) + ) + )) + assert not greedy.filter_constraints(bath_cons_3, on_hallway)[1] + +def test_greedy_partition_bathroom_nofalsepositive(): + + usage_lookup.initialize_from_dict(ex.home_asset_usage()) + prob = ex.home_constraints() + stages = generate_indoors.default_greedy_stages() + + bath_cons = prob.constraints['bathroom'] + + on_hallway = r.domain_tag_substitute(stages['on_floor'], cu.variable_room, r.Domain({t.Semantics.Hallway})) + assert not greedy.filter_constraints(bath_cons, on_hallway)[1] + +def test_greedy_partition_plants(): + usage_lookup.initialize_from_dict(ex.home_asset_usage()) + prob = ex.home_constraints() + stages = generate_indoors.default_greedy_stages() + + plant_cons = prob.constraints['plants'] + + on_floor = stages['on_floor'] + on_floor_any = r.domain_tag_substitute(on_floor, cu.variable_room, r.Domain()) + assert greedy.filter_constraints(plant_cons, on_floor_any)[1] + + on_bathroom = r.domain_tag_substitute(on_floor, cu.variable_room, r.Domain({t.Semantics.Bathroom})) + assert greedy.filter_constraints(plant_cons, on_bathroom)[1] + +@pytest.mark.skip # filter_constraints development has been abandoned until a later date +def test_objects_on_generic_obj(): + + usage_lookup.initialize_from_dict(ex.home_asset_usage()) + stages = generate_indoors.default_greedy_stages() + + on_obj = stages['on_obj'] + on_obj = r.domain_tag_substitute(on_obj, cu.variable_room, r.Domain()) + on_obj = r.domain_tag_substitute( + on_obj, cu.variable_obj, r.Domain({t.SpecificObject('thatchair'), t.Semantics.Chair}) + ) + print("ON_OBJ_FILTER", on_obj) + + bathroom = cl.scene()[t.Semantics.Room, t.Semantics.Bathroom] + storage = cl.scene()[t.Semantics.Object, t.Semantics.Storage] + prob = bathroom.all(lambda r: + storage.related_to(r).all(lambda s: ( + cl.scene()[t.Semantics.Object].related_to(s).count().in_range(0, 1) + )) + ) + + cons, relevant = greedy.filter_constraints(prob, on_obj) + assert not relevant + +@pytest.mark.skip # filter_constraints development has been abandoned until a later date +def test_on_obj_coverage(): + + cons = cl.scene()[t.Semantics.Room].all(lambda r: ( + cl.scene()[t.Semantics.Storage].related_to(r).all(lambda s: ( + cl.scene()[t.Semantics.Object].related_to(s).count().in_range(0, 1) + )) + )) + + obj_in_bathroom = r.domain_tag_substitute( + generate_indoors.default_greedy_stages()['on_obj'], + cu.variable_room, + r.Domain({t.Semantics.Bathroom}) + ) + obj_in_bathroom = r.domain_tag_substitute( + obj_in_bathroom, cu.variable_obj, r.Domain({t.Semantics.Storage}) + ) + + res, relevant = greedy.filter_constraints(cons, obj_in_bathroom) + assert relevant + +@pytest.mark.skip # filter_constraints development has been abandoned until a later date +def test_only_bathcons_coverage(): + + usage_lookup.initialize_from_dict(ex.home_asset_usage()) + prob = ex.home_constraints() + stages = generate_indoors.default_greedy_stages() + + bath_cons = prob.constraints['bathroom'] + + dom = r.domain_tag_substitute( + stages['on_floor'], cu.variable_room, r.Domain({t.Semantics.Bathroom}) + ) + assert greedy.filter_constraints(bath_cons, dom)[1] + + dom = r.domain_tag_substitute( + stages['on_wall'], cu.variable_room, r.Domain({t.Semantics.Bathroom}) + ) + assert greedy.filter_constraints(bath_cons, dom)[1] + + dom = r.domain_tag_substitute( + stages['on_obj'], cu.variable_room, r.Domain({t.Semantics.Bathroom}) + ) + dom = r.domain_tag_substitute( + dom, cu.variable_obj, r.Domain({t.Semantics.Storage}) + ) + assert greedy.filter_constraints(bath_cons, dom)[1] + +@pytest.fixture +def precompute_all_coverage(): + + usage_lookup.initialize_from_dict(ex.home_asset_usage()) + prob = ex.home_constraints() + stages = generate_indoors.default_greedy_stages() + + cons_coverage = {k: set() for k in prob.constraints.keys()} + score_coverage = {k: set() for k in prob.score_terms.keys()} + + for k, filter in stages.items(): + + for roomtype in cu.room_types: + room_filter = r.domain_tag_substitute( + copy.deepcopy(filter), cu.variable_room, r.Domain({roomtype}) + ) + + # eliminate the var, assume any object is fine, most generous possible assumption + room_filter = r.domain_tag_substitute( + room_filter, cu.variable_obj, r.Domain() + ) + for name, cons in prob.constraints.items(): + if greedy.filter_constraints(cons, room_filter)[1]: + cons_coverage[name].add((k, roomtype)) + for name, score in prob.score_terms.items(): + if greedy.filter_constraints(score, room_filter)[1]: + score_coverage[name].add((k, roomtype)) + + return cons_coverage, score_coverage + +@pytest.mark.skip # filter_constraints development has been abandoned until a later date +def test_specific_coverage(precompute_all_coverage): + + cons_coverage, _ = precompute_all_coverage + + assert cons_coverage['bathroom'] == { + ('on_floor', t.Semantics.Bathroom), + ('on_wall', t.Semantics.Bathroom), + ('on_obj', t.Semantics.Bathroom), + } + + assert cons_coverage['diningroom'] == { + ('on_floor', t.Semantics.DiningRoom), + ('on_wall', t.Semantics.DiningRoom), + ('on_obj', t.Semantics.DiningRoom), + } + + assert cons_coverage['livingroom'] == { + ('on_floor', t.Semantics.LivingRoom), + ('on_wall', t.Semantics.LivingRoom), + ('on_obj', t.Semantics.LivingRoom), + } + +@pytest.mark.skip # filter_constraints development has been abandoned until a later date +def test_greedy_partition_coverage(precompute_all_coverage): + + cons_coverage, score_coverage = precompute_all_coverage + + for k, v in cons_coverage.items(): + if len(cons_coverage[k]) == 0: + raise ValueError(f"Constraint {k} has no coverage") + for k, v in score_coverage.items(): + if len(score_coverage[k]) == 0: + raise ValueError(f"Score term {k} has no coverage") + +def get_on_diningroom_stage(): + usage_lookup.initialize_from_dict(ex.home_asset_usage()) + stages = generate_indoors.default_greedy_stages() + on_diningroom = r.domain_tag_substitute( + stages['on_floor'], + cu.variable_room, + r.Domain({t.Semantics.DiningRoom, t.Semantics.Room}) + ) + return on_diningroom + +@pytest.mark.skip # filter_constraints development has been abandoned until a later date +def test_greedy_partition_diningroom(): + + on_diningroom = get_on_diningroom_stage() + prob = ex.home_constraints() + diningroom = prob.constraints['diningroom'] + + + for node in diningroom.traverse(): + if isinstance(node, cl.item): + print(node) + + res, relevant = greedy.filter_constraints(diningroom, on_diningroom) + assert relevant + + print("FILTER", on_diningroom) + print("RES", res) + + assert isinstance(res, cl.ForAll) + assert res.pred.__class__ is not cl.constant + +@pytest.mark.skip # filter_constraints development has been abandoned until a later date +def test_diningroom_bounds_active(): + + usage_lookup.initialize_from_dict(ex.home_asset_usage()) + stages = generate_indoors.default_greedy_stages() + on_diningroom = r.domain_tag_substitute( + stages['on_floor'], cu.variable_room, r.Domain({t.Semantics.DiningRoom}) + ) + + prob = ex.home_constraints() + diningroom = prob.constraints['diningroom'] + + bounds_before_preproc = r.constraint_bounds(diningroom) + bounds = propose_discrete.preproc_bounds( + bounds_before_preproc, state_def.State({}), on_diningroom + ) + + assert len(bounds) > 0 + +@pytest.mark.skip # filter_constraints development has been abandoned until a later date +def test_partition_keep_constants(): + + cons = (cl.scene()[t.Semantics.Room].count() * 2) + res, relevant = greedy.filter_constraints(cons, r.Domain({t.Semantics.Room}, [])) + assert relevant + assert r.expr_equal(res, cons) + +@pytest.mark.skip # filter_constraints development has been abandoned until a later date +def test_multiroom_viol(): + + state = make_dummy_state({ + (t.Semantics.Room,): 3 + }) + + state.objs['room_0'].tags.add(t.Semantics.DiningRoom) + + cons = cl.scene()[t.Semantics.Room].all(lambda r: + cl.scene()[{t.Semantics.Object, t.Semantics.Storage}].related_to(r, cu.on_floor).count() == 1 + ) + prob = cl.Problem({'storage': cons}, {}) + + on_diningroom = get_on_diningroom_stage() + prob, relevant = greedy.filter_constraints(prob, on_diningroom) + assert relevant + + print("PRED", prob.constraints['storage'].pred) + print("OBJS", prob.constraints['storage'].objs) + + result = evaluator.evaluate_problem(prob, state) + assert result.viol_count() == 1 # only one room is relevant, so only one violation applies for this stage + + state.objs['stor_1'] = state_def.ObjectState( + obj=None, + generator=None, + tags={t.Semantics.Storage}, + relations=[ + state_def.RelationState( + relation=cl.StableAgainst(), + target_name='room_0' + ) + ] + ) + + result = evaluator.evaluate_problem(prob, state) + assert result.viol_count() == 0 # only one room is relevant, and it has an obj + +@pytest.mark.skip +def test_forall_furnroom(): + + scene = cl.scene() + rooms = scene[t.Semantics.Room] + furniture = scene[t.Semantics.Furniture] + + cons = rooms.all( + lambda r: furniture.related_to(r).count().in_range(0, 1) + ) + + room = r.Domain({t.Semantics.Room}, []) + furn = r.Domain({t.Semantics.Furniture}, []) + furn_room = furn.with_relation(cl.AnyRelation(), room) + furn_no_room = furn.with_relation(-cl.AnyRelation(), room) + + res, rel = greedy.filter_constraints(cons, furn) + assert rel + assert r.expr_equal(res, cons) + + res, rel = greedy.filter_constraints(cons, furn_room) + assert rel + assert r.expr_equal(res, cons) + + res, rel = greedy.filter_constraints(cons, furn_no_room) + assert not rel + +@pytest.mark.skip +def test_forall_narrow_pred(): + + scene = cl.scene() + rooms = scene[t.Semantics.Room] + furniture = scene[t.Semantics.Furniture] + + cons = rooms.all( + lambda r: furniture.related_to(r).count().in_range(0, 1) + ) + + room = r.Domain({t.Semantics.Room}, []) + stor = r.Domain({t.Semantics.Furniture}, []) + stor_room = stor.with_relation(cl.AnyRelation(), room) + stor_no_room = stor.with_relation(-cl.AnyRelation(), room) + + res, rel = greedy.filter_constraints(cons, stor) + assert rel + assert r.expr_equal(res, cons) + + res, rel = greedy.filter_constraints(cons, stor_room) + assert rel + assert r.expr_equal(res, cons) + + res, rel = greedy.filter_constraints(cons, stor_no_room) + assert not rel + +@pytest.mark.skip +def test_forall_narrow_loopvar(): + + scene = cl.scene() + rooms = scene[t.Semantics.Room] + furniture = scene[t.Semantics.Furniture] + + cons = rooms.all( + lambda r: furniture.related_to(r).count().in_range(0, 1) + ) + + droom = r.Domain({t.Semantics.Room, t.Semantics.DiningRoom}, []) + furn = r.Domain({t.Semantics.Furniture}, []) + furn_room = furn.with_relation(cl.AnyRelation(), droom) + furn_no_room = furn.with_relation(-cl.AnyRelation(), droom) + + + cons_narrow = r.FilterByDomain(rooms, droom).all( + lambda r: furniture.related_to(r).count().in_range(0, 1) + ) + + res, rel = greedy.filter_constraints(cons, furn) + print(res) + assert rel + assert r.expr_equal(res, cons_narrow) + + res, rel = greedy.filter_constraints(cons, furn_room) + assert rel + assert r.expr_equal(res, cons_narrow) + + res, rel = greedy.filter_constraints(cons, furn_no_room) + assert not rel + +@pytest.mark.skip +def test_forall_sumconst(): + + scene = cl.scene() + rooms = scene[t.Semantics.Room] + furniture = scene[t.Semantics.Furniture] + + sumcons = rooms.sum(lambda r: cl.constant(1)) + assert greedy.filter_constraints(sumcons, r.Domain({t.Semantics.Room}))[1] + + + diff --git a/tests/solver/test_greedy_stages.py b/tests/solver/test_greedy_stages.py new file mode 100644 index 000000000..4e319384e --- /dev/null +++ b/tests/solver/test_greedy_stages.py @@ -0,0 +1,307 @@ +# 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 logging + +from pprint import pprint +import pytest + +from infinigen.core import tags as t +from infinigen.core.constraints import ( + checks, + constraint_language as cl, + reasoning as r, + usage_lookup, + evaluator +) +from infinigen.core.constraints.example_solver import ( + propose_discrete, + greedy, + state_def +) +from infinigen_examples import indoor_constraint_examples as ex, generate_indoors +from infinigen_examples.util import constraint_util as cu + +from infinigen.assets.tableware import PlantContainerFactory + +from infinigen.core.util import blender as butil + +@pytest.mark.parametrize('key', generate_indoors.default_greedy_stages().keys()) +def test_stages_relations(key): + + pprint(generate_indoors.default_greedy_stages()) + + v = generate_indoors.default_greedy_stages()[key] + + assert not v.is_recursive() + + if len(v.relations) != 0: + + if all(isinstance(r, cl.NegatedRelation) for r, _ in v.relations): + raise ValueError(f"Stage {key} has no positive relation, {[r for r, _ in v.relations]}") + #if any(isinstance(r, cl.AnyRelation) for r, _ in v.relations): + # raise ValueError(f"Stage {key} has an AnyRelation which is underspecified, {v}") + +#@pytest.mark.parametrize('key', generate_indoors.default_greedy_stages().keys()) +#@pytest.mark.parametrize('roomtype', cu.room_types) +#def test_stage_bound_roomsubs(key: str, roomtype: t.Semantics): +# +# stages = generate_indoors.default_greedy_stages() +# stage = stages[key] +# stage = r.domain_tag_substitute(stage, t.Variable('room'), r.Domain({roomtype})) +# +# bounds = r.constraint_bounds(ex.home_constraints()) + +def test_validate_bounds(): + + bounds = r.constraint_bounds(ex.home_constraints()) + + for b in bounds: + for rel, dom in b.domain.relations: + if not r.domain_finalized(dom, check_anyrel=False, check_variable=True): + raise ValueError(f"Unfinalized domain {dom=}") + if isinstance(rel, cl.GeometryRelation): + if rel.child_tags == set(): + raise ValueError(f"GeometryRelation with empty child_tags in {b=}") + if rel.parent_tags == set(): + raise ValueError(f"GeometryRelation with empty parent_tags in {b=}") + +def test_validate_stages(): + stages = generate_indoors.default_greedy_stages() + + wall = stages['on_wall'] + floor = stages['on_floor'] + assert not wall.intersects(floor) + + onobj = stages['obj_ontop_obj'] + assert not onobj.intersects(floor) + + checks.validate_stages(stages) + +def test_example_intersects(): + + on_wall_complex = cl.StableAgainst( + {-t.Subpart.Top, t.Subpart.Back, -t.Subpart.Front}, + {t.Subpart.Wall, t.Subpart.Visible, -t.Subpart.Ceiling, -t.Subpart.SupportSurface} + ) + on_wall_simple = cl.StableAgainst({}, {t.Subpart.Wall}) + assert on_wall_simple.intersects(on_wall_complex) + + dom = r.Domain( + {t.Semantics.WallDecoration, t.Semantics.Object}, + relations=[ + (on_wall_complex, r.Domain({t.Semantics.Room}, [])) + ] + ) + + filter = generate_indoors.default_greedy_stages()['on_wall'] + assert propose_discrete.active_for_stage(dom, filter) + +def test_contradiction_fail(): + + prob = cl.Problem(constraints=[ + cl.scene()[{t.Semantics.Object, -t.Semantics.Object}].count().in_range(1, 3) + ], score_terms=[]) + with pytest.raises(ValueError): + checks.check_contradictory_domains(prob) + +def get_walldec(): + return r.Domain( + {t.Semantics.WallDecoration, t.Semantics.Object, -t.Semantics.Room}, + [( + cl.StableAgainst( + {-t.Subpart.Front, -t.Subpart.Top, t.Subpart.Back}, + {-t.Subpart.SupportSurface, -t.Subpart.Ceiling, t.Subpart.Visible, t.Subpart.Wall} + ), + r.Domain({t.Semantics.Room, -t.Semantics.Object}, []) + )] + ) + +def test_example_walldec(): + + dom = get_walldec() + stages = generate_indoors.default_greedy_stages() + + assert not propose_discrete.active_for_stage(dom, stages['on_ceiling']) + assert not propose_discrete.active_for_stage(dom, stages['on_floor']) + + assert t.satisfies(dom.tags, stages['on_wall'].tags) + print("ONWALL", stages['on_wall']) + + assert propose_discrete.active_for_stage(dom, stages['on_wall']) + +def test_example_floorwall(): + + on = cl.StableAgainst( + {t.Subpart.Bottom, -t.Subpart.Front, -t.Subpart.Top, -t.Subpart.Back}, + {t.Subpart.SupportSurface, t.Subpart.Visible, -t.Subpart.Wall, -t.Subpart.Ceiling} + ) + + against = cl.StableAgainst( + {t.Subpart.Back, -t.Subpart.Top, -t.Subpart.Front}, + {t.Subpart.Visible, t.Subpart.Wall, -t.Subpart.SupportSurface, -t.Subpart.Ceiling} + ) + + dom = r.Domain( + {t.Semantics.Storage, t.Semantics.Furniture, t.Semantics.Object, -t.Semantics.Room}, + [ + (on, r.Domain({t.Semantics.Room, -t.Semantics.Object}, [])), + (against, r.Domain({t.Semantics.Room, -t.Semantics.Object}, [])) + ] + ) + + stages = generate_indoors.default_greedy_stages() + + assert propose_discrete.active_for_stage(dom, stages['on_floor']) + assert not propose_discrete.active_for_stage(dom, stages['on_wall']) + +def test_example_secondary(): + + + floorwall_furn = r.Domain({t.Semantics.Furniture, t.Semantics.Storage, t.Semantics.Object}, [ + (cu.on_floor, r.Domain({t.Semantics.Room, -t.Semantics.Object}, [])), + (cu.against_wall, r.Domain({t.Semantics.Room, -t.Semantics.Object}, [])) + ]) + dom = r.Domain({t.FromGenerator(PlantContainerFactory), t.Semantics.Object, -t.Semantics.Room}, [ + (cl.StableAgainst({t.Subpart.Bottom}, {t.Subpart.Top}), floorwall_furn) + ]) + + stages = generate_indoors.default_greedy_stages() + on_obj = stages['obj_ontop_obj'] + assert propose_discrete.active_for_stage(dom, on_obj) + +def test_example_sideobj(): + + anyroom = r.Domain({t.Semantics.Room, -t.Semantics.Object}, []) + + objonroom = r.Domain({t.Semantics.Object, t.Semantics.Table, -t.Semantics.Room}, [ + (cu.on_floor, anyroom) + ]) + + dom = r.Domain({t.Semantics.Object, t.Semantics.Chair, -t.Semantics.Room}, [ + (cu.front_against, objonroom), + (cu.on_floor, anyroom) + ]) + stages = generate_indoors.default_greedy_stages() + assert propose_discrete.active_for_stage(dom, stages['side_obj']) + assert not propose_discrete.active_for_stage(dom, stages['on_floor']) + +def test_example_monitor(): + + desk = r.Domain({t.Semantics.Object, t.Semantics.Desk, -t.Semantics.Room}, [ + (cu.on_floor, r.Domain({t.Semantics.Room, -t.Semantics.Object}, [])), + (cu.against_wall, r.Domain({t.Semantics.Room, -t.Semantics.Object}, [])) + ]) + + monitor = r.Domain({t.Semantics.Object, t.Semantics.Chair, -t.Semantics.Room}, [ # chair vs other tags doesnt matter + (cu.ontop, desk), + (cu.against_wall, r.Domain({t.Semantics.Room, -t.Semantics.Object}, [])) + ]) + + stages = generate_indoors.default_greedy_stages() + + assert propose_discrete.active_for_stage(monitor, stages['obj_ontop_obj']) + assert not propose_discrete.active_for_stage(monitor, stages['on_wall']) + +def test_example_on_obj(): + + not_others = {-t.Semantics.LivingRoom, -t.Semantics.Hallway, -t.Semantics.Closet, -t.Semantics.Balcony, -t.Semantics.Staircase, -t.Semantics.Garage, -t.Semantics.DiningRoom, -t.Semantics.Utility, -t.Semantics.Bathroom, -t.Semantics.Kitchen} + bedroom_storage = r.Domain({t.Semantics.Object, t.Semantics.Furniture, t.Semantics.Storage}, [ + (cu.on_floor, r.Domain({t.Semantics.Bedroom, t.Semantics.Room}.union(not_others), [])), + (cu.against_wall, r.Domain({t.Semantics.Bedroom, t.Semantics.Room}.union(not_others), [])) + ]) + + obj = r.Domain({t.Semantics.OfficeShelfItem, t.Semantics.Object}, [ + (cu.on, bedroom_storage) + ]) + + onfloor = generate_indoors.default_greedy_stages()['on_floor'] + dining = r.domain_tag_substitute(onfloor, cu.variable_room, r.Domain({t.Semantics.DiningRoom})) + + assert not propose_discrete.active_for_stage(obj, dining) + +def test_active_incorrect_room(): + + onfloor = generate_indoors.default_greedy_stages()['on_floor'] + dining = r.domain_tag_substitute(onfloor, cu.variable_room, r.Domain({t.Semantics.DiningRoom})) + + sofa = r.Domain({t.Semantics.Object, t.Semantics.Seating, -t.Semantics.Room}, [ + (cu.on_floor, r.Domain({t.Semantics.LivingRoom, -t.Semantics.DiningRoom, -t.Semantics.Object}, [])) + ]) + + assert not propose_discrete.active_for_stage(sofa, dining) + +def test_stage_intersect_table(): + + onfloor = generate_indoors.default_greedy_stages()['on_floor'] + onfloor_dining = r.domain_tag_substitute(onfloor, cu.variable_room, r.Domain({t.Semantics.DiningRoom, t.SpecificObject('diningroom01')})) + + dining = r.Domain({t.Semantics.Room, t.Semantics.DiningRoom, -t.Semantics.Object}, []) + table = r.Domain({t.Semantics.Object, t.Semantics.Table, -t.Semantics.Room}, [ + (cu.on_floor, dining) + ]) + + inter = onfloor_dining.intersection(table) + assert len(inter.relations) == 2 + assert inter.relations[0][0].__class__ is cl.StableAgainst + assert inter.relations[1][0].__class__ is cl.NegatedRelation + +def test_obj_on_ceilinglight(): + + bounds = r.constraint_bounds(ex.home_constraints()) + + ceilinglight = r.Domain({t.Semantics.Object, t.Semantics.Lighting, -t.Semantics.Room}, [ + (cu.hanging, r.Domain({t.Semantics.Room, -t.Semantics.Object}, [])) + ]) + + active_bounds = [ + b for b in bounds + if propose_discrete.active_for_stage(ceilinglight, b.domain) + ] + + assert active_bounds == [] + +def test_greedy_partition_home(): + usage_lookup.initialize_from_dict(ex.home_asset_usage()) + prob = ex.home_constraints() + checks.check_problem_greedy_coverage(prob, generate_indoors.default_greedy_stages()) + +def test_contradiction_home(): + prob = ex.home_constraints() + checks.check_contradictory_domains(prob) + +@pytest.mark.parametrize('rtype', sorted(cu.room_types, key=lambda x: x.name)) +def test_room_has_viols_at_init(rtype): + + prob = ex.home_constraints() + + ostate_name = str(rtype) + state = state_def.State({ + ostate_name: state_def.ObjectState( + obj=butil.spawn_cube(), generator=None, tags={rtype, t.Semantics.Room}, relations=[] + ) + }) + + active_count = greedy.update_active_flags(state, {cu.variable_room: ostate_name}) + print("active", rtype, active_count) + assert active_count > 0 + + filter = generate_indoors.default_greedy_stages()['on_floor'] + filter = r.domain_tag_substitute(filter, cu.variable_room, r.Domain({rtype, t.Semantics.Room})) + + result = evaluator.evaluate_problem(prob, state, filter) + + assert result.viol_count() > 0 + + + + + + + + + + \ No newline at end of file diff --git a/tests/solver/test_greedy_substitutions.py b/tests/solver/test_greedy_substitutions.py new file mode 100644 index 000000000..c499628ba --- /dev/null +++ b/tests/solver/test_greedy_substitutions.py @@ -0,0 +1,120 @@ +# 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 + +from infinigen.core import tags as t + +from infinigen.core.constraints import ( + constraint_language as cl, + reasoning as r +) +from infinigen.core.constraints.example_solver import ( + state_def, + greedy +) + +def make_dummy_state(type_counts: dict[tuple[t.Tag], int]): + + objs = {} + for tags, count in type_counts.items(): + for i in range(count): + name = '_'.join([t.value for t in tags]) + f'_{i}' + objs[name] = state_def.ObjectState( + obj=None, + generator=None, + tags=set(tags).union([t.SpecificObject(name)]), + relations=[] + ) + + return state_def.State( + objs=objs + ) + +def test_substitutions_no_vars(): + state = make_dummy_state({ + (t.Semantics.Room,): 3, + }) + + var_dom = r.Domain( + {t.Semantics.Room}, + [] + ) + + subs = list(greedy.substitutions(var_dom, state)) + assert len(subs) == 1 + +def test_substitutions_simple(): + state = make_dummy_state({ + (t.Semantics.Room,): 3, + }) + + var_dom = r.Domain( + {t.Semantics.Room, t.Variable('room')}, + [] + ) + + subs = list(greedy.substitutions(var_dom, state)) + assert len(subs) == 3 + assert t.SpecificObject('room_0') in subs[0].tags + assert t.SpecificObject('room_1') in subs[1].tags + assert t.SpecificObject('room_2') in subs[2].tags + +def test_substitutions_child(): + + state = make_dummy_state({ + (t.Semantics.Room,): 4, + }) + + var_dom = r.Domain( + {t.Semantics.Object}, + [ + (cl.AnyRelation(), r.Domain({t.Semantics.Room, t.Variable('room')}, [])) + ] + ) + + subs = list(greedy.substitutions(var_dom, state)) + assert len(subs) == 4 + + +def test_substitutions_child_complex(): + + state = make_dummy_state({ + (t.Semantics.Room,): 4, + }) + + state.objs['obj_0'] = state_def.ObjectState( + obj=None, + generator=None, + tags={t.Semantics.Object, t.SpecificObject('obj_0')}, + relations=[ + state_def.RelationState( + relation=cl.Touching(), target_name='room_0' + ) + ] + ) + + state.objs['obj_1'] = state_def.ObjectState( + obj=None, + generator=None, + tags={t.Semantics.Object, t.SpecificObject('obj_1')}, + relations=[ + state_def.RelationState( + relation=cl.Touching(), target_name='room_1' + ) + ] + ) + + var_dom = r.Domain( + {t.Semantics.Object, t.Variable('obj')}, + [ + (cl.AnyRelation(), r.Domain({t.Semantics.Room, t.Variable('room')}, [])), + ] + ) + + subs = list(greedy.substitutions(var_dom, state)) + print(subs) + assert len(subs) == 2 + assert len([s for s in subs if t.SpecificObject('obj_0') in s.tags]) == 1 + assert len([s for s in subs if t.SpecificObject('obj_1') in s.tags]) == 1 \ No newline at end of file diff --git a/tests/solver/test_stable_against.py b/tests/solver/test_stable_against.py new file mode 100644 index 000000000..005574260 --- /dev/null +++ b/tests/solver/test_stable_against.py @@ -0,0 +1,164 @@ +# 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: Karhan Kayan + +import bpy +from itertools import chain +from functools import partial + +# import pytest +import numpy as np +import sys +import os + +from infinigen.core.constraints.example_solver.geometry import dof, parse_scene, planes, stability, validity +from mathutils import Vector + +from infinigen.core.constraints import ( + usage_lookup, + example_solver as solver, + constraint_language as cl +) +from infinigen.core import tagging, tags as t +from infinigen.core.util import blender as butil +from infinigen.core.constraints.example_solver import ( + state_def +) + +def make_scene(loc2): + """Create a scene with a table and a cup, and return the state.""" + butil.clear_scene() + objs = {} + + table = butil.spawn_cube(scale=(5, 5, 1), name='table') + cup = butil.spawn_cube(scale=(1, 1, 1), name='cup', location=loc2) + + for o in [table, cup]: + butil.apply_transform(o) + parse_scene.preprocess_obj(o) + tagging.tag_canonical_surfaces(o) + + assert table.scale == Vector((1,1,1)) + assert cup.location != Vector((0,0,0)) + + bpy.context.view_layer.update() + + objs['table'] = state_def.ObjectState(table) + objs['cup'] = state_def.ObjectState(cup) + objs['cup'].relations.append( + state_def.RelationState( + cl.StableAgainst({t.Subpart.Bottom}, {t.Subpart.Top}), + target_name='table', + child_plane_idx=0, + parent_plane_idx=0 + ) + ) + + # butil.save_blend('test.blend') + + return state_def.State(objs=objs) + +def test_stable_against(): + + # too low, intersects ground + assert not validity.check_post_move_validity(make_scene((0, 0, 0.5)), 'cup') + + # exactly touches surface + assert validity.check_post_move_validity(make_scene((0, 0, 1)), 'cup') + + # underneath + assert not validity.check_post_move_validity(make_scene((0, 0, -3)), 'cup') + + # exactly at corner + assert validity.check_post_move_validity(make_scene((2, 2, 1)), 'cup') + + # slightly over corner + assert not validity.check_post_move_validity(make_scene((2.1, 2.1, 1)), 'cup') + + # farr away + assert not validity.check_post_move_validity(make_scene((4, 4, 0.5)), 'cup') + +def test_horizontal_stability(): + butil.clear_scene() + objs = {} + + table = butil.spawn_cube(name='table') + table.dimensions = (4,10,2) + + chair1 = butil.spawn_cube(name='chair1') + chair1.dimensions = (2,2,3) + chair1.location = (3,3,0) + + chair2 = butil.spawn_cube(name='chair2') + chair2.dimensions = (2,2,3) + chair2.location = (3,-3,0) + + chair3 = butil.spawn_cube(name='chair3') + chair3.dimensions = (2,2,3) + chair3.location = (-3,3,0) + + chair4 = butil.spawn_cube(name='chair4') + chair4.dimensions = (2,2,3) + chair4.location = (-3,-3,0) + for o in [table, chair1, chair2, chair3, chair4]: + butil.apply_transform(o) + parse_scene.preprocess_obj(o) + tagging.tag_canonical_surfaces(o) + with butil.SelectObjects([table, chair1, chair2, chair3, chair4]): + # rotate + bpy.ops.transform.rotate(value=np.pi/4, orient_axis='Z', orient_type='GLOBAL') + # butil.save_blend('test.blend') + bpy.context.view_layer.update() + + + + objs['table'] = state_def.ObjectState(table) + objs['chair1'] = state_def.ObjectState(chair1) + objs['chair2'] = state_def.ObjectState(chair2) + objs['chair3'] = state_def.ObjectState(chair3) + objs['chair4'] = state_def.ObjectState(chair4) + objs['chair1'].relations.append( + state_def.RelationState( + cl.StableAgainst({t.Subpart.Back}, {t.Subpart.Front}, check_z=False), + target_name='table', + child_plane_idx=0, + parent_plane_idx=0 + ) + ) + objs['chair2'].relations.append( + state_def.RelationState( + cl.StableAgainst({t.Subpart.Back}, {t.Subpart.Front}, check_z=False), + target_name='table', + child_plane_idx=0, + parent_plane_idx=0 + ) + ) + objs['chair3'].relations.append( + state_def.RelationState( + cl.StableAgainst({t.Subpart.Front}, {t.Subpart.Back}, check_z=False), + target_name='table', + child_plane_idx=0, + parent_plane_idx=0 + ) + ) + objs['chair4'].relations.append( + state_def.RelationState( + cl.StableAgainst({t.Subpart.Front}, {t.Subpart.Back}, check_z=False), + target_name='table', + child_plane_idx=0, + parent_plane_idx=0 + ) + ) + state = state_def.State(objs=objs) + assert validity.check_post_move_validity(state, 'chair1') + assert validity.check_post_move_validity(state, 'chair2') + assert validity.check_post_move_validity(state, 'chair3') + assert validity.check_post_move_validity(state, 'chair4') + + # butil.save_blend('test.blend') + + + +if __name__ == '__main__': + test_horizontal_stability() \ No newline at end of file diff --git a/tests/solver/test_state_def.py b/tests/solver/test_state_def.py new file mode 100644 index 000000000..8b03fd67b --- /dev/null +++ b/tests/solver/test_state_def.py @@ -0,0 +1,44 @@ +# 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 + +from itertools import chain +from functools import partial +import json + +# import pytest +import bpy +import numpy as np +import sys +import os + +from infinigen.core.constraints.example_solver.geometry import dof, parse_scene, planes, stability, validity +from mathutils import Vector + +from infinigen.core.constraints import ( + usage_lookup, + example_solver as solver, + constraint_language as cl +) +from infinigen.core import tagging, tags as t +from infinigen.core.util import blender as butil +from infinigen.core.constraints.example_solver import ( + state_def +) + +from test_stable_against import make_scene + +def test_state_to_json(tmp_path): + + state = make_scene(Vector((1, 0, 0))) + + path = tmp_path/'state.json' + state.to_json(path) + + with path.open() as json_file: + state_json = json.load(json_file) + + assert sorted(list(state_json['objs'].keys())) == ['cup', 'table'] + assert len(state_json['objs']['cup']['relations']) == 1 \ No newline at end of file diff --git a/tests/test_materials_basic.py b/tests/test_materials_basic.py deleted file mode 100644 index c2d3aba9e..000000000 --- a/tests/test_materials_basic.py +++ /dev/null @@ -1,27 +0,0 @@ -from pathlib import Path -import importlib - -import pytest -import bpy -import gin - -from infinigen.core.util import blender as butil - -from utils import ( - setup_gin, - load_txt_list, - import_item -) - -setup_gin() - -@pytest.mark.ci -@pytest.mark.parametrize('pathspec', load_txt_list('test_materials_basic.txt')) -def test_material_runs(pathspec, **kwargs): - - butil.clear_scene() - bpy.ops.mesh.primitive_ico_sphere_add(radius=.8, subdivisions=5) - asset = bpy.context.active_object - - mat = import_item(pathspec) - mat.apply(asset) \ No newline at end of file diff --git a/tests/test_meshes_basic.py b/tests/test_meshes_basic.py deleted file mode 100644 index e840a1446..000000000 --- a/tests/test_meshes_basic.py +++ /dev/null @@ -1,22 +0,0 @@ -from pathlib import Path - -import pytest -import bpy -import gin - -from infinigen.core.util import blender as butil - -from utils import ( - setup_gin, - import_item, - load_txt_list, - check_factory_runs -) - -setup_gin() - -@pytest.mark.ci -@pytest.mark.parametrize('pathspec', load_txt_list('test_meshes_basic.txt')) -def test_factory_runs(pathspec, **kwargs): - fac_class = import_item(pathspec) - check_factory_runs(fac_class, **kwargs) \ No newline at end of file diff --git a/tests/test_terrain_basic.py b/tests/test_terrain_basic.py index 7a0b8becc..968fd9b4f 100644 --- a/tests/test_terrain_basic.py +++ b/tests/test_terrain_basic.py @@ -1,3 +1,8 @@ +# 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: Zeyu Ma + from pathlib import Path import pytest @@ -7,13 +12,16 @@ from infinigen.core.surface import registry from infinigen.core.util.organization import Task -from utils import ( +from infinigen_examples.util.test_utils import ( setup_gin, ) +@pytest.mark.skip_for_ci +@pytest.mark.nature def test_terrain_runs(): setup_gin( + 'infinigen_examples/configs_nature', configs=['fast_terrain_assets'], overrides=[ 'scene.caves_chance=1', @@ -35,4 +43,5 @@ def test_terrain_runs(): terrain = Terrain(0, registry, task=Task.Coarse, on_the_fly_asset_folder="/tmp/terrain_tests") terrain.coarse_terrain() - gin.clear_config() \ No newline at end of file + gin.clear_config() + gin.unlock_config() \ No newline at end of file diff --git a/tests/tools/test_export.py b/tests/tools/test_export.py new file mode 100644 index 000000000..92bb76183 --- /dev/null +++ b/tests/tools/test_export.py @@ -0,0 +1,80 @@ +# 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 copy + +import pytest + +import bpy + +from infinigen.tools import export + +from infinigen.assets.mollusk import MolluskFactory +from infinigen.core.util import blender as butil +from infinigen.tools.export import triangulate_meshes + +TEST_FORMATS = ["obj", "usdc", "fbx", "ply", "usdc"] +TEST_IMAGE_RES = 32 + +@pytest.mark.parametrize("format", TEST_FORMATS) +def test_export_one_obj(format, tmp_path): + + butil.clear_scene() + asset = MolluskFactory(0).spawn_asset(0) + file = export.export_single_obj(asset, tmp_path, format, image_res=TEST_IMAGE_RES) + + assert file.suffix == f".{format}" + + asset_polys = len(asset.data.polygons) + num_objs = len(bpy.data.objects) + butil.clear_scene() + new_obj = butil.import_mesh(file) + + if format == 'usdc': + assert num_objs + 1 == len(bpy.data.objects) #usdc import generates extra "world" prim + else: + assert num_objs == len(bpy.data.objects) + + assert len(new_obj.data.polygons) == asset_polys + + # TODO David Yan add other guarantees (count objects, count/names of materials, any others) + +@pytest.mark.parametrize("format", TEST_FORMATS) +def test_export_curr_scene(format, tmp_path): + + butil.clear_scene() + asset1 = MolluskFactory(0).spawn_asset(0) + asset2 = MolluskFactory(0).spawn_asset(1) + asset2.parent = asset1 + asset2.location.x += 10 + + file = export.export_curr_scene(tmp_path, format, image_res=TEST_IMAGE_RES) + assert file.suffix == f".{format}" + + num_objs = len(bpy.data.objects) + poly_count1 = len(asset1.data.polygons) + poly_count2 = len(asset2.data.polygons) + + butil.clear_scene() + butil.import_mesh(file) + total_polys = 0 + for obj in bpy.data.objects: + if obj.name == 'World': + continue + total_polys += len(obj.data.polygons) + + assert total_polys == poly_count1 + poly_count2 + + if format == 'usdc': + assert num_objs + 1 == len(bpy.data.objects) #usdc import generates extra "world" prim + elif format == 'ply': + assert len(bpy.data.objects) == 1 + else: + assert num_objs == len(bpy.data.objects) + + # TODO David Yan add other guarantees (count objects, count/names of materials, any others) + +# TODO test all export.py features, including individual export, transparent mats, instances \ No newline at end of file diff --git a/tests/utils.py b/tests/utils.py deleted file mode 100644 index 593fce048..000000000 --- a/tests/utils.py +++ /dev/null @@ -1,50 +0,0 @@ - -from pathlib import Path -import importlib - -import gin -import bpy - -from infinigen.core import surface -from infinigen.core.util import blender as butil -from infinigen.core import init - -def setup_gin(configs=None, overrides=None): - - gin.clear_config() - init.apply_gin_configs( - configs_folder='infinigen_examples/configs', - configs=configs, - overrides=overrides, - skip_unknown=True - ) - surface.registry.initialize_from_gin() - - -def import_item(name): - *path_parts, name = name.split('.') - with gin.unlock_config(): - - try: - return importlib.import_module('.' + name, '.'.join(path_parts)) - except ModuleNotFoundError: - mod = importlib.import_module('.'.join(path_parts)) - return getattr(mod, name) - -def load_txt_list(path): - res = (Path(__file__).parent/path).read_text().splitlines() - res = [f.strip() for f in res if not f.startswith('#')] - res = [f for f in res if len(f) > 0] - return sorted(res) - -def check_factory_runs(fac_class, seed1=0, seed2=0, distance_m=50): - butil.clear_scene() - fac = fac_class(seed1) - asset = fac.spawn_asset(seed2, distance=distance_m) - - for o in butil.iter_object_tree(asset): - for i, slot in enumerate(o.material_slots): - if slot.material is None: - raise ValueError(f'{asset.name=} {o.name=} had material slot {i=} {slot=} with {slot.material=}') - - assert isinstance(asset, bpy.types.Object) \ No newline at end of file