subprocess wrapper around the magnetostatic FFI solver - C++ errors are caught and collected and the process dies on its own thread.
this allows errors in parsed .fem files to now provide useful errors back to the user. also stitched in the incomplete functions for the buttons that existed already but has no implementations. - new doc - save/as - zoom - grid - etc --- animations: new engine for defining the movement of a segment between two nodes or multiple pairs of two as a function over time and animated simulations are now capable showing the change in fields of moving objects.
This commit is contained in:
parent
b1b730c675
commit
749a9d3e43
|
|
@ -11,7 +11,7 @@ name = "femm"
|
|||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
femm-sys = { workspace = true }
|
||||
femm-doc-mag = { workspace = true }
|
||||
iced = { version = "0.14", features = ["canvas"] }
|
||||
iced = { version = "0.14", features = ["canvas", "svg", "tokio"] }
|
||||
rfd = "0.17"
|
||||
meval = "0.2"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! draws a FemmDoc on an iced canvas: nodes, segments, arcs, block labels, with pan/zoom and click-to-add.
|
||||
|
||||
use femm_doc_mag::FemmDoc;
|
||||
use femm_doc_mag::{FemmDoc, LengthUnit};
|
||||
use femm_doc_mag::ans::MagSolution;
|
||||
use femm_doc_mag::mesh::Mesh;
|
||||
use iced::widget::canvas::{
|
||||
|
|
@ -13,12 +13,13 @@ const NODE_RADIUS: f32 = 3.0;
|
|||
const STROKE_WIDTH: f32 = 1.2;
|
||||
const LABEL_TICK_PX: f32 = 6.0;
|
||||
const ZOOM_STEP: f32 = 1.1;
|
||||
const ZOOM_MIN: f32 = 0.05;
|
||||
const ZOOM_MAX: f32 = 200.0;
|
||||
pub const ZOOM_MIN: f32 = 0.05;
|
||||
pub const ZOOM_MAX: f32 = 200.0;
|
||||
const CLICK_DRAG_THRESHOLD_PX: f32 = 4.0;
|
||||
const BG: Color = Color::WHITE;
|
||||
const GEOM: Color = Color::BLACK;
|
||||
const LABEL_COLOR: Color = Color::from_rgb(0.25, 0.45, 0.85);
|
||||
const PICK_RADIUS_PX: f32 = 10.0;
|
||||
const MARQUEE_FILL: Color = Color::from_rgba(0.45, 0.65, 0.95, 0.12);
|
||||
const MARQUEE_STROKE_COLOR: Color = Color::from_rgba(0.45, 0.65, 0.95, 0.9);
|
||||
const LABEL_COLOR: Color = Color::from_rgb(0.45, 0.65, 0.95);
|
||||
const PENDING_COLOR: Color = Color::from_rgb(0.85, 0.30, 0.30);
|
||||
const SELECT_COLOR: Color = Color::from_rgb(0.95, 0.20, 0.20);
|
||||
const SELECT_STROKE: f32 = 2.4;
|
||||
|
|
@ -28,6 +29,9 @@ const FLUX_LINE_COLOR: Color = Color::from_rgba(0.0, 0.0, 0.0, 0.85);
|
|||
const FLUX_LINE_STROKE: f32 = 0.6;
|
||||
const BAND_COUNT: usize = 20;
|
||||
const FLUX_LINE_COUNT: usize = 19;
|
||||
const GRID_COLOR: Color = Color::from_rgba(0.5, 0.5, 0.5, 0.25);
|
||||
const GRID_STROKE: f32 = 0.5;
|
||||
const GRID_MIN_PX_SPACING: f32 = 4.0;
|
||||
|
||||
/// field-plot mode applied on top of the FE solution.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -49,29 +53,60 @@ pub enum Tool {
|
|||
AddSegment,
|
||||
}
|
||||
|
||||
/// composition mode for a pick action, derived from modifier keys at click/release time.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PickOp {
|
||||
Replace,
|
||||
Add,
|
||||
Toggle,
|
||||
}
|
||||
|
||||
/// restricts the entity-kind set considered by a pick.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PickRestrict {
|
||||
Any,
|
||||
NodesOnly,
|
||||
}
|
||||
|
||||
/// messages emitted by the canvas back to the app.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CanvasMessage {
|
||||
/// click at a doc-world coordinate.
|
||||
/// click at a doc-world coordinate under a non-Select tool.
|
||||
Click { world: (f64, f64), tool: Tool },
|
||||
/// two-point segment request from the canvas.
|
||||
SegmentBetween { from: (f64, f64), to: (f64, f64) },
|
||||
/// right-click on Select mode, toggling the closest entity to the given world point.
|
||||
TogglePickAt { world: (f64, f64) },
|
||||
/// left-click selection at a world point with modifier-derived op and restrict.
|
||||
PickAt { world: (f64, f64), pick_radius_world: f64, op: PickOp, restrict: PickRestrict },
|
||||
/// left-drag marquee selection between two world points with modifier-derived op and restrict.
|
||||
PickRect { p0: (f64, f64), p1: (f64, f64), op: PickOp, restrict: PickRestrict },
|
||||
/// Delete key in Select mode, removing every selected entity from the doc.
|
||||
DeleteSelected,
|
||||
/// drag delta in canvas pixels accumulating into app-side pan.
|
||||
PanBy { dx: f32, dy: f32 },
|
||||
/// wheel-zoom factor focused on a canvas-pixel point.
|
||||
ZoomAt { factor: f32, focus: Point },
|
||||
/// resets pan and zoom to the natural fit.
|
||||
ResetView,
|
||||
}
|
||||
|
||||
/// pan offset, zoom factor, and click-gesture bookkeeping for the canvas.
|
||||
/// pan offset and zoom factor owned by the app shell.
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct ViewState {
|
||||
pan: Vector,
|
||||
zoom: f32,
|
||||
drag_origin: Option<Point>,
|
||||
pub pan: Vector,
|
||||
pub zoom: f32,
|
||||
}
|
||||
|
||||
/// canvas-local click, drag, marquee, and modifier bookkeeping.
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct CanvasState {
|
||||
pan_drag_from: Option<Point>,
|
||||
marquee_from: Option<Point>,
|
||||
press_origin: Option<Point>,
|
||||
dragged: bool,
|
||||
pending_segment_start: Option<(f64, f64)>,
|
||||
cursor_world: Option<(f64, f64)>,
|
||||
cursor_canvas: Option<Point>,
|
||||
modifiers: iced::keyboard::Modifiers,
|
||||
}
|
||||
|
||||
/// constructs the canvas widget for a doc reference, optional mesh overlay, and optional solution.
|
||||
|
|
@ -81,8 +116,10 @@ pub fn view<'a>(
|
|||
mesh: Option<&'a Mesh>,
|
||||
solution: Option<&'a MagSolution>,
|
||||
render_mode: RenderMode,
|
||||
view_state: ViewState,
|
||||
show_grid: bool,
|
||||
) -> Element<'a, CanvasMessage> {
|
||||
Canvas::new(DocCanvas { doc, tool, mesh, solution, render_mode })
|
||||
Canvas::new(DocCanvas { doc, tool, mesh, solution, render_mode, view_state, show_grid })
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
|
|
@ -94,10 +131,12 @@ struct DocCanvas<'a> {
|
|||
mesh: Option<&'a Mesh>,
|
||||
solution: Option<&'a MagSolution>,
|
||||
render_mode: RenderMode,
|
||||
view_state: ViewState,
|
||||
show_grid: bool,
|
||||
}
|
||||
|
||||
impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
||||
type State = ViewState;
|
||||
type State = CanvasState;
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
|
|
@ -107,36 +146,63 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
cursor: mouse::Cursor,
|
||||
) -> Option<Action<CanvasMessage>> {
|
||||
match event {
|
||||
Event::Keyboard(iced::keyboard::Event::ModifiersChanged(m)) => {
|
||||
state.modifiers = *m;
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
|
||||
if let Some(p) = cursor.position_in(bounds) {
|
||||
state.pan_drag_from = Some(p);
|
||||
return Some(Action::capture());
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) => {
|
||||
state.pan_drag_from = None;
|
||||
return Some(Action::capture());
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||
if let Some(p) = cursor.position_in(bounds) {
|
||||
state.press_origin = Some(p);
|
||||
state.dragged = false;
|
||||
if self.tool == Tool::Select {
|
||||
state.drag_origin = Some(p);
|
||||
state.marquee_from = Some(p);
|
||||
}
|
||||
return Some(Action::capture());
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) => {
|
||||
if self.tool == Tool::Select {
|
||||
if let Some(now) = cursor.position_in(bounds) {
|
||||
let view = ViewTransform::fit(self.doc, bounds, state);
|
||||
let world = view.inverse_map(now);
|
||||
return Some(Action::publish(CanvasMessage::TogglePickAt { world })
|
||||
.and_capture());
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
|
||||
let press = state.press_origin.take();
|
||||
state.drag_origin = None;
|
||||
let marquee_start = state.marquee_from.take();
|
||||
let was_dragged = std::mem::take(&mut state.dragged);
|
||||
if !was_dragged && self.tool != Tool::Select {
|
||||
if let (Some(start), Some(now)) = (press, cursor.position_in(bounds)) {
|
||||
let now_opt = cursor.position_in(bounds);
|
||||
|
||||
if self.tool == Tool::Select {
|
||||
if let (Some(start), Some(now)) = (marquee_start, now_opt) {
|
||||
let view = ViewTransform::fit(self.doc, bounds, &self.view_state);
|
||||
let op = pick_op_from(state.modifiers);
|
||||
let restrict = pick_restrict_from(state.modifiers);
|
||||
if was_dragged {
|
||||
let p0 = view.inverse_map(start);
|
||||
let p1 = view.inverse_map(now);
|
||||
return Some(Action::publish(CanvasMessage::PickRect {
|
||||
p0, p1, op, restrict,
|
||||
}).and_capture());
|
||||
} else {
|
||||
let world = view.inverse_map(now);
|
||||
let pick_radius_world = (PICK_RADIUS_PX as f64) / view.scale.max(1e-9);
|
||||
return Some(Action::publish(CanvasMessage::PickAt {
|
||||
world, pick_radius_world, op, restrict,
|
||||
}).and_capture());
|
||||
}
|
||||
}
|
||||
return Some(Action::capture());
|
||||
}
|
||||
|
||||
if !was_dragged {
|
||||
if let (Some(start), Some(now)) = (press, now_opt) {
|
||||
if (now.x - start.x).abs() < CLICK_DRAG_THRESHOLD_PX
|
||||
&& (now.y - start.y).abs() < CLICK_DRAG_THRESHOLD_PX
|
||||
{
|
||||
let view = ViewTransform::fit(self.doc, bounds, state);
|
||||
let view = ViewTransform::fit(self.doc, bounds, &self.view_state);
|
||||
let world = view.inverse_map(now);
|
||||
if self.tool == Tool::AddSegment {
|
||||
if let Some(from) = state.pending_segment_start.take() {
|
||||
|
|
@ -160,16 +226,26 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
}
|
||||
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
|
||||
if let Some(now) = cursor.position_in(bounds) {
|
||||
let view = ViewTransform::fit(self.doc, bounds, state);
|
||||
let view = ViewTransform::fit(self.doc, bounds, &self.view_state);
|
||||
state.cursor_world = Some(view.inverse_map(now));
|
||||
state.cursor_canvas = Some(now);
|
||||
} else {
|
||||
state.cursor_world = None;
|
||||
state.cursor_canvas = None;
|
||||
}
|
||||
if let (Some(prev), Some(now)) = (state.drag_origin, cursor.position_in(bounds)) {
|
||||
state.pan = state.pan + Vector::new(now.x - prev.x, now.y - prev.y);
|
||||
state.drag_origin = Some(now);
|
||||
if let (Some(prev), Some(now)) = (state.pan_drag_from, cursor.position_in(bounds)) {
|
||||
let dx = now.x - prev.x;
|
||||
let dy = now.y - prev.y;
|
||||
state.pan_drag_from = Some(now);
|
||||
return Some(Action::publish(CanvasMessage::PanBy { dx, dy }).and_capture());
|
||||
}
|
||||
if let (Some(start), Some(now)) = (state.marquee_from, cursor.position_in(bounds)) {
|
||||
if (now.x - start.x).abs() > CLICK_DRAG_THRESHOLD_PX
|
||||
|| (now.y - start.y).abs() > CLICK_DRAG_THRESHOLD_PX
|
||||
{
|
||||
state.dragged = true;
|
||||
return Some(Action::request_redraw().and_capture());
|
||||
return Some(Action::request_redraw());
|
||||
}
|
||||
}
|
||||
if let (Some(start), Some(now)) = (state.press_origin, cursor.position_in(bounds)) {
|
||||
if (now.x - start.x).abs() > CLICK_DRAG_THRESHOLD_PX
|
||||
|
|
@ -188,21 +264,15 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
mouse::ScrollDelta::Lines { y, .. } => *y,
|
||||
mouse::ScrollDelta::Pixels { y, .. } => *y / 40.0,
|
||||
};
|
||||
if state.zoom == 0.0 { state.zoom = 1.0; }
|
||||
let prev = state.zoom;
|
||||
let next = (prev * ZOOM_STEP.powf(lines)).clamp(ZOOM_MIN, ZOOM_MAX);
|
||||
let factor = next / prev;
|
||||
state.pan.x = focus.x - factor * (focus.x - state.pan.x);
|
||||
state.pan.y = focus.y - factor * (focus.y - state.pan.y);
|
||||
state.zoom = next;
|
||||
return Some(Action::request_redraw().and_capture());
|
||||
let factor = ZOOM_STEP.powf(lines);
|
||||
return Some(Action::publish(CanvasMessage::ZoomAt { factor, focus }).and_capture());
|
||||
}
|
||||
}
|
||||
Event::Keyboard(iced::keyboard::Event::KeyPressed { key, .. }) => {
|
||||
if let iced::keyboard::Key::Character(c) = key {
|
||||
if c.as_str().eq_ignore_ascii_case("r") {
|
||||
*state = ViewState::default();
|
||||
return Some(Action::request_redraw());
|
||||
*state = CanvasState::default();
|
||||
return Some(Action::publish(CanvasMessage::ResetView));
|
||||
}
|
||||
}
|
||||
if matches!(
|
||||
|
|
@ -232,14 +302,21 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
&self,
|
||||
state: &Self::State,
|
||||
renderer: &Renderer,
|
||||
_theme: &Theme,
|
||||
theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: mouse::Cursor,
|
||||
) -> Vec<Geometry<Renderer>> {
|
||||
let palette = theme.palette();
|
||||
let bg = palette.background;
|
||||
let geom = palette.text;
|
||||
let mut frame = Frame::new(renderer, bounds.size());
|
||||
frame.fill_rectangle(Point::ORIGIN, bounds.size(), BG);
|
||||
frame.fill_rectangle(Point::ORIGIN, bounds.size(), bg);
|
||||
|
||||
let view = ViewTransform::fit(self.doc, bounds, state);
|
||||
let view = ViewTransform::fit(self.doc, bounds, &self.view_state);
|
||||
|
||||
if self.show_grid {
|
||||
draw_grid(&mut frame, bounds, &view, self.doc.length_units);
|
||||
}
|
||||
|
||||
if let Some(sol) = self.solution {
|
||||
let (lo, hi) = sol.b_magnitude_range();
|
||||
|
|
@ -319,7 +396,7 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
let (color, width) = if s.selected {
|
||||
(SELECT_COLOR, SELECT_STROKE)
|
||||
} else {
|
||||
(GEOM, STROKE_WIDTH)
|
||||
(geom, STROKE_WIDTH)
|
||||
};
|
||||
frame.stroke(&Path::line(a, b),
|
||||
Stroke::default().with_width(width).with_color(color));
|
||||
|
|
@ -333,7 +410,7 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
let (color, width) = if a.selected {
|
||||
(SELECT_COLOR, SELECT_STROKE)
|
||||
} else {
|
||||
(GEOM, STROKE_WIDTH)
|
||||
(geom, STROKE_WIDTH)
|
||||
};
|
||||
if let Some((center, radius, start_angle, end_angle)) =
|
||||
arc_geometry(p0.x, p0.y, p1.x, p1.y, a.arc_length, a.normal_direction)
|
||||
|
|
@ -363,7 +440,7 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
let (color, r) = if n.selected {
|
||||
(SELECT_COLOR, NODE_RADIUS + 2.0)
|
||||
} else {
|
||||
(GEOM, NODE_RADIUS)
|
||||
(geom, NODE_RADIUS)
|
||||
};
|
||||
frame.fill(&Path::circle(p, r), color);
|
||||
}
|
||||
|
|
@ -406,6 +483,18 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
});
|
||||
}
|
||||
|
||||
if let (Some(start), true, Some(now)) =
|
||||
(state.marquee_from, state.dragged, state.cursor_canvas)
|
||||
{
|
||||
let xmin = start.x.min(now.x);
|
||||
let ymin = start.y.min(now.y);
|
||||
let w = (start.x - now.x).abs();
|
||||
let h = (start.y - now.y).abs();
|
||||
let rect = Path::rectangle(Point::new(xmin, ymin), iced::Size::new(w, h));
|
||||
frame.fill(&rect, MARQUEE_FILL);
|
||||
frame.stroke(&rect, Stroke::default().with_width(1.0).with_color(MARQUEE_STROKE_COLOR));
|
||||
}
|
||||
|
||||
vec![frame.into_geometry()]
|
||||
}
|
||||
|
||||
|
|
@ -415,9 +504,12 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
bounds: Rectangle,
|
||||
cursor: mouse::Cursor,
|
||||
) -> mouse::Interaction {
|
||||
if state.drag_origin.is_some() {
|
||||
if state.pan_drag_from.is_some() {
|
||||
return mouse::Interaction::Grabbing;
|
||||
}
|
||||
if state.marquee_from.is_some() && state.dragged {
|
||||
return mouse::Interaction::Crosshair;
|
||||
}
|
||||
if cursor.position_in(bounds).is_some() {
|
||||
return match self.tool {
|
||||
Tool::Select => mouse::Interaction::Grab,
|
||||
|
|
@ -467,7 +559,7 @@ impl ViewTransform {
|
|||
Point::new(px, py)
|
||||
}
|
||||
|
||||
/// inverse of [`map`]: converts a canvas pixel back to a doc-world coordinate.
|
||||
/// converts a canvas pixel back into a doc-world coordinate.
|
||||
fn inverse_map(&self, p: Point) -> (f64, f64) {
|
||||
let x = (p.x - self.offset.x) as f64 / self.scale;
|
||||
let y = self.y_max - (p.y - self.offset.y) as f64 / self.scale;
|
||||
|
|
@ -498,6 +590,18 @@ fn doc_bounds(doc: &FemmDoc) -> (f64, f64, f64, f64) {
|
|||
}
|
||||
|
||||
/// maps a normalized t in [0, 1] onto the jet colormap (blue, cyan, green, yellow, red).
|
||||
/// derives the pick composition mode from a modifier snapshot.
|
||||
fn pick_op_from(m: iced::keyboard::Modifiers) -> PickOp {
|
||||
if m.shift() { PickOp::Add }
|
||||
else if m.command() { PickOp::Toggle }
|
||||
else { PickOp::Replace }
|
||||
}
|
||||
|
||||
/// derives the pick-restrict mode from a modifier snapshot.
|
||||
fn pick_restrict_from(m: iced::keyboard::Modifiers) -> PickRestrict {
|
||||
if m.alt() { PickRestrict::NodesOnly } else { PickRestrict::Any }
|
||||
}
|
||||
|
||||
fn jet(t: f32) -> Color {
|
||||
let t = t.clamp(0.0, 1.0);
|
||||
let (r, g, b) = if t < 0.25 {
|
||||
|
|
@ -516,8 +620,55 @@ fn jet(t: f32) -> Color {
|
|||
Color::from_rgb(r, g, b)
|
||||
}
|
||||
|
||||
/// derives center, radius, and angular extent of an arc segment from its endpoints
|
||||
/// and degree sweep. returns None when endpoints coincide.
|
||||
/// returns the world-space distance equal to one centimetre under the given length unit.
|
||||
pub fn grid_step_for_unit(unit: LengthUnit) -> f64 {
|
||||
match unit {
|
||||
LengthUnit::Inches => 1.0 / 2.54,
|
||||
LengthUnit::Millimeters => 10.0,
|
||||
LengthUnit::Centimeters => 1.0,
|
||||
LengthUnit::Meters => 0.01,
|
||||
LengthUnit::Mils => 1000.0 / 2.54,
|
||||
LengthUnit::Microns => 10_000.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// rounds a world-space coordinate to the nearest 1-cm grid step.
|
||||
pub fn snap_world(x: f64, unit: LengthUnit) -> f64 {
|
||||
let step = grid_step_for_unit(unit);
|
||||
if step <= 0.0 { x } else { (x / step).round() * step }
|
||||
}
|
||||
|
||||
/// renders a faint 1-cm grid under the field plot.
|
||||
fn draw_grid(frame: &mut Frame, bounds: Rectangle, view: &ViewTransform, unit: LengthUnit) {
|
||||
let step = grid_step_for_unit(unit);
|
||||
if step <= 0.0 { return; }
|
||||
let px_per_step = (step * view.scale) as f32;
|
||||
if px_per_step < GRID_MIN_PX_SPACING { return; }
|
||||
|
||||
let (x0_world, y1_world) = view.inverse_map(Point::new(0.0, 0.0));
|
||||
let (x1_world, y0_world) = view.inverse_map(Point::new(bounds.width, bounds.height));
|
||||
let stroke = Stroke::default().with_width(GRID_STROKE).with_color(GRID_COLOR);
|
||||
|
||||
let i_start = (x0_world / step).floor() as i32;
|
||||
let i_end = (x1_world / step).ceil() as i32;
|
||||
for i in i_start..=i_end {
|
||||
let wx = i as f64 * step;
|
||||
let top = view.map(wx, y1_world);
|
||||
let bot = view.map(wx, y0_world);
|
||||
frame.stroke(&Path::line(top, bot), stroke.clone());
|
||||
}
|
||||
|
||||
let j_start = (y0_world / step).floor() as i32;
|
||||
let j_end = (y1_world / step).ceil() as i32;
|
||||
for j in j_start..=j_end {
|
||||
let wy = j as f64 * step;
|
||||
let left = view.map(x0_world, wy);
|
||||
let right = view.map(x1_world, wy);
|
||||
frame.stroke(&Path::line(left, right), stroke.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// derives center, radius, and angular extent of an arc segment from its endpoints and degree sweep, returning None on coincident endpoints.
|
||||
fn arc_geometry(
|
||||
x0: f64, y0: f64,
|
||||
x1: f64, y1: f64,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,154 @@
|
|||
//! continuous-time displacement engine. a Track binds a clamped chord, an axis, a member-node set, and an f(s, t) expression evaluated each tick into a transverse displacement.
|
||||
|
||||
use femm_doc_mag::FemmDoc;
|
||||
|
||||
/// direction of transverse displacement applied to every tracked node.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Axis {
|
||||
PlusX,
|
||||
MinusX,
|
||||
PlusY,
|
||||
MinusY,
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
/// returns the unit vector matching the axis variant.
|
||||
pub fn unit(self) -> (f64, f64) {
|
||||
match self {
|
||||
Axis::PlusX => ( 1.0, 0.0),
|
||||
Axis::MinusX => (-1.0, 0.0),
|
||||
Axis::PlusY => ( 0.0, 1.0),
|
||||
Axis::MinusY => ( 0.0, -1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// parsed expression in chord parameter s and real time t, evaluable to a displacement.
|
||||
pub struct Expression {
|
||||
pub source: String,
|
||||
expr: meval::Expr,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Expression {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Expression({:?})", self.source)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Expression {
|
||||
fn clone(&self) -> Self {
|
||||
Expression::parse(&self.source).unwrap_or_else(|_| Expression::zero())
|
||||
}
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
/// parses an arithmetic expression in chord parameter s and real time t in seconds.
|
||||
pub fn parse(text: &str) -> Result<Self, String> {
|
||||
let expr: meval::Expr = text.parse().map_err(|e: meval::Error| e.to_string())?;
|
||||
Ok(Expression { source: text.to_string(), expr })
|
||||
}
|
||||
|
||||
/// returns a constant zero expression.
|
||||
pub fn zero() -> Self {
|
||||
let expr: meval::Expr = "0".parse().expect("constant 0 parses");
|
||||
Expression { source: String::from("0"), expr }
|
||||
}
|
||||
|
||||
/// evaluates the expression with bindings for s and t.
|
||||
pub fn eval(&self, s: f64, t: f64) -> f64 {
|
||||
let mut ctx = meval::Context::new();
|
||||
ctx.var("s", s);
|
||||
ctx.var("t", t);
|
||||
self.expr.eval_with_context(ctx).unwrap_or(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// one continuous-time track: clamped chord, axis, member nodes, and the closed-form displacement expression.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Track {
|
||||
pub label: String,
|
||||
pub anchor_a: usize,
|
||||
pub anchor_b: usize,
|
||||
pub member_nodes: Vec<usize>,
|
||||
pub axis: Axis,
|
||||
pub expression: Expression,
|
||||
}
|
||||
|
||||
/// projects a node onto the anchor_a-to-anchor_b chord, returning a parameter clamped to [0, 1].
|
||||
pub fn chord_parameter(base: &FemmDoc, anchor_a: usize, anchor_b: usize, node_idx: usize) -> f64 {
|
||||
let (Some(a), Some(b), Some(n)) = (
|
||||
base.nodes.get(anchor_a),
|
||||
base.nodes.get(anchor_b),
|
||||
base.nodes.get(node_idx),
|
||||
) else { return 0.0 };
|
||||
let dx = b.x - a.x;
|
||||
let dy = b.y - a.y;
|
||||
let len2 = dx * dx + dy * dy;
|
||||
if len2 < 1e-18 { return 0.0; }
|
||||
let s = ((n.x - a.x) * dx + (n.y - a.y) * dy) / len2;
|
||||
s.clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
/// resets every node position on doc from base, layering each track's evaluated displacement at simulated time t seconds.
|
||||
pub fn apply_tracks(doc: &mut FemmDoc, base: &FemmDoc, tracks: &[Track], t: f64) {
|
||||
for (i, n) in doc.nodes.iter_mut().enumerate() {
|
||||
if let Some(b) = base.nodes.get(i) {
|
||||
n.x = b.x;
|
||||
n.y = b.y;
|
||||
}
|
||||
}
|
||||
for track in tracks {
|
||||
let (ux, uy) = track.axis.unit();
|
||||
for &node_idx in &track.member_nodes {
|
||||
let s = chord_parameter(base, track.anchor_a, track.anchor_b, node_idx);
|
||||
let delta = track.expression.eval(s, t);
|
||||
if let Some(n) = doc.nodes.get_mut(node_idx) {
|
||||
n.x += ux * delta;
|
||||
n.y += uy * delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// builds one track from selected nodes, picking anchors as the two extremes along the chord axis and treating the rest as members.
|
||||
pub fn track_from_selection(
|
||||
doc: &FemmDoc,
|
||||
selected: &[usize],
|
||||
axis: Axis,
|
||||
default_expression: &str,
|
||||
label: String,
|
||||
) -> Result<Track, String> {
|
||||
if selected.len() < 3 {
|
||||
return Err(String::from("track needs at least three selected nodes: two anchors plus one member"));
|
||||
}
|
||||
let chord_axis_x = matches!(axis, Axis::PlusY | Axis::MinusY);
|
||||
let key = |i: usize| -> Option<f64> {
|
||||
let n = doc.nodes.get(i)?;
|
||||
Some(if chord_axis_x { n.x } else { n.y })
|
||||
};
|
||||
let mut min_idx = selected[0];
|
||||
let mut max_idx = selected[0];
|
||||
let mut min_v = key(min_idx).ok_or_else(|| String::from("invalid selected node index"))?;
|
||||
let mut max_v = min_v;
|
||||
for &i in selected {
|
||||
let v = key(i).ok_or_else(|| String::from("invalid selected node index"))?;
|
||||
if v < min_v { min_v = v; min_idx = i; }
|
||||
if v > max_v { max_v = v; max_idx = i; }
|
||||
}
|
||||
if (max_v - min_v).abs() < 1e-12 {
|
||||
return Err(String::from("selected nodes are collinear with the chosen axis - cannot pick anchors"));
|
||||
}
|
||||
let members: Vec<usize> = selected.iter().copied()
|
||||
.filter(|&i| i != min_idx && i != max_idx)
|
||||
.collect();
|
||||
let expression = Expression::parse(default_expression)
|
||||
.map_err(|e| format!("default expression failed to parse: {e}"))?;
|
||||
Ok(Track {
|
||||
label,
|
||||
anchor_a: min_idx,
|
||||
anchor_b: max_idx,
|
||||
member_nodes: members,
|
||||
axis,
|
||||
expression,
|
||||
})
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,79 @@
|
|||
//! SPICE-notation time and quantity parser covering the f/p/n/u/m/k/meg/g/t suffixes and an optional trailing s.
|
||||
|
||||
/// parses a SPICE-suffixed quantity into its base value, taking time as seconds when read as a duration.
|
||||
pub fn parse_spice(text: &str) -> Option<f64> {
|
||||
let s = text.trim().to_ascii_lowercase();
|
||||
if s.is_empty() { return None; }
|
||||
let s = s.trim_end_matches('s');
|
||||
let split_at = s
|
||||
.char_indices()
|
||||
.find(|(_, c)| {
|
||||
!(c.is_ascii_digit() || *c == '.' || *c == '+' || *c == '-' || *c == 'e')
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(s.len());
|
||||
let (num_part, suf) = s.split_at(split_at);
|
||||
let num_part = num_part.trim_end_matches('e').trim_end_matches('E');
|
||||
let value: f64 = num_part.parse().ok()?;
|
||||
let factor = suffix_factor(suf.trim())?;
|
||||
Some(value * factor)
|
||||
}
|
||||
|
||||
/// maps a SPICE suffix to its decimal factor, keeping meg at 1e6 separate from m at 1e-3.
|
||||
fn suffix_factor(suf: &str) -> Option<f64> {
|
||||
Some(match suf {
|
||||
"" => 1.0,
|
||||
"f" => 1.0e-15,
|
||||
"p" => 1.0e-12,
|
||||
"n" => 1.0e-9,
|
||||
"u" | "µ" => 1.0e-6,
|
||||
"m" => 1.0e-3,
|
||||
"k" => 1.0e3,
|
||||
"meg" => 1.0e6,
|
||||
"g" => 1.0e9,
|
||||
"t" => 1.0e12,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
/// formats a base value back into SPICE notation with a trailing s, picking the smallest suffix yielding a magnitude of at least 1.
|
||||
pub fn format_spice_time(seconds: f64) -> String {
|
||||
if seconds == 0.0 { return String::from("0s"); }
|
||||
let abs = seconds.abs();
|
||||
let (factor, suf) = if abs >= 1.0 { (1.0, "") }
|
||||
else if abs >= 1.0e-3 { (1.0e-3, "m") }
|
||||
else if abs >= 1.0e-6 { (1.0e-6, "u") }
|
||||
else if abs >= 1.0e-9 { (1.0e-9, "n") }
|
||||
else if abs >= 1.0e-12 { (1.0e-12, "p") }
|
||||
else { (1.0e-15, "f") };
|
||||
let scaled = seconds / factor;
|
||||
if (scaled.round() - scaled).abs() < 1e-9 {
|
||||
format!("{}{}s", scaled.round() as i64, suf)
|
||||
} else {
|
||||
format!("{:.3}{}s", scaled, suf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn spice_suffixes_round_trip_through_seconds() {
|
||||
// standard SPICE suffixes resolve to their decimal factors, keeping m and meg apart.
|
||||
assert!((parse_spice("100u").unwrap() - 1.0e-4).abs() < 1e-12);
|
||||
assert!((parse_spice("1.5n").unwrap() - 1.5e-9).abs() < 1e-15);
|
||||
assert!((parse_spice("2meg").unwrap() - 2.0e6).abs() < 1e-3);
|
||||
assert!((parse_spice("33m").unwrap() - 33.0e-3).abs() < 1e-9);
|
||||
assert!((parse_spice("1ms").unwrap() - 1.0e-3).abs() < 1e-9);
|
||||
assert!((parse_spice("12.34").unwrap() - 12.34).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_input_rejected() {
|
||||
// empty, alpha-only, and unknown suffixes all return None.
|
||||
assert!(parse_spice("").is_none());
|
||||
assert!(parse_spice("xyz").is_none());
|
||||
assert!(parse_spice("1z").is_none());
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,58 @@ impl FemmDoc {
|
|||
self.add_segment_with_marker(n0, n1, "")
|
||||
}
|
||||
|
||||
/// splits one segment into `n` equal pieces by inserting `n - 1` intermediate nodes, returning the new node indices in order from n0 toward n1.
|
||||
pub fn subdivide_segment(&mut self, idx: usize, n: usize) -> Vec<i32> {
|
||||
if n < 2 || idx >= self.segments.len() { return Vec::new(); }
|
||||
let seg = self.segments[idx].clone();
|
||||
let n0 = seg.n0; let n1 = seg.n1;
|
||||
let nn = self.nodes.len() as i32;
|
||||
if n0 < 0 || n1 < 0 || n0 >= nn || n1 >= nn { return Vec::new(); }
|
||||
let (x0, y0) = (self.nodes[n0 as usize].x, self.nodes[n0 as usize].y);
|
||||
let (x1, y1) = (self.nodes[n1 as usize].x, self.nodes[n1 as usize].y);
|
||||
let dx = (x1 - x0) / n as f64;
|
||||
let dy = (y1 - y0) / n as f64;
|
||||
|
||||
let mut new_nodes: Vec<i32> = Vec::with_capacity(n - 1);
|
||||
for i in 1..n {
|
||||
let nx = x0 + dx * i as f64;
|
||||
let ny = y0 + dy * i as f64;
|
||||
let idx_new = self.nodes.len() as i32;
|
||||
self.nodes.push(Node {
|
||||
x: nx, y: ny,
|
||||
boundary_marker: String::new(),
|
||||
in_group: seg.in_group,
|
||||
selected: false,
|
||||
});
|
||||
new_nodes.push(idx_new);
|
||||
}
|
||||
|
||||
self.segments.remove(idx);
|
||||
let mut prev = n0;
|
||||
for &mid in &new_nodes {
|
||||
self.segments.push(Segment {
|
||||
n0: prev,
|
||||
n1: mid,
|
||||
max_side_length: seg.max_side_length,
|
||||
boundary_marker: seg.boundary_marker.clone(),
|
||||
hidden: seg.hidden,
|
||||
in_group: seg.in_group,
|
||||
selected: false,
|
||||
});
|
||||
prev = mid;
|
||||
}
|
||||
self.segments.push(Segment {
|
||||
n0: prev,
|
||||
n1: n1,
|
||||
max_side_length: seg.max_side_length,
|
||||
boundary_marker: seg.boundary_marker.clone(),
|
||||
hidden: seg.hidden,
|
||||
in_group: seg.in_group,
|
||||
selected: false,
|
||||
});
|
||||
new_nodes
|
||||
}
|
||||
|
||||
/// PSLG-aware variant propagating a boundary-marker name onto every resulting segment piece.
|
||||
pub fn add_segment_with_marker(&mut self, n0: i32, n1: i32, marker: &str) -> bool {
|
||||
if n0 == n1 { return false; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "femm-mag-solve"
|
||||
version = "0.0.1"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
publish.workspace = true
|
||||
description = "subprocess wrapper around the magnetostatic FFI solver - isolates C++ aborts from the GUI"
|
||||
|
||||
[[bin]]
|
||||
name = "femm-mag-solve"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
femm-sys = { workspace = true }
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
//! subprocess wrapper around the magnetostatic FFI pipeline, keyed by a stem path on argv.
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::process::ExitCode;
|
||||
|
||||
const EXIT_OK: u8 = 0;
|
||||
const EXIT_USAGE: u8 = 2;
|
||||
const EXIT_BAD_STEM: u8 = 3;
|
||||
const EXIT_DOC_NULL: u8 = 10;
|
||||
const EXIT_LOAD_FEM: u8 = 11;
|
||||
const EXIT_LOAD_MESH: u8 = 12;
|
||||
const EXIT_RENUMBER: u8 = 13;
|
||||
const EXIT_SOLVE: u8 = 14;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let stem = match std::env::args().nth(1) {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
eprintln!("usage: femm-mag-solve <stem-path>");
|
||||
return ExitCode::from(EXIT_USAGE);
|
||||
}
|
||||
};
|
||||
let cstem = match CString::new(stem.as_str()) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("stem contains NUL: {e}");
|
||||
return ExitCode::from(EXIT_BAD_STEM);
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let doc = femm_sys::femm_mag_doc_new();
|
||||
if doc.is_null() {
|
||||
eprintln!("femm_mag_doc_new returned null");
|
||||
return ExitCode::from(EXIT_DOC_NULL);
|
||||
}
|
||||
if femm_sys::femm_mag_doc_load_fem(doc, cstem.as_ptr()) == 0 {
|
||||
femm_sys::femm_mag_doc_free(doc);
|
||||
eprintln!("femm_mag_doc_load_fem returned 0 - the .fem could not be parsed by the engine");
|
||||
return ExitCode::from(EXIT_LOAD_FEM);
|
||||
}
|
||||
if femm_sys::femm_mag_doc_load_mesh(doc) == 0 {
|
||||
femm_sys::femm_mag_doc_free(doc);
|
||||
eprintln!("femm_mag_doc_load_mesh returned 0 - .node/.ele/.pbc files missing or unreadable");
|
||||
return ExitCode::from(EXIT_LOAD_MESH);
|
||||
}
|
||||
if femm_sys::femm_mag_doc_renumber(doc) == 0 {
|
||||
femm_sys::femm_mag_doc_free(doc);
|
||||
eprintln!("femm_mag_doc_renumber returned 0 - mesh renumbering rejected the topology");
|
||||
return ExitCode::from(EXIT_RENUMBER);
|
||||
}
|
||||
if femm_sys::femm_mag_doc_solve(doc) == 0 {
|
||||
femm_sys::femm_mag_doc_free(doc);
|
||||
eprintln!("femm_mag_doc_solve returned 0 - linear solve failed (singular materials, missing block labels, or unbounded air region)");
|
||||
return ExitCode::from(EXIT_SOLVE);
|
||||
}
|
||||
femm_sys::femm_mag_doc_free(doc);
|
||||
}
|
||||
|
||||
ExitCode::from(EXIT_OK)
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
[Format] = 4.0
|
||||
[Frequency] = 0
|
||||
[Precision] = 1e-008
|
||||
[MinAngle] = 30
|
||||
[Depth] = 1
|
||||
[LengthUnits] = inches
|
||||
[ProblemType] = planar
|
||||
[Coordinates] = cartesian
|
||||
[ACSolver] = 0
|
||||
[Comment] = "Add comments here."
|
||||
[PointProps] = 0
|
||||
[BdryProps] = 1
|
||||
<BeginBdry>
|
||||
<BdryName> = "A=0"
|
||||
<BdryType> = 0
|
||||
<A_0> = 0
|
||||
<A_1> = 0
|
||||
<A_2> = 0
|
||||
<Phi> = 0
|
||||
<c0> = 0
|
||||
<c0i> = 0
|
||||
<c1> = 0
|
||||
<c1i> = 0
|
||||
<Mu_ssd> = 0
|
||||
<Sigma_ssd> = 0
|
||||
<EndBdry>
|
||||
[BlockProps] = 2
|
||||
<BeginBlock>
|
||||
<BlockName> = "Air"
|
||||
<Mu_x> = 1
|
||||
<Mu_y> = 1
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "NdFeB 40 MGOe"
|
||||
<Mu_x> = 1.0489999999999999
|
||||
<Mu_y> = 1.0489999999999999
|
||||
<H_c> = 979000
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0.66700000000000004
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
[CircuitProps] = 0
|
||||
[NumPoints] = 8
|
||||
0 0 0 0
|
||||
0 1 0 0
|
||||
10 1 0 0
|
||||
10 0 0 0
|
||||
-2 -2 0 0
|
||||
-2 3 0 0
|
||||
12 3 0 0
|
||||
12 -2 0 0
|
||||
[NumSegments] = 8
|
||||
1 0 -1 0 0 0
|
||||
2 3 -1 0 0 0
|
||||
1 2 -1 0 0 0
|
||||
0 3 -1 0 0 0
|
||||
5 4 -1 1 0 0
|
||||
5 6 -1 1 0 0
|
||||
7 6 -1 1 0 0
|
||||
4 7 -1 1 0 0
|
||||
[NumArcSegments] = 0
|
||||
[NumHoles] = 0
|
||||
[NumBlockLabels] = 2
|
||||
5 -0.5 1 0.083872811355419435 0 0 0 1 0
|
||||
5 0.5 2 0.083872811355419435 0 0 0 1 0 "x*180"
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
[Format] = 4.0
|
||||
[Frequency] = 0
|
||||
[Precision] = 1e-008
|
||||
[MinAngle] = 30
|
||||
[Depth] = 4
|
||||
[LengthUnits] = centimeters
|
||||
[ProblemType] = planar
|
||||
[Coordinates] = cartesian
|
||||
[ACSolver] = 0
|
||||
[Comment] = "Add comments here."
|
||||
[PointProps] = 0
|
||||
[BdryProps] = 1
|
||||
<BeginBdry>
|
||||
<BdryName> = "A=0"
|
||||
<BdryType> = 0
|
||||
<A_0> = 0
|
||||
<A_1> = 0
|
||||
<A_2> = 0
|
||||
<Phi> = 0
|
||||
<c0> = 0
|
||||
<c0i> = 0
|
||||
<c1> = 0
|
||||
<c1i> = 0
|
||||
<Mu_ssd> = 0
|
||||
<Sigma_ssd> = 0
|
||||
<EndBdry>
|
||||
[BlockProps] = 3
|
||||
<BeginBlock>
|
||||
<BlockName> = "Magnet"
|
||||
<Mu_x> = 1.05
|
||||
<Mu_y> = 1.05
|
||||
<H_c> = 909456.81766797299
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "Air"
|
||||
<Mu_x> = 1
|
||||
<Mu_y> = 1
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "Steel"
|
||||
<Mu_x> = 5000
|
||||
<Mu_y> = 5000
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
[CircuitProps] = 0
|
||||
[NumPoints] = 20
|
||||
2.6811555509164231 2.2497566339028872 0 0
|
||||
-2.6811555509164231 -2.2497566339028872 0 0
|
||||
2.987573328164014 2.5068716777775029 0 0
|
||||
-2.987573328164014 -2.5068716777775029 0 0
|
||||
4 0 0 0
|
||||
-4 0 0 0
|
||||
-2.6811555509164227 2.2497566339028876 0 0
|
||||
2.6811555509164227 -2.2497566339028876 0 0
|
||||
-2.9875733281640136 2.5068716777775029 0 0
|
||||
2.9875733281640136 -2.5068716777775029 0 0
|
||||
2.2497566339028876 2.6811555509164227 0 0
|
||||
2.5068716777775029 2.9875733281640136 0 0
|
||||
-2.5068716777775029 -2.9875733281640136 0 0
|
||||
-2.2497566339028876 -2.6811555509164227 0 0
|
||||
2.2497566339028872 -2.6811555509164231 0 0
|
||||
2.5068716777775024 -2.9875733281640136 0 0
|
||||
-2.5068716777775024 2.9875733281640136 0 0
|
||||
-2.2497566339028872 2.6811555509164231 0 0
|
||||
5 0 0 0
|
||||
-5 6.1232339957367663e-016 0 0
|
||||
[NumSegments] = 8
|
||||
0 2 -1 0 0 0
|
||||
7 9 -1 0 0 0
|
||||
3 1 -1 0 0 0
|
||||
8 6 -1 0 0 0
|
||||
10 11 -1 0 0 0
|
||||
12 13 -1 0 0 0
|
||||
14 15 -1 0 0 0
|
||||
16 17 -1 0 0 0
|
||||
[NumArcSegments] = 16
|
||||
0 10 10 1 0 0 0
|
||||
17 6 10.000000000000004 1 0 0 0
|
||||
6 1 80 1 0 0 0
|
||||
1 13 10 1 0 0 0
|
||||
14 7 10.000000000000004 1 0 0 0
|
||||
7 0 80 1 0 0 0
|
||||
11 16 80.000000000000043 1 0 0 0
|
||||
8 3 80 1 0 0 0
|
||||
12 15 80.000000000000043 1 0 0 0
|
||||
9 2 80 1 0 0 0
|
||||
4 5 180 1 0 0 0
|
||||
5 4 180 1 0 0 0
|
||||
10 17 80.000000000000043 1 0 0 0
|
||||
13 14 80 1 0 0 0
|
||||
18 19 180 1 1 0 0
|
||||
19 18 180 1 1 0 0
|
||||
[NumHoles] = 0
|
||||
[NumBlockLabels] = 7
|
||||
-0.26000000000000001 0.34000000000000002 3 -1 0 0 0 1 0
|
||||
3.7000000000000002 0 1 -1 0 0 0 1 0
|
||||
-3.7000000000000002 0 1 -1 0 180 0 1 0
|
||||
2.6699999999999999 2.6099999999999999 2 -1 0 0 0 1 0
|
||||
2.2655965784226036e-016 3.7000000000000002 1 -1 0 -90 0 1 0
|
||||
-2.2655965784226036e-016 -3.7000000000000002 1 -1 0 90 0 1 0
|
||||
0 4.4000000000000004 3 -1 0 0 0 1 0
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
[Format] = 4.0
|
||||
[Frequency] = 0
|
||||
[Precision] = 1e-008
|
||||
[MinAngle] = 25
|
||||
[Depth] = 5
|
||||
[LengthUnits] = millimeters
|
||||
[ProblemType] = planar
|
||||
[Coordinates] = cartesian
|
||||
[Comment] = "guitar pickup geometry: 25.5-inch scale, 6 plain steel strings (0.010-0.046 inch, real gauges), NdFeB bar magnets above and below the string plane with opposite faces presented."
|
||||
[PointProps] = 0
|
||||
[BdryProps] = 1
|
||||
<BeginBdry>
|
||||
<BdryName> = "A=0"
|
||||
<BdryType> = 0
|
||||
<A_0> = 0
|
||||
<A_1> = 0
|
||||
<A_2> = 0
|
||||
<Phi> = 0
|
||||
<c0> = 0
|
||||
<c0i> = 0
|
||||
<c1> = 0
|
||||
<c1i> = 0
|
||||
<Mu_ssd> = 0
|
||||
<Sigma_ssd> = 0
|
||||
<EndBdry>
|
||||
[BlockProps] = 3
|
||||
<BeginBlock>
|
||||
<BlockName> = "Air"
|
||||
<Mu_x> = 1
|
||||
<Mu_y> = 1
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "NdFeB"
|
||||
<Mu_x> = 1.05
|
||||
<Mu_y> = 1.05
|
||||
<H_c> = 915000
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0.667
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "Steel"
|
||||
<Mu_x> = 2500
|
||||
<Mu_y> = 2500
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 5.8
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
[CircuitProps] = 0
|
||||
[NumPoints] = 36
|
||||
-50 -80 0 0
|
||||
700 -80 0 0
|
||||
700 80 0 0
|
||||
-50 80 0 0
|
||||
0 30 0 0
|
||||
647.7 30 0 0
|
||||
647.7 35 0 0
|
||||
0 35 0 0
|
||||
0 -35 0 0
|
||||
647.7 -35 0 0
|
||||
647.7 -30 0 0
|
||||
0 -30 0 0
|
||||
0 -26.377 0 0
|
||||
647.7 -26.377 0 0
|
||||
647.7 -26.123 0 0
|
||||
0 -26.123 0 0
|
||||
0 -15.915 0 0
|
||||
647.7 -15.915 0 0
|
||||
647.7 -15.585 0 0
|
||||
0 -15.585 0 0
|
||||
0 -5.466 0 0
|
||||
647.7 -5.466 0 0
|
||||
647.7 -5.034 0 0
|
||||
0 -5.034 0 0
|
||||
0 4.920 0 0
|
||||
647.7 4.920 0 0
|
||||
647.7 5.580 0 0
|
||||
0 5.580 0 0
|
||||
0 15.293 0 0
|
||||
647.7 15.293 0 0
|
||||
647.7 16.207 0 0
|
||||
0 16.207 0 0
|
||||
0 25.666 0 0
|
||||
647.7 25.666 0 0
|
||||
647.7 26.834 0 0
|
||||
0 26.834 0 0
|
||||
[NumSegments] = 36
|
||||
0 1 -1 1 0 0
|
||||
1 2 -1 1 0 0
|
||||
2 3 -1 1 0 0
|
||||
3 0 -1 1 0 0
|
||||
4 5 -1 0 0 0
|
||||
5 6 -1 0 0 0
|
||||
6 7 -1 0 0 0
|
||||
7 4 -1 0 0 0
|
||||
8 9 -1 0 0 0
|
||||
9 10 -1 0 0 0
|
||||
10 11 -1 0 0 0
|
||||
11 8 -1 0 0 0
|
||||
12 13 -1 0 0 0
|
||||
13 14 -1 0 0 0
|
||||
14 15 -1 0 0 0
|
||||
15 12 -1 0 0 0
|
||||
16 17 -1 0 0 0
|
||||
17 18 -1 0 0 0
|
||||
18 19 -1 0 0 0
|
||||
19 16 -1 0 0 0
|
||||
20 21 -1 0 0 0
|
||||
21 22 -1 0 0 0
|
||||
22 23 -1 0 0 0
|
||||
23 20 -1 0 0 0
|
||||
24 25 -1 0 0 0
|
||||
25 26 -1 0 0 0
|
||||
26 27 -1 0 0 0
|
||||
27 24 -1 0 0 0
|
||||
28 29 -1 0 0 0
|
||||
29 30 -1 0 0 0
|
||||
30 31 -1 0 0 0
|
||||
31 28 -1 0 0 0
|
||||
32 33 -1 0 0 0
|
||||
33 34 -1 0 0 0
|
||||
34 35 -1 0 0 0
|
||||
35 32 -1 0 0 0
|
||||
[NumArcSegments] = 0
|
||||
[NumHoles] = 0
|
||||
[NumBlockLabels] = 9
|
||||
325 70 1 -1 0 0 0 1 2
|
||||
325 32.5 2 5 0 270 0 1 0
|
||||
325 -32.5 2 5 0 270 0 1 0
|
||||
325 -26.25 3 1 0 0 0 1 0
|
||||
325 -15.75 3 1 0 0 0 1 0
|
||||
325 -5.25 3 1 0 0 0 1 0
|
||||
325 5.25 3 1 0 0 0 1 0
|
||||
325 15.75 3 1 0 0 0 1 0
|
||||
325 26.25 3 1 0 0 0 1 0
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
[Format] = 4.0
|
||||
[Frequency] = 0
|
||||
[Precision] = 1e-008
|
||||
[MinAngle] = 30
|
||||
[Depth] = 4
|
||||
[LengthUnits] = centimeters
|
||||
[ProblemType] = planar
|
||||
[Coordinates] = cartesian
|
||||
[ACSolver] = 0
|
||||
[Comment] = "Add comments here."
|
||||
[PointProps] = 0
|
||||
[BdryProps] = 1
|
||||
<BeginBdry>
|
||||
<BdryName> = "A=0"
|
||||
<BdryType> = 0
|
||||
<A_0> = 0
|
||||
<A_1> = 0
|
||||
<A_2> = 0
|
||||
<Phi> = 0
|
||||
<c0> = 0
|
||||
<c0i> = 0
|
||||
<c1> = 0
|
||||
<c1i> = 0
|
||||
<Mu_ssd> = 0
|
||||
<Sigma_ssd> = 0
|
||||
<EndBdry>
|
||||
[BlockProps] = 3
|
||||
<BeginBlock>
|
||||
<BlockName> = "Magnet"
|
||||
<Mu_x> = 1.05
|
||||
<Mu_y> = 1.05
|
||||
<H_c> = 909456.81766797299
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "Air"
|
||||
<Mu_x> = 1
|
||||
<Mu_y> = 1
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "Steel"
|
||||
<Mu_x> = 5000
|
||||
<Mu_y> = 5000
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
[CircuitProps] = 0
|
||||
[NumPoints] = 20
|
||||
2.6811555509164231 2.2497566339028872 0 0
|
||||
-2.6811555509164231 -2.2497566339028872 0 0
|
||||
2.987573328164014 2.5068716777775029 0 0
|
||||
-2.987573328164014 -2.5068716777775029 0 0
|
||||
4 0 0 0
|
||||
-4 0 0 0
|
||||
-2.6811555509164227 2.2497566339028876 0 0
|
||||
2.6811555509164227 -2.2497566339028876 0 0
|
||||
-2.9875733281640136 2.5068716777775029 0 0
|
||||
2.9875733281640136 -2.5068716777775029 0 0
|
||||
2.2497566339028876 2.6811555509164227 0 0
|
||||
2.5068716777775029 2.9875733281640136 0 0
|
||||
-2.5068716777775029 -2.9875733281640136 0 0
|
||||
-2.2497566339028876 -2.6811555509164227 0 0
|
||||
2.2497566339028872 -2.6811555509164231 0 0
|
||||
2.5068716777775024 -2.9875733281640136 0 0
|
||||
-2.5068716777775024 2.9875733281640136 0 0
|
||||
-2.2497566339028872 2.6811555509164231 0 0
|
||||
5 0 0 0
|
||||
-5 6.1232339957367663e-016 0 0
|
||||
[NumSegments] = 8
|
||||
0 2 -1 0 0 0
|
||||
7 9 -1 0 0 0
|
||||
3 1 -1 0 0 0
|
||||
8 6 -1 0 0 0
|
||||
10 11 -1 0 0 0
|
||||
12 13 -1 0 0 0
|
||||
14 15 -1 0 0 0
|
||||
16 17 -1 0 0 0
|
||||
[NumArcSegments] = 16
|
||||
0 10 10 1 0 0 0
|
||||
17 6 10.000000000000004 1 0 0 0
|
||||
6 1 80 1 0 0 0
|
||||
1 13 10 1 0 0 0
|
||||
14 7 10.000000000000004 1 0 0 0
|
||||
7 0 80 1 0 0 0
|
||||
11 16 80.000000000000043 1 0 0 0
|
||||
8 3 80 1 0 0 0
|
||||
12 15 80.000000000000043 1 0 0 0
|
||||
9 2 80 1 0 0 0
|
||||
4 5 180 1 0 0 0
|
||||
5 4 180 1 0 0 0
|
||||
10 17 80.000000000000043 1 0 0 0
|
||||
13 14 80 1 0 0 0
|
||||
18 19 180 1 1 0 0
|
||||
19 18 180 1 1 0 0
|
||||
[NumHoles] = 0
|
||||
[NumBlockLabels] = 7
|
||||
-0.26000000000000001 0.34000000000000002 3 -1 0 0 0 1 0
|
||||
3.7000000000000002 0 1 -1 0 0 0 1 0 "theta"
|
||||
-3.7000000000000002 0 1 -1 0 0 0 1 0 "theta"
|
||||
2.6699999999999999 2.6099999999999999 2 -1 0 0 0 1 0
|
||||
2.2655965784226036e-016 3.7000000000000002 1 -1 0 0 0 1 0 "theta+180"
|
||||
-2.2655965784226036e-016 -3.7000000000000002 1 -1 0 0 0 1 0 "theta+180"
|
||||
0 4.4000000000000004 3 -1 0 0 0 1 0
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
[Format] = 4.0
|
||||
[Frequency] = 0
|
||||
[Precision] = 1e-008
|
||||
[MinAngle] = 30
|
||||
[DoSmartMesh] = 1
|
||||
[Depth] = 1
|
||||
[LengthUnits] = inches
|
||||
[ProblemType] = axisymmetric
|
||||
[Coordinates] = cartesian
|
||||
[ACSolver] = 0
|
||||
[PrevType] = 0
|
||||
[PrevSoln] = ""
|
||||
[Comment] = "Add comments here."
|
||||
[PointProps] = 0
|
||||
[BdryProps] = 1
|
||||
<BeginBdry>
|
||||
<BdryName> = "A=0"
|
||||
<BdryType> = 0
|
||||
<A_0> = 0
|
||||
<A_1> = 0
|
||||
<A_2> = 0
|
||||
<Phi> = 0
|
||||
<c0> = 0
|
||||
<c0i> = 0
|
||||
<c1> = 0
|
||||
<c1i> = 0
|
||||
<Mu_ssd> = 0
|
||||
<Sigma_ssd> = 0
|
||||
<innerangle> = 0
|
||||
<outerangle> = 0
|
||||
<EndBdry>
|
||||
[BlockProps] = 8
|
||||
<BeginBlock>
|
||||
<BlockName> = "u1"
|
||||
<Mu_x> = 2.40892426667406
|
||||
<Mu_y> = 2.40892426667406
|
||||
<H_c> = 795774.71545947704
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "u2"
|
||||
<Mu_x> = 0.149299291057435
|
||||
<Mu_y> = 0.149299291057435
|
||||
<H_c> = 795774.71545947704
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "u3"
|
||||
<Mu_x> = 13.817592213008799
|
||||
<Mu_y> = 13.817592213008799
|
||||
<H_c> = 795774.71545947704
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "u4"
|
||||
<Mu_x> = 0.058217224585269498
|
||||
<Mu_y> = 0.058217224585269498
|
||||
<H_c> = 795774.71545947704
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "u5"
|
||||
<Mu_x> = 45.655954531895603
|
||||
<Mu_y> = 45.655954531895603
|
||||
<H_c> = 795774.71545947704
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "u6"
|
||||
<Mu_x> = 0.033867295231772801
|
||||
<Mu_y> = 0.033867295231772801
|
||||
<H_c> = 795774.71545947704
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "u7"
|
||||
<Mu_x> = 429.04642800993503
|
||||
<Mu_y> = 429.04642800993503
|
||||
<H_c> = 795774.71545947704
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "Air"
|
||||
<Mu_x> = 1
|
||||
<Mu_y> = 1
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
[CircuitProps] = 0
|
||||
[NumPoints] = 16
|
||||
0 2.7705849999999996 0 0
|
||||
0 8.7494149999999991 0 0
|
||||
0 3.0423499999999999 0 0
|
||||
0 8.4776500000000006 0 0
|
||||
0 3.0035264285714285 0 0
|
||||
0 8.5164735714285715 0 0
|
||||
0 2.9647028571428571 0 0
|
||||
0 8.5552971428571425 0 0
|
||||
0 2.9258792857142852 0 0
|
||||
0 8.5941207142857152 0 0
|
||||
0 2.8870557142857138 0 0
|
||||
0 8.6329442857142862 0 0
|
||||
0 2.8482321428571424 0 0
|
||||
0 8.6717678571428571 0 0
|
||||
0 2.8094085714285715 0 0
|
||||
0 8.7105914285714281 0 0
|
||||
[NumSegments] = 15
|
||||
0 14 -1 0 0 0
|
||||
2 3 -1 0 0 0
|
||||
3 5 -1 0 0 0
|
||||
4 2 -1 0 0 0
|
||||
5 7 -1 0 0 0
|
||||
6 4 -1 0 0 0
|
||||
7 9 -1 0 0 0
|
||||
8 6 -1 0 0 0
|
||||
9 11 -1 0 0 0
|
||||
10 8 -1 0 0 0
|
||||
11 13 -1 0 0 0
|
||||
12 10 -1 0 0 0
|
||||
13 15 -1 0 0 0
|
||||
14 12 -1 0 0 0
|
||||
15 1 -1 0 0 0
|
||||
[NumArcSegments] = 8
|
||||
2 3 180 1 0 0 0 1
|
||||
4 5 180 1 0 0 0 1
|
||||
6 7 180 1 0 0 0 1
|
||||
8 9 180 1 0 0 0 1
|
||||
10 11 180 1 0 0 0 1
|
||||
12 13 180 1 0 0 0 1
|
||||
14 15 180 1 0 0 0 1
|
||||
0 1 180 1 1 0 0 1
|
||||
[NumHoles] = 0
|
||||
[NumBlockLabels] = 8
|
||||
2.6844699109827519 6.2939742651530386 1 -1 0 90 0 1 0
|
||||
2.5645836660620689 6.8222853363234215 2 -1 0 90 0 1 0
|
||||
2.3403449415837985 7.3237684953287836 3 -1 0 90 0 1 0
|
||||
2.0177521810862022 7.7777521810862016 4 -1 0 90 0 1 0
|
||||
1.6069069365792328 8.1649061813516273 5 -1 0 90 0 1 0
|
||||
1.1217138866072494 8.4680568781494578 6 -1 0 90 0 1 0
|
||||
0.57941888346393566 8.6729354353217101 7 -1 0 90 0 1 0
|
||||
0.90000000000000002 5.7999999999999998 8 -1 0 0 0 1 0
|
||||
|
|
@ -30,17 +30,23 @@ if [ ! -f "$TRI" ]; then
|
|||
fi
|
||||
|
||||
echo "Building Rust workspace (release)..."
|
||||
cargo build --release -p femm-app
|
||||
cargo build --release -p femm-app -p femm-mag-solve
|
||||
|
||||
BIN="$ROOT/target/release/femm"
|
||||
SOLVE="$ROOT/target/release/femm-mag-solve"
|
||||
if [ ! -f "$BIN" ]; then
|
||||
echo "ERROR: femm binary not found at $BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$SOLVE" ]; then
|
||||
echo "ERROR: femm-mag-solve binary not found at $SOLVE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$APP"
|
||||
mkdir -p "$MACOS" "$RESOURCES"
|
||||
cp "$BIN" "$MACOS/femm"
|
||||
cp "$SOLVE" "$MACOS/femm-mag-solve"
|
||||
cp "$TRI" "$MACOS/triangle"
|
||||
|
||||
if [ -f "$SVG" ]; then
|
||||
|
|
|
|||
Loading…
Reference in New Issue