A Python app to pack circular wires into the smallest possible circular bundle.
It supports multi-start optimization, sleeve layers, and bundle plotting.
- Features
- Installation & Run
- UI Walkthrough
- Predefined Sizes (
wire_types.yaml) - Predefined Sleeves (
sleeve_types.yaml) - How it Works (Math)
- Tips & Troubleshooting
- Screenshots
- Define any number of wire types (custom or from predefined sizes via YAML).
- Color-coded wires.
- Manufacturing margin (percentage) inflates radii to enforce spacing.
- Multi-start SLSQP with parallel runs to escape local minima.
- Inner exclusion constraint from prior sleeves (no wire can cross a sleeve).
- Live plot:
- Wires placement
- Dashed current outer boundary
- Colored sleeve rings
You can run the app in two ways:
Option 1: Using uv (recommended)
uv run main.pyThis automatically manages a virtual environment and dependencies.
First install dependencies:
pip install PyQt6 numpy scipy pyyamlThen run:
python main.py-
1. Define Wire Types
-
Set Count and Wire Diameter:
- Custom: enter diameter in mm.
- Predefined Sizes: pick a label defined in
wire_types.yaml.
-
Pick a Color and click Add Wire. Identical (color + diameter) entries merge counts automatically.
-
-
2. Optimization Parameters
- Number of Solver Initializations: more restarts → better layout, slower.
- Max Solver Iterations: SLSQP cap per run.
- Manufacturing Tolerance Margin: extra spacing (percent) added to radii.
-
3. Defined Wires
- Shows the current working set.
- Remove Selected Wire or Clear All (clears wires, layers, and results).
-
4. Sleeving
-
Choose Custom thickness or select from Predefined Sleeves (from
sleeve_types.yaml). -
Pick a Sleeve color.
-
Click Add Sleeve to add one ring. You can click multiple times to stack multiple sleeves.
-
The first sleeve after an optimize locks the current wire layout as a layer; subsequent sleeves can be added without re-optimizing. When clicked:
- The current optimized bundle becomes a locked layer with the specified sleeve thickness and color.
- The inner exclusion radius is updated.
- The working wire list is cleared for defining the next ring.
-
-
Optimize and Plot
-
Runs the multi-start optimizer and updates:
- Plot (including historic layers)
- Outer diameter in mm and inches.
-
-
Results
- The plot is centered and scaled to fit.
- The window is scrollable if content exceeds the viewport.
Place this file next to main.py. It maps labels → diameter (mm):
# Example — adapt to your catalog
"16 AWG": 1.291
"18 AWG": 1.024
"20 AWG": 0.812
"1.00 mm": 1.000
"0.90 mm": 0.900
"Ethernet": 2.000In the UI, choose Predefined Sizes to select one of these keys.
Place this file next to main.py. It maps labels → thickness (mm):
"Copper mesh (thin)": 0.200
"Copper mesh (heavy)": 0.500
"PET sleeving": 0.500
"PVC jacket": 1.000In the UI, choose Predefined Sleeves to select one of these keys. You can also use Custom to enter any thickness.
Minimize the enclosing radius
Variables
- Centers
$(x_i, y_i)$ for$i = 1..n$ - Outer radius
$R$
Effective radii
Constraints
-
Containment:
$|c_i| + r_i^{\mathrm{eff}} \le R$ -
Non-overlap:
$|c_i - c_j| \ge r_i^{\mathrm{eff}} + r_j^{\mathrm{eff}}$ -
Frozen core (from sleeves):
$|c_i| \ge R_{\text{core}} + r_i^{\mathrm{eff}}$
Objective
Minimize
- If optimization is slow, reduce initializations.
- Add Sleeve enables after a valid solution or when sleeves already exist.
- Clear All resets everything (wires, layers, results, frozen core).
This is the main window where you define wires, optimization parameters, and sleeve layers:
After optimization and adding sleeves, the layout will look like this:

