Coverage for pyrc\core\connecting.py: 100%
109 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-29 15:57 +0200
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-29 15:57 +0200
1# -------------------------------------------------------------------------------
2# Copyright (C) 2026 Joel Kimmich, Tim Jourdan
3# ------------------------------------------------------------------------------
4# License
5# This file is part of PyRC, distributed under GPL-3.0-or-later.
6# ------------------------------------------------------------------------------
8from __future__ import annotations
10from typing import TYPE_CHECKING
12from rtree import index
13from vpython import color
15from pyrc.core.components.resistor import Resistor
16from pyrc.core.resistors import CombinedResistor
18if TYPE_CHECKING:
19 from pyrc.core.components.templates import Cell
22def connect_cells_with_resistors(cells: list[Cell], tolerance: float = 1e-9) -> list[Resistor]:
23 """
24 Connect cells that share touching surfaces with resistor objects using AABB tree.
26 Existing connections are skipped.
28 Parameters
29 ----------
30 cells : list
31 List of Cell objects to connect
32 tolerance : float, default=1e-9
33 Floating point tolerance for boundary comparison
35 Returns
36 -------
37 list :
38 List of created Resistor objects
39 """
41 resistors = []
42 connections = set()
44 # Build R-tree spatial index with cell bounding boxes (3D)
45 p = index.Property()
46 p.dimension = 3
47 idx = index.Index(properties=p)
49 for i, cell in enumerate(cells):
50 bounds = cell.boundaries # [-x, x, -y, y, -z, z]
51 # rtree expects (minx, miny, minz, maxx, maxy, maxz)
52 bbox = (bounds[0], bounds[2], bounds[4], bounds[1], bounds[3], bounds[5])
53 idx.insert(i, bbox)
55 # Check each cell against candidates from spatial index
56 for i, cell1 in enumerate(cells):
57 bounds1 = cell1.boundaries
59 # Expand bbox slightly for tolerance to catch touching surfaces
60 bbox_query = (
61 bounds1[0] - tolerance,
62 bounds1[2] - tolerance,
63 bounds1[4] - tolerance,
64 bounds1[1] + tolerance,
65 bounds1[3] + tolerance,
66 bounds1[5] + tolerance,
67 )
69 # Query overlapping bounding boxes
70 for j in idx.intersection(bbox_query):
71 cell2 = cells[j]
73 # Check if already connected
74 connection_id = tuple(sorted([id(cell1), id(cell2)]))
75 if connection_id in connections:
76 continue
78 # Check if cells have touching surfaces
79 bounds2 = cell2.boundaries
80 if check_contact_between_cells(bounds1, bounds2, tolerance):
81 # Create resistor and connect cells
82 resistor = CombinedResistor()
83 resistor.double_connect(cell1, cell2)
85 resistors.append(resistor)
86 connections.add(connection_id)
88 return resistors
91# vibe coded algorithms to get the connected surfaces visualized.
92# it is not updated for this project yet
95def check_contact_between_cells(bounds1: list, bounds2: list, tolerance: float | int):
96 """
97 Check if two cells have touching surfaces.
99 Parameters
100 ----------
101 bounds1 : list
102 Boundaries of cell1 [-x, x, -y, y, -z, z]
103 bounds2 : list
104 Boundaries of cell2 [-x, x, -y, y, -z, z]
105 tolerance : float
106 Floating point tolerance
108 Returns
109 -------
110 bool
111 True if surfaces touch with overlapping area, False otherwise
112 """
114 # X-axis: check if faces are coplanar and overlap in y,z
115 if abs(bounds1[1] - bounds2[0]) <= tolerance or abs(bounds1[0] - bounds2[1]) <= tolerance:
116 y_overlap = min(bounds1[3], bounds2[3]) - max(bounds1[2], bounds2[2])
117 z_overlap = min(bounds1[5], bounds2[5]) - max(bounds1[4], bounds2[4])
118 if y_overlap > tolerance and z_overlap > tolerance:
119 return True
121 # Y-axis: check if faces are coplanar and overlap in x,z
122 if abs(bounds1[3] - bounds2[2]) <= tolerance or abs(bounds1[2] - bounds2[3]) <= tolerance:
123 x_overlap = min(bounds1[1], bounds2[1]) - max(bounds1[0], bounds2[0])
124 z_overlap = min(bounds1[5], bounds2[5]) - max(bounds1[4], bounds2[4])
125 if x_overlap > tolerance and z_overlap > tolerance:
126 return True
128 # Z-axis: check if faces are coplanar and overlap in x,y
129 if abs(bounds1[5] - bounds2[4]) <= tolerance or abs(bounds1[4] - bounds2[5]) <= tolerance:
130 x_overlap = min(bounds1[1], bounds2[1]) - max(bounds1[0], bounds2[0])
131 y_overlap = min(bounds1[3], bounds2[3]) - max(bounds1[2], bounds2[2])
132 if x_overlap > tolerance and y_overlap > tolerance:
133 return True
135 return False
138def visualize_contact_area(bounds1: list, bounds2: list, tolerance: float, contact_color=color.red):
139 """
140 Create a thin box and cylinder representing the contact area between two cells.
142 Parameters
143 ----------
144 bounds1 : list
145 Boundaries of cell1 [-x, x, -y, y, -z, z]
146 bounds2 : list
147 Boundaries of cell2 [-x, x, -y, y, -z, z]
148 tolerance : float
149 Floating point tolerance
150 contact_color : vpython.color
151 Color for contact area visualization
153 Returns
154 -------
155 tuple or None
156 (box, cylinder) representing contact area, or None if no contact
157 """
158 from vpython import box, cylinder, vector
160 thickness = tolerance * 10
162 # Calculate overlap ranges once
163 x_min, x_max = max(bounds1[0], bounds2[0]), min(bounds1[1], bounds2[1])
164 y_min, y_max = max(bounds1[2], bounds2[2]), min(bounds1[3], bounds2[3])
165 z_min, z_max = max(bounds1[4], bounds2[4]), min(bounds1[5], bounds2[5])
167 x_overlap = x_max - x_min
168 y_overlap = y_max - y_min
169 z_overlap = z_max - z_min
171 pos = None
172 size = None
173 cyl_axis = None
175 # X-axis: cell1 +x face touches cell2 -x face
176 if abs(bounds1[1] - bounds2[0]) <= tolerance < y_overlap and z_overlap > tolerance:
177 pos = vector(bounds1[1], (y_min + y_max) / 2, (z_min + z_max) / 2)
178 size = vector(thickness, y_overlap, z_overlap)
179 delta1_x = bounds1[1] - bounds1[0]
180 delta2_x = bounds2[1] - bounds2[0]
181 cyl_length = delta1_x / 4 + delta2_x / 4
182 cyl_axis = vector(cyl_length, 0, 0)
184 # X-axis: cell1 -x face touches cell2 +x face
185 elif abs(bounds1[0] - bounds2[1]) <= tolerance < y_overlap and z_overlap > tolerance:
186 pos = vector(bounds1[0], (y_min + y_max) / 2, (z_min + z_max) / 2)
187 size = vector(thickness, y_overlap, z_overlap)
188 delta1_x = bounds1[1] - bounds1[0]
189 delta2_x = bounds2[1] - bounds2[0]
190 cyl_length = delta1_x / 4 + delta2_x / 4
191 cyl_axis = vector(-cyl_length, 0, 0)
193 # Y-axis: cell1 +y face touches cell2 -y face
194 elif abs(bounds1[3] - bounds2[2]) <= tolerance < x_overlap and z_overlap > tolerance:
195 pos = vector((x_min + x_max) / 2, bounds1[3], (z_min + z_max) / 2)
196 size = vector(x_overlap, thickness, z_overlap)
197 delta1_y = bounds1[3] - bounds1[2]
198 delta2_y = bounds2[3] - bounds2[2]
199 cyl_length = delta1_y / 4 + delta2_y / 4
200 cyl_axis = vector(0, cyl_length, 0)
202 # Y-axis: cell1 -y face touches cell2 +y face
203 elif abs(bounds1[2] - bounds2[3]) <= tolerance < x_overlap and z_overlap > tolerance:
204 pos = vector((x_min + x_max) / 2, bounds1[2], (z_min + z_max) / 2)
205 size = vector(x_overlap, thickness, z_overlap)
206 delta1_y = bounds1[3] - bounds1[2]
207 delta2_y = bounds2[3] - bounds2[2]
208 cyl_length = delta1_y / 4 + delta2_y / 4
209 cyl_axis = vector(0, -cyl_length, 0)
211 # Z-axis: cell1 +z face touches cell2 -z face
212 elif abs(bounds1[5] - bounds2[4]) <= tolerance < x_overlap and y_overlap > tolerance:
213 pos = vector((x_min + x_max) / 2, (y_min + y_max) / 2, bounds1[5])
214 size = vector(x_overlap, y_overlap, thickness)
215 delta1_z = bounds1[5] - bounds1[4]
216 delta2_z = bounds2[5] - bounds2[4]
217 cyl_length = delta1_z / 4 + delta2_z / 4
218 cyl_axis = vector(0, 0, cyl_length)
220 # Z-axis: cell1 -z face touches cell2 +z face
221 elif abs(bounds1[4] - bounds2[5]) <= tolerance < x_overlap and y_overlap > tolerance:
222 pos = vector((x_min + x_max) / 2, (y_min + y_max) / 2, bounds1[4])
223 size = vector(x_overlap, y_overlap, thickness)
224 delta1_z = bounds1[5] - bounds1[4]
225 delta2_z = bounds2[5] - bounds2[4]
226 cyl_length = delta1_z / 4 + delta2_z / 4
227 cyl_axis = vector(0, 0, -cyl_length)
229 if pos is not None and size is not None and cyl_axis is not None:
230 contact_box = box(pos=pos, size=size, color=contact_color, opacity=0.7)
232 # Calculate cylinder diameter as 1/8 of minimal box dimension (excluding thickness)
233 box_dims = [abs(s) for s in [size.x, size.y, size.z] if abs(s) > thickness * 2]
234 cyl_radius = min(box_dims) / 16 if box_dims else thickness
236 contact_cylinder = cylinder(pos=pos, axis=cyl_axis, radius=cyl_radius, color=contact_color)
238 return contact_box, contact_cylinder
240 return None