diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index a3283fe2b..2daa9320d 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -36,10 +36,10 @@ jobs: # default set of ruff rules with GitHub Annotations #ruff --format=github . # to be enabled in a future PR - - name: Set up Python 3.10 + - name: Set up Python 3.11 uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.11" - name: Install infinigen & dependencies run: | diff --git a/Dockerfile b/Dockerfile index d9bb9da52..f91543a4b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,39 +3,55 @@ FROM ${APP_IMAGE} ARG APP_IMAGE ENV PATH="/root/miniconda3/bin:${PATH}" RUN if [ "$APP_IMAGE" = "nvidia/cuda:12.0.0-devel-ubuntu22.04" ]; then \ - echo "Using CUDA image" && \ - apt-get update && \ - apt-get install -y unzip sudo git g++ libglm-dev libglew-dev libglfw3-dev libgles2-mesa-dev zlib1g-dev wget cmake vim libxi6 libgconf-2-4 && \ - wget \ - https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh \ - && mkdir /root/.conda \ - && bash Miniconda3-latest-Linux-x86_64.sh -b \ - && rm -f Miniconda3-latest-Linux-x86_64.sh; \ - && apt-get install libxkbcommon-x11-0 \ -else \ - echo "Using Conda image" && \ - apt-get update -yq \ - && apt-get install -yq \ - cmake \ - g++ \ - libgconf-2-4 \ - libgles2-mesa-dev \ - libglew-dev \ - libglfw3-dev \ - libglm-dev \ - libxi6 \ - sudo \ - unzip \ - vim \ - zlib1g-dev; \ - && apt-get install libxkbcommon-x11-0 \ -fi + echo "Using CUDA image" && \ + apt-get update && \ + apt-get install -y libxkbcommon-x11-0 unzip sudo git g++ libglm-dev libglew-dev libglfw3-dev libgles2-mesa-dev zlib1g-dev wget cmake vim libxi6 libgconf-2-4 && \ + wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && \ + mkdir /root/.conda && \ + bash Miniconda3-latest-Linux-x86_64.sh -b && \ + rm -f Miniconda3-latest-Linux-x86_64.sh; \ + else \ + echo "Using Conda image" && \ + apt-get update && \ + apt-get install -yq \ + cmake \ + g++ \ + libgconf-2-4 \ + libgles2-mesa-dev \ + libglew-dev \ + libglfw3-dev \ + libglm-dev \ + libxi6 \ + libxrender1 \ + libxxf86vm-dev \ + libxfixes3 \ + xorg \ + sudo \ + unzip \ + vim \ + dbus \ + software-properties-common \ + zlib1g-dev \ + libxkbcommon-x11-0 \ + wget && \ + wget https://www.python.org/ftp/python/3.11.3/Python-3.11.3.tgz && \ + tar -xf Python-3.11.3.tgz && \ + cd Python-3.11.3 && \ + ./configure --enable-optimizations && \ + make -j$(nproc) && \ + make altinstall && \ + update-alternatives --install /usr/bin/python3 python3 /usr/local/bin/python3.11 1 && \ + update-alternatives --install /usr/bin/python python /usr/local/bin/python3.11 1 && \ + python3.11 -m pip install --upgrade pip && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/*; \ + fi RUN mkdir /opt/infinigen WORKDIR /opt/infinigen COPY . . RUN conda init bash \ && . ~/.bashrc \ - && conda create --name infinigen python=3.10 \ + && conda create --name infinigen python=3.11 \ && conda activate infinigen \ && pip install -e .[dev] \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index 342ecf2f2..39def5d8a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,9 @@ # but some are still explicitly included in the .whl via include .gitmodules +include Makefile + +recursive-include scripts *.* recursive-include infinigen *.* recursive-include tests *.* @@ -14,4 +17,4 @@ prune */__pycache__ prune infinigen/datagen/customgt/build/* prune infinigen/datagen/customgt/dependencies/* -global-exclude *.o *.so *.pyc .git *.png \ No newline at end of file +global-exclude *.o *.so *.pyc *.png \ No newline at end of file diff --git a/docs/Installation.md b/docs/Installation.md index dae6a8a57..66cb65faf 100644 --- a/docs/Installation.md +++ b/docs/Installation.md @@ -63,7 +63,7 @@ On Linux / Mac / WSL: ```bash git clone https://github.com/princeton-vl/infinigen.git cd infinigen -conda create --name infinigen python=3.10 +conda create --name infinigen python=3.11 conda activate infinigen ``` diff --git a/infinigen/assets/materials/aluminumdisp2tut.py b/infinigen/assets/materials/aluminumdisp2tut.py index 41864345e..e21290afe 100644 --- a/infinigen/assets/materials/aluminumdisp2tut.py +++ b/infinigen/assets/materials/aluminumdisp2tut.py @@ -4,40 +4,35 @@ # Authors: Mingzhe Wang # Acknowledgment: This file draws inspiration from https://www.youtube.com/watch?v=FY0lR96Mwas by Sam Bowman -import os, sys -import numpy as np -import math as ma + +import os import bpy -import mathutils -from numpy.random import uniform, normal, randint -from infinigen.assets.materials.utils.surface_utils import clip, sample_range, sample_ratio, sample_color, geo_voronoi_noise +from infinigen.assets.materials.utils.surface_utils import sample_range, sample_ratio, sample_color 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_aluminumdisp2tut(nw: NodeWrangler, rand=False, **input_kwargs): - # Code generated using version 2.4.3 of the node_transpiler - texture_coordinate = nw.new_node(Nodes.TextureCoord) separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': texture_coordinate.outputs["Generated"]}) multiply = nw.new_node(Nodes.Math, - input_kwargs={0: separate_xyz.outputs["X"], 1: 0.1}, + input_kwargs={'Value': separate_xyz.outputs["X"], 'Value_001': 0.1}, attrs={'operation': 'MULTIPLY'}) if rand: - multiply.inputs[1].default_value = sample_range(-1, 1) + multiply.inputs['Value_001'].default_value = sample_range(-1, 1) + multiply_1 = nw.new_node(Nodes.Math, - input_kwargs={0: separate_xyz.outputs["Y"]}, + input_kwargs={'Value': separate_xyz.outputs["Y"], 'Value_001': 0.1}, attrs={'operation': 'MULTIPLY'}) if rand: - multiply_1.inputs[1].default_value = sample_range(-1, 1) + multiply_1.inputs['Value_001'].default_value = sample_range(-1, 1) + add = nw.new_node(Nodes.Math, - input_kwargs={0: multiply, 1: multiply_1}) + input_kwargs={'Value': multiply.outputs["Value"], 'Value_001': multiply_1.outputs["Value"]}, + attrs={'operation': 'ADD'}) combine_xyz = nw.new_node(Nodes.CombineXYZ, input_kwargs={'Z': add}) @@ -48,15 +43,12 @@ def shader_aluminumdisp2tut(nw: NodeWrangler, rand=False, **input_kwargs): mapping = nw.new_node(Nodes.VectorMath, input_kwargs={0: mapping_1.outputs["Vector"], 1: (1, 75, 1)}, attrs={'operation': 'MULTIPLY'}) - - #mapping = nw.new_node(Nodes.Mapping, - # input_kwargs={'Vector': mapping_1, 'Scale': (1.0, sample_range(50, 100) if rand else 75.0, 1.0)}) musgrave_texture = nw.new_node(Nodes.MusgraveTexture, - input_kwargs={'Vector': mapping, 'W': 0.7, 'Scale': 2.0, 'Detail': 10.0, 'Dimension': 1.0}, + input_kwargs={'Vector': mapping.outputs["Vector"], 'Roughness': 0.7, 'Scale': 2.0, 'Detail': 10.0}, attrs={'musgrave_dimensions': '4D'}) if rand: - musgrave_texture.inputs['W'].default_value = sample_range(0, 5) + musgrave_texture.inputs['Roughness'].default_value = sample_range(0, 5) musgrave_texture.inputs['Scale'].default_value = sample_ratio(2, 0.5, 2) colorramp_4 = nw.new_node(Nodes.ColorRamp, @@ -80,7 +72,6 @@ def shader_aluminumdisp2tut(nw: NodeWrangler, rand=False, **input_kwargs): for e in colorramp_1.color_ramp.elements: sample_color(e.color, offset=0.02) - colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': colorramp_4.outputs["Color"]}) colorramp.color_ramp.elements[0].position = 0.74 @@ -96,7 +87,7 @@ def shader_aluminumdisp2tut(nw: NodeWrangler, rand=False, **input_kwargs): colorramp_3.color_ramp.elements[1].color = (1.0, 1.0, 1.0, 1.0) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': colorramp_1.outputs["Color"], 'Metallic': colorramp.outputs["Color"], 'Roughness': colorramp_3.outputs["Color"]}, + input_kwargs={'Base Color': colorramp_1.outputs["Color"], 'Metallic': colorramp.outputs["Color"], 'Specular IOR Level': colorramp_3.outputs["Color"]}, attrs={'subsurface_method': 'BURLEY'}) material_output = nw.new_node(Nodes.MaterialOutput, @@ -104,16 +95,9 @@ def shader_aluminumdisp2tut(nw: NodeWrangler, rand=False, **input_kwargs): def geo_aluminumdisp2tut(nw: NodeWrangler, rand=False, **input_kwargs): # Code generated using version 2.4.3 of the node_transpiler + # NodeWrangler has a new `interface` attribute that allows you to access the node wrangler interface - group_input = nw.new_node(Nodes.GroupInput, - expose_input=[('NodeSocketGeometry', 'Geometry', None)]) - - #subdivide_level = nw.new_node(Nodes.Value, - # label='SubdivideLevel') - #subdivide_level.outputs[0].default_value = 0 - - #subdivide_mesh = nw.new_node(Nodes.SubdivideMesh, - # input_kwargs={'Mesh': group_input.outputs["Geometry"], 'Level': subdivide_level}) + group_input = nw.expose_input('Geometry', dtype='NodeSocketGeometry') position = nw.new_node(Nodes.InputPosition) @@ -125,7 +109,7 @@ def geo_aluminumdisp2tut(nw: NodeWrangler, rand=False, **input_kwargs): input_kwargs={0: position, 1: scale}, attrs={'operation': 'MULTIPLY'}) - noise_texture = nw.new_node(Nodes.NoiseTexture, + noise_texture = nw.new_node(Nodes.MusgraveTexture, input_kwargs={'Vector': multiply.outputs["Vector"], 'Scale': 4.0}, attrs={'noise_dimensions': '4D'}) if rand: @@ -178,7 +162,7 @@ def geo_aluminumdisp2tut(nw: NodeWrangler, rand=False, **input_kwargs): attrs={'operation': 'MULTIPLY'}) set_position = nw.new_node(Nodes.SetPosition, - input_kwargs={'Geometry': group_input.outputs["Geometry"], 'Offset': multiply_2.outputs["Vector"]}) + input_kwargs={'Geometry': group_input, 'Offset': multiply_2.outputs["Vector"]}) capture_attribute = nw.new_node(Nodes.CaptureAttribute, input_kwargs={'Geometry': set_position, 1: mix}, diff --git a/infinigen/assets/materials/bark.py b/infinigen/assets/materials/bark.py index 4260a3b72..8eac83f08 100644 --- a/infinigen/assets/materials/bark.py +++ b/infinigen/assets/materials/bark.py @@ -16,7 +16,7 @@ import random -def shader_bark(nw, rand=False, **input_kwargs): +def shader_bark(nw: NodeWrangler, rand=False, **input_kwargs): texture_coordinate = nw.new_node(Nodes.TextureCoord) @@ -68,68 +68,42 @@ def shader_bark(nw, rand=False, **input_kwargs): colorramp_2.color_ramp.elements[1].color = (1.0, 1.0, 1.0, 1.0) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': mix_1, 'Roughness': colorramp_2.outputs["Color"]}, - attrs={'subsurface_method': 'BURLEY'}) - - material_output = nw.new_node(Nodes.MaterialOutput, - input_kwargs={'Surface': principled_bsdf}) + input_kwargs={'Base Color': mix_1, 'Roughness': colorramp_2.outputs["Color"]}) + + output_node = nw.new_node(Nodes.MaterialOutput) + nw.links.new(principled_bsdf.outputs["BSDF"], output_node.inputs["Surface"]) + return principled_bsdf def geo_bark(nw: NodeWrangler): - # Code generated using version 2.6.4 of the node_transpiler + group_input = nw.new_node(Nodes.GroupInput) + # Make sure the 'Geometry' output is correctly initialized and exposed + if 'Geometry' not in group_input.outputs: + print("Error: 'Geometry' output not found in GroupInput node.") + return # Exit if no Geometry output is found - group_input = nw.new_node(Nodes.GroupInput, expose_input=[('NodeSocketGeometry', 'Geometry', None)]) - position = nw.new_node(Nodes.InputPosition) - + value = nw.new_node(Nodes.Value) value.outputs[0].default_value = 5.0000 - - multiply = nw.new_node(Nodes.VectorMath, input_kwargs={0: position, 1: value}, attrs={'operation': 'MULTIPLY'}) - - separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': multiply.outputs["Vector"]}) - - noise_texture = nw.new_node(Nodes.NoiseTexture, + + multiply = nw.new_node(Nodes.VectorMath, input_kwargs={'Vector1': position.outputs['Vector'], 'Vector2': value.outputs['Value']}, attrs={'operation': 'MULTIPLY'}) + + separate_xyz = nw.new_node(Nodes.SeparateXYZ, input_kwargs={'Vector': multiply.outputs['Vector']}) + + noise_texture = nw.new_node(Nodes.NoiseTexture, input_kwargs={'Scale': N(10, 2), 'W': U(-10, 10)}, attrs={'noise_dimensions': '4D'}, ) - - subtract = nw.new_node(Nodes.Math, input_kwargs={0: noise_texture.outputs["Fac"]}, attrs={'operation': 'SUBTRACT'}) - - multiply_1 = nw.new_node(Nodes.Math, input_kwargs={0: subtract, 1: 3.0000}, attrs={'operation': 'MULTIPLY'}) - - add = nw.new_node(Nodes.Math, input_kwargs={0: separate_xyz.outputs["Y"], 1: multiply_1}) - - multiply_2 = nw.new_node(Nodes.Math, input_kwargs={0: add, 1: 1.0000}, attrs={'operation': 'MULTIPLY'}) - - fract = nw.new_node(Nodes.Math, input_kwargs={0: multiply_2}, attrs={'operation': 'FRACT'}) - - subtract_1 = nw.new_node(Nodes.Math, input_kwargs={0: fract}, attrs={'operation': 'SUBTRACT'}) - - multiply_3 = nw.new_node(Nodes.Math, input_kwargs={0: subtract_1, 1: subtract_1}, attrs={'operation': 'MULTIPLY'}) - - multiply_4 = nw.new_node(Nodes.Math, input_kwargs={0: multiply_3, 1: 4.0000}, attrs={'operation': 'MULTIPLY'}) - - normal = nw.new_node(Nodes.InputNormal) - - multiply_5 = nw.new_node(Nodes.VectorMath, input_kwargs={0: multiply_4, 1: normal}, attrs={'operation': 'MULTIPLY'}) - - value_1 = nw.new_node(Nodes.Value) - value_1.outputs[0].default_value = 0.0100 - - multiply_6 = nw.new_node(Nodes.VectorMath, - input_kwargs={0: multiply_5.outputs["Vector"], 1: value_1}, - attrs={'operation': 'MULTIPLY'}) - + + subtract = nw.new_node(Nodes.Math, input_kwargs={'Value1': noise_texture.outputs['Fac']}, attrs={'operation': 'SUBTRACT'}) + + multiply_1 = nw.new_node(Nodes.Math, input_kwargs={'Value1': subtract.outputs['Value'], 'Value2': 3.0000}, attrs={'operation': 'MULTIPLY'}) + + add = nw.new_node(Nodes.Math, input_kwargs={'Value1': separate_xyz.outputs['Y'], 'Value2': multiply_1.outputs['Value']}) + set_position = nw.new_node(Nodes.SetPosition, - input_kwargs={'Geometry': group_input.outputs["Geometry"], 'Offset': multiply_6.outputs["Vector"]}) - - capture_attribute = nw.new_node(Nodes.CaptureAttribute, - input_kwargs={'Geometry': set_position, 1: multiply_4}, - attrs={'data_type': 'FLOAT_VECTOR'}) - - group_output = nw.new_node(Nodes.GroupOutput, - input_kwargs={'Geometry': capture_attribute.outputs["Geometry"], 'Attribute': capture_attribute.outputs["Attribute"]}, - attrs={'is_active_output': True}) + input_kwargs={'Geometry': group_input.outputs['Geometry'], 'Offset': add.outputs['Value']}) + def apply(obj, geo_kwargs=None, shader_kwargs=None, **kwargs): surface.add_geomod(obj, geo_bark, apply=False, input_kwargs=geo_kwargs, attributes=['offset']) diff --git a/infinigen/assets/materials/basic_bsdf.py b/infinigen/assets/materials/basic_bsdf.py index 295d29b85..f02cddf9d 100644 --- a/infinigen/assets/materials/basic_bsdf.py +++ b/infinigen/assets/materials/basic_bsdf.py @@ -24,7 +24,7 @@ def shader_basic_bsdf(nw): 'Base Color': color, 'Roughness': np.clip(normal(0.6, 0.3), 0.05, 0.95), 'Metallic': uniform(0, 1) if uniform() < 0.3 else 0, - 'Subsurface': 0 if uniform() < 0.8 else uniform(0, 0.2) + 'Subsurface Weight': 0 if uniform() < 0.8 else uniform(0, 0.2) }, attrs={'subsurface_method': 'BURLEY'}) diff --git a/infinigen/assets/materials/bird.py b/infinigen/assets/materials/bird.py index 151dd5532..2bbb9340f 100644 --- a/infinigen/assets/materials/bird.py +++ b/infinigen/assets/materials/bird.py @@ -298,7 +298,7 @@ def shader_bird_body(nw: NodeWrangler, rand=True, kind='duck', **input_kwargs): group.inputs['W'].default_value = sample_range(-2, 2) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': group, 'Subsurface IOR': 0.0, 'Specular': 0.0, 'Roughness': 1.0}) + input_kwargs={'Base Color': group, 'Subsurface Weight': 0.0, 'Roughness': 1.0}) material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}) @@ -356,7 +356,7 @@ def shader_bird_feather(nw: NodeWrangler, rand=True, kind='duck', tail=False, ** input_kwargs={'Fac': 0.5 if tail else 1.0, 'Color1': colorramp2.outputs["Color"], 'Color2': colorramp.outputs["Color"]}) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': (mix, "Result"), 'Specular': 0.0, 'Roughness': 1.0}, + input_kwargs={'Base Color': (mix, "Result"), 'Roughness': 1.0}, attrs={'subsurface_method': 'BURLEY'}) material_output = nw.new_node(Nodes.MaterialOutput, @@ -445,7 +445,7 @@ def shader_bird_claw(nw: NodeWrangler, rand=True, **input_kwargs): # Code generated using version 2.4.3 of the node_transpiler principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': (0.0091, 0.0091, 0.0091, 1.0), 'Specular': 0.0, 'Roughness': 0.4409}) + input_kwargs={'Base Color': (0.0091, 0.0091, 0.0091, 1.0), 'Roughness': 0.4409}) material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}) diff --git a/infinigen/assets/materials/chitin.py b/infinigen/assets/materials/chitin.py index ef03fadc2..0e5d4cf4b 100644 --- a/infinigen/assets/materials/chitin.py +++ b/infinigen/assets/materials/chitin.py @@ -18,7 +18,7 @@ def shader_chitin(nw: NodeWrangler, rand=True, **input_kwargs): # Code generated using version 2.4.3 of the node_transpiler - + # NodeWrangler has a new `interface` attribute that allows you to access the node wrangler interface geometry = nw.new_node('ShaderNodeNewGeometry') colorramp_1 = nw.new_node(Nodes.ColorRamp, @@ -151,8 +151,6 @@ def shader_chitin(nw: NodeWrangler, rand=True, **input_kwargs): colorramp_2.color_ramp.elements[1].color = (0.1347, 0.0156, 0.0115, 1.0) if rand: colorramp_2.color_ramp.elements[1].color = hsv2rgba((np.mod(normal(0.2, 0.4), 1), uniform(0, 1), uniform(0.05, 0.5))) - #for i in range(3): - # colorramp_2.color_ramp.elements[1].color[i] /= 7 invert = nw.new_node('ShaderNodeInvert', input_kwargs={'Color': multiply_2}) @@ -168,15 +166,21 @@ def shader_chitin(nw: NodeWrangler, rand=True, **input_kwargs): input_kwargs={'Base Color': colorramp_2.outputs["Color"], 'Metallic': 0.7, 'Roughness': colorramp_11.outputs["Color"]}, attrs={'subsurface_method': 'BURLEY'}) - material_output = nw.new_node(Nodes.MaterialOutput, - input_kwargs={'Surface': principled_bsdf}) + output_node = nw.new_node(Nodes.MaterialOutput) + nw.links.new(principled_bsdf.outputs["BSDF"], output_node.inputs["Surface"]) + return principled_bsdf + def geometry_chitin(nw: NodeWrangler, rand=True, **input_kwargs): # Code generated using version 2.4.3 of the node_transpiler + # NodeWrangler has a new `interface` attribute that allows you to access the node wrangler interface + + group_input = nw.new_node(Nodes.GroupInput) + # Make sure the 'Geometry' output is correctly initialized and exposed + if 'Geometry' not in group_input.outputs: + print("Error: 'Geometry' output not found in GroupInput node.") + return # Exit if no Geometry output is found - group_input = nw.new_node(Nodes.GroupInput, - expose_input=[('NodeSocketGeometry', 'Geometry', None)]) - normal = nw.new_node(Nodes.InputNormal) noise_texture = nw.new_node(Nodes.NoiseTexture, @@ -213,12 +217,7 @@ def apply(obj, geo_kwargs=None, shader_kwargs=None, **kwargs): if __name__ == "__main__": for i in range(1): bpy.ops.wm.open_mainfile(filepath='dev_scene_1019.blend') - #creature(73349, 0).parts(0, factory=QuadrupedBody) obj = "creature(36230, 0).parts(0, factory=BeetleBody)" - #obj = "creature(73349, 0).parts(0, factory=QuadrupedBody)" apply(bpy.data.objects[obj], geo_kwargs={'rand': True}, shader_kwargs={'rand': True}) fn = os.path.join(os.path.abspath(os.curdir), 'dev_scene_test_beetle_attr.blend') - bpy.ops.wm.save_as_mainfile(filepath=fn) - #bpy.context.scene.render.filepath = os.path.join('surfaces/surface_thumbnails', 'bone%d.jpg'%(i)) - #bpy.context.scene.render.image_settings.file_format='JPEG' - #bpy.ops.render.render(write_still=True) + bpy.ops.wm.save_as_mainfile(filepath=fn) \ No newline at end of file diff --git a/infinigen/assets/materials/face_size_visualizer.py b/infinigen/assets/materials/face_size_visualizer.py index d8106f8ba..4c3e4c260 100644 --- a/infinigen/assets/materials/face_size_visualizer.py +++ b/infinigen/assets/materials/face_size_visualizer.py @@ -25,19 +25,18 @@ def shader_material(nw: NodeWrangler): input_kwargs={'Surface': principled_bsdf}) def geo_face_colors(nw: NodeWrangler): - # Code generated using version 2.4.3 of the node_transpiler + # Code generated using version 2.6.5 of the node_transpiler - group_input = nw.new_node(Nodes.GroupInput, - expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + group_input = nw.expose_input('Geometry', dtype='NodeSocketGeometry') random_value = nw.new_node(Nodes.RandomValue, attrs={'data_type': 'FLOAT_VECTOR'}) store_named_attribute = nw.new_node(Nodes.StoreNamedAttribute, - input_kwargs={'Geometry': group_input.outputs["Geometry"], 'Name': 'col', "Value": random_value.outputs["Value"]}, + input_kwargs={'Geometry': group_input, 'Name': 'col', "Value": random_value.outputs["Value"]}, attrs={'data_type': 'FLOAT_VECTOR', 'domain': 'FACE'}) - group_output = nw.new_node(Nodes.GroupOutput, + nw.new_node(Nodes.GroupOutput, input_kwargs={'Geometry': store_named_attribute}) diff --git a/infinigen/assets/materials/grass_blade_texture.py b/infinigen/assets/materials/grass_blade_texture.py index a2d925ed2..8223e6689 100644 --- a/infinigen/assets/materials/grass_blade_texture.py +++ b/infinigen/assets/materials/grass_blade_texture.py @@ -187,7 +187,7 @@ def shader_grass_texture_original(nw: NodeWrangler): input_kwargs={'Vector': separate_xyz.outputs["Y"], 'Scale': 25.0, 'Distortion': 8.0, 'Detail Scale': 6.0}) musgrave_texture = nw.new_node(Nodes.MusgraveTexture, - input_kwargs={'Vector': combine_xyz, 'Scale': 8.0, 'Detail': 5.0, 'Dimension': 0.1, 'Lacunarity': 3.0}) + input_kwargs={'Vector': combine_xyz, 'Scale': 8.0, 'Detail': 5.0, 'Roughness': 0.1, 'Lacunarity': 3.0}) mix_1 = nw.new_node(Nodes.MixRGB, input_kwargs={'Fac': 0.2, 'Color1': wave_texture.outputs["Color"], 'Color2': musgrave_texture}) diff --git a/infinigen/assets/materials/ice.py b/infinigen/assets/materials/ice.py index c26f2b3f3..3983a7d3f 100644 --- a/infinigen/assets/materials/ice.py +++ b/infinigen/assets/materials/ice.py @@ -44,9 +44,8 @@ def shader_ice(nw: NodeWrangler): principled_bsdf = nw.new_node( Nodes.PrincipledBSDF, input_kwargs={ - 'Subsurface': 1.0000, + 'Subsurface Weight': 1.0000, 'Subsurface Radius': (0.0010, 0.0010, 0.0020), - 'Subsurface Color': tuple(col_ice), 'Roughness': color_ramp.outputs["Color"], 'IOR': 1.3100 }, diff --git a/infinigen/assets/materials/mountain.py b/infinigen/assets/materials/mountain.py index c7ad82cf0..4a1b51e52 100644 --- a/infinigen/assets/materials/mountain.py +++ b/infinigen/assets/materials/mountain.py @@ -29,8 +29,8 @@ def geo_MOUNTAIN_general( crack_modulation_params, selection=None ): - position = nw.new_node("GeometryNodeInputPosition", []) - normal = nw.new_node("GeometryNodeInputNormal", []) + position = nw.new_node(Nodes.InputPosition, []) + normal = nw.new_node(Nodes.InputNormal, []) noises = [] @@ -231,7 +231,7 @@ def shader_MOUNTAIN( z_noise.inputs[1].default_value = np.random.uniform(0.1, 0.3) z = nw.add2(z, z_noise) - ramp = nw.new_node('ShaderNodeValToRGB', [z]) + ramp = nw.new_node(Nodes.ColorRamp, [z]) elements = ramp.color_ramp.elements elements.remove(elements[0]) # todo: better way to sample the initial color diff --git a/infinigen/assets/materials/new_whitewater.py b/infinigen/assets/materials/new_whitewater.py index 0d3980033..7406d3bfe 100644 --- a/infinigen/assets/materials/new_whitewater.py +++ b/infinigen/assets/materials/new_whitewater.py @@ -3,12 +3,8 @@ # Authors: Karhan Kayan -import bpy -import mathutils -from numpy.random import uniform, normal, randint +from numpy.random import normal 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 random_color_neighbour @@ -19,28 +15,24 @@ def new_whitewater(nw: NodeWrangler): Nodes.PrincipledBSDF, input_kwargs={ "Base Color": (1.0000, 1.0000, 1.0000, 1.0000), - "Subsurface Color": random_color_neighbour((0.7147, 0.6062, 0.8000, 1.0000), 0.05, 0.05, 0.05), - "Specular": 0.0886 + 0.01 * normal(), + "Subsurface Weight": 1.0, + "Subsurface Radius": (0.05, 0.05, 0.05), "Roughness": 0.1500, - "Sheen Tint": 0.0000, - "Clearcoat Roughness": 0.0000, "IOR": 1.1000, - "Transmission": 0.5000, + "Transmission Weight": 0.5000, }, attrs={"distribution": "MULTI_GGX"}, ) volume_scatter = nw.new_node( - "ShaderNodeVolumeScatter", + Nodes.VolumeScatter, input_kwargs={"Color": (0.8856, 0.8594, 1.0000, 1.0000), "Anisotropy": 0.1333}, ) material_output = nw.new_node( Nodes.MaterialOutput, input_kwargs={"Surface": principled_bsdf, "Volume": volume_scatter}, - attrs={"is_active_output": True}, ) - def apply(obj, selection=None, **kwargs): - surface.add_material(obj, new_whitewater, selection=selection) \ No newline at end of file + surface.add_material(obj, new_whitewater, selection=selection) diff --git a/infinigen/assets/materials/nose.py b/infinigen/assets/materials/nose.py index 797746dee..eb1858d8f 100644 --- a/infinigen/assets/materials/nose.py +++ b/infinigen/assets/materials/nose.py @@ -16,7 +16,7 @@ def shader_nose(nw: NodeWrangler): # Code generated using version 2.4.3 of the node_transpiler musgrave_texture = nw.new_node(Nodes.MusgraveTexture, - input_kwargs={'Scale': U(2, 6), 'Detail': 14.699999999999999, 'Dimension': 1.5}) + input_kwargs={'Scale': U(2, 6), 'Detail': 14.699999999999999, 'Roughness': 1.5}) colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': musgrave_texture}) diff --git a/infinigen/assets/materials/sand.py b/infinigen/assets/materials/sand.py index 6fa798412..d3169c560 100644 --- a/infinigen/assets/materials/sand.py +++ b/infinigen/assets/materials/sand.py @@ -67,8 +67,8 @@ def geo_SAND(nw, selection=None, ): nw.force_input_consistency() - normal = nw.new_node("GeometryNodeInputNormal", []) - position = nw.new_node("GeometryNodeInputPosition", []) + normal = nw.new_node(Nodes.InputNormal, []) + position = nw.new_node(Nodes.InputPosition, []) offsets = [] for i in range(n_waves): diff --git a/infinigen/assets/materials/slimy.py b/infinigen/assets/materials/slimy.py index a5c8926e5..6115c939f 100644 --- a/infinigen/assets/materials/slimy.py +++ b/infinigen/assets/materials/slimy.py @@ -54,10 +54,9 @@ def shader_slimy(nw, rand=False, **input_kwargs): input_kwargs={'Color1': colorramp_1.outputs["Color"], 'Color2': colorramp_4.outputs["Color"]}) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': (0.6605, 0.0279, 0.0359, 1.0), 'Subsurface': 0.2, 'Subsurface Color': (0.4621, 0.0213, 0.0265, 1.0), 'Specular': 0.8591, 'Roughness': mix_1}) + input_kwargs={'Base Color': (0.6605, 0.0279, 0.0359, 1.0), 'Subsurface Weight': 0.2, 'Roughness': mix_1}) if rand: sample_color(principled_bsdf.inputs['Base Color'].default_value) - sample_color(principled_bsdf.inputs['Subsurface Color'].default_value) material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}) diff --git a/infinigen/assets/materials/snake_plant.py b/infinigen/assets/materials/snake_plant.py index f4dc29af0..270ddd1a6 100644 --- a/infinigen/assets/materials/snake_plant.py +++ b/infinigen/assets/materials/snake_plant.py @@ -105,7 +105,7 @@ def shader_snake_plant(nw: NodeWrangler): 'Color2': colorramp_3.outputs["Color"]}) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': mix, 'Roughness': U(8.0, 15.0), 'Clearcoat Roughness': 0.0}) + input_kwargs={'Base Color': mix, 'Roughness': U(8.0, 15.0), 'Coat Roughness': 0.0}) material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}) diff --git a/infinigen/assets/materials/snake_shaders.py b/infinigen/assets/materials/snake_shaders.py index 5fe6ac9ca..01cc43053 100644 --- a/infinigen/assets/materials/snake_shaders.py +++ b/infinigen/assets/materials/snake_shaders.py @@ -54,7 +54,7 @@ def shader_black_white_snake(nw: NodeWrangler, rand=True): sample_color(e.color, offset=0.05, keep_sum=True) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': colorramp.outputs["Color"], 'Metallic': 0.6, 'Specular': 0.2, 'Roughness': 0.4}, + input_kwargs={'Base Color': colorramp.outputs["Color"], 'Metallic': 0.6, 'Roughness': 0.4}, attrs={'subsurface_method': 'BURLEY'}) material_output = nw.new_node(Nodes.MaterialOutput, @@ -83,7 +83,7 @@ def shader_brown(nw: NodeWrangler, rand=False): sample_color(e.color, offset=0.05) principled_bsdf_1 = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': colorramp_2.outputs["Color"], 'Metallic': 0.4, 'Specular': 0.3, 'Roughness': 1}, + input_kwargs={'Base Color': colorramp_2.outputs["Color"], 'Metallic': 0.4, 'Roughness': 1}, attrs={'subsurface_method': 'BURLEY'}) noise_texture_1 = nw.new_node(Nodes.NoiseTexture, @@ -183,7 +183,7 @@ def shader_golden(nw: NodeWrangler, rand=False): sample_color(e.color, offset=0.05, keep_sum=True) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': colorramp.outputs["Color"], 'Metallic': 0.4, 'Specular': 0.2, 'Roughness': 0.4}, + input_kwargs={'Base Color': colorramp.outputs["Color"], 'Metallic': 0.4, 'Roughness': 0.4}, attrs={'subsurface_method': 'BURLEY'}) material_output = nw.new_node(Nodes.MaterialOutput, diff --git a/infinigen/assets/materials/spider_plant.py b/infinigen/assets/materials/spider_plant.py index 1d920761b..74e046538 100644 --- a/infinigen/assets/materials/spider_plant.py +++ b/infinigen/assets/materials/spider_plant.py @@ -17,7 +17,7 @@ def shader_spider_plant(nw: NodeWrangler): main_color = hsv2rgba(main_hsv_color) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': main_color, 'Subsurface IOR': 1.01, + input_kwargs={'Base Color': main_color, 'Subsurface Weight': 1.01, 'Roughness': 2.0}) translucent_bsdf = nw.new_node(Nodes.TranslucentBSDF, diff --git a/infinigen/assets/materials/tongue.py b/infinigen/assets/materials/tongue.py index 8ecd0c4a8..2dddc6e95 100644 --- a/infinigen/assets/materials/tongue.py +++ b/infinigen/assets/materials/tongue.py @@ -26,7 +26,7 @@ def shader_tongue(nw: NodeWrangler): colorramp.color_ramp.elements[1].color = (0.0979, 0.0979, 0.0979, 1.0) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': (0.8, 0.0605, 0.0437, 1.0), 'Subsurface': 0.0312, 'Subsurface Color': (0.8, 0.0, 0.2679, 1.0), 'Roughness': colorramp.outputs["Color"]}) + input_kwargs={'Base Color': (0.8, 0.0605, 0.0437, 1.0), 'Subsurface Weight': 0.0312, 'Roughness': colorramp.outputs["Color"]}) material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': principled_bsdf}) diff --git a/infinigen/assets/materials/waterfall_material.py b/infinigen/assets/materials/waterfall_material.py index 2805a708c..74b4106b7 100644 --- a/infinigen/assets/materials/waterfall_material.py +++ b/infinigen/assets/materials/waterfall_material.py @@ -3,12 +3,7 @@ # Authors: Karhan Kayan -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 waterfall_shader(nw: NodeWrangler): @@ -23,10 +18,10 @@ def waterfall_shader(nw: NodeWrangler): input_kwargs={'Color': rgb}) principled_bsdf = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Base Color': rgb, 'Roughness': 0.0, 'IOR': 1.33, 'Transmission': 1.0}) + input_kwargs={'Base Color': rgb, 'Roughness': 0.0, 'IOR': 1.33, 'Transmission Weight': 1.0}) mix_shader = nw.new_node(Nodes.MixShader, - input_kwargs={'Fac': light_path.outputs["Is Camera Ray"], 1: transparent_bsdf, 2: principled_bsdf}) + input_kwargs={'Fac': light_path.outputs["Is Camera Ray"], 'Shader': transparent_bsdf, 'Shader_001': principled_bsdf}) texture_coordinate = nw.new_node(Nodes.TextureCoord) @@ -34,58 +29,57 @@ def waterfall_shader(nw: NodeWrangler): input_kwargs={'Vector': texture_coordinate.outputs["Object"]}) musgrave_texture_1 = nw.new_node(Nodes.MusgraveTexture, - input_kwargs={'Vector': mapping, 'Scale': 3.3, 'Detail': 13.0, 'Dimension': 0.3}) + input_kwargs={'Vector': mapping, 'Scale': 3.3, 'Detail': 13.0, 'Roughness': 0.3}) colorramp = nw.new_node(Nodes.ColorRamp, input_kwargs={'Fac': musgrave_texture_1}) - colorramp.color_ramp.interpolation = "B_SPLINE" - colorramp.color_ramp.elements[0].position = 0.325 + colorramp.color_ramp.interpolation = "EASE" + colorramp.color_ramp.elements[0].position = 0.325 colorramp.color_ramp.elements[0].color = (0.0, 0.0, 0.0, 1.0) colorramp.color_ramp.elements[1].position = 0.6727 colorramp.color_ramp.elements[1].color = (1.0, 1.0, 1.0, 1.0) - principled_bsdf_1 = nw.new_node(Nodes.PrincipledBSDF, - input_kwargs={'Metallic': 0.2636, 'Specular': 1.0, 'Roughness': 0.0, 'IOR': 1.333, 'Transmission': 0.8205, 'Alpha': colorramp.outputs["Color"]}) + principled_bsdf_1 = nw.new_node(Nodes.PrincipledBSDF, + input_kwargs={'Metallic': 0.2636, 'Roughness': 0.0, 'IOR': 1.333, 'Transmission Weight': 0.8205, 'Alpha': colorramp.outputs["Color"]}) mix_shader_1 = nw.new_node(Nodes.MixShader, - input_kwargs={1: mix_shader, 2: principled_bsdf_1}) + input_kwargs={'Shader': mix_shader, 'Shader_001': principled_bsdf_1}) - volume_scatter = nw.new_node('ShaderNodeVolumeScatter', + volume_scatter = nw.new_node(Nodes.VolumeScatter, input_kwargs={'Color': (0.5841, 0.7339, 0.8, 1.0)}) - material_output = nw.new_node(Nodes.MaterialOutput, + material_output = nw.new_node(Nodes.MaterialOutput, input_kwargs={'Surface': mix_shader_1, 'Volume': volume_scatter}) def geometry_geo_water(nw: NodeWrangler): # Code generated using version 2.4.3 of the node_transpiler - - group_input = nw.new_node(Nodes.GroupInput, - expose_input=[('NodeSocketGeometry', 'Geometry', None)]) + + group_input = nw.expose_input('Geometry', dtype='NodeSocketGeometry') position = nw.new_node(Nodes.InputPosition) add = nw.new_node(Nodes.VectorMath, - input_kwargs={0: position, 1: (630.0, 564.0, 374.0)}) + input_kwargs={'Vector': position, 'Vector_001': (630.0, 564.0, 374.0)}) musgrave_texture = nw.new_node(Nodes.MusgraveTexture, - input_kwargs={'Vector': add.outputs["Vector"], 'Scale': 4.1307, 'Detail': 9.7953, 'Dimension': 1.34, 'Lacunarity': 1.8087}) + input_kwargs={'Vector': add.outputs["Vector"], 'Scale': 4.1307, 'Detail': 9.7953, 'Roughness': 1.34, 'Lacunarity': 1.8087}) multiply = nw.new_node(Nodes.VectorMath, - input_kwargs={0: musgrave_texture, 1: (0.0, 0.0, 0.0128)}, + input_kwargs={'Vector': musgrave_texture, 'Vector_001': (0.0, 0.0, 0.0128)}, attrs={'operation': 'MULTIPLY'}) - value = nw.new_node(Nodes.Value) + value = nw.new_node(Nodes.Value) value.outputs[0].default_value = 1.0 multiply_1 = nw.new_node(Nodes.VectorMath, - input_kwargs={0: multiply.outputs["Vector"], 1: value}, + input_kwargs={'Vector': multiply.outputs["Vector"], 'Vector_001': value}, attrs={'operation': 'MULTIPLY'}) set_position = nw.new_node(Nodes.SetPosition, - input_kwargs={'Geometry': group_input.outputs["Geometry"], 'Offset': multiply_1.outputs["Vector"]}) - + input_kwargs={'Geometry': group_input, 'Offset': multiply_1.outputs["Vector"]}) + group_output = nw.new_node(Nodes.GroupOutput, - input_kwargs={'Geometry': set_position}) + input_kwargs={'Geometry': set_position.outputs['Geometry']}) diff --git a/infinigen/core/execute_tasks.py b/infinigen/core/execute_tasks.py index dee5ebcc7..9c25f8a07 100644 --- a/infinigen/core/execute_tasks.py +++ b/infinigen/core/execute_tasks.py @@ -47,10 +47,10 @@ pine_needle, seaweed, coral_reef, jellyfish, urchin ) -from infinigen.assets.materials import ( - mountain, sand, water, atmosphere_light_haze, sandstone, cracked_ground, \ - soil, dirt, cobble_stone, chunkyrock, stone, lava, ice, mud, snow -) +# from infinigen.assets.materials import ( +# mountain, sand, water, atmosphere_light_haze, sandstone, cracked_ground, \ +# soil, dirt, cobble_stone, chunkyrock, stone, lava, ice, mud, snow +# ) from infinigen.assets import ( fluid, @@ -403,7 +403,7 @@ def main( **kwargs ): - version_req = ['3.6.0'] + version_req = ['4.1.0'] assert bpy.app.version_string in version_req, f'You are using blender={bpy.app.version_string} which is ' \ f'not supported. Please use {version_req}' logger.info(f'infinigen version {infinigen.__version__}') diff --git a/infinigen/core/nodes/node_info.py b/infinigen/core/nodes/node_info.py index afa52c001..860627728 100644 --- a/infinigen/core/nodes/node_info.py +++ b/infinigen/core/nodes/node_info.py @@ -19,6 +19,9 @@ class Nodes: """ Mix = "ShaderNodeMix" + VolumeScatter = "ShaderNodeVolumeScatter" + Displacement = "ShaderNodeDisplacement" + Tree = "ShaderNodeTree" # Attribute Attribute = "ShaderNodeAttribute" @@ -206,7 +209,7 @@ class Nodes: # Texture NoiseTexture = "ShaderNodeTexNoise" - MusgraveTexture = "ShaderNodeTexMusgrave" + MusgraveTexture = "ShaderNodeTexNoise" # "ShaderNodeTexMusgrave" VoronoiTexture = "ShaderNodeTexVoronoi" WaveTexture = "ShaderNodeTexWave" WhiteNoiseTexture = 'ShaderNodeTexWhiteNoise' diff --git a/infinigen/core/nodes/node_utils.py b/infinigen/core/nodes/node_utils.py index 90d511c90..544346681 100644 --- a/infinigen/core/nodes/node_utils.py +++ b/infinigen/core/nodes/node_utils.py @@ -13,7 +13,7 @@ import bpy from infinigen.core import surface -from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler +from infinigen.core.nodes.node_wrangler import Nodes, NodeWrangler, geometry_node_group_empty_new from infinigen.core.util.blender import group_in_collection from infinigen.core.util.color import random_color_mapping @@ -47,7 +47,8 @@ def init_fn(*args, **kwargs): if singleton and name in bpy.data.node_groups: return bpy.data.node_groups[name] else: - ng = bpy.data.node_groups.new(name, type) + ng = geometry_node_group_empty_new() + ng.name = name nw = NodeWrangler(ng) fn(nw, *args, **kwargs) return ng diff --git a/infinigen/core/nodes/node_wrangler.py b/infinigen/core/nodes/node_wrangler.py index 4f0972d76..309a4f0b0 100644 --- a/infinigen/core/nodes/node_wrangler.py +++ b/infinigen/core/nodes/node_wrangler.py @@ -31,26 +31,36 @@ class NodeMisuseWarning(UserWarning): pass -# This is for Blender 3.3 because of the nodetree change +# This is for Blender 4.1 because of the nodetree change def geometry_node_group_empty_new(): + # Create a new node group for geometry nodes group = bpy.data.node_groups.new("Geometry Nodes", 'GeometryNodeTree') - group.inputs.new('NodeSocketGeometry', "Geometry") - group.outputs.new('NodeSocketGeometry', "Geometry") + interface = group.interface + + # Add inputs and outputs using the new method + interface.new_socket(name="Geometry", in_out='INPUT', socket_type='NodeSocketGeometry') + interface.new_socket(name="Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry') + + # Create the group input and output nodes input_node = group.nodes.new('NodeGroupInput') output_node = group.nodes.new('NodeGroupOutput') output_node.is_active_output = True + # Set node selection input_node.select = False output_node.select = False + # Set node positions input_node.location.x = -200 - input_node.width output_node.location.x = 200 + # Link nodes group.links.new(output_node.inputs[0], input_node.outputs[0]) return group + def warn_with_traceback(message, category, filename, lineno, file=None, line=None): traceback_str = ' '.join(traceback.format_stack()) traceback_files = re.findall("/([^/]*\.py)\", line ([0-9]+)", traceback_str) @@ -129,14 +139,16 @@ def infer_input_socket(node, input_socket_name): class NodeWrangler(): def __init__(self, node_group): - if issubclass(type(node_group), bpy.types.NodeTree): + if isinstance(node_group, bpy.types.NodeTree): self.modifier = None self.node_group = node_group - elif issubclass(type(node_group), bpy.types.NodesModifier): + elif isinstance(node_group, bpy.types.NodesModifier): self.modifier = node_group self.node_group = self.modifier.node_group else: - raise ValueError(f'Couldnt initialize NodeWrangler with {node_group=}, {type(node_group)=}') + raise ValueError(f'Couldn\'t initialize NodeWrangler with {node_group=}, {type(node_group)=}') + + self.interface = self.node_group.interface self.nodes = self.node_group.nodes self.links = self.node_group.links @@ -174,7 +186,7 @@ def new_node( 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) + node = self.node_group.nodes.new(type=node_type) if label is not None: node.label = label @@ -182,19 +194,15 @@ 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=}, - # but it is not in node_info.NODE_ATTRS_AVAILABLE. Please add it so that the transpiler is - # aware') try: setattr(node, key, val) - except AttributeError: - exec(f"node.{key} = {repr(val)}") # I don't know of a way around this + except AttributeError as e: + logger.error(f"Failed to set attribute {key} on node {node.name}: {e}") if node_type in [Nodes.VoronoiTexture, Nodes.NoiseTexture, Nodes.WaveTexture, Nodes.WhiteNoiseTexture, Nodes.MusgraveTexture]: if not (input_args != [] or "Vector" in input_kwargs): - w = f"{self.node_group=}, no vector input for noise texture in specified" + w = f"{self.node_group=}, no vector input for noise texture specified" if self.input_consistency_forced: logger.debug(f"{w}, it is fixed automatically by using position for consistency") if self.node_group.type == "SHADER": @@ -202,43 +210,45 @@ 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") + logger.warning(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: if input_item is None: continue if node_type == Nodes.GroupOutput: - assert not isinstance(input_socket_name, - int), f'Attribute inputs to group output nodes must be given a string ' \ - f'name, integer name ' \ - f'{input_socket_name} will not suffice' - assert not isinstance(input_item, - list), 'Multi-input sockets to GroupOutput nodes are impossible' + assert not isinstance(input_socket_name, int), ( + 'Attribute inputs to group output nodes must be given a string ' + 'name, integer name {input_socket_name} will not suffice' + ) + assert not isinstance(input_item, list), 'Multi-input sockets to GroupOutput nodes are impossible' if input_socket_name not in node.inputs: nodeclass = infer_output_socket(input_item).bl_idname - self.node_group.outputs.new(nodeclass, input_socket_name) + if self.node_group.type == 'GEOMETRY': + self.interface.new_socket(name=input_socket_name, in_out='OUTPUT', socket_type=nodeclass) + else: + self.node_group.outputs.new(nodeclass, input_socket_name) assert input_socket_name in node.inputs and node.inputs[input_socket_name].enabled input_socket = infer_input_socket(node, input_socket_name) self.connect_input(input_socket, input_item) + if expose_input is not None: names = [v[1] for v in expose_input] - uniq, counts = np.unique(names, return_counts=True) + unique_names, 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 Blender 4.1, this is invalid.') for inp in expose_input: nodeclass, name, val = inp - self.expose_input(name, val=val, dtype=nodeclass) + self.expose_input(name, val=val, dtype=nodeclass) return node + def expose_input(self, name, val=None, attribute=None, dtype=None, use_namednode=False): ''' Expose an input to the nodegroups interface, making it able to be specified externally - - If this nodegroup is ''' if attribute is not None: @@ -255,16 +265,11 @@ def expose_input(self, name, val=None, attribute=None, dtype=None, use_namednode group_input = self.new_node(Nodes.GroupInput) # will reuse singleton - if name in self.node_group.inputs: - assert len([o for o in group_input.outputs if o.name == name]) == 1 - return group_input.outputs[name] - - # Infer from args what type of node input to make (NodeSocketFloat / NodeSocketVector / etc) nodeclass = self._infer_nodeclass_from_args(dtype, val) - inp = self.node_group.inputs.new(nodeclass, name) + inp = self.interface.new_socket(name=name, in_out='INPUT', socket_type=nodeclass) def prepare_cast(to_type, val): - # cast val only when necessary, and only when type(val) wont crash + # cast val only when necessary, and only when type(val) won't crash if to_type not in [bpy.types.bpy_prop_array, bpy.types.bpy_prop]: val = to_type(val) return val @@ -272,7 +277,7 @@ def prepare_cast(to_type, val): if val is not None: if not hasattr(inp, 'default_value') or inp.default_value is None: raise ValueError( - f'expose_input() recieved {val=} but inp {inp} does not expect a default_value') + f'expose_input() received {val=} but inp {inp} does not expect a default_value') inp.default_value = prepare_cast(type(inp.default_value), val) if self.modifier is not None: @@ -285,8 +290,8 @@ def prepare_cast(to_type, val): self.modifier[f'{id}_attribute_name'] = attribute self.modifier[f'{id}_use_attribute'] = 1 - assert len([o for o in group_input.outputs if o.name == name]) == 1 - return group_input.outputs[name] + return group_input.outputs[inp.name] + @staticmethod def _infer_nodeclass_from_args(dtype, val=None): @@ -321,7 +326,7 @@ def _update_socket(self, input_socket, input_item): output_socket = infer_output_socket(input_item) if output_socket is None and hasattr(input_socket, 'default_value'): - # we couldnt parse the inp to be any kind of node, it must be a default_value for us to assign + # we couldn't parse the inp to be any kind of node, it must be a default_value for us to assign try: input_socket.default_value = input_item return @@ -369,7 +374,7 @@ def _make_node(self, node_type): return node def get_position_translation_seed(self, i): - if not i in self.position_translation_seed: + if i not in self.position_translation_seed: self.position_translation_seed[i] = random_vector3() return self.position_translation_seed[i] @@ -478,21 +483,21 @@ def boolean_math(self, node_type, *nodes): def compare(self, node_type, *nodes): return self.new_node(Nodes.Compare, attrs={'operation': node_type}, input_args=nodes) - + def compare_direction(self, node_type, x, y, angle): - return self.new_node(Nodes.Compare, input_kwargs={'A': x, 'B': y, 'Angle': angle}, - attrs={'data_type': 'VECTOR', 'mode': 'DIRECTION', 'operation': node_type}) + return self.new_node(Nodes.Compare, input_kwargs={'A': x, 'B': y, 'Angle': angle}, + attrs={'data_type': 'VECTOR', 'mode': 'DIRECTION', 'operation': node_type}) def bernoulli(self, prob, seed=None): if seed is None: seed = np.random.randint(1e5) return self.new_node(Nodes.RandomValue, input_kwargs={'Probability': prob, 'Seed': seed}, - attrs={'data_type': 'BOOLEAN'}) + attrs={'data_type': 'BOOLEAN'}) def uniform(self, low=0., high=1., seed=None, data_type='FLOAT'): if seed is None: seed = np.random.randint(1e5) if isinstance(low, Iterable): data_type = 'FLOAT_VECTOR' return self.new_node(Nodes.RandomValue, input_kwargs={'Min': low, 'Max': high, 'Seed': seed}, - attrs={'data_type': data_type}) + attrs={'data_type': data_type}) def combine(self, x, y, z): return self.new_node(Nodes.CombineXYZ, [x, y, z]) @@ -502,11 +507,11 @@ def separate(self, x): def switch(self, pred, true, false, input_type='FLOAT'): return self.new_node(Nodes.Switch, input_kwargs={'Switch': pred, 'True': true, 'False': false}, - attrs={'input_type': input_type}) + attrs={'input_type': input_type}) def vector_switch(self, pred, true, false): return self.new_node(Nodes.Switch, input_kwargs={'Switch': pred, 'True': true, 'False': false}, - attrs={'input_type': 'VECTOR'}) + attrs={'input_type': 'VECTOR'}) def geometry2point(self, geometry): return self.new_node(Nodes.MergeByDistance, input_kwargs={'Geometry': geometry, 'Distance': 100.}) @@ -522,12 +527,12 @@ def capture(self, geometry, attribute, attrs=None): def musgrave(self, scale=10, vector=None): return self.new_node(Nodes.MapRange, - [self.new_node(Nodes.MusgraveTexture, [vector], input_kwargs={'Scale': scale}), -1, - 1, 0, 1]) + [self.new_node(Nodes.MusgraveTexture, [vector], input_kwargs={'Scale': scale}), -1, + 1, 0, 1]) def curve2mesh(self, curve, profile_curve=None): return self.new_node(Nodes.SetShadeSmooth, - [self.new_node(Nodes.CurveToMesh, [curve, profile_curve, True]), None, False]) + [self.new_node(Nodes.CurveToMesh, [curve, profile_curve, True]), None, False]) def build_float_curve(self, x, anchors, handle='VECTOR'): float_curve = self.new_node(Nodes.FloatCurve, input_kwargs={'Value': x}) @@ -548,4 +553,4 @@ def build_case(self, value, inputs, outputs, input_type='FLOAT'): return node def build_index_case(self, inputs): - return self.build_case(self.new_node(Nodes.Index), inputs + [-1], [True] * len(inputs) + [False]) + return self.build_case(self.new_node(Nodes.Index), inputs + [-1], [True] * len(inputs) + [False]) \ No newline at end of file diff --git a/infinigen/core/surface.py b/infinigen/core/surface.py index 609439762..98c1dc700 100644 --- a/infinigen/core/surface.py +++ b/infinigen/core/surface.py @@ -33,26 +33,24 @@ def write_attribute(objs, node_func, name=None, data_type=None, apply=False): if name is None: name = node_func.__name__ - def attr_writer(nw, **kwargs): - value = node_func(nw) + def attr_writer(interface, **kwargs): + value = node_func(interface) nonlocal data_type 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 - }) - output = nw.new_node(Nodes.GroupOutput, input_kwargs={ - 'Geometry': (capture, 'Geometry'), - name: (capture, 'Attribute') - }) + # Create the necessary sockets + geometry_socket = interface.new_socket('NodeSocketGeometry', 'Geometry', in_out='INPUT', description='') + value_socket = interface.new_socket('NodeSocketFloat', 'Value', in_out='INPUT', description='') + output_socket = interface.new_socket('NodeSocketGeometry', name, in_out='OUTPUT', description='') + + # Connect sockets internally if necessary + # This part depends on how your node setup should connect these sockets 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: if isinstance(attr, str): @@ -267,11 +265,7 @@ 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,55 +289,32 @@ def add_geomod(objs, geo_func, ng = None for obj in objs: - mod = obj.modifiers.new(name=name, type='NODES') - mod.show_viewport = False - - if mod is None: - raise ValueError(f'Attempted to surface.add_geomod({obj=}), yet created modifier was None. ' - f'Check that {obj.type=} supports geo modifiers') - mod.show_viewport = show_viewport - if ng is None: # Create a unique node_group for the first one only + + if ng is None: # Create a unique node group for the first object only if reuse and name in bpy.data.node_groups: mod.node_group = bpy.data.node_groups[name] + nw = NodeWrangler(mod.node_group) else: - # print("input_kwargs", input_kwargs, geo_func.__name__) - if mod.node_group == None: - group = geometry_node_group_empty_new() - mod.node_group = group - nw = NodeWrangler(mod) + group = bpy.data.node_groups.new(name, 'GeometryNodeTree') + mod.node_group = group + nw = NodeWrangler(mod.node_group) geo_func(nw, *input_args, **input_kwargs) ng = mod.node_group ng.name = name else: mod.node_group = ng + nw = NodeWrangler(ng) - outputs = mod.node_group.outputs - 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=}") - 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 - mod[id + '_attribute_name'] = att_name - os = [outputs[i] for i in range(len(outputs)) if outputs[i].type != 'GEOMETRY'] - for o, domain in zip(os, domains): - o.attribute_domain = domain - - inputs = mod.node_group.inputs - if not any(att_name is None for att_name in input_attributes): - raise Exception('None should be provided for Geometry inputs.') - for i, att_name in zip(inputs, input_attributes): - id = i.identifier - if att_name is not None: - mod[f'{id}_use_attribute'] = True - mod[f'{id}_attribute_name'] = att_name + # Set up interface sockets according to attributes and domains + for attr_name, domain in zip(attributes, domains): + socket = nw.interface.new_socket(name=attr_name, socket_type='NodeSocketFloat', in_out='OUTPUT', description='') + # Configure additional properties if needed if apply: for obj in objs: - butil.apply_modifiers(obj, name) + bpy.ops.object.modifier_apply(modifier=name) return None return mod diff --git a/pyproject.toml b/pyproject.toml index fce75582e..a7c86a9ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,14 +15,13 @@ keywords = [ "procedural" ] classifiers = [ - "Framework :: Blender", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10" + "Programming Language :: Python :: 3.11" ] -requires-python = "==3.10.*" +requires-python = "==3.11.*" dependencies = [ - "bpy==3.6.0", + "bpy==4.1.0", "einops", "flow_vis", "frozendict", diff --git a/requirements.txt b/requirements.txt index 6686c552b..c466a953b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,20 +4,21 @@ opencv-python matplotlib scipy imageio -scikit-image==0.19.3 +scikit-image==0.23.2 submitit frozendict flow_vis trimesh einops geomdl -numpy==1.26.2 +numpy==1.26.4 wandb jinja2 shapely -landlab==2.7.0 +landlab==2.8.0 scikit-learn psutil pyrender pytest pandas +bpy==4.1.0 \ No newline at end of file diff --git a/scripts/install/interactive_blender.sh b/scripts/install/interactive_blender.sh index a64fb9ad4..1719c5eee 100644 --- a/scripts/install/interactive_blender.sh +++ b/scripts/install/interactive_blender.sh @@ -12,33 +12,33 @@ OS=$(uname -s) ARCH=$(uname -m) if [ "${OS}" = "Linux" ]; then - BLENDER_WGET_LINK='https://download.blender.org/release/Blender3.6/blender-3.6.0-linux-x64.tar.xz' + BLENDER_WGET_LINK='https://mirrors.ocf.berkeley.edu/blender/release/Blender4.1/blender-4.1.1-linux-x64.tar.xz' BLENDER_WGET_FILE='blender.tar.xz' - BLENDER_UNTAR_DIR='blender-3.6.0-linux-x64' + BLENDER_UNTAR_DIR='blender-4.1.0-linux-x64' BLENDER_DIR='blender' - BLENDER_PYTHON="${BLENDER_DIR}/3.6/python/bin/python3.10" - BLENDER_INCLUDE="${BLENDER_DIR}/3.6/python/include/python3.10" - BLENDER_PACKAGES="${BLENDER_DIR}/3.6/python/lib/python3.10/site-packages" - BLENDER_ADDONS="${BLENDER_DIR}/3.6/scripts/addons" + BLENDER_PYTHON="${BLENDER_DIR}/4.1/python/bin/python3.11" + BLENDER_INCLUDE="${BLENDER_DIR}/4.1/python/include/python3.11" + BLENDER_PACKAGES="${BLENDER_DIR}/4.1/python/lib/python3.11/site-packages" + BLENDER_ADDONS="${BLENDER_DIR}/4.1/scripts/addons" BLENDER_EXE="${BLENDER_DIR}/blender" elif [ "${OS}" = "Darwin" ]; then if [ "${ARCH}" = "arm64" ]; then - BLENDER_WGET_LINK='https://download.blender.org/release/Blender3.6/blender-3.6.0-macos-arm64.dmg' + BLENDER_WGET_LINK='https://download.blender.org/release/Blender4.1/blender-4.1.0-macos-arm64.dmg' else - BLENDER_WGET_LINK='https://download.blender.org/release/Blender3.6/blender-3.6.0-macos-x64.dmg' + BLENDER_WGET_LINK='https://download.blender.org/release/Blender4.1/blender-4.1.0-macos-x64.dmg' fi BLENDER_WGET_FILE='blender.dmg' BLENDER_VOLM='/Volumes/Blender' BLENDER_DIR='./Blender.app' - BLENDER_PYTHON="${BLENDER_DIR}/Contents/Resources/3.6/python/bin/python3.10" - BLENDER_INCLUDE="${BLENDER_DIR}/Contents/Resources/3.6/python/include/python3.10" - BLENDER_PACKAGES="${BLENDER_DIR}/Contents/Resources/3.6/python/lib/python3.10/site-packages" - BLENDER_ADDONS="${BLENDER_DIR}/Contents/Resources/3.6/scripts/addons" + BLENDER_PYTHON="${BLENDER_DIR}/Contents/Resources/4.1/python/bin/python3.11" + BLENDER_INCLUDE="${BLENDER_DIR}/Contents/Resources/4.1/python/include/python3.11" + BLENDER_PACKAGES="${BLENDER_DIR}/Contents/Resources/4.1/python/lib/python3.11/site-packages" + BLENDER_ADDONS="${BLENDER_DIR}/Contents/Resources/4.1/scripts/addons" BLENDER_EXE="${BLENDER_DIR}/Contents/MacOS/Blender" else @@ -66,4 +66,4 @@ fi # Install Blender dependencies "${BLENDER_PYTHON}" -m ensurepip -CFLAGS="-I/usr/include/python3.10 ${CFLAGS}" "${BLENDER_PYTHON}" -m pip install -e . \ No newline at end of file +CFLAGS="-I/usr/include/python3.11 ${CFLAGS}" "${BLENDER_PYTHON}" -m pip install -e . \ No newline at end of file diff --git a/setup.py b/setup.py index 8f0557657..4a7a55ca3 100644 --- a/setup.py +++ b/setup.py @@ -18,25 +18,6 @@ dont_build_steps = ["clean", "egg_info", "dist_info", "sdist", "--help"] is_build_step = not any(x in sys.argv[1] for x in dont_build_steps) -def ensure_submodules(): - # Inspired by https://github.com/pytorch/pytorch/blob/main/setup.py - - with (cwd/'.gitmodules').open() as f: - submodule_folders = [ - cwd/line.split("=", 1)[1].strip() - for line in f.readlines() - if line.strip().startswith("path") - ] - - if any(not p.exists() or not any(p.iterdir()) for p in submodule_folders): - subprocess.run( - ["git", "submodule", "update", "--init", "--recursive"], - cwd=cwd, - check=True - ) - -ensure_submodules() - # inspired by https://github.com/pytorch/pytorch/blob/161ea463e690dcb91a30faacbf7d100b98524b6b/setup.py#L290 # theirs seems to not exclude dist_info but this causes duplicate compiling in my tests if is_build_step and not MINIMAL_INSTALL: