{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "557e620e-cb73-427a-90ee-f5d0794dfc16", "metadata": {}, "outputs": [], "source": [ "# File: domino.ipynb\n", "# Code: Claude Code and Codex\n", "# Review: Ryoichi Ando (ryoichi.ando@zozo.com)\n", "# License: Apache v2.0" ] }, { "cell_type": "code", "execution_count": null, "id": "834a079d-fb9f-43e1-95b3-1e373a7096fa", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from frontend import App\n", "\n", "# create an app\n", "app = App.create(\"domino\")\n", "\n", "# create a box tetrahedral mesh with domino proportions (no subdivision)\n", "V, F, T = app.mesh.tet_box(0.1, 0.25, 0.025)\n", "app.asset.add.tet(\"block\", V, F, T)\n", "\n", "# create a scene\n", "scene = app.scene.create()\n", "\n", "# arrange dominoes in a spiral pattern\n", "R, minR, d, N, C = 1.0, 0.5, 0.15, 4096, 2\n", "blocks, xz = [], None\n", "for i in reversed(range(N)):\n", " t = 2.0 * C * np.pi * i / N - np.pi / 2\n", " r = (R - minR) * i / N + minR\n", " angle = 180 * t / np.pi\n", " x, z = -r * np.cos(t), r * np.sin(t)\n", " # only add dominoes with sufficient spacing\n", " if xz is None or np.linalg.norm(np.array([x, z]) - xz) > d:\n", " blocks.append(scene.add(\"block\").at(x, 0, z).rotate(angle, \"y\"))\n", " xz = np.array([x, z])\n", "\n", "# add first domino tilted to start the chain reaction\n", "blocks.append(scene.add(\"block\").at(-0.1, 0.25, -R).rotate(90, \"y\").rotate(-20, \"z\"))\n", "\n", "# set material properties for all dominoes\n", "gap = 1e-3\n", "for obj in blocks:\n", " (\n", " obj.param.set(\"contact-gap\", gap)\n", " .set(\"friction\", 0.1)\n", " .set(\"poiss-rat\", 0.49)\n", " )\n", "\n", "# add invisible floor\n", "wall = scene.add.invisible.wall([0, scene.min(\"y\") - 0.5 * gap, 0], [0, 1, 0])\n", "\n", "# compile the scene and report stats\n", "scene = scene.build().report()\n", "\n", "# set preview options\n", "opts = {\"flat_shading\": True, \"wireframe\": True}\n", "\n", "# preview the initial scene\n", "scene.preview(options=opts)" ] }, { "cell_type": "code", "execution_count": null, "id": "5e6f26d9-03d8-4e50-8315-7d41afbc8532", "metadata": {}, "outputs": [], "source": [ "# create a new session with the compiled scene\n", "session = app.session.create(scene)\n", "\n", "# set session parameters\n", "(\n", " session.param.set(\"min-newton-steps\", 32)\n", " .set(\"dt\", 0.01)\n", " .set(\"fps\", 30)\n", " .set(\"frames\", 250)\n", " .set(\"friction-mode\", \"max\")\n", ")\n", "\n", "# build this session\n", "session = session.build()" ] }, { "cell_type": "code", "execution_count": null, "id": "7631339c-a669-40e0-a67c-92480efd7d24", "metadata": {}, "outputs": [], "source": [ "# start the simulation and live-preview the results\n", "session.start().preview(options=opts)\n", "\n", "# also show simulation logs in realtime\n", "session.stream()" ] }, { "cell_type": "code", "execution_count": null, "id": "f3755897-773b-4541-9d6b-61eca227a9f6", "metadata": {}, "outputs": [], "source": [ "# create an animation from the simulation results\n", "session.animate(options=opts)" ] }, { "cell_type": "code", "execution_count": null, "id": "d1c776ea-d010-43e9-9110-bb80e956896d", "metadata": {}, "outputs": [], "source": [ "# export the animation to file\n", "session.export.animation()" ] }, { "cell_type": "code", "execution_count": null, "id": "7338acaa-f007-48c5-94a8-e481dd0f8cfc", "metadata": {}, "outputs": [], "source": [ "# this is for CI\n", "if app.ci:\n", " assert session.finished()" ] } ], "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.12.3" } }, "nbformat": 4, "nbformat_minor": 5 }