From 31af2b260c660f53c3846056c466167b5177beb3 Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 20 Oct 2023 18:24:45 +0200 Subject: WIP --- coil_parasitics.py | 98 ++++++++++++--- coil_parasitics_materials.yml | 50 +------- sim_runner.py | 282 ++++++++++++++++++++++++++++++++++++++++++ twisted_coil_gen_twolayer.py | 41 +++--- 4 files changed, 390 insertions(+), 81 deletions(-) create mode 100644 sim_runner.py diff --git a/coil_parasitics.py b/coil_parasitics.py index 3ed02dd..d759bd0 100644 --- a/coil_parasitics.py +++ b/coil_parasitics.py @@ -115,8 +115,9 @@ def cli(): @cli.command() @click.option('-d', '--sim-dir', type=click.Path(dir_okay=True, file_okay=False, path_type=Path)) +@click.option('-o', '--output', type=click.Path(dir_okay=False, writable=True, path_type=Path), help='Capacitance matrix output file') @click.argument('mesh_file', type=click.Path(dir_okay=False, path_type=Path)) -def capacitance_matrix(mesh_file, sim_dir): +def capacitance_matrix(mesh_file, sim_dir, output): physical = dict(enumerate_mesh_bodies(mesh_file)) if sim_dir is not None: sim_dir = Path(sim_dir) @@ -179,6 +180,8 @@ def capacitance_matrix(mesh_file, sim_dir): stderr_log=(tmpdir / 'ElmerSolver_stderr.log')) capacitance_matrix = np.loadtxt(tmpdir / 'capacitance.txt') + np.savetxt(output, capacitance_matrix) + @cli.command() @click.option('-d', '--sim-dir', type=click.Path(dir_okay=True, file_okay=False, path_type=Path)) @@ -500,34 +503,91 @@ def self_capacitance(mesh_file, sim_dir): stdout_log=solver_stdout, stderr_log=solver_stderr) - P, R, U_mag = None, None, None + C, U_elec = None, None solver_error = False for l in res.stdout.splitlines(): - if (m := re.fullmatch(r'StatCurrentSolve:\s*Total Heating Power\s*:\s*([0-9.+-Ee]+)\s*', l)): - P = float(m.group(1)) - elif (m := re.fullmatch(r'StatCurrentSolve:\s*Effective Resistance\s*:\s*([0-9.+-Ee]+)\s*', l)): - R = float(m.group(1)) - elif (m := re.fullmatch(r'MagnetoDynamicsCalcFields:\s*ElectroMagnetic Field Energy\s*:\s*([0-9.+-Ee]+)\s*', l)): - U_mag = float(m.group(1)) + if (m := re.fullmatch(r'StatElecSolve:\s*Tot. Electric Energy\s*:\s*([0-9.+-Ee]+)\s*', l)): + U_elec = float(m.group(1)) + elif (m := re.fullmatch(r'StatElecSolve:\s*Capacitance\s*:\s*([0-9.+-Ee]+)\s*', l)): + C = float(m.group(1)) elif re.fullmatch(r'IterSolve: Linear iteration did not converge to tolerance', l): solver_error = True if solver_error: raise click.ClickException(f'Error: One of the solvers did not converge. See log files for details:\n{solver_stdout.absolute()}\n{solver_stderr.absolute()}') - elif P is None or R is None or U_mag is None: + elif C is None or U_elec is None: raise click.ClickException(f'Error during solver execution. Electrical parameters could not be calculated. See log files for details:\n{solver_stdout.absolute()}\n{solver_stderr.absolute()}') - V = math.sqrt(P*R) - I = math.sqrt(P/R) - L = 2*U_mag / (I**2) - - assert math.isclose(V, 1.0, abs_tol=1e-3) - - print(f'Total magnetic field energy: {format_si(U_mag, "J")}') - print(f'Reference coil current: {format_si(I, "Ω")}') - print(f'Coil resistance calculated by solver: {format_si(R, "Ω")}') - print(f'Inductance calucated from field: {format_si(L, "H")}') + print(f'Total electric field energy: {format_si(U_elec, "J")}') + print(f'Total parasitic capacitance: {format_si(C, "F")}') +@cli.command() +@click.option('-d', '--sim-dir', type=click.Path(dir_okay=True, file_okay=False, path_type=Path)) +@click.option('--capacitance-matrix-file', type=click.Path(dir_okay=False, exists=True)) +@click.option('--total-inductance', type=float, required=True, help='Total inductance in Henry') +@click.option('--total-resistance', type=float, required=True, help='Total resistance in Ohm') +@click.option('--plot-out', type=click.Path(dir_okay=False, writable=True), help='Optional SVG plot output file') +def resonance(sim_dir, capacitance_matrix_file, total_inductance, total_resistance, plot_out): + import PySpice.Unit + from PySpice.Spice.Library import SpiceLibrary + from PySpice.Spice.Netlist import Circuit + from PySpice.Plot.BodeDiagram import bode_diagram + import scipy.signal + from matplotlib import pyplot as plt + + capacitance_matrix = np.loadtxt(capacitance_matrix_file) + num_elements = capacitance_matrix.shape[0] + + circ = Circuit('LC ladder parasitic sim') + inputs = 'Vplus', circ.gnd + coil_in = 'coil_in' + + Rtest = circ.R('Rtest', inputs[0], coil_in, 50@PySpice.Unit.u_Ohm) + + intermediate_nodes = [f'intermediate{i}' for i in range(num_elements-1)] + inductor_nodes = [(a, b) for a, b in zip([coil_in, *intermediate_nodes], [*intermediate_nodes, inputs[1]])] + inductor_midpoints = [f'midpoint{i}' for i in range(num_elements)] + + circ.SinusoidalVoltageSource('input', inputs[0], inputs[1], amplitude=1@PySpice.Unit.u_V) + + for i, ((a, b), m) in enumerate(zip(inductor_nodes, inductor_midpoints)): + L = total_inductance / num_elements / 2 + R = total_resistance / num_elements / 2 + circ.L(f'L{i}A', a, f'R{i}A1', L@PySpice.Unit.u_H) + circ.R(f'R{i}A', f'R{i}A1', m, R@PySpice.Unit.u_Ohm) + circ.R(f'R{i}B', m, f'R{i}B1', R@PySpice.Unit.u_Ohm) + circ.L(f'L{i}B', f'R{i}B1', b, L@PySpice.Unit.u_H) + + for i in range(num_elements): + for j in range(i): + circ.C(f'C{i}_{j}', inductor_midpoints[i], inductor_midpoints[j], capacitance_matrix[i, j]@PySpice.Unit.u_F) + + sim = circ.simulator(temperature=25, nominal_temperature=25) + ana = sim.ac(start_frequency=10@PySpice.Unit.u_kHz, stop_frequency=1000@PySpice.Unit.u_MHz, number_of_points=1000, variation='dec') + figure, axs = plt.subplots(2, figsize=(20, 10), sharex=True) + + freq = ana.frequency + gain = 20*np.log10(np.absolute(ana.coil_in)) + + peaks, peak_props = scipy.signal.find_peaks(-gain, height=20) + for peak in peaks[:3]: + print(f'Resonance at {float(freq[peak])/1e6:.3f} MHz') + + if plot_out: + plt.title("Bode Diagram of a Low-Pass RC Filter") + bode_diagram(axes=axs, + frequency=freq, + gain=gain, + phase=np.angle(ana.coil_in, deg=False), + linestyle='-', + ) + + for peak in peaks[:3]: + for ax in axs: + ax.axvline(float(freq[peak]), color='red', alpha=0.5) + + plt.tight_layout() + plt.savefig(plot_out) if __name__ == '__main__': diff --git a/coil_parasitics_materials.yml b/coil_parasitics_materials.yml index ecb49b7..d1875d7 100644 --- a/coil_parasitics_materials.yml +++ b/coil_parasitics_materials.yml @@ -9,39 +9,21 @@ ro4003c: Density: 1790 # 23°C Relative Permeability: 1 Relative Permittivity: 3.55 +fr4: + Density: 1850 # 23°C + Relative Permeability: 1 + Relative Permittivity: 4.4 + Heat Conductivity: 0.81 # in-plane 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 + Electric Conductivity: 59600000 # 20°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 @@ -50,26 +32,6 @@ steel_1.4541: 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 diff --git a/sim_runner.py b/sim_runner.py new file mode 100644 index 0000000..d615caa --- /dev/null +++ b/sim_runner.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 + +import threading +import queue +import itertools +import pathlib +import tempfile +import sys +import sqlite3 +import time +import math +import json +import subprocess + +import tqdm +import click +from tabulate import tabulate + + +def mesh_args(db, coil_id, mesh_type, mesh_file, outfile): + mesh_type = {'split': '--mesh-split-out', 'normal': '--mesh-out', 'mutual': '--mesh-mutual-out'}[mesh_type] + rows = db.execute('SELECT key, value FROM results WHERE coil_id=?', (coil_id,)).fetchall() + args = ['python', '-m', 'twisted_coil_gen_twolayer', mesh_type, mesh_file, '--pcb'] + for k, v in rows: + prefix, _, k = k.partition('.') + if v != 'False' and prefix == 'gen': + args.append('--' + k.replace('_', '-')) + if v != 'True': + args.append(str(v)) + args.append(outfile) + return args + + +def get_mesh_file(db, mesh_dir, run_id, coil_id, mesh_type): + db.execute('CREATE TABLE IF NOT EXISTS meshes(coil_id INTEGER, mesh_type TEXT, error INTEGER, filename TEXT, timestamp TEXT DEFAULT current_timestamp, FOREIGN KEY (coil_id) REFERENCES coils(coil_id))') + + row = db.execute('SELECT * FROM meshes WHERE coil_id=? AND mesh_type=? ORDER BY timestamp DESC LIMIT 1', (coil_id, mesh_type)).fetchone() + if row is not None: + mesh_file = mesh_dir / row['filename'] + if mesh_file.is_file(): + return mesh_file + + timestamp = time.strftime('%Y-%m-%d_%H-%M-%S') + return mesh_dir / f'mesh-{run_id}-{coil_id}-{mesh_type}-{timestamp}.msh' + + +def ensure_mesh(db, mesh_dir, log_dir, run_id, coil_id, mesh_type): + mesh_file = get_mesh_file(db, mesh_dir, run_id, coil_id, mesh_type) + + if mesh_file.is_file(): + return mesh_file + + db.execute('INSERT INTO meshes(coil_id, mesh_type, error, filename) VALUES (?, ?, 0, ?)', (coil_id, mesh_type, mesh_file.name)) + db.commit() + + mesh_file.parent.mkdir(exist_ok=True) + with tempfile.NamedTemporaryFile(suffix='.kicad_pcb') as f: + args = mesh_args(db, coil_id, mesh_type, mesh_file, f.name) + tqdm.tqdm.write(' '.join(map(str, args))) + logfile = log_dir / mesh_file.with_suffix('.log').name + logfile.parent.mkdir(exist_ok=True) + try: + res = subprocess.run(args, check=True, capture_output=True, text=True) + logfile.write_text(res.stdout + res.stderr) + + except subprocess.CalledProcessError as e: + print('Mesh generation failed with exit code {e.returncode}', file=sys.stderr) + logfile.write_text(e.stdout + e.stderr) + print(e.stdout + e.stderr) + raise + + return mesh_file + + +@click.group() +@click.option('-d', '--database', default='coil_parameters.sqlite3') +@click.pass_context +def cli(ctx, database): + ctx.ensure_object(dict) + def connect(): + db = sqlite3.connect(database) + db.row_factory = sqlite3.Row + return db + ctx.obj['db_connect'] = connect + + +@cli.command() +@click.pass_context +def list_runs(ctx): + for row in ctx.obj['db_connect']().execute('SELECT * FROM runs ORDER BY timestamp').fetchall(): + print(row['run_id'], row['timestamp'], row['version']) + + +@cli.command() +@click.pass_context +def list_runs(ctx): + for row in ctx.obj['db_connect']().execute('SELECT * FROM runs ORDER BY timestamp').fetchall(): + print(row['run_id'], row['timestamp'], row['version']) + + +@cli.command() +@click.option('-r', '--run-id') +@click.option('-m', '--mesh-dir', default='meshes') +@click.pass_context +def list_coils(ctx, run_id, mesh_dir): + db = ctx.obj['db_connect']() + if run_id is None: + run_id, = db.execute('SELECT run_id FROM runs ORDER BY timestamp DESC LIMIT 1').fetchone() + timestamp, = db.execute('SELECT timestamp FROM runs WHERE run_id=?', (run_id,)).fetchone() + mesh_dir = pathlib.Path(mesh_dir) + + print(f'Listing meshes for run {run_id} at {timestamp}') + print() + + keys = {'gen.turns': 'N', + 'gen.twists': 'T', + 'gen.single_layer': '1L', + 'gen.inner_diameter': 'ID[mm]', + 'gen.outer_diameter': 'OD[mm]', + 'calculated_fill_factor': 'Fill factor', + 'calculated_approximate_inductance': 'L [µH]', + 'calculated_trace_length': 'track len [mm]', + 'calculated_approximate_resistance': 'R [mΩ]'} + out = [] + for row in db.execute('SELECT *, MAX(meshes.timestamp) FROM coils LEFT JOIN meshes ON coils.coil_id=meshes.coil_id WHERE run_id=? GROUP BY coils.coil_id, mesh_type ORDER BY meshes.timestamp', (run_id,)).fetchall(): + if row['timestamp']: + if row['error']: + state = 'ERROR' + elif not (mesh_dir / row['filename']).is_file(): + state = 'NOT FOUND' + else: + state = 'SUCCESS' + else: + state = 'NOT RUN' + + params = dict(db.execute('SELECT key, value FROM results WHERE coil_id=?', (row['coil_id'],)).fetchall()) + + if 'calculated_approximate_inductance' in params: + params['calculated_approximate_inductance'] = f'{float(params["calculated_approximate_inductance"])*1e6:.02f}' + + if 'calculated_trace_length' in params: + params['calculated_trace_length'] = f'{float(params["calculated_trace_length"])*1e3:.03f}' + + if 'calculated_approximate_resistance' in params: + params['calculated_approximate_resistance'] = f'{float(params["calculated_approximate_resistance"])*1e3:.03f}' + + if 'calculated_fill_factor' in params: + params['calculated_fill_factor'] = f'{float(params["calculated_fill_factor"]):.03f}' + + out.append([row['coil_id'], row['mesh_type'], state, row['timestamp']] + [params.get(key, '-') for key in keys]) + + print(tabulate(out, headers=['coil', 'mesh', 'state', 'time'] + list(keys.values()), disable_numparse=True, stralign='right')) + +@cli.command() +@click.argument('coil_id', type=int) +@click.argument('mesh_type', type=click.Choice(['normal', 'split', 'mutual'])) +@click.option('--mesh-file', default='/tmp/test.msh') +@click.option('--pcb-file', default='/tmp/test.kicad_pcb') +@click.pass_context +def cmdline(ctx, coil_id, mesh_type, mesh_file, pcb_file): + print(' '.join(mesh_args(ctx.obj['db_connect'](), coil_id, mesh_type, mesh_file, pcb_file))) + +@cli.group() +@click.option('-r', '--run-id') +@click.option('-l', '--log-dir', default='logs') +@click.option('-m', '--mesh-dir', default='meshes') +@click.pass_context +def run(ctx, run_id, log_dir, mesh_dir): + if run_id is None: + run_id, = ctx.obj['db_connect']().execute('SELECT run_id FROM runs ORDER BY timestamp DESC LIMIT 1').fetchone() + ctx.obj['run_id'] = run_id + ctx.obj['log_dir'] = pathlib.Path(log_dir) + ctx.obj['mesh_dir'] = pathlib.Path(mesh_dir) + + +@run.command() +@click.option('-j', '--num-jobs', type=int, default=1, help='Number of jobs to run in parallel') +@click.pass_context +def generate_meshes(ctx, num_jobs): + db = ctx.obj['db_connect']() + rows = [row['coil_id'] for row in db.execute('SELECT coil_id FROM coils WHERE run_id=?', (ctx.obj['run_id'],)).fetchall()] + mesh_types = ['split', 'normal', 'mutual'] + + params = list(itertools.product(rows, mesh_types)) + all_files = {get_mesh_file(db, ctx.obj['mesh_dir'], ctx.obj['run_id'], coil_id, mesh_type): (coil_id, mesh_type) for coil_id, mesh_type in params} + todo = [(coil_id, mesh_type) for f, (coil_id, mesh_type) in all_files.items() if not f.is_file()] + + q = queue.Queue() + for elem in todo: + q.put(elem) + + tq = tqdm.tqdm(total=len(todo)) + def queue_worker(): + try: + while True: + coil_id, mesh_type = q.get_nowait() + try: + ensure_mesh(ctx.obj['db_connect'](), ctx.obj['mesh_dir'], ctx.obj['log_dir'], ctx.obj['run_id'], coil_id, mesh_type) + except subprocess.CalledProcessError: + tqdm.tqdm.write(f'Error generating {mesh_type} mesh for {coil_id=}') + tq.update(1) + q.task_done() + except queue.Empty: + pass + + tqdm.tqdm.write(f'Found {len(params)-len(todo)} meshes out of a total of {len(params)}.') + tqdm.tqdm.write(f'Processing the remaining {len(todo)} meshes on {num_jobs} workers in parallel.') + threads = [] + for i in range(num_jobs): + t = threading.Thread(target=queue_worker, daemon=True) + t.start() + threads.append(t) + q.join() + +@run.command() +@click.option('-j', '--num-jobs', type=int, default=1, help='Number of jobs to run in parallel') +@click.pass_context +def self_inductance(ctx, num_jobs): + db = ctx.obj['db_connect']() + + q = queue.Queue() + + def queue_worker(): + try: + while True: + mesh_file, logfile = q.get_nowait() + with tempfile.TemporaryDirectory() as tmpdir: + try: + tqdm.tqdm.write(f'Processing {mesh_file}') + res = subprocess.run(['python', '-m', 'coil_parasitics', 'inductance', '--sim-dir', tmpdir, mesh_file], check=True, capture_output=True) + logfile.write_text(res.stdout+res.stderr) + except subprocess.CalledProcessError as e: + print(f'Error running simulation, rc={e.returncode}') + logfile.write_text(e.stdout+e.stderr) + tq.update(1) + q.task_done() + except queue.Empty: + pass + + num_meshes, num_params, num_completed = 0, 0, 0 + for coil_id, in db.execute('SELECT coil_id FROM coils WHERE run_id=?', (ctx.obj['run_id'],)).fetchall(): + num_params += 1 + mesh_file = get_mesh_file(ctx.obj['db_connect'](), ctx.obj['mesh_dir'], ctx.obj['run_id'], coil_id, 'normal') + if mesh_file.is_file(): + num_meshes += 1 + logfile = ctx.obj['log_dir'] / (mesh_file.stem + '_elmer_self_inductance.log') + if logfile.is_file(): + num_completed += 1 + else: + q.put((mesh_file, logfile)) + + tqdm.tqdm.write(f'Found {num_meshes} meshes out of a total of {num_params} with {num_completed} completed simulations.') + tqdm.tqdm.write(f'Processing the remaining {num_meshes-num_completed} simulations on {num_jobs} workers in parallel.') + + tq = tqdm.tqdm(total=num_meshes-num_completed) + threads = [] + for i in range(num_jobs): + t = threading.Thread(target=queue_worker, daemon=True) + t.start() + threads.append(t) + q.join() + +@run.command() +@click.pass_context +def self_capacitance(ctx): + db = ctx.obj['db_connect']() + for coil_id, in tqdm.tqdm(db.execute('SELECT coil_id FROM coils WHERE run_id=?', (ctx.obj['run_id'],)).fetchall()): + mesh_file = get_mesh_file(ctx.obj['db_connect'](), ctx.obj['mesh_dir'], ctx.obj['run_id'], coil_id, 'normal') + if mesh_file.is_file(): + logfile = ctx.obj['log_dir'] / (mesh_file.stem + '_elmer_self_capacitance.log') + with tempfile.TemporaryDirectory() as tmpdir: + try: + res = subprocess.run(['python', '-m', 'coil_parasitics', 'self-capacitance', '--sim-dir', tmpdir, mesh_file], check=True, capture_output=True) + logfile.write_text(res.stdout+res.stderr) + except subprocess.CalledProcessError as e: + print(f'Error running simulation, rc={e.returncode}') + logfile.write_text(e.stdout+e.stderr) + + +if __name__ == '__main__': + cli() + diff --git a/twisted_coil_gen_twolayer.py b/twisted_coil_gen_twolayer.py index 5f06c39..204e7aa 100644 --- a/twisted_coil_gen_twolayer.py +++ b/twisted_coil_gen_twolayer.py @@ -138,16 +138,19 @@ def traces_to_gmsh(traces, mesh_out, bbox, model_name='gerbonara_board', log=Tru trace_field = gmsh.model.mesh.field.add('BoundaryLayer') gmsh.model.mesh.field.setNumbers(trace_field, 'CurvesList', getCurves(*trace_tags.values())) gmsh.model.mesh.field.setNumber(trace_field, 'Size', 0.5) - gmsh.model.mesh.field.setNumber(trace_field, 'SizeFar', 10.0) + gmsh.model.mesh.field.setNumber(trace_field, 'SizeFar', 5.0) + #gmsh.model.mesh.field.setAsBackgroundMesh(trace_field) - substrate_field = gmsh.model.mesh.field.add('AttractorAnisoCurve') - gmsh.model.mesh.field.setNumbers(substrate_field, 'CurvesList', getCurves(substrate)) - gmsh.model.mesh.field.setNumber(substrate_field, 'DistMax', 10) - gmsh.model.mesh.field.setNumber(substrate_field, 'DistMin', 0) - gmsh.model.mesh.field.setNumber(substrate_field, 'SizeMinNormal', board_thickness/3) - gmsh.model.mesh.field.setNumber(substrate_field, 'SizeMaxNormal', 10.0) - gmsh.model.mesh.field.setNumber(substrate_field, 'SizeMinTangent', 0.5) - gmsh.model.mesh.field.setNumber(substrate_field, 'SizeMaxTangent', 10.0) + substrate_field = gmsh.model.mesh.field.add('Box') + gmsh.model.mesh.field.setNumber(substrate_field, 'VIn', board_thickness) + gmsh.model.mesh.field.setNumber(substrate_field, 'VOut', 10.0) + gmsh.model.mesh.field.setNumber(substrate_field, 'XMin', x1) + gmsh.model.mesh.field.setNumber(substrate_field, 'YMin', y1) + gmsh.model.mesh.field.setNumber(substrate_field, 'ZMin', -board_thickness) + gmsh.model.mesh.field.setNumber(substrate_field, 'XMax', x2) + gmsh.model.mesh.field.setNumber(substrate_field, 'YMax', y2) + gmsh.model.mesh.field.setNumber(substrate_field, 'ZMax', 0) + gmsh.model.mesh.field.setNumber(substrate_field, 'Thickness', 2*board_thickness) background_field = gmsh.model.mesh.field.add('MinAniso') gmsh.model.mesh.field.setNumbers(background_field, 'FieldsList', [trace_field, substrate_field]) @@ -339,17 +342,19 @@ def traces_to_gmsh_mag(traces, mesh_out, bbox, model_name='gerbonara_board', log trace_field = gmsh.model.mesh.field.add('BoundaryLayer') gmsh.model.mesh.field.setNumbers(trace_field, 'CurvesList', getCurves(toplevel_tag)) gmsh.model.mesh.field.setNumber(trace_field, 'Size', 0.5) - gmsh.model.mesh.field.setNumber(trace_field, 'SizeFar', 10.0) + gmsh.model.mesh.field.setNumber(trace_field, 'SizeFar', 5.0) #gmsh.model.mesh.field.setAsBackgroundMesh(trace_field) - substrate_field = gmsh.model.mesh.field.add('AttractorAnisoCurve') - gmsh.model.mesh.field.setNumbers(substrate_field, 'CurvesList', getCurves(substrate)) - gmsh.model.mesh.field.setNumber(substrate_field, 'DistMax', 10) - gmsh.model.mesh.field.setNumber(substrate_field, 'DistMin', 0) - gmsh.model.mesh.field.setNumber(substrate_field, 'SizeMinNormal', board_thickness/3) - gmsh.model.mesh.field.setNumber(substrate_field, 'SizeMaxNormal', 10.0) - gmsh.model.mesh.field.setNumber(substrate_field, 'SizeMinTangent', 0.5) - gmsh.model.mesh.field.setNumber(substrate_field, 'SizeMaxTangent', 10.0) + substrate_field = gmsh.model.mesh.field.add('Box') + gmsh.model.mesh.field.setNumber(substrate_field, 'VIn', board_thickness) + gmsh.model.mesh.field.setNumber(substrate_field, 'VOut', 10.0) + gmsh.model.mesh.field.setNumber(substrate_field, 'XMin', x1) + gmsh.model.mesh.field.setNumber(substrate_field, 'YMin', y1) + gmsh.model.mesh.field.setNumber(substrate_field, 'ZMin', -board_thickness) + gmsh.model.mesh.field.setNumber(substrate_field, 'XMax', x2) + gmsh.model.mesh.field.setNumber(substrate_field, 'YMax', y2) + gmsh.model.mesh.field.setNumber(substrate_field, 'ZMax', 0) + gmsh.model.mesh.field.setNumber(substrate_field, 'Thickness', 2*board_thickness) background_field = gmsh.model.mesh.field.add('MinAniso') gmsh.model.mesh.field.setNumbers(background_field, 'FieldsList', [trace_field, substrate_field]) -- cgit