autoroute
jaseg 2023-09-29 10:37:40 +02:00
rodzic 6e12adb07e
commit fdaf3c7e93
5 zmienionych plików z 632 dodań i 4 usunięć

149
coil_parasitics.py 100644
Wyświetl plik

@ -0,0 +1,149 @@
#!/usr/bin/env python3
from pathlib import Path
import multiprocessing
import re
import tempfile
import subprocess
import fnmatch
import shutil
import numpy as np
from pyelmer import elmer
import click
from scipy import constants
def enumerate_mesh_bodies(msh_file):
with open(msh_file, 'r') as f:
for line in f:
if line.startswith('$PhysicalNames'):
break
else:
raise ValueError('No physcial bodies found in mesh file.')
_num_names = next(f)
for line in f:
if line.startswith('$EndPhysicalNames'):
break
dim, _, line = line.strip().partition(' ')
tag, _, name = line.partition(' ')
yield name.strip().strip('"'), (int(dim), int(tag))
INPUT_EXT_MAP = {
'.grd': 1,
'.mesh*': 2,
'.ep': 3,
'.ansys': 4,
'.inp': 5,
'.fil': 6,
'.FDNEUT': 7,
'.unv': 8,
'.mphtxt': 9,
'.dat': 10,
'.node': 11,
'.ele': 11,
'.mesh': 12,
'.msh': 14,
'.ep.i': 15,
'.2dm': 16}
OUTPUT_EXT_MAP = {
'.grd': 1,
'.mesh*': 2,
'.ep': 3,
'.msh': 4,
'.vtu': 5}
def elmer_grid(infile, outfile=None, intype=None, outtype=None, cwd=None, **kwargs):
infile = Path(infile)
if outfile is not None:
outfile = Path(outfile)
if intype is None:
intype = str(INPUT_EXT_MAP[infile.suffix])
if outtype is None:
if outfile is not None and outfile.suffix:
outtype = str(OUTPUT_EXT_MAP[outfile.suffix])
else:
outtype = '2'
if outfile is not None:
kwargs['out'] = str(outfile)
args = ['ElmerGrid', intype, outtype, infile]
for key, value in kwargs.items():
args.append(f'-{key}')
if isinstance(value, (tuple, list)):
args.extend(str(v) for v in value)
else:
args.append(str(value))
subprocess.run(args, cwd=cwd)
def elmer_solver(cwd):
subprocess.run(['ElmerSolver'], cwd=cwd)
@click.command()
@click.option('-d', '--sim-dir', type=click.Path(dir_okay=True, file_okay=False, path_type=Path))
@click.argument('mesh_file', type=click.Path(dir_okay=False, path_type=Path))
def run_simulation(mesh_file, sim_dir):
physical = dict(enumerate_mesh_bodies(mesh_file))
if sim_dir is not None:
sim_dir = Path(sim_dir)
sim_dir.mkdir(exist_ok=True)
sim = elmer.load_simulation('3D_steady', 'coil_parasitics_sim.yml')
mesh_dir = '.'
mesh_fn = 'mesh'
sim.header['Mesh DB'] = f'"{mesh_dir}" "{mesh_fn}"'
sim.constants.update({
'Permittivity of Vacuum': str(constants.epsilon_0),
'Gravity(4)': f'0 -1 0 {constants.g}',
'Boltzmann Constant': str(constants.Boltzmann),
'Unit Charge': str(constants.elementary_charge)})
air = elmer.load_material('air', sim, 'coil_parasitics_materials.yml')
ro4003c = elmer.load_material('ro4003c', sim, 'coil_parasitics_materials.yml')
solver_electrostatic = elmer.load_solver('Electrostatics_Capacitance', sim, 'coil_parasitics_solvers.yml')
solver_electrostatic.data['Potential Difference'] = '1.0'
eqn = elmer.Equation(sim, 'main', [solver_electrostatic])
bdy_sub = elmer.Body(sim, 'substrate', [physical['substrate'][1]])
bdy_sub.material = ro4003c
bdy_sub.equation = eqn
bdy_ab = elmer.Body(sim, 'airbox', [physical['airbox'][1]])
bdy_ab.material = air
bdy_ab.equation = eqn
# boundaries
for name, identity in physical.items():
if (m := re.fullmatch(r'trace([0-9]+)', name)):
num = int(m.group(1))
bndry_m2 = elmer.Boundary(sim, name, [identity[1]])
bndry_m2.data['Capacitance Body'] = str(num)
boundary_airbox = elmer.Boundary(sim, 'FarField', [physical['airbox_surface'][1]])
boundary_airbox.data['Electric Infinity BC'] = 'True'
with tempfile.TemporaryDirectory() as tmpdir:
if sim_dir:
tmpdir = str(sim_dir)
sim.write_startinfo(tmpdir)
sim.write_sif(tmpdir)
# Convert mesh from gmsh to elemer formats. Also scale it from 1 unit = 1 mm to 1 unit = 1 m (SI units)
elmer_grid(mesh_file.name, 'mesh', cwd=tmpdir, scale=[1e-3, 1e-3, 1e-3])
elmer_solver(tmpdir)
capacitance_matrix = np.loadtxt(tmpdir / 'capacitance.txt')
if __name__ == '__main__':
run_simulation()

