merge: upstream/main (57 commits) — preserve PR #102 net label fix
Merged upstream/main into our fork. Conflict in connection_schematic.py resolved by taking upstream's file and re-applying our fix: - all_match_points = connected_wire_points | label positions - Allows nets where labels are placed directly at pin endpoints (no wire) Upstream changes include: security fixes (8 vulns), new schematic tools (get_net_at_point, find_orphaned_wires, snap_to_grid, get_wire_connections), generate_netlist rewrite via kicad-cli, wire preservation on component move, schematic analysis tools, KiCad 10 support.
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
from skip import Schematic
|
||||
import os
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from skip import Schematic
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Import new wire and pin managers
|
||||
try:
|
||||
from commands.wire_manager import WireManager
|
||||
from commands.pin_locator import PinLocator
|
||||
from commands.wire_manager import WireManager
|
||||
|
||||
WIRE_MANAGER_AVAILABLE = True
|
||||
except ImportError:
|
||||
@@ -24,180 +25,14 @@ class ConnectionManager:
|
||||
_pin_locator = None
|
||||
|
||||
@classmethod
|
||||
def get_pin_locator(cls):
|
||||
def get_pin_locator(cls) -> Any:
|
||||
"""Get or create pin locator instance"""
|
||||
if cls._pin_locator is None and WIRE_MANAGER_AVAILABLE:
|
||||
cls._pin_locator = PinLocator()
|
||||
return cls._pin_locator
|
||||
|
||||
@staticmethod
|
||||
def add_wire(
|
||||
schematic_path: Path,
|
||||
start_point: list,
|
||||
end_point: list,
|
||||
properties: dict = None,
|
||||
):
|
||||
"""
|
||||
Add a wire between two points using WireManager
|
||||
|
||||
Args:
|
||||
schematic_path: Path to .kicad_sch file
|
||||
start_point: [x, y] coordinates for wire start
|
||||
end_point: [x, y] coordinates for wire end
|
||||
properties: Optional wire properties (stroke_width, stroke_type)
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
if not WIRE_MANAGER_AVAILABLE:
|
||||
logger.error("WireManager not available")
|
||||
return False
|
||||
|
||||
stroke_width = properties.get("stroke_width", 0) if properties else 0
|
||||
stroke_type = (
|
||||
properties.get("stroke_type", "default") if properties else "default"
|
||||
)
|
||||
|
||||
success = WireManager.add_wire(
|
||||
schematic_path,
|
||||
start_point,
|
||||
end_point,
|
||||
stroke_width=stroke_width,
|
||||
stroke_type=stroke_type,
|
||||
)
|
||||
return success
|
||||
except Exception as e:
|
||||
logger.error(f"Error adding wire: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_pin_location(symbol, pin_name: str):
|
||||
"""
|
||||
Get the absolute location of a pin on a symbol
|
||||
|
||||
Args:
|
||||
symbol: Symbol object
|
||||
pin_name: Name or number of the pin (e.g., "1", "GND", "VCC")
|
||||
|
||||
Returns:
|
||||
[x, y] coordinates or None if pin not found
|
||||
"""
|
||||
try:
|
||||
if not hasattr(symbol, "pin"):
|
||||
logger.warning(f"Symbol {symbol.property.Reference.value} has no pins")
|
||||
return None
|
||||
|
||||
# Find the pin by name
|
||||
target_pin = None
|
||||
for pin in symbol.pin:
|
||||
if pin.name == pin_name:
|
||||
target_pin = pin
|
||||
break
|
||||
|
||||
if not target_pin:
|
||||
logger.warning(
|
||||
f"Pin '{pin_name}' not found on {symbol.property.Reference.value}"
|
||||
)
|
||||
return None
|
||||
|
||||
# Get pin location relative to symbol
|
||||
pin_loc = target_pin.location
|
||||
# Get symbol location
|
||||
symbol_at = symbol.at.value
|
||||
|
||||
# Calculate absolute position
|
||||
# pin_loc is relative to symbol origin, need to add symbol position
|
||||
abs_x = symbol_at[0] + pin_loc[0]
|
||||
abs_y = symbol_at[1] + pin_loc[1]
|
||||
|
||||
return [abs_x, abs_y]
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting pin location: {e}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def add_connection(
|
||||
schematic_path: Path,
|
||||
source_ref: str,
|
||||
source_pin: str,
|
||||
target_ref: str,
|
||||
target_pin: str,
|
||||
routing: str = "direct",
|
||||
):
|
||||
"""
|
||||
Add a wire connection between two component pins
|
||||
|
||||
Args:
|
||||
schematic_path: Path to .kicad_sch file
|
||||
source_ref: Reference designator of source component (e.g., "R1", "R1_")
|
||||
source_pin: Pin name/number on source component
|
||||
target_ref: Reference designator of target component (e.g., "C1", "C1_")
|
||||
target_pin: Pin name/number on target component
|
||||
routing: Routing style ('direct', 'orthogonal_h', 'orthogonal_v')
|
||||
|
||||
Returns:
|
||||
True if connection was successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
if not WIRE_MANAGER_AVAILABLE:
|
||||
logger.error("WireManager/PinLocator not available")
|
||||
return False
|
||||
|
||||
locator = ConnectionManager.get_pin_locator()
|
||||
if not locator:
|
||||
logger.error("Pin locator unavailable")
|
||||
return False
|
||||
|
||||
# Get pin locations
|
||||
source_loc = locator.get_pin_location(
|
||||
schematic_path, source_ref, source_pin
|
||||
)
|
||||
target_loc = locator.get_pin_location(
|
||||
schematic_path, target_ref, target_pin
|
||||
)
|
||||
|
||||
if not source_loc or not target_loc:
|
||||
logger.error("Could not determine pin locations")
|
||||
return False
|
||||
|
||||
# Create wire based on routing style
|
||||
if routing == "direct":
|
||||
# Simple direct wire
|
||||
success = WireManager.add_wire(schematic_path, source_loc, target_loc)
|
||||
elif routing == "orthogonal_h":
|
||||
# Orthogonal routing (horizontal first)
|
||||
path = WireManager.create_orthogonal_path(
|
||||
source_loc, target_loc, prefer_horizontal_first=True
|
||||
)
|
||||
success = WireManager.add_polyline_wire(schematic_path, path)
|
||||
elif routing == "orthogonal_v":
|
||||
# Orthogonal routing (vertical first)
|
||||
path = WireManager.create_orthogonal_path(
|
||||
source_loc, target_loc, prefer_horizontal_first=False
|
||||
)
|
||||
success = WireManager.add_polyline_wire(schematic_path, path)
|
||||
else:
|
||||
logger.error(f"Unknown routing style: {routing}")
|
||||
return False
|
||||
|
||||
if success:
|
||||
logger.info(
|
||||
f"Connected {source_ref}/{source_pin} to {target_ref}/{target_pin} (routing: {routing})"
|
||||
)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error adding connection: {e}")
|
||||
import traceback
|
||||
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def add_net_label(schematic: Schematic, net_name: str, position: list):
|
||||
def add_net_label(schematic: Schematic, net_name: str, position: list) -> Any:
|
||||
"""
|
||||
Add a net label to the schematic
|
||||
|
||||
@@ -214,9 +49,7 @@ class ConnectionManager:
|
||||
logger.error("Schematic does not have label collection")
|
||||
return None
|
||||
|
||||
label = schematic.label.append(
|
||||
text=net_name, at={"x": position[0], "y": position[1]}
|
||||
)
|
||||
label = schematic.label.append(text=net_name, at={"x": position[0], "y": position[1]})
|
||||
logger.info(f"Added net label '{net_name}' at {position}")
|
||||
return label
|
||||
except Exception as e:
|
||||
@@ -226,9 +59,9 @@ class ConnectionManager:
|
||||
@staticmethod
|
||||
def connect_to_net(
|
||||
schematic_path: Path, component_ref: str, pin_name: str, net_name: str
|
||||
):
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Connect a component pin to a named net using a wire stub and label
|
||||
Connect a component pin to a named net using a wire stub and label.
|
||||
|
||||
Args:
|
||||
schematic_path: Path to .kicad_sch file
|
||||
@@ -237,59 +70,78 @@ class ConnectionManager:
|
||||
net_name: Name of the net to connect to (e.g., "VCC", "GND", "SIGNAL_1")
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
Dict with keys:
|
||||
success – bool
|
||||
pin_location – [x, y] exact pin endpoint used (present on success)
|
||||
label_location – [x, y] where the net label was placed (present on success)
|
||||
wire_stub – [[x1,y1],[x2,y2]] the wire segment added (present on success)
|
||||
message – human-readable status
|
||||
"""
|
||||
try:
|
||||
if not WIRE_MANAGER_AVAILABLE:
|
||||
logger.error("WireManager/PinLocator not available")
|
||||
return False
|
||||
return {"success": False, "message": "WireManager/PinLocator not available"}
|
||||
|
||||
locator = ConnectionManager.get_pin_locator()
|
||||
if not locator:
|
||||
logger.error("Pin locator unavailable")
|
||||
return False
|
||||
return {"success": False, "message": "Pin locator unavailable"}
|
||||
|
||||
# Get pin location using PinLocator
|
||||
pin_loc = locator.get_pin_location(schematic_path, component_ref, pin_name)
|
||||
if not pin_loc:
|
||||
logger.error(f"Could not locate pin {component_ref}/{pin_name}")
|
||||
return False
|
||||
msg = f"Could not locate pin {component_ref}/{pin_name}"
|
||||
logger.error(msg)
|
||||
return {"success": False, "message": msg}
|
||||
|
||||
# Add a small wire stub from the pin (2.54mm = 0.1 inch, standard grid spacing)
|
||||
# Stub direction follows the pin's outward angle from the PinLocator
|
||||
pin_angle_deg = getattr(locator, '_last_pin_angle', 0)
|
||||
try:
|
||||
pin_angle_deg = locator.get_pin_angle(schematic_path, component_ref, pin_name) or 0
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Could not get pin angle for {component_ref}/{pin_name}, defaulting to 0: {e}"
|
||||
)
|
||||
pin_angle_deg = 0
|
||||
import math as _math
|
||||
|
||||
angle_rad = _math.radians(pin_angle_deg)
|
||||
stub_end = [round(pin_loc[0] + 2.54 * _math.cos(angle_rad), 4),
|
||||
round(pin_loc[1] - 2.54 * _math.sin(angle_rad), 4)]
|
||||
stub_end = [
|
||||
round(pin_loc[0] + 2.54 * _math.cos(angle_rad), 4),
|
||||
round(pin_loc[1] - 2.54 * _math.sin(angle_rad), 4),
|
||||
]
|
||||
|
||||
# Create wire stub using WireManager
|
||||
wire_success = WireManager.add_wire(schematic_path, pin_loc, stub_end)
|
||||
if not wire_success:
|
||||
logger.error(f"Failed to create wire stub for net connection")
|
||||
return False
|
||||
msg = "Failed to create wire stub for net connection"
|
||||
logger.error(msg)
|
||||
return {"success": False, "message": msg}
|
||||
|
||||
# Add label at the end of the stub using WireManager
|
||||
label_success = WireManager.add_label(
|
||||
schematic_path, net_name, stub_end, label_type="label"
|
||||
)
|
||||
if not label_success:
|
||||
logger.error(f"Failed to add net label '{net_name}'")
|
||||
return False
|
||||
msg = f"Failed to add net label '{net_name}'"
|
||||
logger.error(msg)
|
||||
return {"success": False, "message": msg}
|
||||
|
||||
logger.info(f"Connected {component_ref}/{pin_name} to net '{net_name}'")
|
||||
return True
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Connected {component_ref}/{pin_name} to net '{net_name}'",
|
||||
"pin_location": pin_loc,
|
||||
"label_location": stub_end,
|
||||
"wire_stub": [pin_loc, stub_end],
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error connecting to net: {e}")
|
||||
import traceback
|
||||
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
return {"success": False, "message": str(e)}
|
||||
|
||||
@staticmethod
|
||||
def connect_passthrough(
|
||||
@@ -298,7 +150,7 @@ class ConnectionManager:
|
||||
target_ref: str,
|
||||
net_prefix: str = "PIN",
|
||||
pin_offset: int = 0,
|
||||
):
|
||||
) -> Dict[str, List[str]]:
|
||||
"""
|
||||
Connect all pins of source_ref to matching pins of target_ref via shared net labels.
|
||||
Useful for passthrough adapters: J1 pin N <-> J2 pin N on net {net_prefix}_{N}.
|
||||
@@ -335,20 +187,24 @@ class ConnectionManager:
|
||||
|
||||
for pin_num in sorted(src_pins.keys(), key=lambda x: int(x) if x.isdigit() else 0):
|
||||
try:
|
||||
net_name = f"{net_prefix}_{int(pin_num) + pin_offset}" if pin_num.isdigit() else f"{net_prefix}_{pin_num}"
|
||||
net_name = (
|
||||
f"{net_prefix}_{int(pin_num) + pin_offset}"
|
||||
if pin_num.isdigit()
|
||||
else f"{net_prefix}_{pin_num}"
|
||||
)
|
||||
|
||||
ok_src = ConnectionManager.connect_to_net(
|
||||
res_src = ConnectionManager.connect_to_net(
|
||||
schematic_path, source_ref, pin_num, net_name
|
||||
)
|
||||
if not ok_src:
|
||||
if not res_src.get("success"):
|
||||
failed.append(f"{source_ref}/{pin_num}")
|
||||
continue
|
||||
|
||||
if pin_num in tgt_pins:
|
||||
ok_tgt = ConnectionManager.connect_to_net(
|
||||
res_tgt = ConnectionManager.connect_to_net(
|
||||
schematic_path, target_ref, pin_num, net_name
|
||||
)
|
||||
if not ok_tgt:
|
||||
if not res_tgt.get("success"):
|
||||
failed.append(f"{target_ref}/{pin_num}")
|
||||
continue
|
||||
else:
|
||||
@@ -365,7 +221,7 @@ class ConnectionManager:
|
||||
@staticmethod
|
||||
def get_net_connections(
|
||||
schematic: Schematic, net_name: str, schematic_path: Optional[Path] = None
|
||||
):
|
||||
) -> List[Dict]:
|
||||
"""
|
||||
Get all connections for a named net using wire graph analysis
|
||||
|
||||
@@ -383,7 +239,7 @@ class ConnectionManager:
|
||||
connections = []
|
||||
tolerance = 0.5 # 0.5mm tolerance for point coincidence (grid spacing consideration)
|
||||
|
||||
def points_coincide(p1, p2):
|
||||
def points_coincide(p1: Any, p2: Any) -> bool:
|
||||
"""Check if two points are the same (within tolerance)"""
|
||||
if not p1 or not p2:
|
||||
return False
|
||||
@@ -407,52 +263,55 @@ class ConnectionManager:
|
||||
logger.info(f"No labels found for net '{net_name}'")
|
||||
return connections
|
||||
|
||||
logger.debug(
|
||||
f"Found {len(net_label_positions)} labels for net '{net_name}'"
|
||||
)
|
||||
logger.debug(f"Found {len(net_label_positions)} labels for net '{net_name}'")
|
||||
|
||||
# 2. Find all wires connected to these label positions
|
||||
if not hasattr(schematic, "wire"):
|
||||
logger.warning("Schematic has no wires")
|
||||
return connections
|
||||
|
||||
connected_wire_points = set()
|
||||
if hasattr(schematic, "wire"):
|
||||
for wire in schematic.wire:
|
||||
if hasattr(wire, "pts") and hasattr(wire.pts, "xy"):
|
||||
# Get all points in this wire (polyline)
|
||||
wire_points = []
|
||||
for point in wire.pts.xy:
|
||||
if hasattr(point, "value"):
|
||||
wire_points.append(
|
||||
[float(point.value[0]), float(point.value[1])]
|
||||
)
|
||||
for wire in schematic.wire:
|
||||
if hasattr(wire, "pts") and hasattr(wire.pts, "xy"):
|
||||
# Get all points in this wire (polyline)
|
||||
wire_points = []
|
||||
for point in wire.pts.xy:
|
||||
if hasattr(point, "value"):
|
||||
wire_points.append([float(point.value[0]), float(point.value[1])])
|
||||
|
||||
# Check if any wire point touches a label
|
||||
wire_connected = False
|
||||
for wire_pt in wire_points:
|
||||
for label_pt in net_label_positions:
|
||||
if points_coincide(wire_pt, label_pt):
|
||||
wire_connected = True
|
||||
break
|
||||
if wire_connected:
|
||||
# Check if any wire point touches a label
|
||||
wire_connected = False
|
||||
for wire_pt in wire_points:
|
||||
for label_pt in net_label_positions:
|
||||
if points_coincide(wire_pt, label_pt):
|
||||
wire_connected = True
|
||||
break
|
||||
|
||||
# If this wire is connected to the net, add all its points
|
||||
if wire_connected:
|
||||
for pt in wire_points:
|
||||
connected_wire_points.add((pt[0], pt[1]))
|
||||
break
|
||||
|
||||
# Build the full set of candidate match points:
|
||||
# wire endpoints that touch this net PLUS label positions themselves.
|
||||
# This handles labels placed directly at pin endpoints (no wire needed).
|
||||
# If this wire is connected to the net, add all its points
|
||||
if wire_connected:
|
||||
for pt in wire_points:
|
||||
connected_wire_points.add((pt[0], pt[1]))
|
||||
|
||||
# Build match points: union of wire endpoints AND label positions.
|
||||
# This handles the valid KiCad style where a net label is placed
|
||||
# directly at a pin endpoint with no wire segment in between.
|
||||
all_match_points = connected_wire_points | {
|
||||
(p[0], p[1]) for p in net_label_positions
|
||||
}
|
||||
|
||||
if not all_match_points:
|
||||
logger.debug(f"No connection points found for net '{net_name}'")
|
||||
return connections
|
||||
|
||||
logger.debug(
|
||||
f"Net '{net_name}': {len(connected_wire_points)} wire points, "
|
||||
f"Found {len(connected_wire_points)} wire points, "
|
||||
f"{len(net_label_positions)} direct label positions, "
|
||||
f"{len(all_match_points)} total match points"
|
||||
f"{len(all_match_points)} total match points for net '{net_name}'"
|
||||
)
|
||||
|
||||
# 3. Find component pins at wire endpoints or direct label positions
|
||||
# 3. Find component pins at wire endpoints
|
||||
if not hasattr(schematic, "symbol"):
|
||||
logger.warning("Schematic has no symbols")
|
||||
return connections
|
||||
@@ -487,19 +346,15 @@ class ConnectionManager:
|
||||
# Check each pin
|
||||
for pin_num, pin_data in pins.items():
|
||||
# Get pin location
|
||||
pin_loc = locator.get_pin_location(
|
||||
schematic_path, ref, pin_num
|
||||
)
|
||||
pin_loc = locator.get_pin_location(schematic_path, ref, pin_num)
|
||||
if not pin_loc:
|
||||
continue
|
||||
|
||||
# Check if pin coincides with any wire point or label position
|
||||
for match_pt in all_match_points:
|
||||
if points_coincide(pin_loc, list(match_pt)):
|
||||
connections.append(
|
||||
{"component": ref, "pin": pin_num}
|
||||
)
|
||||
break # Pin found, no need to check more points
|
||||
# Check if pin coincides with any match point
|
||||
for wire_pt_tup in all_match_points:
|
||||
if points_coincide(pin_loc, list(wire_pt_tup)):
|
||||
connections.append({"component": ref, "pin": pin_num})
|
||||
break # Pin found, no need to check more wire points
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error matching pins for {ref}: {e}")
|
||||
@@ -515,10 +370,10 @@ class ConnectionManager:
|
||||
symbol_x = float(symbol_pos[0])
|
||||
symbol_y = float(symbol_pos[1])
|
||||
|
||||
# Check if symbol is near any wire point or label position (within 10mm)
|
||||
for wire_pt in all_match_points:
|
||||
# Check if symbol is near any match point (within 10mm)
|
||||
for wire_pt_tup in all_match_points:
|
||||
dist = (
|
||||
(symbol_x - wire_pt[0]) ** 2 + (symbol_y - wire_pt[1]) ** 2
|
||||
(symbol_x - wire_pt_tup[0]) ** 2 + (symbol_y - wire_pt_tup[1]) ** 2
|
||||
) ** 0.5
|
||||
if dist < 10.0: # 10mm proximity threshold
|
||||
connections.append({"component": ref, "pin": "unknown"})
|
||||
@@ -535,7 +390,9 @@ class ConnectionManager:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def generate_netlist(schematic: Schematic, schematic_path: Optional[Path] = None):
|
||||
def generate_netlist(
|
||||
schematic: Schematic, schematic_path: Optional[Path] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a netlist from the schematic
|
||||
|
||||
@@ -572,9 +429,7 @@ class ConnectionManager:
|
||||
component_info = {
|
||||
"reference": symbol.property.Reference.value,
|
||||
"value": (
|
||||
symbol.property.Value.value
|
||||
if hasattr(symbol.property, "Value")
|
||||
else ""
|
||||
symbol.property.Value.value if hasattr(symbol.property, "Value") else ""
|
||||
),
|
||||
"footprint": (
|
||||
symbol.property.Footprint.value
|
||||
@@ -597,9 +452,7 @@ class ConnectionManager:
|
||||
schematic, net_name, schematic_path
|
||||
)
|
||||
if connections:
|
||||
netlist["nets"].append(
|
||||
{"name": net_name, "connections": connections}
|
||||
)
|
||||
netlist["nets"].append({"name": net_name, "connections": connections})
|
||||
|
||||
logger.info(
|
||||
f"Generated netlist with {len(netlist['nets'])} nets and {len(netlist['components'])} components"
|
||||
@@ -609,35 +462,3 @@ class ConnectionManager:
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating netlist: {e}")
|
||||
return {"nets": [], "components": []}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example Usage (for testing)
|
||||
from schematic import (
|
||||
SchematicManager,
|
||||
) # Assuming schematic.py is in the same directory
|
||||
|
||||
# Create a new schematic
|
||||
test_sch = SchematicManager.create_schematic("ConnectionTestSchematic")
|
||||
|
||||
# Add some wires
|
||||
wire1 = ConnectionManager.add_wire(test_sch, [100, 100], [200, 100])
|
||||
wire2 = ConnectionManager.add_wire(test_sch, [200, 100], [200, 200])
|
||||
|
||||
# Note: add_connection, remove_connection, get_net_connections are placeholders
|
||||
# and require more complex implementation based on kicad-skip's structure.
|
||||
|
||||
# Example of how you might add a net label (requires finding a point on a wire)
|
||||
# from skip import Label
|
||||
# if wire1:
|
||||
# net_label_pos = wire1.start # Or calculate a point on the wire
|
||||
# net_label = test_sch.add_label(text="Net_01", at=net_label_pos)
|
||||
# print(f"Added net label 'Net_01' at {net_label_pos}")
|
||||
|
||||
# Save the schematic (optional)
|
||||
# SchematicManager.save_schematic(test_sch, "connection_test.kicad_sch")
|
||||
|
||||
# Clean up (if saved)
|
||||
# if os.path.exists("connection_test.kicad_sch"):
|
||||
# os.remove("connection_test.kicad_sch")
|
||||
# print("Cleaned up connection_test.kicad_sch")
|
||||
|
||||
Reference in New Issue
Block a user