{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import pyvista\npyvista.OFF_SCREEN = True\npyvista.set_jupyter_backend('static')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Building a model from a pyvista grid\n\nWhen you don't have a CDB on disk, the easiest way to build a\n:class:`femorph_solver.Model` from scratch is to author a\n:class:`pyvista.UnstructuredGrid` directly and hand it to\n:meth:`femorph_solver.Model.from_grid`.  Here we build a 4-element\nHEX8 cantilever beam this way \u2014 no foreign deck required.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from __future__ import annotations\n\nimport numpy as np\nimport pyvista as pv\nfrom vtkmodules.util.vtkConstants import VTK_HEXAHEDRON\n\nimport femorph_solver\nfrom femorph_solver import ELEMENTS"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Author the nodes\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "LX, LY, LZ = 4.0, 1.0, 1.0\nNX = 4\n\npoints: list[tuple[float, float, float]] = []\nnode_id: dict[tuple[int, int, int], int] = {}\nnn = 0\nfor i in range(NX + 1):\n    for j in range(2):\n        for k in range(2):\n            points.append((i * LX / NX, j * LY, k * LZ))\n            node_id[(i, j, k)] = nn  # 0-based pyvista index\n            nn += 1\n\npoints_arr = np.asarray(points, dtype=float)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Author the hex connectivity\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "cells: list[int] = []\nfor i in range(NX):\n    cells.extend(\n        [\n            8,\n            node_id[(i, 0, 0)],\n            node_id[(i + 1, 0, 0)],\n            node_id[(i + 1, 1, 0)],\n            node_id[(i, 1, 0)],\n            node_id[(i, 0, 1)],\n            node_id[(i + 1, 0, 1)],\n            node_id[(i + 1, 1, 1)],\n            node_id[(i, 1, 1)],\n        ]\n    )\n\ncell_types = np.full(NX, VTK_HEXAHEDRON, dtype=np.uint8)\ngrid = pv.UnstructuredGrid(np.asarray(cells), cell_types, points_arr)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Wrap as a Model and stamp material\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "m = femorph_solver.Model.from_grid(grid)\nm.assign(ELEMENTS.HEX8, material={\"EX\": 2.1e11, \"PRXY\": 0.30, \"DENS\": 7850.0})\n\nprint(f\"model: {m.n_nodes} nodes, {m.n_elements} elements\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Apply BCs and solve\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "node_nums = np.asarray(grid.point_data[\"ansys_node_num\"])\npts = np.asarray(grid.points)\n\nfixed = node_nums[pts[:, 0] < 1e-9]\nm.fix(nodes=fixed.tolist(), dof=\"ALL\")\n\ntip_mask = pts[:, 0] > LX - 1e-9\ntip_nodes = node_nums[tip_mask]\nfor tip_nn in tip_nodes:\n    m.apply_force(int(tip_nn), fz=-250.0)\n\nres = m.solve_static()\ndof_map = m.dof_map()\ntip_uz = np.array(\n    [\n        res.displacement[int(np.where((dof_map[:, 0] == nn) & (dof_map[:, 1] == 2))[0][0])]\n        for nn in tip_nodes\n    ]\n)\nprint(f\"tip UZ (mean over 4 corner nodes): {tip_uz.mean():.4e} m\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Render\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "deformed = femorph_solver.io.static_result_to_grid(m, res)\nplotter = pv.Plotter(off_screen=True)\nplotter.add_mesh(deformed, style=\"wireframe\", color=\"gray\", opacity=0.35, label=\"undeformed\")\nplotter.add_mesh(\n    deformed.warp_by_vector(\"displacement\", factor=500.0),\n    scalars=\"displacement_magnitude\",\n    show_edges=True,\n    cmap=\"viridis\",\n    scalar_bar_args={\"title\": \"|u| [m]\"},\n    label=\"deformed (\u00d7500)\",\n)\nplotter.add_legend()\nplotter.add_axes()\nplotter.show()"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "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": 0
}