Wyświetl plik

@ -0,0 +1,76 @@
air:
Density: 1.1885 # 20°C
Electric Conductivity: 0.0
Heat Capacity: 1006.4 # 20°C
Heat Conductivity: 0.025873 # 20°C
Relative Permeability: 1
Relative Permittivity: 1
ro4003c:
Density: 1790 # 23°C
Relative Permeability: 1
Relative Permittivity: 3.55
ideal:
Relative Permittivity: 1
copper_inductor:
Density: 8960.0 # 20°C
Electric Conductivity: 0.0 # necessary for 2D
Emissivity: 0.012 # 327°C
Heat Capacity: 384.4 # interpolated for 20°C
Heat Conductivity: 401.0
Relative Permeability: 1
Relative Permittivity: 1
copper:
Density: 8960.0 # 0°C
Electric Conductivity: 32300000 # 200°C
Emissivity: 0.012 # 327°C
Heat Capacity: 415.0 # 200°C
Heat Conductivity: 401.0 # 0°C
Relative Permeability: 1
Relative Permittivity: 1
graphite_CZ3-R6300: # crucible
Density: 1730.0
Electric Conductivity: 58800
Emissivity: 0.81 # 205°C
Heat Capacity: 1237.0
Heat Conductivity: 65 # 20°C
Relative Permeability: 1
Relative Permittivity: 1
graphite_FU8957: # heater
Density: 1750.0
Emissivity: 0.81 # 250°C
Heat Capacity: 1237.0
Heat Conductivity: 105 # averaged over different given values
Relative Permeability: 1
Relative Permittivity: 1
steel_1.4541:
Density: 7900.0 # 20°C
Electric Conductivity: 1370
Emissivity: 0.111 # 200°C
Heat Capacity: 470.0 # 20°C
Heat Conductivity: 15.0 # 20°C
Relative Permeability: 1
Relative Permittivity: 1
tin_liquid:
Density: 6980.0
Electric Conductivity: 2080000
Emissivity: 0.064 # set equal to solid
Heat Capacity: 252.7
Heat Conductivity: 29.0
Relative Permeability: 1
Relative Permittivity: 1
Liquid: 'Logical True'
tin_solid:
Density: 7179.0
Electric Conductivity: 4380000
Emissivity: 0.064
Heat Capacity: 244.0
Heat Conductivity: 60.0
Relative Permeability: 1
Relative Permittivity: 1
Solid: 'Logical True'
Melting Point: 505
Latent Heat: 59600
water:
Density: 1000.0
Heat Capacity: 4182.0
Heat Conductivity: 0.6

Wyświetl plik

