Skip to content

Instantly share code, notes, and snippets.

@manzt
Created September 8, 2023 03:39
Show Gist options
  • Save manzt/f85edcd8c8a4610df308bd10da920571 to your computer and use it in GitHub Desktop.
Save manzt/f85edcd8c8a4610df308bd10da920571 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"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