{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "cbaa6f21-27f8-41d9-8030-78f8b3a587b8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 37920 characters]",
      "text/plain": [
       "<Figure size 600x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Gyroid Infill 0.0% ===\n",
      "Total Area (approx)    = 185.5843 mm²\n",
      "Shell Area (approx)    = 90.6929 mm²\n",
      "Infill Area (approx)   = 94.8914 mm²\n",
      "Centroid (x_c, y_c)    = (0.0052, 0.0562)\n",
      "I_x (about centroid)   = 7920.0624\n",
      "I_y (about centroid)   = 7734.7227\n",
      "I_xy (product)         = -27.3251\n",
      "Polar moment, J        = 15654.7851\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 38528 characters]",
      "text/plain": [
       "<Figure size 600x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Gyroid Infill 13.1% ===\n",
      "Total Area (approx)    = 171.3331 mm²\n",
      "Shell Area (approx)    = 90.6929 mm²\n",
      "Infill Area (approx)   = 80.6402 mm²\n",
      "Centroid (x_c, y_c)    = (-0.0024, -0.0007)\n",
      "I_x (about centroid)   = 7489.1696\n",
      "I_y (about centroid)   = 7502.6263\n",
      "I_xy (product)         = -50.4279\n",
      "Polar moment, J        = 14991.7959\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 39396 characters]",
      "text/plain": [
       "<Figure size 600x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Gyroid Infill 32.2% ===\n",
      "Total Area (approx)    = 160.8960 mm²\n",
      "Shell Area (approx)    = 90.6929 mm²\n",
      "Infill Area (approx)   = 70.2031 mm²\n",
      "Centroid (x_c, y_c)    = (-0.0016, -0.0029)\n",
      "I_x (about centroid)   = 7290.6465\n",
      "I_y (about centroid)   = 7223.9049\n",
      "I_xy (product)         = 6.3870\n",
      "Polar moment, J        = 14514.5514\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 42032 characters]",
      "text/plain": [
       "<Figure size 600x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Gyroid Infill 59.9% ===\n",
      "Total Area (approx)    = 189.2903 mm²\n",
      "Shell Area (approx)    = 90.6929 mm²\n",
      "Infill Area (approx)   = 98.5974 mm²\n",
      "Centroid (x_c, y_c)    = (0.0042, 0.0079)\n",
      "I_x (about centroid)   = 7941.1741\n",
      "I_y (about centroid)   = 7968.3638\n",
      "I_xy (product)         = -3.6752\n",
      "Polar moment, J        = 15909.5379\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 28176 characters]",
      "text/plain": [
       "<Figure size 600x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Gyroid Infill 100.0% ===\n",
      "Total Area (approx)    = 402.0075 mm²\n",
      "Shell Area (approx)    = 90.6929 mm²\n",
      "Infill Area (approx)   = 311.3146 mm²\n",
      "Centroid (x_c, y_c)    = (-0.0000, -0.0000)\n",
      "I_x (about centroid)   = 13467.4200\n",
      "I_y (about centroid)   = 13467.4200\n",
      "I_xy (product)         = 0.0000\n",
      "Polar moment, J        = 26934.8400\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def compute_area_moments(XX, YY, mask):\n",
    "    dx = XX[0, 1] - XX[0, 0]\n",
    "    dy = YY[1, 0] - YY[0, 0]\n",
    "    dA = dx * dy\n",
    "\n",
    "    A = np.count_nonzero(mask) * dA\n",
    "    if A == 0:\n",
    "        return A, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0\n",
    "\n",
    "    x_vals = XX[mask]\n",
    "    y_vals = YY[mask]\n",
    "    x_c = np.sum(x_vals * dA) / A\n",
    "    y_c = np.sum(y_vals * dA) / A\n",
    "    x_shift = x_vals - x_c\n",
    "    y_shift = y_vals - y_c\n",
    "\n",
    "    I_x = np.sum((y_shift**2) * dA)\n",
    "    I_y = np.sum((x_shift**2) * dA)\n",
    "    I_xy = np.sum((x_shift * y_shift) * dA)\n",
    "    J = I_x + I_y\n",
    "\n",
    "    return A, x_c, y_c, I_x, I_y, I_xy, J\n",
    "\n",
    "def geometric_infill_fractions(width, layer_width, shell_layers, steps=5):\n",
    "    A_total = width ** 2\n",
    "    shell_thickness = 2 * layer_width * shell_layers\n",
    "    A_shell = A_total - (width - shell_thickness) ** 2\n",
    "    r = (A_total / A_shell) ** (1 / (steps - 1))\n",
    "    return [(A_shell * r**i - A_shell) / (A_total - A_shell) for i in range(steps)]\n",
    "\n",
    "def gyroid_infill_rectangular_with_shell(\n",
    "    width,\n",
    "    height,\n",
    "    infill_fraction,  # Now passed as 0.0 to 1.0\n",
    "    z_height=0.0,\n",
    "    line_thickness=0.1,\n",
    "    shell_layers=3,\n",
    "    layer_width=0.4,\n",
    "    plot=True\n",
    "):\n",
    "    base_frequency = 1.0\n",
    "    max_multiplier = 5.0\n",
    "    #scale_factor = base_frequency + (max_multiplier - 1.0) * infill_fraction\n",
    "    scale_factor = base_frequency / np.sqrt(1.0 - 0.999 * infill_fraction)\n",
    "\n",
    "\n",
    "    N = 400\n",
    "    xs = np.linspace(-width / 2, width / 2, N)\n",
    "    ys = np.linspace(-height / 2, height / 2, N)\n",
    "    XX, YY = np.meshgrid(xs, ys)\n",
    "\n",
    "    x_s = scale_factor * XX\n",
    "    y_s = scale_factor * YY\n",
    "    z_s = scale_factor * z_height\n",
    "\n",
    "    f_vals = (np.sin(x_s) * np.cos(y_s)\n",
    "              + np.sin(y_s) * np.cos(z_s)\n",
    "              + np.sin(z_s) * np.cos(x_s))\n",
    "\n",
    "    if infill_fraction >= 1.0:\n",
    "        line_mask = np.ones_like(f_vals, dtype=bool)\n",
    "    else:\n",
    "        line_mask = np.abs(f_vals) < line_thickness\n",
    "\n",
    "    shell_thickness = shell_layers * layer_width\n",
    "    inner_w = width / 2 - shell_thickness\n",
    "    inner_h = height / 2 - shell_thickness\n",
    "\n",
    "    shell_mask = (\n",
    "        (np.abs(XX) >= inner_w) |\n",
    "        (np.abs(YY) >= inner_h)\n",
    "    )\n",
    "\n",
    "    inner_mask = (\n",
    "        (np.abs(XX) < inner_w) &\n",
    "        (np.abs(YY) < inner_h)\n",
    "    )\n",
    "\n",
    "    gyroid_mask = line_mask & inner_mask\n",
    "    final_mask = shell_mask | gyroid_mask\n",
    "\n",
    "    A_total, x_c, y_c, I_x, I_y, I_xy, J = compute_area_moments(XX, YY, final_mask)\n",
    "    A_shell, *_ = compute_area_moments(XX, YY, shell_mask)\n",
    "\n",
    "    if plot:\n",
    "        plt.figure(figsize=(6, 6))\n",
    "        pattern_img = np.where(final_mask, 1.0, np.nan)\n",
    "        plt.imshow(\n",
    "            pattern_img,\n",
    "            origin='lower',\n",
    "            extent=[-width / 2, width / 2, -height / 2, height / 2],\n",
    "            interpolation='none'\n",
    "        )\n",
    "        plt.title(\n",
    "            f\"Gyroid Infill @ {infill_fraction*100:.1f}%\\nShells={shell_layers}×{layer_width}mm\"\n",
    "        )\n",
    "        plt.xlabel(\"X (mm)\")\n",
    "        plt.ylabel(\"Y (mm)\")\n",
    "        plt.gca().set_aspect('equal', 'box')\n",
    "        plt.grid(True)\n",
    "        plt.show()\n",
    "\n",
    "    print(f\"=== Gyroid Infill {infill_fraction*100:.1f}% ===\")\n",
    "    print(f\"Total Area (approx)    = {A_total:.4f} mm²\")\n",
    "    print(f\"Shell Area (approx)    = {A_shell:.4f} mm²\")\n",
    "    print(f\"Infill Area (approx)   = {A_total - A_shell:.4f} mm²\")\n",
    "    print(f\"Centroid (x_c, y_c)    = ({x_c:.4f}, {y_c:.4f})\")\n",
    "    print(f\"I_x (about centroid)   = {I_x:.4f}\")\n",
    "    print(f\"I_y (about centroid)   = {I_y:.4f}\")\n",
    "    print(f\"I_xy (product)         = {I_xy:.4f}\")\n",
    "    print(f\"Polar moment, J        = {J:.4f}\")\n",
    "    print()\n",
    "\n",
    "    return final_mask\n",
    "\n",
    "# Run the 5-step gyroid fill pattern demo\n",
    "if __name__ == \"__main__\":\n",
    "    width = 20\n",
    "    shell_layers = 3\n",
    "    layer_width = 0.4\n",
    "    line_thickness = 0.40\n",
    "    z_height = 2.5\n",
    "\n",
    "    fractions = geometric_infill_fractions(width, layer_width, shell_layers, steps=5)\n",
    "\n",
    "    for f in fractions:\n",
    "        gyroid_infill_rectangular_with_shell(\n",
    "            width=width,\n",
    "            height=width,\n",
    "            infill_fraction=f,\n",
    "            z_height=z_height,\n",
    "            line_thickness=line_thickness,\n",
    "            shell_layers=shell_layers,\n",
    "            layer_width=layer_width,\n",
    "            plot=True\n",
    "        )\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "da593f73-5609-43ec-8ca2-d877d41eb25d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 30996 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Infill 0.0% ====\n",
      "Total Area            = 161.6485\n",
      "Shell Area            = 138.9338\n",
      "Infill Area           = 22.7147\n",
      "Centroid (x_c, y_c)   = (-0.0000, -0.0000)\n",
      "I_x                   = 20764.2030\n",
      "I_y                   = 20764.2030\n",
      "I_xy                  = -1427.5163\n",
      "Polar moment J        = 41528.4060\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 47492 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Infill 10.8% ====\n",
      "Total Area            = 348.1033\n",
      "Shell Area            = 138.9338\n",
      "Infill Area           = 209.1695\n",
      "Centroid (x_c, y_c)   = (-0.2654, -0.2654)\n",
      "I_x                   = 34242.7410\n",
      "I_y                   = 34242.7410\n",
      "I_xy                  = -350.7013\n",
      "Polar moment J        = 68485.4820\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 63784 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Infill 28.2% ====\n",
      "Total Area            = 445.9947\n",
      "Shell Area            = 138.9338\n",
      "Infill Area           = 307.0609\n",
      "Centroid (x_c, y_c)   = (0.0335, 0.0335)\n",
      "I_x                   = 39073.3662\n",
      "I_y                   = 39073.3662\n",
      "I_xy                  = -130.3023\n",
      "Polar moment J        = 78146.7325\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 52352 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Infill 55.8% ====\n",
      "Total Area            = 521.3240\n",
      "Shell Area            = 138.9338\n",
      "Infill Area           = 382.3902\n",
      "Centroid (x_c, y_c)   = (0.2949, 0.2949)\n",
      "I_x                   = 43938.0877\n",
      "I_y                   = 43938.0877\n",
      "I_xy                  = -83.4094\n",
      "Polar moment J        = 87876.1754\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 20320 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Infill 100.0% ====\n",
      "Total Area            = 904.5169\n",
      "Shell Area            = 138.9338\n",
      "Infill Area           = 765.5831\n",
      "Centroid (x_c, y_c)   = (-0.0000, -0.0000)\n",
      "I_x                   = 68178.8138\n",
      "I_y                   = 68178.8138\n",
      "I_xy                  = -0.0000\n",
      "Polar moment J        = 136357.6276\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def compute_area_moments(XX, YY, mask):\n",
    "    dx = XX[0, 1] - XX[0, 0] if XX.shape[1] > 1 else 0.0\n",
    "    dy = YY[1, 0] - YY[0, 0] if YY.shape[0] > 1 else 0.0\n",
    "    dA = dx * dy\n",
    "\n",
    "    A = np.count_nonzero(mask) * dA\n",
    "    if A == 0:\n",
    "        return {\"A\": 0.0, \"x_c\": 0.0, \"y_c\": 0.0, \"I_x\": 0.0, \"I_y\": 0.0, \"I_xy\": 0.0, \"J\": 0.0}\n",
    "\n",
    "    x_vals = XX[mask]\n",
    "    y_vals = YY[mask]\n",
    "    x_c = np.sum(x_vals * dA) / A\n",
    "    y_c = np.sum(y_vals * dA) / A\n",
    "\n",
    "    x_shift = x_vals - x_c\n",
    "    y_shift = y_vals - y_c\n",
    "\n",
    "    I_x = np.sum((y_shift**2) * dA)\n",
    "    I_y = np.sum((x_shift**2) * dA)\n",
    "    I_xy = np.sum((x_shift * y_shift) * dA)\n",
    "    J = I_x + I_y\n",
    "\n",
    "    return {\"A\": A, \"x_c\": x_c, \"y_c\": y_c, \"I_x\": I_x, \"I_y\": I_y, \"I_xy\": I_xy, \"J\": J}\n",
    "\n",
    "def geometric_infill_fractions(width, layer_width, shell_layers, steps=5):\n",
    "    A_total = width ** 2\n",
    "    shell_thickness = 2 * layer_width * shell_layers\n",
    "    A_shell = A_total - (width - shell_thickness) ** 2\n",
    "    r = (A_total / A_shell) ** (1 / (steps - 1))\n",
    "    return [(A_shell * r**i - A_shell) / (A_total - A_shell) for i in range(steps)]\n",
    "\n",
    "def cubic_corner_infill_rectangular_with_shell_fractional(\n",
    "    width=20.0,\n",
    "    height=20.0,\n",
    "    spacing=5.0,\n",
    "    infill_fraction=1.0,\n",
    "    line_thickness=0.2,\n",
    "    z_height=0.0,\n",
    "    shell_layers=3,\n",
    "    layer_width=0.4,\n",
    "    plot=True\n",
    "):\n",
    "    N = 400\n",
    "    xs = np.linspace(-width / 2, width / 2, N)\n",
    "    ys = np.linspace(-height / 2, height / 2, N)\n",
    "    XX, YY = np.meshgrid(xs, ys)\n",
    "    Z = z_height\n",
    "\n",
    "    def dist_mod(coord, spc):\n",
    "        remainder = np.mod(coord, spc)\n",
    "        return np.minimum(remainder, spc - remainder)\n",
    "\n",
    "    effective_spacing = spacing / np.sqrt(infill_fraction) if infill_fraction > 0 else 1e6\n",
    "\n",
    "    dist1 = dist_mod(XX + YY, effective_spacing)\n",
    "    dist2 = dist_mod(XX + Z, effective_spacing)\n",
    "    dist3 = dist_mod(YY + Z, effective_spacing)\n",
    "\n",
    "    pattern_mask = (\n",
    "        (dist1 < line_thickness) |\n",
    "        (dist2 < line_thickness) |\n",
    "        (dist3 < line_thickness)\n",
    "    )\n",
    "\n",
    "    if infill_fraction >= 1.0:\n",
    "        pattern_mask = np.ones_like(pattern_mask, dtype=bool)\n",
    "\n",
    "    shell_thickness = shell_layers * layer_width\n",
    "    outer_rect = (\n",
    "        (np.abs(XX) <= width / 2) &\n",
    "        (np.abs(YY) <= height / 2)\n",
    "    )\n",
    "    inner_rect = (\n",
    "        (np.abs(XX) <= (width / 2 - shell_thickness)) &\n",
    "        (np.abs(YY) <= (height / 2 - shell_thickness))\n",
    "    )\n",
    "\n",
    "    shell_mask = outer_rect & (~inner_rect)\n",
    "    infill_mask = pattern_mask & inner_rect\n",
    "    final_mask = shell_mask | infill_mask\n",
    "\n",
    "    total_moments = compute_area_moments(XX, YY, final_mask)\n",
    "    shell_moments = compute_area_moments(XX, YY, shell_mask)\n",
    "    infill_area = total_moments[\"A\"] - shell_moments[\"A\"]\n",
    "\n",
    "    if plot:\n",
    "        plt.figure()\n",
    "        pattern_img = np.where(final_mask, 1.0, np.nan)\n",
    "        plt.imshow(pattern_img, origin='lower', extent=[-width / 2, width / 2, -height / 2, height / 2])\n",
    "        plt.title(f'Cubic Infill @ {infill_fraction*100:.1f}%, shell={shell_layers}×{layer_width}mm')\n",
    "        plt.xlabel('X')\n",
    "        plt.ylabel('Y')\n",
    "        plt.axis('equal')\n",
    "        plt.grid(True)\n",
    "        plt.show()\n",
    "\n",
    "    print(f\"==== Infill {infill_fraction*100:.1f}% ====\")\n",
    "    print(f\"Total Area            = {total_moments['A']:.4f}\")\n",
    "    print(f\"Shell Area            = {shell_moments['A']:.4f}\")\n",
    "    print(f\"Infill Area           = {infill_area:.4f}\")\n",
    "    print(f\"Centroid (x_c, y_c)   = ({total_moments['x_c']:.4f}, {total_moments['y_c']:.4f})\")\n",
    "    print(f\"I_x                   = {total_moments['I_x']:.4f}\")\n",
    "    print(f\"I_y                   = {total_moments['I_y']:.4f}\")\n",
    "    print(f\"I_xy                  = {total_moments['I_xy']:.4f}\")\n",
    "    print(f\"Polar moment J        = {total_moments['J']:.4f}\")\n",
    "    print()\n",
    "\n",
    "# --- RUN SIMULATION ---\n",
    "if __name__ == \"__main__\":\n",
    "    width = 30\n",
    "    shell_layers = 3\n",
    "    layer_width = 0.4\n",
    "    spacing = 3.0\n",
    "    line_thickness = 1.05*0.4  # realistic for a 0.4 mm nozzle\n",
    "    steps = 5\n",
    "\n",
    "    fractions = geometric_infill_fractions(width, layer_width, shell_layers, steps=steps)\n",
    "\n",
    "    for f in fractions:\n",
    "        cubic_corner_infill_rectangular_with_shell_fractional(\n",
    "            width=width,\n",
    "            height=width,\n",
    "            spacing=spacing,\n",
    "            infill_fraction=f,\n",
    "            line_thickness=line_thickness,\n",
    "            z_height=22.65,\n",
    "            shell_layers=shell_layers,\n",
    "            layer_width=layer_width,\n",
    "            plot=True\n",
    "        )\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "8c1f08b2-609a-4625-82d0-c3b68d578f30",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 21292 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Infill 0.0% ====\n",
      "Total Area            = 118.3510\n",
      "Shell Area            = 90.6929\n",
      "Infill Area           = 27.6581\n",
      "I_x                   = 5758.9127\n",
      "I_y                   = 5758.9127\n",
      "I_xy                  = -0.0000\n",
      "Polar moment J        = 11517.8255\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 22888 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Infill 13.1% ====\n",
      "Total Area            = 169.8080\n",
      "Shell Area            = 90.6929\n",
      "Infill Area           = 79.1151\n",
      "I_x                   = 7237.4443\n",
      "I_y                   = 7237.4443\n",
      "I_xy                  = 0.0000\n",
      "Polar moment J        = 14474.8886\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 25108 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Infill 32.2% ====\n",
      "Total Area            = 216.1192\n",
      "Shell Area            = 90.6929\n",
      "Infill Area           = 125.4263\n",
      "I_x                   = 8591.1870\n",
      "I_y                   = 8591.1870\n",
      "I_xy                  = -0.0000\n",
      "Polar moment J        = 17182.3741\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 25508 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Infill 59.9% ====\n",
      "Total Area            = 257.2848\n",
      "Shell Area            = 90.6929\n",
      "Infill Area           = 166.5919\n",
      "I_x                   = 9774.6930\n",
      "I_y                   = 9774.6930\n",
      "I_xy                  = 0.0000\n",
      "Polar moment J        = 19549.3860\n",
      "\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 24336 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Infill 100.0% ====\n",
      "Total Area            = 293.3047\n",
      "Shell Area            = 90.6929\n",
      "Infill Area           = 202.6118\n",
      "I_x                   = 10715.9519\n",
      "I_y                   = 10715.9519\n",
      "I_xy                  = 0.0000\n",
      "Polar moment J        = 21431.9039\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def compute_area_moments(XX, YY, mask):\n",
    "    dx = XX[0, 1] - XX[0, 0] if XX.shape[1] > 1 else 0.0\n",
    "    dy = YY[1, 0] - YY[0, 0] if YY.shape[0] > 1 else 0.0\n",
    "    dA = dx * dy\n",
    "\n",
    "    A = np.count_nonzero(mask) * dA\n",
    "    if A == 0:\n",
    "        return {\"A\": 0.0, \"x_c\": 0.0, \"y_c\": 0.0, \"I_x\": 0.0, \"I_y\": 0.0, \"I_xy\": 0.0, \"J\": 0.0}\n",
    "\n",
    "    x_vals = XX[mask]\n",
    "    y_vals = YY[mask]\n",
    "    x_c = np.sum(x_vals * dA) / A\n",
    "    y_c = np.sum(y_vals * dA) / A\n",
    "\n",
    "    x_shift = x_vals - x_c\n",
    "    y_shift = y_vals - y_c\n",
    "\n",
    "    I_x = np.sum((y_shift**2) * dA)\n",
    "    I_y = np.sum((x_shift**2) * dA)\n",
    "    I_xy = np.sum((x_shift * y_shift) * dA)\n",
    "    J = I_x + I_y\n",
    "\n",
    "    return {\"A\": A, \"x_c\": x_c, \"y_c\": y_c, \"I_x\": I_x, \"I_y\": I_y, \"I_xy\": I_xy, \"J\": J}\n",
    "\n",
    "def geometric_infill_fractions(width, layer_width, shell_layers, steps=5):\n",
    "    A_total = width ** 2\n",
    "    shell_thickness = 2 * layer_width * shell_layers\n",
    "    A_shell = A_total - (width - shell_thickness) ** 2\n",
    "    r = (A_total / A_shell) ** (1 / (steps - 1))\n",
    "\n",
    "    fractions = []\n",
    "    for i in range(steps):\n",
    "        A_i = A_shell * (r ** i)\n",
    "        f = (A_i - A_shell) / (A_total - A_shell)\n",
    "        fractions.append(f)\n",
    "    return fractions\n",
    "\n",
    "def grid_infill_rectangular_with_shell(width, height, spacing, infill_fraction,\n",
    "                                       line_thickness, shell_layers, layer_width, plot=True):\n",
    "    N = 400\n",
    "    xs = np.linspace(-width / 2, width / 2, N)\n",
    "    ys = np.linspace(-height / 2, height / 2, N)\n",
    "    XX, YY = np.meshgrid(xs, ys)\n",
    "\n",
    "    def dist_mod(coord, spc):\n",
    "        remainder = np.mod(coord, spc)\n",
    "        return np.minimum(remainder, spc - remainder)\n",
    "\n",
    "    effective_spacing = spacing / np.sqrt(infill_fraction) if infill_fraction > 0 else 1e6\n",
    "    dist_x = dist_mod(XX, effective_spacing)\n",
    "    dist_y = dist_mod(YY, effective_spacing)\n",
    "    pattern_mask = (dist_x < line_thickness) | (dist_y < line_thickness)\n",
    "\n",
    "    shell_thickness = shell_layers * layer_width\n",
    "    outer_rect = (np.abs(XX) <= width / 2) & (np.abs(YY) <= height / 2)\n",
    "    inner_rect = (np.abs(XX) <= (width / 2 - shell_thickness)) & (np.abs(YY) <= (height / 2 - shell_thickness))\n",
    "\n",
    "    shell_mask = outer_rect & (~inner_rect)\n",
    "    infill_mask = pattern_mask & inner_rect\n",
    "    final_mask = shell_mask | infill_mask\n",
    "\n",
    "    total_moments = compute_area_moments(XX, YY, final_mask)\n",
    "    shell_moments = compute_area_moments(XX, YY, shell_mask)\n",
    "    infill_area = total_moments[\"A\"] - shell_moments[\"A\"]\n",
    "\n",
    "    if plot:\n",
    "        plt.figure()\n",
    "        pattern_img = np.where(final_mask, 1.0, np.nan)\n",
    "        plt.imshow(pattern_img, origin='lower', extent=[-width / 2, width / 2, -height / 2, height / 2])\n",
    "        plt.title(f\"Infill={infill_fraction*100:.1f}%, Shell={shell_layers}×{layer_width}mm\")\n",
    "        plt.xlabel(\"X\")\n",
    "        plt.ylabel(\"Y\")\n",
    "        plt.axis(\"equal\")\n",
    "        plt.grid(True)\n",
    "        plt.show()\n",
    "\n",
    "    print(f\"==== Infill {infill_fraction*100:.1f}% ====\")\n",
    "    print(f\"Total Area            = {total_moments['A']:.4f}\")\n",
    "    print(f\"Shell Area            = {shell_moments['A']:.4f}\")\n",
    "    print(f\"Infill Area           = {infill_area:.4f}\")\n",
    "    print(f\"I_x                   = {total_moments['I_x']:.4f}\")\n",
    "    print(f\"I_y                   = {total_moments['I_y']:.4f}\")\n",
    "    print(f\"I_xy                  = {total_moments['I_xy']:.4f}\")\n",
    "    print(f\"Polar moment J        = {total_moments['J']:.4f}\")\n",
    "    print()\n",
    "\n",
    "    return total_moments\n",
    "\n",
    "# ---- Run 5 levels with geometric infill growth ----\n",
    "if __name__ == \"__main__\":\n",
    "    width = 20.0\n",
    "    shell_layers = 3\n",
    "    layer_width = 0.4\n",
    "    spacing = 2.0\n",
    "    line_thickness = 0.4\n",
    "\n",
    "    fractions = geometric_infill_fractions(width, layer_width, shell_layers, steps=5)\n",
    "\n",
    "    for f in fractions:\n",
    "        grid_infill_rectangular_with_shell(\n",
    "            width=width,\n",
    "            height=width,\n",
    "            spacing=spacing,\n",
    "            infill_fraction=f,\n",
    "            line_thickness=line_thickness,\n",
    "            shell_layers=shell_layers,\n",
    "            layer_width=layer_width,\n",
    "            plot=True\n",
    "        )\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a188471d-8c1a-44fd-8f14-0864efa1a47a",
   "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.13.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