@ -0,0 +1,50 @@
2D_steady:
Max Output Level: 4
Coordinate System: Cartesian 2D
Simulation Type: Steady state
Steady State Max Iterations: 10
3D_steady:
Max Output Level: 5
Coordinate System: Cartesian
Coordinate Mapping(3): 1 2 3
Simulation Type: Steady state
Steady State Max Iterations: 1
Output Intervals: 1
Timestepping Method: BDF
BDF Order: 1
Solver Input File: case.sif
Post File: case.vtu
Output File: case.result
2D_transient:
Max Output Level: 4
Coordinate System: Cartesian 2D
Simulation Type: Transient
Steady State Max Iterations: 10
Output File: case.result
Output Intervals: 10
Timestep Sizes: 0.1
Timestep Intervals: 100
Timestepping Method: BDF
BDF Order: 1
axi-symmetric_steady:
Max Output Level: 4
Coordinate System: Axi Symmetric
Simulation Type: Steady state
Steady State Max Iterations: 10
Output File: case.result
axi-symmetric_transient:
Max Output Level: 4
Coordinate System: Axi Symmetric
Simulation Type: Transient
Steady State Max Iterations: 10
Output File: case.result
Output Intervals: 10
Timestep Sizes: 0.1
Timestep Intervals: 100
Timestepping Method: BDF
BDF Order: 1

Wyświetl plik

