pcb-stator-coil-generator/coil_version2.ipynb

692 lines
445 KiB
Text
Raw Normal View History

{
"cells": [
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 97,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
2022-10-08 09:27:38 +00:00
"\n",
"# import matplotlib as plt\n",
"import matplotlib.pyplot as plt\n",
"import scipy\n",
"from skspatial.objects import LineSegment, Line\n",
"from enum import Enum"
]
},
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 98,
"metadata": {},
"outputs": [],
"source": [
"VIA_DIAM = 0.8\n",
"VIA_DRILL = 0.4\n",
2022-10-08 18:17:54 +00:00
"STATOR_HOLE_RADIUS = 5\n",
"TRACK_WIDTH = 0.2\n",
"TRACK_SPACING = 0.2\n",
2022-10-08 18:17:54 +00:00
"TURNS = 8\n",
"STATOR_RADIUS = 18\n",
"COIL_CENTER_RADIUS = 11\n",
2022-10-08 16:59:50 +00:00
"USE_SPIRAL = False\n",
"Layer = Enum(\"Layer\", \"FRONT BACK\")"
]
},
2022-10-08 16:59:50 +00:00
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Arbitrary Coil Generation"
]
},
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 99,
"metadata": {},
"outputs": [],
"source": [
"template = [(-1.5, -0.1), (1.5, -2), (2.0, -1), (2.0, 1), (1.5, 2), (-1.5, 0.1)]"
]
},
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 100,
"metadata": {},
2022-10-08 18:17:54 +00:00
"outputs": [
{
"data": {
"text/plain": [
"[<matplotlib.lines.Line2D at 0x122482260>]"
]
},
"execution_count": 100,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 640x480 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# plot the template shape wrapping around to the first point\n",
2022-10-08 09:27:38 +00:00
"plt.plot(\n",
" [x for x, y in template] + [template[0][0]],\n",
" [y for x, y in template] + [template[0][1]],\n",
")"
]
},
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 101,
"metadata": {},
"outputs": [],
"source": [
"# this speeds things up dramatically as we only have to compute the line intersections once\n",
"# there are probably much faster ways of doing this - we're just doing a brute force search\n",
"# for the intersections - consult algorithms from games for inspiration...\n",
"def get_template_point_cache(template):\n",
" # sweep a line from the origin through 360 degress times the number of turns in 1 degree increments\n",
" # and find the intersection points with the template shape\n",
" cache = {}\n",
" for angle in np.arange(0, 360 + 1, 1):\n",
" line = LineSegment(\n",
" np.array([0, 0]),\n",
" np.array(\n",
" [1000 * np.cos(np.deg2rad(angle)), 1000 * np.sin(np.deg2rad(angle))]\n",
" ),\n",
" )\n",
" for i in range(len(template)):\n",
" segment = LineSegment(\n",
" np.array(template[i]), np.array(template[(i + 1) % len(template)])\n",
" )\n",
" try:\n",
" intersection = line.intersect_line_segment(segment)\n",
" if intersection is not None:\n",
" cache[angle] = (intersection, segment)\n",
" except ValueError:\n",
2022-10-08 09:27:38 +00:00
" try:\n",
" # nudge the angle slightly\n",
" new_angle = angle + 0.1\n",
" line = LineSegment(\n",
" np.array([0, 0]),\n",
" np.array(\n",
" [\n",
" 1000 * np.cos(np.deg2rad(new_angle)),\n",
" 1000 * np.sin(np.deg2rad(new_angle)),\n",
" ]\n",
" ),\n",
" )\n",
" intersection = line.intersect_line_segment(segment)\n",
" if intersection is not None:\n",
" cache[angle] = (intersection, segment)\n",
" except ValueError:\n",
" pass\n",
" return cache\n",
"\n",
"\n",
"def get_point(angle, template, layer, spacing, cache):\n",
" if layer == Layer.BACK:\n",
" angle = angle + 180\n",
" intersection, segment = cache[angle % 360]\n",
" return intersection, segment\n",
"\n",
"\n",
"# get the points in a coil shape\n",
"# Use reverse for bottom layer (basically flips the y coordinate so that the coil goes in the opposite direction)\n",
"# Also rotates the endpoints by 90 degress so that the exit point on the bottom layer is to the left hand side\n",
"def get_points(template, turns, spacing, layer=Layer.FRONT, cache=None):\n",
" if cache is None:\n",
" cache = get_template_point_cache(template)\n",
" coil_points = []\n",
" last_segment = None\n",
" for angle in np.arange(0, 360 * turns + 1, 1):\n",
" offset = spacing * angle / 360\n",
" intersection, segment = get_point(angle, template, layer, spacing, cache)\n",
" vector = np.array(segment.point_a) - np.array(segment.point_b)\n",
" normal = vector / np.linalg.norm(vector)\n",
" # rotate the vector 90 degrees\n",
" normal = np.array([-normal[1], normal[0]])\n",
" # move the intersection point along the normal vector by the spacing\n",
" coil_point = intersection + normal * offset\n",
" if layer == Layer.BACK:\n",
" coil_points.append((coil_point[0], -coil_point[1], segment))\n",
" else:\n",
" coil_points.append((coil_point[0], coil_point[1], segment))\n",
" # run through the generated coil points and where the line segments change add a point that is the intersection of the previous and next lines\n",
" # this prevents any corner cutting\n",
" points = []\n",
" last_segment = coil_points[0][2]\n",
" for i in range(len(coil_points)):\n",
" x, y, segment = coil_points[i]\n",
" same_segment = (\n",
" (last_segment.point_a == segment.point_a).all()\n",
" and (last_segment.point_b == segment.point_b).all()\n",
" ).all()\n",
" if (not same_segment) and i > 2 and i < len(coil_points) - 2:\n",
" # create a line from the previous two points\n",
" line = Line(\n",
" np.array(coil_points[i - 2][0:2]),\n",
" np.array(coil_points[i - 2][0:2]) - np.array(coil_points[i - 1][0:2]),\n",
" )\n",
" # create a line from the next two points\n",
" line2 = Line(\n",
" np.array(coil_points[i][0:2]),\n",
" np.array(coil_points[i][0:2]) - np.array(coil_points[i + 1][0:2]),\n",
" )\n",
" # find the intersection of the two lines\n",
" intersection = line.intersect_line(line2)\n",
" # add the intersection point to the list of points\n",
" points.append(intersection)\n",
" last_segment = segment\n",
" points.append((x, y))\n",
" return points\n",
"\n",
"\n",
"def optimize_points(points):\n",
" # follow the line and remove points that are in the same direction as the previous poin\n",
" # keep doing this until the direction changes significantly\n",
" # this is a very simple optimization that removes a lot of points\n",
" # it's not perfect but it's a good start\n",
" optimized_points = []\n",
" for i in range(len(points)):\n",
" if i == 0:\n",
" optimized_points.append(points[i])\n",
" else:\n",
" vector1 = np.array(points[i]) - np.array(points[i - 1])\n",
" vector2 = np.array(points[(i + 1) % len(points)]) - np.array(points[i])\n",
" length1 = np.linalg.norm(vector1)\n",
" length2 = np.linalg.norm(vector2)\n",
" if length1 > 0 and length2 > 0:\n",
" dot = np.dot(vector1, vector2) / (length1 * length2)\n",
" # clamp dot between -1 and 1\n",
" dot = max(-1, min(1, dot))\n",
" angle = np.arccos(dot)\n",
2022-10-08 09:27:38 +00:00
" if angle > np.deg2rad(5):\n",
" optimized_points.append(points[i])\n",
" print(\"Optimised from {} to {} points\".format(len(points), len(optimized_points)))\n",
" return optimized_points\n",
"\n",
"\n",
"def chaikin(points, iterations):\n",
" if iterations == 0:\n",
" return points\n",
" l = len(points)\n",
" smoothed = []\n",
" for i in range(l - 1):\n",
" x1, y1 = points[i]\n",
" x2, y2 = points[i + 1]\n",
" smoothed.append([0.9 * x1 + 0.1 * x2, 0.9 * y1 + 0.1 * y2])\n",
" smoothed.append([0.1 * x1 + 0.9 * x2, 0.1 * y1 + 0.9 * y2])\n",
" smoothed.append(points[l - 1])\n",
" return chaikin(smoothed, iterations - 1)"
]
},
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 102,
"metadata": {},
"outputs": [],
"source": [
2022-10-08 16:59:50 +00:00
"cache = get_template_point_cache(template)"
]
},
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 103,
"metadata": {},
2022-10-08 18:17:54 +00:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Optimised from 2929 to 50 points\n",
"Optimised from 2929 to 50 points\n",
"Track points 394 394\n"
]
}
],
"source": [
2022-10-08 16:59:50 +00:00
"if not USE_SPIRAL:\n",
" points_f = get_points(\n",
" template, TURNS, TRACK_SPACING + TRACK_WIDTH, Layer.FRONT, cache\n",
" )\n",
" points_b = get_points(\n",
" template, TURNS, TRACK_SPACING + TRACK_WIDTH, Layer.BACK, cache\n",
" )\n",
"\n",
" points_f = [(0, 0)] + chaikin(optimize_points(points_f), 3)\n",
" points_b = [(0, 0)] + chaikin(optimize_points(points_b), 3)\n",
" print(\"Track points\", len(points_f), len(points_b))\n",
"else:\n",
" print(\"Using spiral\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Basic Spiral Coil Generation"
]
},
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 104,
"metadata": {},
"outputs": [],
"source": [
2022-10-08 16:59:50 +00:00
"def get_spiral(turns, start_radius, thickness, layer=Layer.FRONT):\n",
" points = []\n",
" # create a starting point in the center\n",
" for angle in np.arange(0, turns * 360, 1):\n",
" radius = start_radius + thickness * angle / 360\n",
" if layer == Layer.BACK:\n",
" x = radius * np.cos(np.deg2rad(angle + 180))\n",
" y = radius * np.sin(np.deg2rad(angle + 180))\n",
" points.append((x, -y))\n",
" else:\n",
" x = radius * np.cos(np.deg2rad(angle))\n",
" y = radius * np.sin(np.deg2rad(angle))\n",
" points.append((x, y))\n",
" return points"
]
},
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 105,
2022-10-08 16:59:50 +00:00
"metadata": {},
2022-10-08 18:17:54 +00:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Using template\n"
]
}
],
2022-10-08 16:59:50 +00:00
"source": [
"if USE_SPIRAL:\n",
" points_f = get_spiral(\n",
" TURNS, VIA_DIAM + TRACK_SPACING, TRACK_SPACING + TRACK_WIDTH, Layer.FRONT\n",
" )\n",
" points_b = get_spiral(\n",
" TURNS, VIA_DIAM + TRACK_SPACING, TRACK_SPACING + TRACK_WIDTH, Layer.BACK\n",
" )\n",
2022-10-08 09:27:38 +00:00
"\n",
2022-10-08 16:59:50 +00:00
" points_f = [(0, 0)] + points_f\n",
" points_b = [(0, 0)] + points_b\n",
" print(\"Track points\", len(points_f), len(points_b))\n",
"else:\n",
" print(\"Using template\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Generate PCB Layout"
]
},
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 106,
2022-10-08 16:59:50 +00:00
"metadata": {},
2022-10-08 18:17:54 +00:00
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Total length front 183.9872139925838\n",
"Total length back 183.77066506609512\n"
]
}
],
2022-10-08 16:59:50 +00:00
"source": [
2022-10-08 09:27:38 +00:00
"# calculat the total length of the track to compute the resistance\n",
"total_length_front = 0\n",
"for i in range(len(points_f) - 1):\n",
" total_length_front += np.linalg.norm(\n",
" np.array(points_f[i + 1]) - np.array(points_f[i])\n",
" )\n",
"print(\"Total length front\", total_length_front)\n",
"\n",
"total_length_back = 0\n",
"for i in range(len(points_b) - 1):\n",
" total_length_back += np.linalg.norm(\n",
" np.array(points_b[i + 1]) - np.array(points_b[i])\n",
" )\n",
2022-10-08 16:59:50 +00:00
"print(\"Total length back\", total_length_back)"
]
},
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 107,
2022-10-08 16:59:50 +00:00
"metadata": {},
"outputs": [],
"source": [
"vias = []\n",
"\n",
"angle_A = 0\n",
"angle_B = 120\n",
"angle_C = 240\n",
"\n",
2022-10-08 09:27:38 +00:00
"# get the point on an arc at the given angle\n",
"def get_arc_point(angle, radius):\n",
" return (\n",
" radius * np.cos(np.deg2rad(angle)),\n",
" radius * np.sin(np.deg2rad(angle)),\n",
" )\n",
"\n",
"\n",
"# draw an arc\n",
"def draw_arc(start_angle, end_angle, radius, step=10):\n",
" points = []\n",
" for angle in np.arange(start_angle, end_angle + step, step):\n",
" x = radius * np.cos(np.deg2rad(angle))\n",
" y = radius * np.sin(np.deg2rad(angle))\n",
" points.append((x, y))\n",
" return points\n",
"\n",
"\n",
"# roate the points by the required angle\n",
"def rotate(points, angle):\n",
" return [\n",
" [\n",
" x * np.cos(np.deg2rad(angle)) - y * np.sin(np.deg2rad(angle)),\n",
" x * np.sin(np.deg2rad(angle)) + y * np.cos(np.deg2rad(angle)),\n",
" ]\n",
" for x, y in points\n",
" ]\n",
"\n",
"\n",
"# move the points out to the distance at the requited angle\n",
"def translate(points, distance, angle):\n",
" return [\n",
" [\n",
" x + distance * np.cos(np.deg2rad(angle)),\n",
" y + distance * np.sin(np.deg2rad(angle)),\n",
" ]\n",
" for x, y in points\n",
" ]\n",
"\n",
"\n",
2022-10-08 09:27:38 +00:00
"def create_via(point):\n",
" return {\"x\": point[0], \"y\": point[1]}\n",
"\n",
"\n",
"# flip the y coordinate\n",
"def flip(points):\n",
" return [[x, -y] for x, y in points]\n",
"\n",
"\n",
"# the main coils\n",
"coil_A_f = translate(rotate(points_f, angle_A), COIL_CENTER_RADIUS, angle_A)\n",
"coil_A_b = translate(rotate(points_b, angle_A), COIL_CENTER_RADIUS, angle_A)\n",
"\n",
"coil_B_f = translate(rotate(points_f, angle_B), COIL_CENTER_RADIUS, angle_B)\n",
"coil_B_b = translate(rotate(points_b, angle_B), COIL_CENTER_RADIUS, angle_B)\n",
"\n",
"coil_C_f = translate(rotate(points_f, angle_C), COIL_CENTER_RADIUS, angle_C)\n",
"coil_C_b = translate(rotate(points_b, angle_C), COIL_CENTER_RADIUS, angle_C)\n",
"\n",
"# the opposite coils - for more power!\n",
"angle_A_opp = angle_A + 180\n",
"angle_B_opp = angle_B + 180\n",
"angle_C_opp = angle_C + 180\n",
"\n",
"coil_A_opp_f = translate(\n",
" rotate(flip(points_f), angle_A_opp), COIL_CENTER_RADIUS, angle_A_opp\n",
")\n",
"coil_A_opp_b = translate(\n",
" rotate(flip(points_b), angle_A_opp), COIL_CENTER_RADIUS, angle_A_opp\n",
")\n",
"\n",
"coil_B_opp_f = translate(\n",
" rotate(flip(points_f), angle_B_opp), COIL_CENTER_RADIUS, angle_B_opp\n",
")\n",
"coil_B_opp_b = translate(\n",
" rotate(flip(points_b), angle_B_opp), COIL_CENTER_RADIUS, angle_B_opp\n",
")\n",
"\n",
"coil_C_opp_f = translate(\n",
" rotate(flip(points_f), angle_C_opp), COIL_CENTER_RADIUS, angle_C_opp\n",
")\n",
"coil_C_opp_b = translate(\n",
" rotate(flip(points_b), angle_C_opp), COIL_CENTER_RADIUS, angle_C_opp\n",
")\n",
"\n",
2022-10-08 09:27:38 +00:00
"# connect the front and back coils together\n",
"vias.append(create_via(get_arc_point(angle_A, COIL_CENTER_RADIUS)))\n",
"vias.append(create_via(get_arc_point(angle_B, COIL_CENTER_RADIUS)))\n",
"vias.append(create_via(get_arc_point(angle_C, COIL_CENTER_RADIUS)))\n",
"vias.append(create_via(get_arc_point(angle_A_opp, COIL_CENTER_RADIUS)))\n",
"vias.append(create_via(get_arc_point(angle_B_opp, COIL_CENTER_RADIUS)))\n",
"vias.append(create_via(get_arc_point(angle_C_opp, COIL_CENTER_RADIUS)))\n",
"\n",
"# connect the front copper opposite coils together\n",
"common_connection_radius = STATOR_RADIUS - (TRACK_WIDTH + TRACK_SPACING)\n",
2022-10-08 09:27:38 +00:00
"common_coil_connections_b = draw_arc(angle_A_opp, angle_C_opp, common_connection_radius)\n",
"coil_A_opp_f.append(get_arc_point(angle_A_opp, common_connection_radius))\n",
"coil_B_opp_f.append(get_arc_point(angle_B_opp, common_connection_radius))\n",
"coil_C_opp_f.append(get_arc_point(angle_C_opp, common_connection_radius))\n",
"\n",
"vias.append(create_via(get_arc_point(angle_A_opp, common_connection_radius)))\n",
"vias.append(create_via(get_arc_point(angle_B_opp, common_connection_radius)))\n",
"vias.append(create_via(get_arc_point(angle_C_opp, common_connection_radius)))\n",
"\n",
"# route coil A and coil C round to coil B so we have a good place to connect to them\n",
"coil_input_radius = STATOR_RADIUS - (TRACK_WIDTH + TRACK_SPACING * 2 + VIA_DIAM / 2)\n",
"coil_A_input_connection_b = draw_arc(angle_A, 80, coil_input_radius)\n",
"coil_B_input_connection_f = draw_arc(90, angle_B, coil_input_radius)\n",
"coil_C_input_connection_b = draw_arc(100, angle_C, coil_input_radius)\n",
"\n",
2022-10-08 09:27:38 +00:00
"coil_A_f.append(coil_A_input_connection_b[0])\n",
"coil_B_f.append(get_arc_point(angle_B, coil_input_radius))\n",
"coil_C_f.append(coil_C_input_connection_b[-1])\n",
"\n",
"vias.append(create_via(coil_A_input_connection_b[0]))\n",
"vias.append(create_via(coil_C_input_connection_b[-1]))\n",
"\n",
"\n",
"# wires for connecting to opposite coils\n",
"connection_radius1 = STATOR_HOLE_RADIUS + (TRACK_SPACING)\n",
"connection_radius2 = connection_radius1 + (TRACK_SPACING + VIA_DIAM / 2)\n",
2022-10-08 09:27:38 +00:00
"\n",
"# draw a 45 degree line from each coil at connection radius 1\n",
"# then connect up to connection radius 2\n",
"# draw a 45 degree line to the opposite coil\n",
2022-10-08 09:27:38 +00:00
"\n",
"# coil A\n",
"coil_A_b.append(get_arc_point(angle_A, connection_radius1))\n",
"coil_A_opp_b.append(get_arc_point(angle_A_opp, connection_radius2))\n",
"a_connection_b = draw_arc(angle_A, angle_A + 90, connection_radius1)\n",
"a_connection_f = draw_arc(angle_A + 90, angle_A + 180, connection_radius2)\n",
"a_connection_b.append(a_connection_f[0])\n",
"\n",
2022-10-08 09:27:38 +00:00
"# coil B\n",
"coil_B_b.append(get_arc_point(angle_B, connection_radius1))\n",
"coil_B_opp_b.append(get_arc_point(angle_B_opp, connection_radius2))\n",
"b_connection_b = draw_arc(angle_B, angle_B + 90, connection_radius1)\n",
"b_connection_f = draw_arc(angle_B + 90, angle_B + 180, connection_radius2)\n",
"b_connection_b.append(b_connection_f[0])\n",
"\n",
2022-10-08 09:27:38 +00:00
"# coil C\n",
"coil_C_b.append(get_arc_point(angle_C, connection_radius1))\n",
"coil_C_opp_b.append(get_arc_point(angle_C_opp, connection_radius2))\n",
"c_connection_b = draw_arc(angle_C, angle_C + 90, connection_radius1)\n",
"c_connection_f = draw_arc(angle_C + 90, angle_C + 180, connection_radius2)\n",
"c_connection_b.append(c_connection_f[0])\n",
"\n",
"vias.append(create_via(a_connection_f[0]))\n",
"vias.append(create_via(b_connection_f[0]))\n",
"vias.append(create_via(c_connection_f[0]))\n",
"\n",
"vias.append(create_via(a_connection_f[-1]))\n",
"vias.append(create_via(b_connection_f[-1]))\n",
"vias.append(create_via(c_connection_f[-1]))"
]
},
{
"cell_type": "code",
2022-10-08 18:17:54 +00:00
"execution_count": 108,
"metadata": {},
2022-10-08 18:17:54 +00:00
"outputs": [
{
"data": {
"image/png": "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
"text/plain": [
"<Figure size 1000x1000 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"def create_track(points):\n",
2022-10-08 16:59:50 +00:00
" return [{\"x\": x, \"y\": y} for x, y in points]\n",
"\n",
"\n",
"# dump out the results to json\n",
"json_result = {\n",
" \"parameters\": {\n",
" \"trackWidth\": TRACK_WIDTH,\n",
" \"statorHoleRadius\": STATOR_HOLE_RADIUS,\n",
2022-10-08 18:17:54 +00:00
" \"statorRadius\": STATOR_RADIUS,\n",
" \"viaDiameter\": VIA_DIAM,\n",
" \"viaDrillDiameter\": VIA_DRILL,\n",
" },\n",
2022-10-08 09:27:38 +00:00
" \"vias\": vias,\n",
" \"silk\": [\n",
" {\n",
" \"x\": COIL_CENTER_RADIUS * np.cos(np.deg2rad(angle_A)),\n",
" \"y\": COIL_CENTER_RADIUS * np.sin(np.deg2rad(angle_A)),\n",
" \"text\": \"A\",\n",
" },\n",
" {\n",
" \"x\": COIL_CENTER_RADIUS * np.cos(np.deg2rad(angle_B)),\n",
" \"y\": COIL_CENTER_RADIUS * np.sin(np.deg2rad(angle_B)),\n",
" \"text\": \"B\",\n",
" },\n",
" {\n",
" \"x\": COIL_CENTER_RADIUS * np.cos(np.deg2rad(angle_C)),\n",
" \"y\": COIL_CENTER_RADIUS * np.sin(np.deg2rad(angle_C)),\n",
" \"text\": \"C\",\n",
" },\n",
" ],\n",
" \"tracks\": {\n",
" \"f\": [\n",
" create_track(points)\n",
" for points in [\n",
" coil_A_f,\n",
" coil_A_opp_f,\n",
" coil_B_f,\n",
" coil_B_opp_f,\n",
" coil_C_f,\n",
" coil_C_opp_f,\n",
" a_connection_f,\n",
" b_connection_f,\n",
" c_connection_f,\n",
2022-10-08 09:27:38 +00:00
" coil_B_input_connection_f,\n",
" ]\n",
" ],\n",
" \"b\": [\n",
" create_track(points)\n",
" for points in [\n",
" coil_A_b,\n",
" coil_A_opp_b,\n",
" coil_B_b,\n",
" coil_B_opp_b,\n",
" coil_C_b,\n",
" coil_C_opp_b,\n",
" common_coil_connections_b,\n",
" a_connection_b,\n",
" b_connection_b,\n",
" c_connection_b,\n",
2022-10-08 09:27:38 +00:00
" coil_A_input_connection_b,\n",
" coil_C_input_connection_b,\n",
" ]\n",
" ],\n",
" },\n",
"}\n",
"\n",
"import json\n",
"\n",
"json.dump(json_result, open(\"coil.json\", \"w\"))\n",
"\n",
"\n",
2022-10-08 16:59:50 +00:00
"# df = pd.DataFrame(coil_A_f, columns=[\"x\", \"y\"])\n",
"# ax = df.plot.line(x=\"x\", y=\"y\", label=\"Coil A\", color=\"blue\")\n",
"# ax.axis(\"equal\")\n",
"# df = pd.DataFrame(coil_A_b, columns=[\"x\", \"y\"])\n",
"# ax = df.plot.line(x=\"x\", y=\"y\", label=\"Coil B\", color=\"green\")\n",
"# ax.axis(\"equal\")\n",
2022-10-08 09:27:38 +00:00
"\n",
"\n",
"# plot the back tracks\n",
"ax = None\n",
"for track in json_result[\"tracks\"][\"b\"]:\n",
" df = pd.DataFrame(track, columns=[\"x\", \"y\"])\n",
" ax = df.plot.line(x=\"x\", y=\"y\", color=\"blue\", ax=ax)\n",
" ax.axis(\"equal\")\n",
"\n",
"# plot the front tracks\n",
"for track in json_result[\"tracks\"][\"f\"]:\n",
" df = pd.DataFrame(track, columns=[\"x\", \"y\"])\n",
" ax = df.plot.line(x=\"x\", y=\"y\", color=\"red\", ax=ax)\n",
" ax.axis(\"equal\")\n",
"\n",
"# hide the legend\n",
"ax.legend().set_visible(False)\n",
"# make the plot bigger\n",
"ax.figure.set_size_inches(10, 10)\n",
"\n",
"# plot the vias\n",
"for via in json_result[\"vias\"]:\n",
" ax.add_patch(\n",
" plt.Circle(\n",
" (via[\"x\"], via[\"y\"]),\n",
" radius=VIA_DIAM / 2,\n",
" fill=True,\n",
" color=\"black\",\n",
" )\n",
" )"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.10.7 ('venv': venv)",
"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.7"
},
"vscode": {
"interpreter": {
2022-10-08 18:17:54 +00:00
"hash": "fc384f9db26c31784edfba3761ba3d2c7b2f9b8a63e03a9eb0778fc35334efe1"
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}