Coverage for pyrc \ core \ connecting.py: 6%
109 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-13 16:59 +0200
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-13 16:59 +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 rtree import index
9from vpython import color
11from pyrc.core.components.resistor import Resistor
12from pyrc.core.resistors import CombinedResistor
15def connect_cells_with_resistors(cells: list, tolerance: float = 1e-9) -> list[Resistor]:
16 """
17 Connect cells that share touching surfaces with resistor objects using AABB tree.
19 Parameters
20 ----------
21 cells : list
22 List of Cell objects to connect
23 tolerance : float
24 Floating point tolerance for boundary comparison
26 Returns
27 -------
28 list :
29 List of created Resistor objects
30 """
32 resistors = []
33 connections = set()
35 # Build R-tree spatial index with cell bounding boxes (3D)
36 p = index.Property()
37 p.dimension = 3
38 idx = index.Index(properties=p)
40 for i, cell in enumerate(cells):
41 bounds = cell.boundaries # [-x, x, -y, y, -z, z]
42 # rtree expects (minx, miny, minz, maxx, maxy, maxz)
43 bbox = (bounds[0], bounds[2], bounds[4], bounds[1], bounds[3], bounds[5])
44 idx.insert(i, bbox)
46 # Check each cell against candidates from spatial index
47 for i, cell1 in enumerate(cells):
48 bounds1 = cell1.boundaries
50 # Expand bbox slightly for tolerance to catch touching surfaces
51 bbox_query = (
52 bounds1[0] - tolerance,
53 bounds1[2] - tolerance,
54 bounds1[4] - tolerance,
55 bounds1[1] + tolerance,
56 bounds1[3] + tolerance,
57 bounds1[5] + tolerance,
58 )
60 # Query overlapping bounding boxes
61 for j in idx.intersection(bbox_query):
62 if j <= i: # Avoid duplicate checks and self-comparison
63 continue
65 cell2 = cells[j]
67 # Check if already connected
68 connection_id = tuple(sorted([id(cell1), id(cell2)]))
69 if connection_id in connections:
70 continue
72 # Check if cells have touching surfaces
73 bounds2 = cell2.boundaries
74 if check_contact_between_cells(bounds1, bounds2, tolerance):
75 # Create resistor and connect cells
76 resistor = CombinedResistor()
77 resistor.double_connect(cell1, cell2)
79 resistors.append(resistor)
80 connections.add(connection_id)
82 return resistors
85# vibe coded algorithms to get the connected surfaces visualized.
86# it is not updated for this project yet
89def check_contact_between_cells(bounds1: list, bounds2: list, tolerance: float | int):
90 """
91 Check if two cells have touching surfaces.
93 Parameters
94 ----------
95 bounds1 : list
96 Boundaries of cell1 [-x, x, -y, y, -z, z]
97 bounds2 : list
98 Boundaries of cell2 [-x, x, -y, y, -z, z]
99 tolerance : float
100 Floating point tolerance
102 Returns
103 -------
104 bool
105 True if surfaces touch with overlapping area, False otherwise
106 """
108 # X-axis: check if faces are coplanar and overlap in y,z
109 if abs(bounds1[1] - bounds2[0]) < tolerance or abs(bounds1[0] - bounds2[1]) < tolerance:
110 y_overlap = min(bounds1[3], bounds2[3]) - max(bounds1[2], bounds2[2])
111 z_overlap = min(bounds1[5], bounds2[5]) - max(bounds1[4], bounds2[4])
112 if y_overlap > tolerance and z_overlap > tolerance:
113 return True
115 # Y-axis: check if faces are coplanar and overlap in x,z
116 if abs(bounds1[3] - bounds2[2]) < tolerance or abs(bounds1[2] - bounds2[3]) < tolerance:
117 x_overlap = min(bounds1[1], bounds2[1]) - max(bounds1[0], bounds2[0])
118 z_overlap = min(bounds1[5], bounds2[5]) - max(bounds1[4], bounds2[4])
119 if x_overlap > tolerance and z_overlap > tolerance:
120 return True
122 # Z-axis: check if faces are coplanar and overlap in x,y
123 if abs(bounds1[5] - bounds2[4]) < tolerance or abs(bounds1[4] - bounds2[5]) < tolerance:
124 x_overlap = min(bounds1[1], bounds2[1]) - max(bounds1[0], bounds2[0])
125 y_overlap = min(bounds1[3], bounds2[3]) - max(bounds1[2], bounds2[2])
126 if x_overlap > tolerance and y_overlap > tolerance:
127 return True
129 return False
132def visualize_contact_area(bounds1: list, bounds2: list, tolerance: float, contact_color=color.red):
133 """
134 Create a thin box and cylinder representing the contact area between two cells.
136 Parameters
137 ----------
138 bounds1 : list
139 Boundaries of cell1 [-x, x, -y, y, -z, z]
140 bounds2 : list
141 Boundaries of cell2 [-x, x, -y, y, -z, z]
142 tolerance : float
143 Floating point tolerance
144 contact_color : vpython.color
145 Color for contact area visualization
147 Returns
148 -------
149 tuple or None
150 (box, cylinder) representing contact area, or None if no contact
151 """
152 from vpython import box, cylinder, vector
154 thickness = tolerance * 10
156 # Calculate overlap ranges once
157 x_min, x_max = max(bounds1[0], bounds2[0]), min(bounds1[1], bounds2[1])
158 y_min, y_max = max(bounds1[2], bounds2[2]), min(bounds1[3], bounds2[3])
159 z_min, z_max = max(bounds1[4], bounds2[4]), min(bounds1[5], bounds2[5])
161 x_overlap = x_max - x_min
162 y_overlap = y_max - y_min
163 z_overlap = z_max - z_min
165 pos = None
166 size = None
167 cyl_axis = None
169 # X-axis: cell1 +x face touches cell2 -x face
170 if abs(bounds1[1] - bounds2[0]) < tolerance < y_overlap and z_overlap > tolerance:
171 pos = vector(bounds1[1], (y_min + y_max) / 2, (z_min + z_max) / 2)
172 size = vector(thickness, y_overlap, z_overlap)
173 delta1_x = bounds1[1] - bounds1[0]
174 delta2_x = bounds2[1] - bounds2[0]
175 cyl_length = delta1_x / 4 + delta2_x / 4
176 cyl_axis = vector(cyl_length, 0, 0)
178 # X-axis: cell1 -x face touches cell2 +x face
179 elif abs(bounds1[0] - bounds2[1]) < tolerance < y_overlap and z_overlap > tolerance:
180 pos = vector(bounds1[0], (y_min + y_max) / 2, (z_min + z_max) / 2)
181 size = vector(thickness, y_overlap, z_overlap)
182 delta1_x = bounds1[1] - bounds1[0]
183 delta2_x = bounds2[1] - bounds2[0]
184 cyl_length = delta1_x / 4 + delta2_x / 4
185 cyl_axis = vector(-cyl_length, 0, 0)
187 # Y-axis: cell1 +y face touches cell2 -y face
188 elif abs(bounds1[3] - bounds2[2]) < tolerance < x_overlap and z_overlap > tolerance:
189 pos = vector((x_min + x_max) / 2, bounds1[3], (z_min + z_max) / 2)
190 size = vector(x_overlap, thickness, z_overlap)
191 delta1_y = bounds1[3] - bounds1[2]
192 delta2_y = bounds2[3] - bounds2[2]
193 cyl_length = delta1_y / 4 + delta2_y / 4
194 cyl_axis = vector(0, cyl_length, 0)
196 # Y-axis: cell1 -y face touches cell2 +y face
197 elif abs(bounds1[2] - bounds2[3]) < tolerance < x_overlap and z_overlap > tolerance:
198 pos = vector((x_min + x_max) / 2, bounds1[2], (z_min + z_max) / 2)
199 size = vector(x_overlap, thickness, z_overlap)
200 delta1_y = bounds1[3] - bounds1[2]
201 delta2_y = bounds2[3] - bounds2[2]
202 cyl_length = delta1_y / 4 + delta2_y / 4
203 cyl_axis = vector(0, -cyl_length, 0)
205 # Z-axis: cell1 +z face touches cell2 -z face
206 elif abs(bounds1[5] - bounds2[4]) < tolerance < x_overlap and y_overlap > tolerance:
207 pos = vector((x_min + x_max) / 2, (y_min + y_max) / 2, bounds1[5])
208 size = vector(x_overlap, y_overlap, thickness)
209 delta1_z = bounds1[5] - bounds1[4]
210 delta2_z = bounds2[5] - bounds2[4]
211 cyl_length = delta1_z / 4 + delta2_z / 4
212 cyl_axis = vector(0, 0, cyl_length)
214 # Z-axis: cell1 -z face touches cell2 +z face
215 elif abs(bounds1[4] - bounds2[5]) < tolerance < x_overlap and y_overlap > tolerance:
216 pos = vector((x_min + x_max) / 2, (y_min + y_max) / 2, bounds1[4])
217 size = vector(x_overlap, y_overlap, thickness)
218 delta1_z = bounds1[5] - bounds1[4]
219 delta2_z = bounds2[5] - bounds2[4]
220 cyl_length = delta1_z / 4 + delta2_z / 4
221 cyl_axis = vector(0, 0, -cyl_length)
223 if pos is not None and size is not None and cyl_axis is not None:
224 contact_box = box(pos=pos, size=size, color=contact_color, opacity=0.7)
226 # Calculate cylinder diameter as 1/8 of minimal box dimension (excluding thickness)
227 box_dims = [abs(s) for s in [size.x, size.y, size.z] if abs(s) > thickness * 2]
228 cyl_radius = min(box_dims) / 16 if box_dims else thickness
230 contact_cylinder = cylinder(pos=pos, axis=cyl_axis, radius=cyl_radius, color=contact_color)
232 return contact_box, contact_cylinder
234 return None