@ -0,0 +1,203 @@
Electrostatics_Capacitance:
Equation: Electrostatics
Calculate Electric Field: True
Calculate Capacitance Matrix: True
Capacitance Matrix Filename: capacitance.txt
Procedure: '"StatElecSolve" "StatElecSolver"'
Variable: Potential
Calculate Electric Energy: True
Exec Solver: Always
Stabilize: True
Bubbles: False
Lumped Mass Matrix: False
Optimize Bandwidth: True
Steady State Convergence Tolerance: 1.0e-5
Nonlinear System Convergence Tolerance: 1.0e-7
Nonlinear System Max Iterations: 20
Nonlinear System Newton After Iterations: 3
Nonlinear System Newton After Tolerance: 1.0e-3
Nonlinear System Relaxation Factor: 1
Linear System Solver: Iterative
Linear System Iterative Method: BiCGStab
Linear System Max Iterations: 500
Linear System Convergence Tolerance: 1.0e-10
BiCGstabl polynomial degree: 2
Linear System Preconditioning: ILU0
Linear System ILUT Tolerance: 1.0e-3
Linear System Abort Not Converged: False
Linear System Residual Output: 10
Linear System Precondition Recompute: 1
Electrostatics:
Equation: Electrostatics
Calculate Electric Field: True
Procedure: '"StatElecSolve" "StatElecSolver"'
Variable: Potential
Calculate Electric Energy: True
Exec Solver: Always
Stabilize: True
Bubbles: False
Lumped Mass Matrix: False
Optimize Bandwidth: True
Steady State Convergence Tolerance: 1.0e-5
Nonlinear System Convergence Tolerance: 1.0e-7
Nonlinear System Max Iterations: 20
Nonlinear System Newton After Iterations: 3
Nonlinear System Newton After Tolerance: 1.0e-3
Nonlinear System Relaxation Factor: 1
Linear System Solver: Iterative
Linear System Iterative Method: BiCGStab
Linear System Max Iterations: 500
Linear System Convergence Tolerance: 1.0e-10
BiCGstabl polynomial degree: 2
Linear System Preconditioning: ILU0
Linear System ILUT Tolerance: 1.0e-3
Linear System Abort Not Converged: False
Linear System Residual Output: 10
Linear System Precondition Recompute: 1
ThermoElectricSolver:
Equation: ThermoElectric
Procedure: '"ThermoElectricSolver" "ThermoElectricSolver"'
Variable: POT[Temperature:1 Potential:1]
Element: '"p:1"'
Calculate Loads: True
Exec Solver: Always
Nonlinear System Convergence Tolerance: 1.0e-6
Nonlinear System Max Iterations: 100
Nonlinear System Newton After Iterations : 1
Nonlinear System Newton After Tolerance: 1e-9
Linear System Solver: '"Iterative"'
Linear System Iterative Method: BicgstabL
Bicgstabl Polynomial Degree: 2
Linear System Max Iterations: 200
Linear System Residual Output: 40
Linear System Preconditioning: Ilu
Linear System Convergence Tolerance: 1e-8
Steady State Convergence Tolerance: 1e-6
HeatSolver:
Equation: HeatSolver
Procedure: '"HeatSolve" "HeatSolver"'
Variable: '"Temperature"'
Variable Dofs: 1
Calculate Loads: True
Exec Solver: Always
Nonlinear System Convergence Tolerance: 1.0e-6
Nonlinear System Max Iterations: 1000
Nonlinear System Relaxation Factor: 0.7
Steady State Convergence Tolerance: 1.0e-6
Stabilize: True # Necessary in convection-dominated systems
Optimize Bandwidth: True
Linear System Solver: Iterative
Linear System Iterative Method: BiCGStab
Linear System Max Iterations: 1000
Linear System Preconditioning: ILU
Linear System Precondition Recompute: 1
Linear System Convergence Tolerance: 1.0e-8
Linear System Abort Not Converged: True
Linear System Residual Output: 1
Smart Heater Control After Tolerance: 1.0e-4
MagnetoDynamics2DHarmonic:
Equation: MagnetoDynamics2DHarmonic
Procedure: '"MagnetoDynamics2D" "MagnetoDynamics2DHarmonic"'
Variable: 'Potential[Potential Re:1 Potential Im:1]'
Variable Dofs: 2
Exec Solver: Always
Nonlinear System Convergence Tolerance: 1.0e-6
Nonlinear System Max Iterations: 1000
Nonlinear System Relaxation Factor: 0.7
Steady State Convergence Tolerance: 1.0e-6
Optimize Bandwidth: True
Linear System Solver: Iterative
Linear System Iterative Method: BiCGStab
Linear System Max Iterations: 1000
Linear System Preconditioning: ILU
Linear System Precondition Recompute: 1
Linear System Convergence Tolerance: 1.0e-8
Linear System Abort Not Converged: True
Linear System Residual Output: 1
MagnetoDynamicsCalcFields:
Equation: MagnetoDynamicsCalcFields
Procedure: '"MagnetoDynamics" "MagnetoDynamicsCalcFields"'
Potential Variable: Potential
Angular Frequency: 8.48e4
Calculate Joule Heating: True
Calculate Magnetic Field Strength: True
Calculate Electric Field: True
Exec Solver: Always
Calculate Nodal Fields: Logical False
Calculate Elemental Fields: Logical True
StatMagSolver:
Equation: StatMagSolver
Procedure: '"StatMagSolve" "StatMagSolver"'
Variable: Potential
Variable DOFs: 2
Calculate Joule Heating: 'Logical True'
Calculate Magnetic Flux: 'Logical True'
Exec Solver: Always
Nonlinear System Convergence Tolerance: 1.0e-6
Nonlinear System Max Iterations: 1000
Nonlinear System Relaxation Factor: 0.7
Steady State Convergence Tolerance: 1.0e-6
Optimize Bandwidth: True
Linear System Solver: Iterative
Linear System Iterative Method: BiCGStab
Linear System Max Iterations: 1000
Linear System Preconditioning: ILU
Linear System Precondition Recompute: 1
Linear System Convergence Tolerance: 1.0e-8
Linear System Abort Not Converged: True
Linear System Residual Output: 1
SaveMaterials:
Exec Solver: 'After timestep'
Procedure: 'File "SaveData" "SaveMaterials"'
Parameter 1: 'String "Heat Conductivity"'
ResultOutputSolver:
Exec Solver: 'After timestep'
Equation: ResultOutputSolver
Procedure: '"ResultOutputSolve" "ResultOutputSolver"'
VTU Format: True
Save Geometry Ids: 'Logical True'
FluxSolver:
Exec Solver: 'After timestep'
Equation: 'Flux Solver'
Procedure: '"FluxSolver" "FluxSolver"'
Calculate Grad: 'Logical True'
Calculate Flux: 'Logical True'
Target Variable: 'String "Temperature"'
Flux Coefficient: 'String "Heat Conductivity"'
Exec Solver: Always
Nonlinear System Convergence Tolerance: 1.0e-6
Nonlinear System Max Iterations: 1000
Nonlinear System Relaxation Factor: 0.7
Steady State Convergence Tolerance: 1.0e-6
Optimize Bandwidth: True
Linear System Solver: Iterative
Linear System Iterative Method: BiCGStab
Linear System Max Iterations: 1000
Linear System Preconditioning: ILU
Linear System Precondition Recompute: 1
Linear System Convergence Tolerance: 1.0e-8
Linear System Abort Not Converged: True
Linear System Residual Output: 1
SaveScalars:
Exec Solver: 'After timestep'
Equation: SaveScalars
Procedure: '"SaveData" "SaveScalars"'
Filename: '"boundary_scalars.dat"'
Output Directory: './results'
Operator 1: 'boundary sum'
Variable 1: 'Temperature Loads'
Operator 2: 'diffusive flux'
Variable 2: Temperature
Coefficient 2: 'Heat Conductivity'
SaveLine:
Exec Solver: 'After timestep'
Equation: '"SaveLine"'
Procedure: '"SaveData" "SaveLine"'
Filename: '"boundary_lines.dat"'
Output Directory: './results'
Variable 1: Temperature Loads
SteadyPhaseChange:
Equation: SteadyPhaseChange
Variable: '"Phase Surface"'
Procedure: '"SteadyPhaseChange" "SteadyPhaseChange"'
Internal Mesh Movement: 'Logical True'

Wyświetl plik

@ -7,7 +7,10 @@ import os
from math import *
from pathlib import Path
from itertools import cycle
from scipy.constants import mu_0
import numpy as np
import click
import matplotlib as mpl
from gerbonara.cad.kicad import pcb as kicad_pcb
@ -16,7 +19,7 @@ from gerbonara.cad.kicad import graphical_primitives as kicad_gr
from gerbonara.cad.kicad import primitives as kicad_pr
from gerbonara.utils import Tag
from gerbonara import graphic_primitives as gp
import click
from gerbonara import graphic_objects as go
__version__ = '1.0.0'
@ -64,7 +67,7 @@ def traces_to_gmsh(traces, mesh_out, bbox, model_name='gerbonara_board', log=Tru
trace_tags = {}
trace_ends = set()
render_cache = {}
for i, tr in enumerate(traces):
for i, tr in enumerate(traces, start=1):
layer = tr[1].layer
z0 = 0 if layer == 'F.Cu' else -(board_thickness+copper_thickness)
@ -141,6 +144,141 @@ def traces_to_gmsh(traces, mesh_out, bbox, model_name='gerbonara_board', log=Tru
gmsh.write(str(mesh_out))
def traces_to_gmsh_mag(traces, mesh_out, bbox, model_name='gerbonara_board', log=True, copper_thickness=0.035, board_thickness=0.8, air_box_margin=5.0):
import gmsh
occ = gmsh.model.occ
eps = 1e-6
board_thickness -= 2*copper_thickness
gmsh.initialize()
gmsh.model.add('gerbonara_board')
if log:
gmsh.logger.start()
trace_tags = []
trace_ends = set()
render_cache = {}
for i, tr in enumerate(traces, start=1):
layer = tr[1].layer
z0 = 0 if layer == 'F.Cu' else -(board_thickness+copper_thickness)
objs = [obj
for elem in tr
for obj in elem.render(cache=render_cache)]
tags = []
for ob in objs:
if isinstance(ob, go.Line):
length = dist((ob.x1, ob.y1), (ob.x2, ob.y2))
w = ob.aperture.equivalent_width('mm')
box_tag = occ.addBox(0, -w/2, 0, length, w, copper_thickness)
angle = atan2(ob.y2 - ob.y1, ob.x2 - ob.x1)
occ.rotate([(3, box_tag)], 0, 0, 0, 0, 0, 1, angle)
occ.translate([(3, box_tag)], ob.x1, ob.y1, z0)
tags.append(box_tag)
for x, y in ((ob.x1, ob.y1), (ob.x2, ob.y2)):
disc_id = (round(x, 3), round(y, 3), round(z0, 3), round(w, 3))
if disc_id in trace_ends:
continue
trace_ends.add(disc_id)
cylinder_tag = occ.addCylinder(x, y, z0, 0, 0, copper_thickness, w/2)
tags.append(cylinder_tag)
for elem in tr:
if isinstance(elem, kicad_pcb.Via):
cylinder_tag = occ.addCylinder(elem.at.x, elem.at.y, 0, 0, 0, -board_thickness, elem.drill)
tags.append(cylinder_tag)
print('fusing', tags)
tags, tag_map = occ.fuse([(3, tags[0])], [(3, tag) for tag in tags[1:]])
print(tags)
assert len(tags) == 1
(_dim, tag), = tags
trace_tags.append(tag)
print('fusing top-level', trace_tags)
tags, tag_map = occ.fuse([(3, trace_tags[0])], [(3, tag) for tag in trace_tags[1:]])
print(tags)
assert len(tags) == 1
(_dim, toplevel_tag), = tags
first_geom = traces[0][0]
interface_tag_top = occ.addDisk(first_geom.start.x, first_geom.start.y, 0, first_geom.width, first_geom.width)
interface_tag_bottom = occ.addDisk(first_geom.end.x, first_geom.end.y, -board_thickness, first_geom.width, first_geom.width)
(x1, y1), (x2, y2) = bbox
substrate = occ.addBox(x1, y1, -board_thickness, x2-x1, y2-y1, board_thickness)
x1, y1 = x1-air_box_margin, y1-air_box_margin
x2, y2 = x2+air_box_margin, y2+air_box_margin
w, d = x2-x1, y2-y1
z0 = -board_thickness-air_box_margin
ab_h = board_thickness + 2*air_box_margin
airbox = occ.addBox(x1, y1, z0, w, d, ab_h)
print('Cutting airbox')
occ.cut([(3, airbox)], [(3, toplevel_tag)], removeObject=True, removeTool=False)
print('Cutting substrate')
occ.cut([(3, substrate)], [(3, toplevel_tag)], removeObject=True, removeTool=False)
print('Synchronizing')
occ.synchronize()
substrate_physical = gmsh.model.add_physical_group(3, [substrate], name='substrate')
airbox_physical = gmsh.model.add_physical_group(3, [airbox], name='airbox')
trace_physical_surfaces = [
gmsh.model.add_physical_group(2, list(gmsh.model.getAdjacencies(3, tag)[1]), name=f'trace{i}')
for i, tag in trace_tags.items()]
airbox_adjacent = set(gmsh.model.getAdjacencies(3, airbox)[1])
in_bbox = {tag for _dim, tag in gmsh.model.getEntitiesInBoundingBox(x1+eps, y1+eps, z0+eps, x1+w-eps, y1+d-eps, z0+ab_h-eps, dim=3)}
airbox_physical_surface = gmsh.model.add_physical_group(2, list(airbox_adjacent - in_bbox), name='airbox_surface')
points_airbox_adjacent = set(gmsh.model.getAdjacencies(0, airbox)[1])
points_inside = {tag for _dim, tag in gmsh.model.getEntitiesInBoundingBox(x1+eps, y1+eps, z0+eps, x1+w-eps, y1+d-eps, z0+ab_h-eps, dim=0)}
gmsh.model.mesh.setSize([(0, tag) for tag in points_airbox_adjacent - points_inside], 10e-3)
gmsh.option.setNumber('Mesh.MeshSizeFromCurvature', 90)
gmsh.option.setNumber('Mesh.Smoothing', 10)
gmsh.option.setNumber('Mesh.Algorithm3D', 10)
gmsh.option.setNumber('Mesh.MeshSizeMax', 1)
gmsh.option.setNumber('General.NumThreads', multiprocessing.cpu_count())
print('Meshing')
gmsh.model.mesh.generate(dim=3)
print('Writing')
gmsh.write(str(mesh_out))
def traces_to_magneticalc(traces, out, pcb_thickness=0.8):
coords = []
last_x, last_y, last_z = None, None, None
def coord(x, y, z):
nonlocal coords, last_x, last_y, last_z
if (x, y, z) != (last_x, last_y, last_z):
coords.append((x, y, z))
render_cache = {}
for tr in traces:
z = pcb_thickness if tr[1].layer == 'F.Cu' else 0
objs = [obj
for elem in tr
for obj in elem.render(cache=render_cache)
if isinstance(elem, (kicad_pcb.TrackSegment, kicad_pcb.TrackArc))]
# start / switch layer
coord(objs[0].x1, objs[0].y1, z)
for ob in objs:
coord(ob.x2, ob.y2, z)
np.savetxt(out, np.array(coords) / 10) # magneticalc expects centimeters, not millimeters.
class SVGPath:
def __init__(self, **attrs):
self.d = ''
@ -235,16 +373,19 @@ def print_valid_twists(ctx, param, value):
@click.option('--keepout-zone/--no-keepout-zone', default=True, help='Add a keepout are to the footprint (default: yes)')
@click.option('--keepout-margin', type=float, default=5, help='Margin between outside of coil and keepout area (mm, default: 5)')
@click.option('--twists', type=int, default=1, help='Number of twists per revolution. Note that this number must be co-prime to the number of turns. Run with --show-twists to list valid values. (default: 1)')
@click.option('--circle-segments', type=int, default=64, help='When not using arcs, the number of points to use for arc interpolation per 360 degrees.')
@click.option('--show-twists', callback=print_valid_twists, expose_value=False, type=int, is_eager=True, help='Calculate and show valid --twists counts for the given number of turns. Takes the number of turns as a value.')
@click.option('--clearance', type=float, default=None)
@click.option('--arc-tolerance', type=float, default=0.02)
@click.option('--mesh-out', type=click.Path(writable=True, dir_okay=False, path_type=Path))
@click.option('--mag-mesh-out', type=click.Path(writable=True, dir_okay=False, path_type=Path))
@click.option('--magneticalc-out', type=click.Path(writable=True, dir_okay=False, path_type=Path))
@click.option('--clipboard/--no-clipboard', help='Use clipboard integration (requires wl-clipboard)')
@click.option('--counter-clockwise/--clockwise', help='Direction of generated spiral. Default: clockwise when wound from the inside.')
@click.version_option()
def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_drill, via_offset, trace_width, clearance,
footprint_name, layer_pair, twists, clipboard, counter_clockwise, keepout_zone, keepout_margin,
arc_tolerance, pcb, mesh_out):
arc_tolerance, pcb, mesh_out, magneticalc_out, circle_segments, mag_mesh_out):
if 'WAYLAND_DISPLAY' in os.environ:
copy, paste, cliputil = ['wl-copy'], ['wl-paste'], 'xclip'
else:
@ -256,6 +397,9 @@ def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_d
if mesh_out and not pcb:
raise click.ClickException('--pcb is required when --mesh-out is used.')
if magneticalc_out and not pcb:
raise click.ClickException('--pcb is required when --magneticalc-out is used.')
outer_radius = outer_diameter/2
inner_radius = inner_diameter/2
turns_per_layer = turns/2
@ -466,7 +610,7 @@ def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_d
end_angle = fold_angle + sweeping_angle
x = inverse[i]*floor(2*sweeping_angle / (2*pi)) * 2*pi
(x0, y0), (xn, yn), clen = do_spiral(0, outer_radius, inner_radius, start_angle, fold_angle, (x + start_angle)/total_angle, (x + fold_angle)/total_angle)
(x0, y0), (xn, yn), clen = do_spiral(0, outer_radius, inner_radius, start_angle, fold_angle, (x + start_angle)/total_angle, (x + fold_angle)/total_angle, circle_segments)
do_spiral(1, inner_radius, outer_radius, fold_angle, end_angle, (x + fold_angle)/total_angle, (x + end_angle)/total_angle)
xv, yv = inner_via_ring_radius*cos(fold_angle), inner_via_ring_radius*sin(fold_angle)
@ -578,6 +722,12 @@ def generate(outfile, turns, outer_diameter, inner_diameter, via_diameter, via_d
if mesh_out:
traces_to_gmsh(traces, mesh_out, ((-r, -r), (r, r)))
if mag_mesh_out:
traces_to_gmsh_mag(traces, mesh_out, ((-r, -r), (r, r)))
if magneticalc_out:
traces_to_magneticalc(traces, magneticalc_out)
# for trace in traces:
# print(f'Trace {i}', file=sys.stderr)
# print(f' Length: {len(trace)}', file=sys.stderr)