Created
September 8, 2023 03:39
-
-
Save manzt/f85edcd8c8a4610df308bd10da920571 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"id": "64014c50-9a03-492a-bda4-1f58ac5a088f", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Device with name AMD Radeon Pro 5500M supports metal minimum requirements\n", | |
"METAL API - DETECTED GPU: AMD Radeon Pro 5500M\n", | |
"Fra:1 Mem:19.56M (Peak 20.21M) | Time:00:00.13 | Syncing Light\n", | |
"Fra:1 Mem:19.56M (Peak 20.21M) | Time:00:00.13 | Syncing Camera\n", | |
"Fra:1 Mem:19.57M (Peak 20.21M) | Time:00:00.13 | Syncing Torus\n", | |
"Fra:1 Mem:20.95M (Peak 20.95M) | Time:00:00.15 | Rendering 1 / 64 samples\n", | |
"Fra:1 Mem:20.17M (Peak 20.95M) | Time:00:00.28 | Rendering 26 / 64 samples\n", | |
"Fra:1 Mem:20.17M (Peak 20.95M) | Time:00:00.33 | Rendering 51 / 64 samples\n", | |
"Fra:1 Mem:20.17M (Peak 20.95M) | Time:00:00.36 | Rendering 64 / 64 samples\n", | |
"Saved: 'test.png'\n", | |
" Time: 00:00.58 (Saving: 00:00.20)\n", | |
"\n" | |
] | |
}, | |
{ | |
"data": { | |
"application/vnd.jupyter.widget-view+json": { | |
"model_id": "34e14c59334444008a671670a6f1eef0", | |
"version_major": 2, | |
"version_minor": 0 | |
}, | |
"text/plain": [ | |
"AppLayout(children=(HBox(children=(Counter(), IntSlider(value=0)), layout=Layout(grid_area='header')), Image(v…" | |
] | |
}, | |
"execution_count": 1, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"import anywidget\n", | |
"import ipywidgets\n", | |
"import traitlets\n", | |
"import bpy\n", | |
"import pathlib\n", | |
"\n", | |
"# ref: https://github.com/kolibril13/ipyblender-experimental/blob/1f49b339be94164f8688b0471f3095b0ac6081d3/src/ipyblender_experimental/__init__.py#L17\n", | |
"def render(counter: int, light_position: int):\n", | |
" light_position_normed = light_position / 20\n", | |
" # Delete all mesh objects from the scene\n", | |
" bpy.ops.object.select_all(action=\"DESELECT\")\n", | |
" bpy.ops.object.select_by_type(type=\"MESH\")\n", | |
" bpy.ops.object.delete()\n", | |
"\n", | |
" # Add a torus\n", | |
" bpy.ops.mesh.primitive_torus_add(\n", | |
" major_radius=1.5,\n", | |
" minor_radius=0.75,\n", | |
" major_segments=48 * 4,\n", | |
" minor_segments=12 * 4,\n", | |
" align=\"WORLD\",\n", | |
" location=(0, 1, 1),\n", | |
" )\n", | |
"\n", | |
" # Assigning the torus to a variable\n", | |
" torus = bpy.context.active_object\n", | |
"\n", | |
" # Create a new material and assign it to the torus\n", | |
" material = bpy.data.materials.new(name=\"RainbowGradient\")\n", | |
" torus.data.materials.append(material)\n", | |
" material.use_nodes = True\n", | |
" nodes = material.node_tree.nodes\n", | |
"\n", | |
" # Clear default nodes\n", | |
" for node in nodes:\n", | |
" nodes.remove(node)\n", | |
"\n", | |
" # Add a Gradient Texture and set it to a color ramp of a rainbow\n", | |
" gradient = nodes.new(type=\"ShaderNodeTexGradient\")\n", | |
" gradient.gradient_type = \"LINEAR\"\n", | |
" gradient.location = (0, 0)\n", | |
"\n", | |
" ramp = nodes.new(type=\"ShaderNodeValToRGB\")\n", | |
" ramp.color_ramp.interpolation = \"LINEAR\"\n", | |
" ramp.location = (200, 0)\n", | |
"\n", | |
" color_gradients = {\n", | |
" 0: [(1, 0.5, 0, 1), (1, 0.3, 0, 1)], # Orange\n", | |
" 1: [(1, 1, 0, 1), (0.8, 0.8, 0, 1)], # Yellow\n", | |
" 2: [(0, 1, 0, 1), (0, 0.8, 0, 1)], # Green\n", | |
" 3: [(0, 0.5, 1, 1), (0, 0.3, 1, 1)], # Blue\n", | |
" 4: [(0.3, 0, 0.5, 1), (0.1, 0, 0.3, 1)], # Indigo\n", | |
" 5: [(0.5, 0, 1, 1), (0.3, 0, 0.8, 1)], # Violet\n", | |
" 6: [(1, 0, 0, 1), (0.8, 0, 0, 1)], # Red\n", | |
" }\n", | |
" colors = color_gradients.get(counter % 7)\n", | |
"\n", | |
" ramp.color_ramp.elements[0].color = colors[0]\n", | |
" ramp.color_ramp.elements[1].color = colors[1]\n", | |
"\n", | |
" # Add Shader nodes\n", | |
" bsdf = nodes.new(type=\"ShaderNodeBsdfPrincipled\")\n", | |
" bsdf.location = (400, 0)\n", | |
"\n", | |
" output = nodes.new(type=\"ShaderNodeOutputMaterial\")\n", | |
" output.location = (600, 0)\n", | |
"\n", | |
" # Connect the nodes\n", | |
" material.node_tree.links.new\n", | |
" material.node_tree.links.new(gradient.outputs[\"Color\"], ramp.inputs[0])\n", | |
" material.node_tree.links.new(ramp.outputs[\"Color\"], bsdf.inputs[\"Base Color\"])\n", | |
" material.node_tree.links.new(bsdf.outputs[\"BSDF\"], output.inputs[\"Surface\"])\n", | |
"\n", | |
" # Rotate the gradient to apply it from left to right\n", | |
" torus.rotation_euler = (0, 0, 1.5708) # Rotate 90 degrees on the Z axis\n", | |
"\n", | |
" # Light\n", | |
" light = bpy.data.objects[\"Light\"]\n", | |
" light.location = (light_position_normed, 0, 2) # Position the light\n", | |
"\n", | |
" # Camera\n", | |
" camera = bpy.data.objects[\"Camera\"]\n", | |
" camera.location = (5, -3, 4)\n", | |
" camera.data.dof.use_dof = True\n", | |
" camera.data.dof.focus_distance = 5\n", | |
" camera.data.dof.aperture_fstop = 4\n", | |
"\n", | |
" # Render\n", | |
" path = \"test.png\"\n", | |
" bpy.context.scene.render.resolution_x = 1000\n", | |
" bpy.context.scene.render.resolution_y = 400\n", | |
" bpy.context.scene.render.image_settings.file_format = \"PNG\"\n", | |
" bpy.context.scene.render.filepath = path\n", | |
" bpy.ops.render.render(write_still=True)\n", | |
" bpy.data.images[\"Render Result\"].save_render(\n", | |
" filepath=bpy.context.scene.render.filepath\n", | |
" )\n", | |
"\n", | |
" # Read the saved image into memory and encode it to base64\n", | |
" temp_filepath = pathlib.Path(bpy.context.scene.render.filepath)\n", | |
" with temp_filepath.open(\"rb\") as f:\n", | |
" bdata = f.read()\n", | |
"\n", | |
" # Optionally, you can delete the temporary saved image\n", | |
" temp_filepath.unlink()\n", | |
"\n", | |
" return bdata\n", | |
"\n", | |
"\n", | |
"class Counter(anywidget.AnyWidget):\n", | |
" _esm = \"\"\"\n", | |
" export function render({ model, el }) {\n", | |
" let button = document.createElement(\"button\");\n", | |
" button.innerHTML = `count is ${model.get(\"value\")}`;\n", | |
" button.addEventListener(\"click\", () => {\n", | |
" model.set(\"value\", model.get(\"value\") + 1);\n", | |
" model.save_changes();\n", | |
" });\n", | |
" model.on(\"change:value\", () => {\n", | |
" button.innerHTML = `count is ${model.get(\"value\")}`;\n", | |
" });\n", | |
" el.appendChild(button);\n", | |
" }\n", | |
" \"\"\"\n", | |
" value = traitlets.Int(0).tag(sync=True)\n", | |
"\n", | |
"\n", | |
"image = ipywidgets.Image()\n", | |
"counter = Counter()\n", | |
"slider = ipywidgets.IntSlider()\n", | |
"\n", | |
"def rerender(*args, **kwargs):\n", | |
" image.value = render(counter.value, slider.value)\n", | |
"\n", | |
"counter.observe(rerender, \"value\")\n", | |
"slider.observe(rerender, \"value\")\n", | |
"\n", | |
"rerender()\n", | |
"header = ipywidgets.HBox([counter, slider])\n", | |
"ipywidgets.AppLayout(header=header, center=image)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"id": "4c7e11ce-bed6-455f-8742-766e0f9ce5d8", | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3 (ipykernel)", | |
"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": 5 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment