Add standalone brain CLI, test geometries, and robustness sweep outputs

This commit is contained in:
2026-02-16 00:12:12 +00:00
parent 4bec4063a5
commit 9d4c37234a
22 changed files with 323144 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
"""CLI entry point for the Adaptive Isogrid Python Brain standalone pipeline."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any, Dict
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from shapely.geometry import Polygon
from src.atomizer_study import DEFAULT_PARAMS
from .density_field import evaluate_density_grid
from .triangulation import generate_triangulation
from .pocket_profiles import generate_pockets
from .profile_assembly import assemble_profile, profile_to_json
from .validation import validate_profile
def _load_json(path: Path) -> Dict[str, Any]:
with path.open("r", encoding="utf-8") as f:
return json.load(f)
def _merge_params(geometry: Dict[str, Any], params_file: Path | None) -> Dict[str, Any]:
params = dict(DEFAULT_PARAMS)
if params_file is not None:
user_params = _load_json(params_file)
if not isinstance(user_params, dict):
raise ValueError("--params must point to a JSON object")
params.update(user_params)
params["thickness"] = float(geometry.get("thickness", params.get("thickness", 10.0)))
return params
def _plot_density(geometry: Dict[str, Any], params: Dict[str, Any], out_path: Path, resolution: float = 3.0) -> None:
X, Y, eta = evaluate_density_grid(geometry, params, resolution=resolution)
fig, ax = plt.subplots(figsize=(8, 6), dpi=160)
m = ax.pcolormesh(X, Y, eta, shading="auto", cmap="viridis", vmin=0.0, vmax=1.0)
fig.colorbar(m, ax=ax, label="Density η")
outer = np.asarray(geometry["outer_boundary"])
ax.plot(np.r_[outer[:, 0], outer[0, 0]], np.r_[outer[:, 1], outer[0, 1]], "w-", lw=1.5)
for hole in geometry.get("holes", []):
hb = np.asarray(hole["boundary"])
ax.plot(np.r_[hb[:, 0], hb[0, 0]], np.r_[hb[:, 1], hb[0, 1]], "k-", lw=0.9)
ax.set_aspect("equal", adjustable="box")
ax.set_title("Density Field Heatmap")
ax.set_xlabel("x [mm]")
ax.set_ylabel("y [mm]")
fig.tight_layout()
fig.savefig(out_path)
plt.close(fig)
def _plot_triangulation(geometry: Dict[str, Any], triangulation: Dict[str, Any], out_path: Path) -> None:
verts = triangulation["vertices"]
tris = triangulation["triangles"]
fig, ax = plt.subplots(figsize=(8, 6), dpi=160)
ax.triplot(verts[:, 0], verts[:, 1], tris, color="#1f77b4", lw=0.4, alpha=0.85)
outer = np.asarray(geometry["outer_boundary"])
ax.plot(np.r_[outer[:, 0], outer[0, 0]], np.r_[outer[:, 1], outer[0, 1]], "k-", lw=1.6)
for hole in geometry.get("holes", []):
hb = np.asarray(hole["boundary"])
ax.plot(np.r_[hb[:, 0], hb[0, 0]], np.r_[hb[:, 1], hb[0, 1]], "r-", lw=1.0)
ax.set_aspect("equal", adjustable="box")
ax.set_title("Constrained Delaunay Triangulation / Rib Pattern")
ax.set_xlabel("x [mm]")
ax.set_ylabel("y [mm]")
fig.tight_layout()
fig.savefig(out_path)
plt.close(fig)
def _plot_final_profile(geometry, pockets, ribbed_plate, out_path: Path) -> None:
fig, ax = plt.subplots(figsize=(8, 6), dpi=160)
outer = np.asarray(geometry["outer_boundary"])
ax.plot(np.r_[outer[:, 0], outer[0, 0]], np.r_[outer[:, 1], outer[0, 1]], "k-", lw=1.8, label="Outer boundary")
for pocket in pockets:
pv = np.asarray(pocket["vertices"])
ax.fill(pv[:, 0], pv[:, 1], color="#88ccee", alpha=0.35, lw=0.0)
if ribbed_plate.geom_type == "Polygon":
geoms = [ribbed_plate]
else:
geoms = list(ribbed_plate.geoms)
for g in geoms:
x, y = g.exterior.xy
ax.plot(x, y, color="#228833", lw=1.2)
for i in g.interiors:
ix, iy = i.xy
ax.plot(ix, iy, color="#cc6677", lw=0.9)
ax.set_aspect("equal", adjustable="box")
ax.set_title("Final Ribbed Plate Profile with Pockets")
ax.set_xlabel("x [mm]")
ax.set_ylabel("y [mm]")
fig.tight_layout()
fig.savefig(out_path)
plt.close(fig)
def run_pipeline(geometry_path: Path, params_path: Path | None, output_dir: Path, output_json_name: str = "rib_profile.json") -> Dict[str, Any]:
geometry = _load_json(geometry_path)
params = _merge_params(geometry, params_path)
triangulation = generate_triangulation(geometry, params)
pockets = generate_pockets(triangulation, geometry, params)
ribbed_plate = assemble_profile(geometry, pockets, params)
is_valid, checks = validate_profile(ribbed_plate, params)
profile_json = profile_to_json(ribbed_plate, params)
profile_json["checks"] = checks
profile_json["pipeline"] = {
"geometry_file": str(geometry_path),
"num_vertices": int(len(triangulation.get("vertices", []))),
"num_triangles": int(len(triangulation.get("triangles", []))),
"num_pockets": int(len(pockets)),
"validation_ok": bool(is_valid),
}
output_dir.mkdir(parents=True, exist_ok=True)
out_json = output_dir / output_json_name
with out_json.open("w", encoding="utf-8") as f:
json.dump(profile_json, f, indent=2)
stem = geometry_path.stem
_plot_density(geometry, params, output_dir / f"{stem}_density.png")
_plot_triangulation(geometry, triangulation, output_dir / f"{stem}_triangulation.png")
_plot_final_profile(geometry, pockets, ribbed_plate, output_dir / f"{stem}_final_profile.png")
summary = {
"geometry": geometry.get("plate_id", geometry_path.name),
"output_json": str(out_json),
"mass_g": checks.get("mass_estimate_g"),
"num_pockets": len(pockets),
"validation_ok": is_valid,
"checks": checks,
}
return summary
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Adaptive Isogrid Python Brain standalone runner")
parser.add_argument("--geometry", required=True, help="Path to geometry JSON")
parser.add_argument("--params", default=None, help="Optional path to parameters JSON override")
parser.add_argument("--output-dir", default=".", help="Directory for rib_profile.json and PNGs")
parser.add_argument("--output-json", default="rib_profile.json", help="Output profile JSON file name")
return parser.parse_args()
def main() -> None:
args = _parse_args()
geometry_path = Path(args.geometry)
params_path = Path(args.params) if args.params else None
output_dir = Path(args.output_dir)
summary = run_pipeline(geometry_path, params_path, output_dir, output_json_name=args.output_json)
print("=== Adaptive Isogrid Brain Summary ===")
print(f"Geometry: {summary['geometry']}")
print(f"Output JSON: {summary['output_json']}")
print(f"Mass estimate [g]: {summary['mass_g']:.2f}")
print(f"Number of pockets: {summary['num_pockets']}")
print(f"Validation OK: {summary['validation_ok']}")
print("Validation checks:")
for k, v in summary["checks"].items():
print(f" - {k}: {v}")
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 KiB

View File

@@ -0,0 +1,11 @@
run,status,validation_ok,triangles,num_pockets,mass_g,eta_0,alpha,R_0,kappa,p,beta,R_edge,s_min,s_max,t_min,t_0,gamma,w_frame,r_f,d_keep,error
1,ok,False,2997,2452,2133.115,0.024,0.8118,36.3612,0.2608,1.4358,0.3021,10.7837,12.2157,36.2497,2.013,4.7926,0.5866,8.7221,1.0967,2.2075,
2,ok,True,3014,2581,2182.834,0.2919,1.3217,31.8735,1.8556,2.7321,0.1183,12.0514,10.7174,31.8916,2.3919,2.5352,1.524,8.7421,1.7291,1.4036,
3,ok,True,3233,2873,2205.513,0.256,1.1255,79.84,0.4385,1.7213,0.4772,16.0137,11.499,41.0598,2.2973,2.6204,1.4774,6.0914,1.7738,1.1377,
4,ok,True,3228,2423,2015.686,0.1204,1.0177,25.7383,1.5873,3.5823,0.3883,15.5176,12.6766,34.0867,2.8631,3.0536,0.6144,17.7988,1.6389,1.2223,
5,ok,True,2940,2591,1910.686,0.2158,1.2016,23.6696,1.5149,3.495,0.1232,38.1432,11.9328,38.6484,2.2568,3.0797,1.4469,6.0402,1.7299,1.3494,
6,ok,True,2918,2622,2205.288,0.26,1.012,20.0891,1.106,1.6643,0.3524,20.6759,8.8503,37.3657,2.2058,4.2842,1.3415,6.237,1.4311,1.4736,
7,ok,True,3115,2669,2449.74,0.1277,0.4673,24.2149,0.746,1.9383,0.2114,20.9257,13.7345,28.7082,3.6721,5.7055,0.4989,6.0535,1.0586,1.8882,
8,ok,True,2936,2345,2126.019,0.1455,0.9476,99.588,1.2621,1.756,0.5792,18.4717,12.4041,39.9381,2.8686,2.4973,1.2103,5.4152,1.1155,1.5955,
9,ok,True,3411,3106,2517.715,0.1134,1.2956,37.7829,0.8749,1.76,0.4007,14.416,19.9532,30.9842,2.8466,5.5171,0.5893,14.9602,1.9872,1.1063,
10,ok,False,3147,2453,2031.064,0.1198,1.2985,16.1554,1.0639,2.1258,0.1814,11.6958,13.9456,28.2304,2.3213,3.4338,0.7143,8.3514,1.0097,1.8065,
1 run status validation_ok triangles num_pockets mass_g eta_0 alpha R_0 kappa p beta R_edge s_min s_max t_min t_0 gamma w_frame r_f d_keep error
2 1 ok False 2997 2452 2133.115 0.024 0.8118 36.3612 0.2608 1.4358 0.3021 10.7837 12.2157 36.2497 2.013 4.7926 0.5866 8.7221 1.0967 2.2075
3 2 ok True 3014 2581 2182.834 0.2919 1.3217 31.8735 1.8556 2.7321 0.1183 12.0514 10.7174 31.8916 2.3919 2.5352 1.524 8.7421 1.7291 1.4036
4 3 ok True 3233 2873 2205.513 0.256 1.1255 79.84 0.4385 1.7213 0.4772 16.0137 11.499 41.0598 2.2973 2.6204 1.4774 6.0914 1.7738 1.1377
5 4 ok True 3228 2423 2015.686 0.1204 1.0177 25.7383 1.5873 3.5823 0.3883 15.5176 12.6766 34.0867 2.8631 3.0536 0.6144 17.7988 1.6389 1.2223
6 5 ok True 2940 2591 1910.686 0.2158 1.2016 23.6696 1.5149 3.495 0.1232 38.1432 11.9328 38.6484 2.2568 3.0797 1.4469 6.0402 1.7299 1.3494
7 6 ok True 2918 2622 2205.288 0.26 1.012 20.0891 1.106 1.6643 0.3524 20.6759 8.8503 37.3657 2.2058 4.2842 1.3415 6.237 1.4311 1.4736
8 7 ok True 3115 2669 2449.74 0.1277 0.4673 24.2149 0.746 1.9383 0.2114 20.9257 13.7345 28.7082 3.6721 5.7055 0.4989 6.0535 1.0586 1.8882
9 8 ok True 2936 2345 2126.019 0.1455 0.9476 99.588 1.2621 1.756 0.5792 18.4717 12.4041 39.9381 2.8686 2.4973 1.2103 5.4152 1.1155 1.5955
10 9 ok True 3411 3106 2517.715 0.1134 1.2956 37.7829 0.8749 1.76 0.4007 14.416 19.9532 30.9842 2.8466 5.5171 0.5893 14.9602 1.9872 1.1063
11 10 ok False 3147 2453 2031.064 0.1198 1.2985 16.1554 1.0639 2.1258 0.1814 11.6958 13.9456 28.2304 2.3213 3.4338 0.7143 8.3514 1.0097 1.8065

View File

@@ -0,0 +1,8 @@
# Parameter Sweep Results
- Geometry: `tests/test_geometries/sample_bracket.json`
- Runs: 10
- Successful executions: 10/10
- Validation OK: 8/10
See `parameter_sweep_results.csv` for full per-run data.

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 KiB

View File

@@ -0,0 +1,574 @@
{
"plate_id": "asymmetric_plate_dense_core",
"units": "mm",
"thickness": 9.0,
"material": "AL6061-T6",
"outer_boundary": [
[
0,
0
],
[
280,
0
],
[
330,
90
],
[
300,
210
],
[
120,
240
],
[
0,
180
]
],
"holes": [
{
"index": 0,
"center": [
55,
55
],
"diameter": 10,
"is_circular": true,
"boundary": [
[
60.0,
55.0
],
[
59.83,
56.294
],
[
59.33,
57.5
],
[
58.536,
58.536
],
[
57.5,
59.33
],
[
56.294,
59.83
],
[
55.0,
60.0
],
[
53.706,
59.83
],
[
52.5,
59.33
],
[
51.464,
58.536
],
[
50.67,
57.5
],
[
50.17,
56.294
],
[
50.0,
55.0
],
[
50.17,
53.706
],
[
50.67,
52.5
],
[
51.464,
51.464
],
[
52.5,
50.67
],
[
53.706,
50.17
],
[
55.0,
50.0
],
[
56.294,
50.17
],
[
57.5,
50.67
],
[
58.536,
51.464
],
[
59.33,
52.5
],
[
59.83,
53.706
]
],
"weight": 0.4
},
{
"index": 1,
"center": [
110,
70
],
"diameter": 8.0,
"is_circular": true,
"boundary": [
[
114.0,
70.0
],
[
113.864,
71.035
],
[
113.464,
72.0
],
[
112.828,
72.828
],
[
112.0,
73.464
],
[
111.035,
73.864
],
[
110.0,
74.0
],
[
108.965,
73.864
],
[
108.0,
73.464
],
[
107.172,
72.828
],
[
106.536,
72.0
],
[
106.136,
71.035
],
[
106.0,
70.0
],
[
106.136,
68.965
],
[
106.536,
68.0
],
[
107.172,
67.172
],
[
108.0,
66.536
],
[
108.965,
66.136
],
[
110.0,
66.0
],
[
111.035,
66.136
],
[
112.0,
66.536
],
[
112.828,
67.172
],
[
113.464,
68.0
],
[
113.864,
68.965
]
],
"weight": 0.26
},
{
"index": 2,
"center": [
190,
120
],
"diameter": 14.0,
"is_circular": true,
"boundary": [
[
200.0,
120.0
],
[
199.659,
122.588
],
[
198.66,
125.0
],
[
197.071,
127.071
],
[
195.0,
128.66
],
[
192.588,
129.659
],
[
190.0,
130.0
],
[
187.412,
129.659
],
[
185.0,
128.66
],
[
182.929,
127.071
],
[
181.34,
125.0
],
[
180.341,
122.588
],
[
180.0,
120.0
],
[
180.341,
117.412
],
[
181.34,
115.0
],
[
182.929,
112.929
],
[
185.0,
111.34
],
[
187.412,
110.341
],
[
190.0,
110.0
],
[
192.588,
110.341
],
[
195.0,
111.34
],
[
197.071,
112.929
],
[
198.66,
115.0
],
[
199.659,
117.412
]
],
"weight": 0.75
},
{
"index": 3,
"center": [
250,
155
],
"diameter": 12,
"is_circular": true,
"boundary": [
[
256.0,
155.0
],
[
255.796,
156.553
],
[
255.196,
158.0
],
[
254.243,
159.243
],
[
253.0,
160.196
],
[
251.553,
160.796
],
[
250.0,
161.0
],
[
248.447,
160.796
],
[
247.0,
160.196
],
[
245.757,
159.243
],
[
244.804,
158.0
],
[
244.204,
156.553
],
[
244.0,
155.0
],
[
244.204,
153.447
],
[
244.804,
152.0
],
[
245.757,
150.757
],
[
247.0,
149.804
],
[
248.447,
149.204
],
[
250.0,
149.0
],
[
251.553,
149.204
],
[
253.0,
149.804
],
[
254.243,
150.757
],
[
255.196,
152.0
],
[
255.796,
153.447
]
],
"weight": 0.75
},
{
"index": 4,
"center": [
90,
170
],
"diameter": 14,
"is_circular": true,
"boundary": [
[
97.0,
170.0
],
[
96.761,
171.812
],
[
96.062,
173.5
],
[
94.95,
174.95
],
[
93.5,
176.062
],
[
91.812,
176.761
],
[
90.0,
177.0
],
[
88.188,
176.761
],
[
86.5,
176.062
],
[
85.05,
174.95
],
[
83.938,
173.5
],
[
83.239,
171.812
],
[
83.0,
170.0
],
[
83.239,
168.188
],
[
83.938,
166.5
],
[
85.05,
165.05
],
[
86.5,
163.938
],
[
88.188,
163.239
],
[
90.0,
163.0
],
[
91.812,
163.239
],
[
93.5,
163.938
],
[
94.95,
165.05
],
[
96.062,
166.5
],
[
96.761,
168.188
]
],
"weight": 0.54
}
]
}

View File

@@ -0,0 +1,674 @@
{
"plate_id": "mixed_hole_weights_plate",
"units": "mm",
"thickness": 10.0,
"material": "AL6061-T6",
"outer_boundary": [
[
0,
0
],
[
320,
0
],
[
320,
220
],
[
0,
220
]
],
"holes": [
{
"index": 0,
"center": [
40,
40
],
"diameter": 12,
"is_circular": true,
"boundary": [
[
46.0,
40.0
],
[
45.796,
41.553
],
[
45.196,
43.0
],
[
44.243,
44.243
],
[
43.0,
45.196
],
[
41.553,
45.796
],
[
40.0,
46.0
],
[
38.447,
45.796
],
[
37.0,
45.196
],
[
35.757,
44.243
],
[
34.804,
43.0
],
[
34.204,
41.553
],
[
34.0,
40.0
],
[
34.204,
38.447
],
[
34.804,
37.0
],
[
35.757,
35.757
],
[
37.0,
34.804
],
[
38.447,
34.204
],
[
40.0,
34.0
],
[
41.553,
34.204
],
[
43.0,
34.804
],
[
44.243,
35.757
],
[
45.196,
37.0
],
[
45.796,
38.447
]
],
"weight": 0.19
},
{
"index": 1,
"center": [
80,
170
],
"diameter": 14,
"is_circular": true,
"boundary": [
[
87.0,
170.0
],
[
86.761,
171.812
],
[
86.062,
173.5
],
[
84.95,
174.95
],
[
83.5,
176.062
],
[
81.812,
176.761
],
[
80.0,
177.0
],
[
78.188,
176.761
],
[
76.5,
176.062
],
[
75.05,
174.95
],
[
73.938,
173.5
],
[
73.239,
171.812
],
[
73.0,
170.0
],
[
73.239,
168.188
],
[
73.938,
166.5
],
[
75.05,
165.05
],
[
76.5,
163.938
],
[
78.188,
163.239
],
[
80.0,
163.0
],
[
81.812,
163.239
],
[
83.5,
163.938
],
[
84.95,
165.05
],
[
86.062,
166.5
],
[
86.761,
168.188
]
],
"weight": 0.4
},
{
"index": 2,
"center": [
160,
110
],
"diameter": 14.0,
"is_circular": true,
"boundary": [
[
169.0,
110.0
],
[
168.693,
112.329
],
[
167.794,
114.5
],
[
166.364,
116.364
],
[
164.5,
117.794
],
[
162.329,
118.693
],
[
160.0,
119.0
],
[
157.671,
118.693
],
[
155.5,
117.794
],
[
153.636,
116.364
],
[
152.206,
114.5
],
[
151.307,
112.329
],
[
151.0,
110.0
],
[
151.307,
107.671
],
[
152.206,
105.5
],
[
153.636,
103.636
],
[
155.5,
102.206
],
[
157.671,
101.307
],
[
160.0,
101.0
],
[
162.329,
101.307
],
[
164.5,
102.206
],
[
166.364,
103.636
],
[
167.794,
105.5
],
[
168.693,
107.671
]
],
"weight": 0.75
},
{
"index": 3,
"center": [
240,
60
],
"diameter": 10,
"is_circular": true,
"boundary": [
[
245.0,
60.0
],
[
244.83,
61.294
],
[
244.33,
62.5
],
[
243.536,
63.536
],
[
242.5,
64.33
],
[
241.294,
64.83
],
[
240.0,
65.0
],
[
238.706,
64.83
],
[
237.5,
64.33
],
[
236.464,
63.536
],
[
235.67,
62.5
],
[
235.17,
61.294
],
[
235.0,
60.0
],
[
235.17,
58.706
],
[
235.67,
57.5
],
[
236.464,
56.464
],
[
237.5,
55.67
],
[
238.706,
55.17
],
[
240.0,
55.0
],
[
241.294,
55.17
],
[
242.5,
55.67
],
[
243.536,
56.464
],
[
244.33,
57.5
],
[
244.83,
58.706
]
],
"weight": 0.61
},
{
"index": 4,
"center": [
280,
180
],
"diameter": 14.0,
"is_circular": true,
"boundary": [
[
288.0,
180.0
],
[
287.727,
182.071
],
[
286.928,
184.0
],
[
285.657,
185.657
],
[
284.0,
186.928
],
[
282.071,
187.727
],
[
280.0,
188.0
],
[
277.929,
187.727
],
[
276.0,
186.928
],
[
274.343,
185.657
],
[
273.072,
184.0
],
[
272.273,
182.071
],
[
272.0,
180.0
],
[
272.273,
177.929
],
[
273.072,
176.0
],
[
274.343,
174.343
],
[
276.0,
173.072
],
[
277.929,
172.273
],
[
280.0,
172.0
],
[
282.071,
172.273
],
[
284.0,
173.072
],
[
285.657,
174.343
],
[
286.928,
176.0
],
[
287.727,
177.929
]
],
"weight": 0.75
},
{
"index": 5,
"center": [
45,
185
],
"diameter": 9,
"is_circular": true,
"boundary": [
[
49.5,
185.0
],
[
49.347,
186.165
],
[
48.897,
187.25
],
[
48.182,
188.182
],
[
47.25,
188.897
],
[
46.165,
189.347
],
[
45.0,
189.5
],
[
43.835,
189.347
],
[
42.75,
188.897
],
[
41.818,
188.182
],
[
41.103,
187.25
],
[
40.653,
186.165
],
[
40.5,
185.0
],
[
40.653,
183.835
],
[
41.103,
182.75
],
[
41.818,
181.818
],
[
42.75,
181.103
],
[
43.835,
180.653
],
[
45.0,
180.5
],
[
46.165,
180.653
],
[
47.25,
181.103
],
[
48.182,
181.818
],
[
48.897,
182.75
],
[
49.347,
183.835
]
],
"weight": 0.12
}
]
}

View File

@@ -0,0 +1,458 @@
{
"plate_id": "small_plate_200x150",
"units": "mm",
"thickness": 8.0,
"material": "AL6061-T6",
"outer_boundary": [
[
0,
0
],
[
200,
0
],
[
200,
150
],
[
0,
150
]
],
"holes": [
{
"index": 0,
"center": [
30,
30
],
"diameter": 10.0,
"is_circular": true,
"boundary": [
[
35.0,
30.0
],
[
34.83,
31.294
],
[
34.33,
32.5
],
[
33.536,
33.536
],
[
32.5,
34.33
],
[
31.294,
34.83
],
[
30.0,
35.0
],
[
28.706,
34.83
],
[
27.5,
34.33
],
[
26.464,
33.536
],
[
25.67,
32.5
],
[
25.17,
31.294
],
[
25.0,
30.0
],
[
25.17,
28.706
],
[
25.67,
27.5
],
[
26.464,
26.464
],
[
27.5,
25.67
],
[
28.706,
25.17
],
[
30.0,
25.0
],
[
31.294,
25.17
],
[
32.5,
25.67
],
[
33.536,
26.464
],
[
34.33,
27.5
],
[
34.83,
28.706
]
],
"weight": 1.0
},
{
"index": 1,
"center": [
170,
30
],
"diameter": 10.0,
"is_circular": true,
"boundary": [
[
175.0,
30.0
],
[
174.83,
31.294
],
[
174.33,
32.5
],
[
173.536,
33.536
],
[
172.5,
34.33
],
[
171.294,
34.83
],
[
170.0,
35.0
],
[
168.706,
34.83
],
[
167.5,
34.33
],
[
166.464,
33.536
],
[
165.67,
32.5
],
[
165.17,
31.294
],
[
165.0,
30.0
],
[
165.17,
28.706
],
[
165.67,
27.5
],
[
166.464,
26.464
],
[
167.5,
25.67
],
[
168.706,
25.17
],
[
170.0,
25.0
],
[
171.294,
25.17
],
[
172.5,
25.67
],
[
173.536,
26.464
],
[
174.33,
27.5
],
[
174.83,
28.706
]
],
"weight": 1.0
},
{
"index": 2,
"center": [
170,
120
],
"diameter": 10.0,
"is_circular": true,
"boundary": [
[
175.0,
120.0
],
[
174.83,
121.294
],
[
174.33,
122.5
],
[
173.536,
123.536
],
[
172.5,
124.33
],
[
171.294,
124.83
],
[
170.0,
125.0
],
[
168.706,
124.83
],
[
167.5,
124.33
],
[
166.464,
123.536
],
[
165.67,
122.5
],
[
165.17,
121.294
],
[
165.0,
120.0
],
[
165.17,
118.706
],
[
165.67,
117.5
],
[
166.464,
116.464
],
[
167.5,
115.67
],
[
168.706,
115.17
],
[
170.0,
115.0
],
[
171.294,
115.17
],
[
172.5,
115.67
],
[
173.536,
116.464
],
[
174.33,
117.5
],
[
174.83,
118.706
]
],
"weight": 1.0
},
{
"index": 3,
"center": [
30,
120
],
"diameter": 10.0,
"is_circular": true,
"boundary": [
[
35.0,
120.0
],
[
34.83,
121.294
],
[
34.33,
122.5
],
[
33.536,
123.536
],
[
32.5,
124.33
],
[
31.294,
124.83
],
[
30.0,
125.0
],
[
28.706,
124.83
],
[
27.5,
124.33
],
[
26.464,
123.536
],
[
25.67,
122.5
],
[
25.17,
121.294
],
[
25.0,
120.0
],
[
25.17,
118.706
],
[
25.67,
117.5
],
[
26.464,
116.464
],
[
27.5,
115.67
],
[
28.706,
115.17
],
[
30.0,
115.0
],
[
31.294,
115.17
],
[
32.5,
115.67
],
[
33.536,
116.464
],
[
34.33,
117.5
],
[
34.83,
118.706
]
],
"weight": 1.0
}
]
}