added more plotting modes and node angles.
also added a very handy SVG pipeline. you can make fem documents in inkscape and whatnot now. added a few examples. use content-info to give your object a material (the title under accesability that way it serves a secondary purpose) class names: Segment Node Block you can claim a rectangle to be a segment, it will create an enclosed object out of segments for you. you should trim your objects to overlap just a smidge or you get some ghost nodes.
This commit is contained in:
parent
d5e0bc2ef9
commit
a2957d907a
146
assets/femm.svg
146
assets/femm.svg
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 17 KiB |
|
|
@ -18,3 +18,4 @@ meval = "0.2"
|
||||||
tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
|
tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
tiny-skia = "0.11"
|
tiny-skia = "0.11"
|
||||||
|
roxmltree = "0.20"
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ pub enum RenderMode {
|
||||||
pub enum Tool {
|
pub enum Tool {
|
||||||
#[default]
|
#[default]
|
||||||
Select,
|
Select,
|
||||||
|
Edit,
|
||||||
AddNode,
|
AddNode,
|
||||||
AddBlockLabel,
|
AddBlockLabel,
|
||||||
AddSegment,
|
AddSegment,
|
||||||
|
|
@ -92,6 +93,10 @@ pub enum CanvasMessage {
|
||||||
ViewportSize { width: f32, height: f32 },
|
ViewportSize { width: f32, height: f32 },
|
||||||
/// canvas-computed view replacement, used by zoom-window and other bounds-aware operations.
|
/// canvas-computed view replacement, used by zoom-window and other bounds-aware operations.
|
||||||
SetView { pan: Vector, zoom: f32 },
|
SetView { pan: Vector, zoom: f32 },
|
||||||
|
/// translates every selected node and label by a world-coordinate delta.
|
||||||
|
TranslateSelection { dx: f64, dy: f64 },
|
||||||
|
/// double-click at a world point, used to enter rename on the nearest block label.
|
||||||
|
DoubleClickAt { world: (f64, f64), pick_radius_world: f64 },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// pan offset and zoom factor owned by the app shell.
|
/// pan offset and zoom factor owned by the app shell.
|
||||||
|
|
@ -113,6 +118,9 @@ pub struct CanvasState {
|
||||||
cursor_canvas: Option<Point>,
|
cursor_canvas: Option<Point>,
|
||||||
modifiers: iced::keyboard::Modifiers,
|
modifiers: iced::keyboard::Modifiers,
|
||||||
last_reported_size: Option<(f32, f32)>,
|
last_reported_size: Option<(f32, f32)>,
|
||||||
|
edit_drag_last_world: Option<(f64, f64)>,
|
||||||
|
last_click_at: Option<std::time::Instant>,
|
||||||
|
last_click_world: Option<(f64, f64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// constructs the canvas widget for a doc reference, optional mesh overlay, and optional solution.
|
/// constructs the canvas widget for a doc reference, optional mesh overlay, and optional solution.
|
||||||
|
|
@ -178,6 +186,34 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
||||||
if self.tool == Tool::Select || self.zoom_window_active {
|
if self.tool == Tool::Select || self.zoom_window_active {
|
||||||
state.marquee_from = Some(p);
|
state.marquee_from = Some(p);
|
||||||
}
|
}
|
||||||
|
if self.tool == Tool::Edit {
|
||||||
|
let view = ViewTransform::fit(self.doc, bounds, &self.view_state);
|
||||||
|
let world = view.inverse_map(p);
|
||||||
|
let pick_radius_world = (PICK_RADIUS_PX as f64) / view.scale.max(1e-9);
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
let is_double = state.last_click_at
|
||||||
|
.map(|t| now.duration_since(t).as_millis() < 400)
|
||||||
|
.unwrap_or(false)
|
||||||
|
&& state.last_click_world
|
||||||
|
.map(|w| (w.0 - world.0).hypot(w.1 - world.1) < pick_radius_world)
|
||||||
|
.unwrap_or(false);
|
||||||
|
if is_double {
|
||||||
|
state.last_click_at = None;
|
||||||
|
state.last_click_world = None;
|
||||||
|
state.edit_drag_last_world = None;
|
||||||
|
return Some(Action::publish(CanvasMessage::DoubleClickAt {
|
||||||
|
world, pick_radius_world,
|
||||||
|
}).and_capture());
|
||||||
|
}
|
||||||
|
state.last_click_at = Some(now);
|
||||||
|
state.last_click_world = Some(world);
|
||||||
|
state.edit_drag_last_world = Some(world);
|
||||||
|
return Some(Action::publish(CanvasMessage::PickAt {
|
||||||
|
world, pick_radius_world,
|
||||||
|
op: PickOp::Replace,
|
||||||
|
restrict: PickRestrict::Any,
|
||||||
|
}).and_capture());
|
||||||
|
}
|
||||||
return Some(Action::capture());
|
return Some(Action::capture());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -187,6 +223,15 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
||||||
let was_dragged = std::mem::take(&mut state.dragged);
|
let was_dragged = std::mem::take(&mut state.dragged);
|
||||||
let now_opt = cursor.position_in(bounds);
|
let now_opt = cursor.position_in(bounds);
|
||||||
|
|
||||||
|
if self.tool == Tool::Edit {
|
||||||
|
state.edit_drag_last_world = None;
|
||||||
|
if was_dragged {
|
||||||
|
state.last_click_at = None;
|
||||||
|
state.last_click_world = None;
|
||||||
|
}
|
||||||
|
return Some(Action::capture());
|
||||||
|
}
|
||||||
|
|
||||||
if self.zoom_window_active {
|
if self.zoom_window_active {
|
||||||
if let (Some(start), Some(now)) = (marquee_start, now_opt) {
|
if let (Some(start), Some(now)) = (marquee_start, now_opt) {
|
||||||
if was_dragged {
|
if was_dragged {
|
||||||
|
|
@ -289,6 +334,18 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
||||||
state.dragged = true;
|
state.dragged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.tool == Tool::Edit && state.dragged {
|
||||||
|
if let (Some(prev), Some(now)) = (state.edit_drag_last_world, cursor.position_in(bounds)) {
|
||||||
|
let view = ViewTransform::fit(self.doc, bounds, &self.view_state);
|
||||||
|
let cur = view.inverse_map(now);
|
||||||
|
let dx = cur.0 - prev.0;
|
||||||
|
let dy = cur.1 - prev.1;
|
||||||
|
if dx != 0.0 || dy != 0.0 {
|
||||||
|
state.edit_drag_last_world = Some(cur);
|
||||||
|
return Some(Action::publish(CanvasMessage::TranslateSelection { dx, dy }).and_capture());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if state.pending_segment_start.is_some() {
|
if state.pending_segment_start.is_some() {
|
||||||
return Some(Action::request_redraw());
|
return Some(Action::request_redraw());
|
||||||
}
|
}
|
||||||
|
|
@ -525,6 +582,22 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
||||||
for probe in self.probes {
|
for probe in self.probes {
|
||||||
let p = view.map(probe.x, probe.y);
|
let p = view.map(probe.x, probe.y);
|
||||||
let color = Color::from_rgba8(probe.color[0], probe.color[1], probe.color[2], probe.color[3] as f32 / 255.0);
|
let color = Color::from_rgba8(probe.color[0], probe.color[1], probe.color[2], probe.color[3] as f32 / 255.0);
|
||||||
|
let theta = probe.angle_deg.to_radians() as f32;
|
||||||
|
let pri_len = 18.0_f32;
|
||||||
|
let per_len = 10.0_f32;
|
||||||
|
let pri_end = Point::new(
|
||||||
|
p.x + theta.cos() * pri_len,
|
||||||
|
p.y - theta.sin() * pri_len,
|
||||||
|
);
|
||||||
|
let per_end = Point::new(
|
||||||
|
p.x + (theta - std::f32::consts::FRAC_PI_2).cos() * per_len,
|
||||||
|
p.y - (theta - std::f32::consts::FRAC_PI_2).sin() * per_len,
|
||||||
|
);
|
||||||
|
frame.stroke(&Path::line(p, pri_end),
|
||||||
|
Stroke::default().with_width(2.0).with_color(color));
|
||||||
|
frame.stroke(&Path::line(p, per_end),
|
||||||
|
Stroke::default().with_width(1.0)
|
||||||
|
.with_color(Color { a: 0.55, ..color }));
|
||||||
frame.fill(&Path::circle(p, 6.0), color);
|
frame.fill(&Path::circle(p, 6.0), color);
|
||||||
frame.stroke(&Path::circle(p, 6.0),
|
frame.stroke(&Path::circle(p, 6.0),
|
||||||
Stroke::default().with_width(1.5).with_color(Color::WHITE));
|
Stroke::default().with_width(1.5).with_color(Color::WHITE));
|
||||||
|
|
@ -567,6 +640,7 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
||||||
if cursor.position_in(bounds).is_some() {
|
if cursor.position_in(bounds).is_some() {
|
||||||
return match self.tool {
|
return match self.tool {
|
||||||
Tool::Select => mouse::Interaction::Grab,
|
Tool::Select => mouse::Interaction::Grab,
|
||||||
|
Tool::Edit => mouse::Interaction::Pointer,
|
||||||
Tool::AddNode | Tool::AddBlockLabel | Tool::AddSegment | Tool::AddProbe => {
|
Tool::AddNode | Tool::AddBlockLabel | Tool::AddSegment | Tool::AddProbe => {
|
||||||
mouse::Interaction::Crosshair
|
mouse::Interaction::Crosshair
|
||||||
}
|
}
|
||||||
|
|
@ -776,6 +850,6 @@ fn arc_geometry(
|
||||||
let cy = mid_y + sign * perp_y * h;
|
let cy = mid_y + sign * perp_y * h;
|
||||||
|
|
||||||
let a0 = (-(y0 - cy)).atan2(x0 - cx);
|
let a0 = (-(y0 - cy)).atan2(x0 - cx);
|
||||||
let a1 = (-(y1 - cy)).atan2(x1 - cx);
|
let a1 = a0 - theta;
|
||||||
Some(((cx, cy), radius, Radians(a0 as f32), Radians(a1 as f32)))
|
Some(((cx, cy), radius, Radians(a0 as f32), Radians(a1 as f32)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -384,6 +384,29 @@ fn render_frame(
|
||||||
|
|
||||||
for probe in probes {
|
for probe in probes {
|
||||||
let (px, py) = view.map(probe.x, probe.y);
|
let (px, py) = view.map(probe.x, probe.y);
|
||||||
|
let theta = probe.angle_deg.to_radians() as f32;
|
||||||
|
let pri_len = 22.0_f32;
|
||||||
|
let per_len = 12.0_f32;
|
||||||
|
let pri_ex = px + theta.cos() * pri_len;
|
||||||
|
let pri_ey = py - theta.sin() * pri_len;
|
||||||
|
let per_ex = px + (theta - std::f32::consts::FRAC_PI_2).cos() * per_len;
|
||||||
|
let per_ey = py - (theta - std::f32::consts::FRAC_PI_2).sin() * per_len;
|
||||||
|
paint.set_color(Color::from_rgba8(probe.color[0], probe.color[1], probe.color[2], 255));
|
||||||
|
let pri_stroke = Stroke { width: 2.2, ..Default::default() };
|
||||||
|
let mut pb = PathBuilder::new();
|
||||||
|
pb.move_to(px, py);
|
||||||
|
pb.line_to(pri_ex, pri_ey);
|
||||||
|
if let Some(path) = pb.finish() {
|
||||||
|
pixmap.stroke_path(&path, &paint, &pri_stroke, Transform::identity(), None);
|
||||||
|
}
|
||||||
|
paint.set_color(Color::from_rgba8(probe.color[0], probe.color[1], probe.color[2], 140));
|
||||||
|
let per_stroke = Stroke { width: 1.2, ..Default::default() };
|
||||||
|
let mut pb = PathBuilder::new();
|
||||||
|
pb.move_to(px, py);
|
||||||
|
pb.line_to(per_ex, per_ey);
|
||||||
|
if let Some(path) = pb.finish() {
|
||||||
|
pixmap.stroke_path(&path, &paint, &per_stroke, Transform::identity(), None);
|
||||||
|
}
|
||||||
paint.set_color(Color::from_rgba8(probe.color[0], probe.color[1], probe.color[2], 255));
|
paint.set_color(Color::from_rgba8(probe.color[0], probe.color[1], probe.color[2], 255));
|
||||||
let mut pb = PathBuilder::new();
|
let mut pb = PathBuilder::new();
|
||||||
pb.push_circle(px, py, 6.0);
|
pb.push_circle(px, py, 6.0);
|
||||||
|
|
@ -460,7 +483,7 @@ fn draw_plot_strip(
|
||||||
let mut points: Vec<(i64, f64)> = Vec::new();
|
let mut points: Vec<(i64, f64)> = Vec::new();
|
||||||
if let Some(s) = samples {
|
if let Some(s) = samples {
|
||||||
for (&fi, &(bx, by)) in s {
|
for (&fi, &(bx, by)) in s {
|
||||||
points.push((fi, probe.mode.extract(bx, by)));
|
points.push((fi, probe.mode.extract(bx, by, probe.angle_deg.to_radians())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if points.is_empty() {
|
if points.is_empty() {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ mod probe;
|
||||||
mod session;
|
mod session;
|
||||||
mod sim_meta;
|
mod sim_meta;
|
||||||
mod spice;
|
mod spice;
|
||||||
|
mod svg_io;
|
||||||
|
|
||||||
use doc_canvas::{CanvasMessage, PickOp, PickRestrict, RenderMode, Tool, ViewState};
|
use doc_canvas::{CanvasMessage, PickOp, PickRestrict, RenderMode, Tool, ViewState};
|
||||||
use femm_doc_mag::{ArcSegment, BlockLabel, FemmDoc, Node, Segment};
|
use femm_doc_mag::{ArcSegment, BlockLabel, FemmDoc, Node, Segment};
|
||||||
|
|
@ -41,6 +42,8 @@ const DEMO_FEM: &str = include_str!("../assets/brgmodel.fem");
|
||||||
const ADD_TOLERANCE: f64 = 0.5;
|
const ADD_TOLERANCE: f64 = 0.5;
|
||||||
const MIN_ANGLE_DEG: f64 = 30.0;
|
const MIN_ANGLE_DEG: f64 = 30.0;
|
||||||
|
|
||||||
|
const LABEL_MATERIAL_INPUT_ID: &str = "label-material-input";
|
||||||
|
|
||||||
const SIM_DEFAULT_DT_S: f64 = 1.0e-4;
|
const SIM_DEFAULT_DT_S: f64 = 1.0e-4;
|
||||||
const SIM_DEFAULT_INTERVAL_S: f64 = 0.05;
|
const SIM_DEFAULT_INTERVAL_S: f64 = 0.05;
|
||||||
const SIM_DEFAULT_SUBDIVISIONS: usize = 20;
|
const SIM_DEFAULT_SUBDIVISIONS: usize = 20;
|
||||||
|
|
@ -101,6 +104,17 @@ enum Message {
|
||||||
ProbeSetMode(usize, probe::ProbeMode),
|
ProbeSetMode(usize, probe::ProbeMode),
|
||||||
ProbeRemove(usize),
|
ProbeRemove(usize),
|
||||||
ProbeExportWav(usize),
|
ProbeExportWav(usize),
|
||||||
|
ProbeRotateBy(usize, f64),
|
||||||
|
ProbeSetAngleText(usize, String),
|
||||||
|
ProbeSubmitAngle(usize),
|
||||||
|
ImportSvg,
|
||||||
|
ExportSvg,
|
||||||
|
LabelSetMaterial(usize, String),
|
||||||
|
LabelSetCircuit(usize, String),
|
||||||
|
LabelSetGroupText(usize, String),
|
||||||
|
LabelSetMaxAreaText(usize, String),
|
||||||
|
LabelSetMagDirText(usize, String),
|
||||||
|
LabelSetTurnsText(usize, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// copyable diagnostic shown when a pipeline stage fails.
|
/// copyable diagnostic shown when a pipeline stage fails.
|
||||||
|
|
@ -248,8 +262,8 @@ impl App {
|
||||||
let mut sim = new_simulation(base_doc, String::new());
|
let mut sim = new_simulation(base_doc, String::new());
|
||||||
if let Some(secs) = spice::parse_spice(&meta.dt_text) { sim.dt = secs; }
|
if let Some(secs) = spice::parse_spice(&meta.dt_text) { sim.dt = secs; }
|
||||||
sim.dt_text = meta.dt_text.clone();
|
sim.dt_text = meta.dt_text.clone();
|
||||||
if let Some(secs) = spice::parse_spice(&meta.interval_text) {
|
if let Some(d) = sim_meta::parse_interval(&meta.interval_text) {
|
||||||
sim.wall_interval = Duration::from_secs_f64(secs);
|
sim.wall_interval = d;
|
||||||
}
|
}
|
||||||
sim.interval_text = meta.interval_text.clone();
|
sim.interval_text = meta.interval_text.clone();
|
||||||
sim.subdivisions = meta.subdivisions;
|
sim.subdivisions = meta.subdivisions;
|
||||||
|
|
@ -314,6 +328,51 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::ImportSvg => {
|
||||||
|
let picked = rfd::FileDialog::new()
|
||||||
|
.add_filter("SVG", &["svg"])
|
||||||
|
.pick_file();
|
||||||
|
let Some(path) = picked else { return Task::none(); };
|
||||||
|
match svg_io::import_svg(&path) {
|
||||||
|
Ok(doc) => {
|
||||||
|
self.doc = doc;
|
||||||
|
self.mesh = None;
|
||||||
|
self.solution = None;
|
||||||
|
self.pristine_doc = None;
|
||||||
|
self.simulation = None;
|
||||||
|
self.source_label = path.file_name().and_then(|s| s.to_str()).unwrap_or("?").to_string();
|
||||||
|
self.source_path = Some(path.clone());
|
||||||
|
self.status = format!(
|
||||||
|
"imported {}: {} nodes, {} segs, {} arcs, {} labels",
|
||||||
|
path.display(),
|
||||||
|
self.doc.nodes.len(),
|
||||||
|
self.doc.segments.len(),
|
||||||
|
self.doc.arcs.len(),
|
||||||
|
self.doc.block_labels.len(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.error = Some(ErrorReport {
|
||||||
|
title: String::from("svg import failed"),
|
||||||
|
body: format!("{path:?}\n\n{e}"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::ExportSvg => {
|
||||||
|
let picked = rfd::FileDialog::new()
|
||||||
|
.add_filter("SVG", &["svg"])
|
||||||
|
.set_file_name("doc.svg")
|
||||||
|
.save_file();
|
||||||
|
let Some(path) = picked else { return Task::none(); };
|
||||||
|
match svg_io::export_svg(&self.doc, &path) {
|
||||||
|
Ok(()) => self.status = format!("exported svg: {}", path.display()),
|
||||||
|
Err(e) => self.error = Some(ErrorReport {
|
||||||
|
title: String::from("svg export failed"),
|
||||||
|
body: format!("{path:?}\n\n{e}"),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::NewDoc => {
|
Message::NewDoc => {
|
||||||
self.doc = FemmDoc::default();
|
self.doc = FemmDoc::default();
|
||||||
self.mesh = None;
|
self.mesh = None;
|
||||||
|
|
@ -428,9 +487,15 @@ impl App {
|
||||||
let outcome = catch_unwind(AssertUnwindSafe(|| run_mesh(&self.doc)));
|
let outcome = catch_unwind(AssertUnwindSafe(|| run_mesh(&self.doc)));
|
||||||
match outcome {
|
match outcome {
|
||||||
Ok(Ok(m)) => {
|
Ok(Ok(m)) => {
|
||||||
self.status = format!("meshed: {} nodes, {} elements", m.nodes.len(), m.elements.len());
|
let (final_mesh, total_added) = autolabel_loop(&mut self.doc, m, 5);
|
||||||
|
self.status = if total_added > 0 {
|
||||||
|
format!("meshed: {} nodes, {} elements, +{} Air label(s) for orphan regions",
|
||||||
|
final_mesh.nodes.len(), final_mesh.elements.len(), total_added)
|
||||||
|
} else {
|
||||||
|
format!("meshed: {} nodes, {} elements", final_mesh.nodes.len(), final_mesh.elements.len())
|
||||||
|
};
|
||||||
self.error = None;
|
self.error = None;
|
||||||
self.mesh = Some(m);
|
self.mesh = Some(final_mesh);
|
||||||
}
|
}
|
||||||
Ok(Err(report)) => {
|
Ok(Err(report)) => {
|
||||||
self.mesh = None;
|
self.mesh = None;
|
||||||
|
|
@ -454,7 +519,8 @@ impl App {
|
||||||
let mesh_outcome = catch_unwind(AssertUnwindSafe(|| run_mesh(&self.doc)));
|
let mesh_outcome = catch_unwind(AssertUnwindSafe(|| run_mesh(&self.doc)));
|
||||||
match mesh_outcome {
|
match mesh_outcome {
|
||||||
Ok(Ok(m)) => {
|
Ok(Ok(m)) => {
|
||||||
self.mesh = Some(m);
|
let (final_mesh, _added) = autolabel_loop(&mut self.doc, m, 5);
|
||||||
|
self.mesh = Some(final_mesh);
|
||||||
self.error = None;
|
self.error = None;
|
||||||
}
|
}
|
||||||
Ok(Err(report)) => {
|
Ok(Err(report)) => {
|
||||||
|
|
@ -533,6 +599,8 @@ impl App {
|
||||||
label: format!("p{}", idx + 1),
|
label: format!("p{}", idx + 1),
|
||||||
color: probe::palette_color(idx),
|
color: probe::palette_color(idx),
|
||||||
mode: probe::ProbeMode::default(),
|
mode: probe::ProbeMode::default(),
|
||||||
|
angle_deg: 0.0,
|
||||||
|
angle_text: String::from("0"),
|
||||||
};
|
};
|
||||||
let mut samples = std::collections::HashMap::new();
|
let mut samples = std::collections::HashMap::new();
|
||||||
for (&fi, sol) in &sim.lut {
|
for (&fi, sol) in &sim.lut {
|
||||||
|
|
@ -544,7 +612,7 @@ impl App {
|
||||||
sim.probe_samples.push(samples);
|
sim.probe_samples.push(samples);
|
||||||
self.status = format!("probe p{} placed at ({:.3}, {:.3})", idx + 1, world.0, world.1);
|
self.status = format!("probe p{} placed at ({:.3}, {:.3})", idx + 1, world.0, world.1);
|
||||||
}
|
}
|
||||||
Tool::Select | Tool::AddSegment => {}
|
Tool::Select | Tool::Edit | Tool::AddSegment => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Canvas(CanvasMessage::SegmentBetween { from, to }) => {
|
Message::Canvas(CanvasMessage::SegmentBetween { from, to }) => {
|
||||||
|
|
@ -588,6 +656,28 @@ impl App {
|
||||||
let summary = apply_pick_rect(&mut self.doc, p0, p1, op, restrict);
|
let summary = apply_pick_rect(&mut self.doc, p0, p1, op, restrict);
|
||||||
self.status = summary;
|
self.status = summary;
|
||||||
}
|
}
|
||||||
|
Message::Canvas(CanvasMessage::TranslateSelection { dx, dy }) => {
|
||||||
|
let moved = translate_selection(&mut self.doc, dx, dy);
|
||||||
|
if moved > 0 {
|
||||||
|
self.mesh = None;
|
||||||
|
self.solution = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::Canvas(CanvasMessage::DoubleClickAt { world, pick_radius_world }) => {
|
||||||
|
let mut best: Option<(usize, f64)> = None;
|
||||||
|
for (i, b) in self.doc.block_labels.iter().enumerate() {
|
||||||
|
let d = (b.x - world.0).hypot(b.y - world.1);
|
||||||
|
if d <= pick_radius_world && best.map(|(_, bd)| d < bd).unwrap_or(true) {
|
||||||
|
best = Some((i, d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((idx, _)) = best {
|
||||||
|
clear_selection(&mut self.doc);
|
||||||
|
self.doc.block_labels[idx].selected = true;
|
||||||
|
self.status = format!("editing label {idx}");
|
||||||
|
return iced::widget::operation::focus(iced::widget::Id::new(LABEL_MATERIAL_INPUT_ID));
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::SetRenderMode(mode) => {
|
Message::SetRenderMode(mode) => {
|
||||||
self.render_mode = mode;
|
self.render_mode = mode;
|
||||||
}
|
}
|
||||||
|
|
@ -748,16 +838,16 @@ impl App {
|
||||||
}
|
}
|
||||||
Message::SimSubmitInterval => {
|
Message::SimSubmitInterval => {
|
||||||
if let Some(sim) = self.simulation.as_mut() {
|
if let Some(sim) = self.simulation.as_mut() {
|
||||||
match spice::parse_spice(&sim.interval_text) {
|
match sim_meta::parse_interval(&sim.interval_text) {
|
||||||
Some(secs) if secs > 0.0 => {
|
Some(d) => {
|
||||||
sim.wall_interval = Duration::from_secs_f64(secs);
|
sim.wall_interval = d;
|
||||||
sim.interval_text = spice::format_spice_time(secs);
|
sim.interval_text = spice::format_spice_time(d.as_secs_f64());
|
||||||
self.status = format!("wall interval = {}", sim.interval_text);
|
self.status = format!("wall interval = {}", sim.interval_text);
|
||||||
}
|
}
|
||||||
_ => {
|
None => {
|
||||||
self.error = Some(ErrorReport {
|
self.error = Some(ErrorReport {
|
||||||
title: String::from("interval parse failed"),
|
title: String::from("interval parse failed"),
|
||||||
body: format!("could not read {:?} as SPICE time", sim.interval_text),
|
body: format!("could not read {:?} as positive SPICE time", sim.interval_text),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1069,8 +1159,8 @@ impl App {
|
||||||
let mut sim = new_simulation(self.doc.clone(), String::new());
|
let mut sim = new_simulation(self.doc.clone(), String::new());
|
||||||
if let Some(secs) = spice::parse_spice(&meta.dt_text) { sim.dt = secs; }
|
if let Some(secs) = spice::parse_spice(&meta.dt_text) { sim.dt = secs; }
|
||||||
sim.dt_text = meta.dt_text.clone();
|
sim.dt_text = meta.dt_text.clone();
|
||||||
if let Some(secs) = spice::parse_spice(&meta.interval_text) {
|
if let Some(d) = sim_meta::parse_interval(&meta.interval_text) {
|
||||||
sim.wall_interval = Duration::from_secs_f64(secs);
|
sim.wall_interval = d;
|
||||||
}
|
}
|
||||||
sim.interval_text = meta.interval_text.clone();
|
sim.interval_text = meta.interval_text.clone();
|
||||||
sim.subdivisions = meta.subdivisions;
|
sim.subdivisions = meta.subdivisions;
|
||||||
|
|
@ -1215,6 +1305,36 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Message::ProbeRotateBy(i, delta_deg) => {
|
||||||
|
if let Some(sim) = self.simulation.as_mut() {
|
||||||
|
if let Some(p) = sim.probes.get_mut(i) {
|
||||||
|
p.angle_deg = (p.angle_deg + delta_deg).rem_euclid(360.0);
|
||||||
|
p.angle_text = format!("{:.1}", p.angle_deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::ProbeSetAngleText(i, v) => {
|
||||||
|
if let Some(sim) = self.simulation.as_mut() {
|
||||||
|
if let Some(p) = sim.probes.get_mut(i) {
|
||||||
|
p.angle_text = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::ProbeSubmitAngle(i) => {
|
||||||
|
if let Some(sim) = self.simulation.as_mut() {
|
||||||
|
if let Some(p) = sim.probes.get_mut(i) {
|
||||||
|
match p.angle_text.trim().parse::<f64>() {
|
||||||
|
Ok(deg) => {
|
||||||
|
p.angle_deg = deg.rem_euclid(360.0);
|
||||||
|
p.angle_text = format!("{:.1}", p.angle_deg);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
p.angle_text = format!("{:.1}", p.angle_deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::ProbeRemove(i) => {
|
Message::ProbeRemove(i) => {
|
||||||
if let Some(sim) = self.simulation.as_mut() {
|
if let Some(sim) = self.simulation.as_mut() {
|
||||||
if i < sim.probes.len() {
|
if i < sim.probes.len() {
|
||||||
|
|
@ -1237,10 +1357,13 @@ impl App {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
let default_name = format!("{}_{}.wav", p.label, match p.mode {
|
let default_name = format!("{}_{}.wav", p.label, match p.mode {
|
||||||
probe::ProbeMode::Magnitude => "magB",
|
probe::ProbeMode::Magnitude => "magB",
|
||||||
probe::ProbeMode::Bx => "Bx",
|
probe::ProbeMode::Bx => "Bx",
|
||||||
probe::ProbeMode::By => "By",
|
probe::ProbeMode::By => "By",
|
||||||
probe::ProbeMode::Angle => "ang",
|
probe::ProbeMode::Angle => "ang",
|
||||||
|
probe::ProbeMode::Primary => "Pri",
|
||||||
|
probe::ProbeMode::Perpendicular => "Per",
|
||||||
|
probe::ProbeMode::Differential => "Diff",
|
||||||
});
|
});
|
||||||
let picked = rfd::FileDialog::new()
|
let picked = rfd::FileDialog::new()
|
||||||
.add_filter("WAV audio", &["wav"])
|
.add_filter("WAV audio", &["wav"])
|
||||||
|
|
@ -1248,7 +1371,7 @@ impl App {
|
||||||
.save_file();
|
.save_file();
|
||||||
let Some(path) = picked else { return Task::none(); };
|
let Some(path) = picked else { return Task::none(); };
|
||||||
let mut ordered: Vec<(i64, f64)> = samples.iter()
|
let mut ordered: Vec<(i64, f64)> = samples.iter()
|
||||||
.map(|(&fi, &(bx, by))| (fi, p.mode.extract(bx, by)))
|
.map(|(&fi, &(bx, by))| (fi, p.mode.extract(bx, by, p.angle_deg.to_radians())))
|
||||||
.collect();
|
.collect();
|
||||||
ordered.sort_by_key(|s| s.0);
|
ordered.sort_by_key(|s| s.0);
|
||||||
let values: Vec<f64> = ordered.into_iter().map(|(_, v)| v).collect();
|
let values: Vec<f64> = ordered.into_iter().map(|(_, v)| v).collect();
|
||||||
|
|
@ -1287,6 +1410,51 @@ impl App {
|
||||||
format!("deleted: {n} nodes, {s} segments, {a} arcs, {b} labels")
|
format!("deleted: {n} nodes, {s} segments, {a} arcs, {b} labels")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Message::LabelSetMaterial(i, v) => {
|
||||||
|
if let Some(b) = self.doc.block_labels.get_mut(i) {
|
||||||
|
b.block_type = v;
|
||||||
|
self.solution = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::LabelSetCircuit(i, v) => {
|
||||||
|
if let Some(b) = self.doc.block_labels.get_mut(i) {
|
||||||
|
b.in_circuit = v;
|
||||||
|
self.solution = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::LabelSetGroupText(i, v) => {
|
||||||
|
if let Some(b) = self.doc.block_labels.get_mut(i) {
|
||||||
|
if let Ok(g) = v.trim().parse::<i32>() {
|
||||||
|
b.in_group = g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::LabelSetMaxAreaText(i, v) => {
|
||||||
|
if let Some(b) = self.doc.block_labels.get_mut(i) {
|
||||||
|
if let Ok(a) = v.trim().parse::<f64>() {
|
||||||
|
b.max_area = a.max(0.0);
|
||||||
|
self.mesh = None;
|
||||||
|
self.solution = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::LabelSetMagDirText(i, v) => {
|
||||||
|
if let Some(b) = self.doc.block_labels.get_mut(i) {
|
||||||
|
if let Ok(d) = v.trim().parse::<f64>() {
|
||||||
|
b.mag_dir = d;
|
||||||
|
b.mag_dir_fctn.clear();
|
||||||
|
self.solution = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::LabelSetTurnsText(i, v) => {
|
||||||
|
if let Some(b) = self.doc.block_labels.get_mut(i) {
|
||||||
|
if let Ok(t) = v.trim().parse::<i32>() {
|
||||||
|
b.turns = t;
|
||||||
|
self.solution = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -1306,10 +1474,13 @@ impl App {
|
||||||
icon_button(ICON_FILE_OPEN, "Open .fem or .femsess", Some(Message::OpenFem)),
|
icon_button(ICON_FILE_OPEN, "Open .fem or .femsess", Some(Message::OpenFem)),
|
||||||
icon_button(ICON_FILE_SAVE, "Save", Some(Message::SaveDoc)),
|
icon_button(ICON_FILE_SAVE, "Save", Some(Message::SaveDoc)),
|
||||||
icon_button(ICON_FILE_SAVE_AS, "Save As", Some(Message::SaveDocAs)),
|
icon_button(ICON_FILE_SAVE_AS, "Save As", Some(Message::SaveDocAs)),
|
||||||
|
text_button("Import SVG", Message::ImportSvg),
|
||||||
|
text_button("Export SVG", Message::ExportSvg),
|
||||||
].spacing(2);
|
].spacing(2);
|
||||||
|
|
||||||
let tool_group = row![
|
let tool_group = row![
|
||||||
tool_icon_button(ICON_TOOL_SELECT, "Select", Tool::Select, self.tool),
|
tool_icon_button(ICON_TOOL_SELECT, "Select", Tool::Select, self.tool),
|
||||||
|
edit_tool_button(self.tool),
|
||||||
tool_icon_button(ICON_TOOL_NODE, "Add Node", Tool::AddNode, self.tool),
|
tool_icon_button(ICON_TOOL_NODE, "Add Node", Tool::AddNode, self.tool),
|
||||||
tool_icon_button(ICON_TOOL_SEGMENT, "Add Segment", Tool::AddSegment, self.tool),
|
tool_icon_button(ICON_TOOL_SEGMENT, "Add Segment", Tool::AddSegment, self.tool),
|
||||||
tool_icon_button(ICON_TOOL_LABEL, "Add Label", Tool::AddBlockLabel, self.tool),
|
tool_icon_button(ICON_TOOL_LABEL, "Add Label", Tool::AddBlockLabel, self.tool),
|
||||||
|
|
@ -1399,6 +1570,20 @@ impl App {
|
||||||
if let Some(sim) = &self.simulation {
|
if let Some(sim) = &self.simulation {
|
||||||
body = body.push(simulation_panel(sim));
|
body = body.push(simulation_panel(sim));
|
||||||
}
|
}
|
||||||
|
let selected_labels: Vec<usize> = self.doc.block_labels.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, b)| b.selected)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.collect();
|
||||||
|
if let Some(&idx) = selected_labels.first() {
|
||||||
|
if selected_labels.len() == 1 {
|
||||||
|
body = body.push(label_edit_panel(idx, &self.doc.block_labels[idx]));
|
||||||
|
} else {
|
||||||
|
body = body.push(
|
||||||
|
text(format!("{} labels selected — select one to edit", selected_labels.len())).size(11),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
body = body.push(canvas_row);
|
body = body.push(canvas_row);
|
||||||
if let Some(sim) = &self.simulation {
|
if let Some(sim) = &self.simulation {
|
||||||
if !sim.probes.is_empty() {
|
if !sim.probes.is_empty() {
|
||||||
|
|
@ -1407,8 +1592,17 @@ impl App {
|
||||||
let mut pane_row = row![].spacing(8);
|
let mut pane_row = row![].spacing(8);
|
||||||
for (i, p) in sim.probes.iter().enumerate() {
|
for (i, p) in sim.probes.iter().enumerate() {
|
||||||
let samples = sim.probe_samples.get(i).unwrap_or(&*EMPTY_MAP);
|
let samples = sim.probe_samples.get(i).unwrap_or(&*EMPTY_MAP);
|
||||||
|
let angle_input = text_input("0", &p.angle_text)
|
||||||
|
.on_input(move |v| Message::ProbeSetAngleText(i, v))
|
||||||
|
.on_submit(Message::ProbeSubmitAngle(i))
|
||||||
|
.size(10)
|
||||||
|
.width(Length::Fixed(40.0));
|
||||||
let mode_row = row![
|
let mode_row = row![
|
||||||
button(text(p.label.clone()).size(11)).style(button::secondary),
|
button(text(p.label.clone()).size(11)).style(button::secondary),
|
||||||
|
button(text("<").size(10)).on_press(Message::ProbeRotateBy(i, -15.0)).style(button::secondary),
|
||||||
|
angle_input,
|
||||||
|
text("deg").size(10),
|
||||||
|
button(text(">").size(10)).on_press(Message::ProbeRotateBy(i, 15.0)).style(button::secondary),
|
||||||
button(text("|B|").size(10)).on_press(Message::ProbeSetMode(i, probe::ProbeMode::Magnitude))
|
button(text("|B|").size(10)).on_press(Message::ProbeSetMode(i, probe::ProbeMode::Magnitude))
|
||||||
.style(if p.mode == probe::ProbeMode::Magnitude { button::primary } else { button::secondary }),
|
.style(if p.mode == probe::ProbeMode::Magnitude { button::primary } else { button::secondary }),
|
||||||
button(text("Bx").size(10)).on_press(Message::ProbeSetMode(i, probe::ProbeMode::Bx))
|
button(text("Bx").size(10)).on_press(Message::ProbeSetMode(i, probe::ProbeMode::Bx))
|
||||||
|
|
@ -1417,6 +1611,12 @@ impl App {
|
||||||
.style(if p.mode == probe::ProbeMode::By { button::primary } else { button::secondary }),
|
.style(if p.mode == probe::ProbeMode::By { button::primary } else { button::secondary }),
|
||||||
button(text("ang").size(10)).on_press(Message::ProbeSetMode(i, probe::ProbeMode::Angle))
|
button(text("ang").size(10)).on_press(Message::ProbeSetMode(i, probe::ProbeMode::Angle))
|
||||||
.style(if p.mode == probe::ProbeMode::Angle { button::primary } else { button::secondary }),
|
.style(if p.mode == probe::ProbeMode::Angle { button::primary } else { button::secondary }),
|
||||||
|
button(text("Pri").size(10)).on_press(Message::ProbeSetMode(i, probe::ProbeMode::Primary))
|
||||||
|
.style(if p.mode == probe::ProbeMode::Primary { button::primary } else { button::secondary }),
|
||||||
|
button(text("Per").size(10)).on_press(Message::ProbeSetMode(i, probe::ProbeMode::Perpendicular))
|
||||||
|
.style(if p.mode == probe::ProbeMode::Perpendicular { button::primary } else { button::secondary }),
|
||||||
|
button(text("Diff").size(10)).on_press(Message::ProbeSetMode(i, probe::ProbeMode::Differential))
|
||||||
|
.style(if p.mode == probe::ProbeMode::Differential { button::primary } else { button::secondary }),
|
||||||
button(text("wav").size(10)).on_press(Message::ProbeExportWav(i)).style(button::secondary),
|
button(text("wav").size(10)).on_press(Message::ProbeExportWav(i)).style(button::secondary),
|
||||||
button(text("x").size(10)).on_press(Message::ProbeRemove(i)).style(button::danger),
|
button(text("x").size(10)).on_press(Message::ProbeRemove(i)).style(button::danger),
|
||||||
].spacing(2);
|
].spacing(2);
|
||||||
|
|
@ -1845,6 +2045,93 @@ fn simulation_panel(sim: &Simulation) -> Element<'_, Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// renders a red-bordered error panel with copy and dismiss controls above the canvas.
|
/// renders a red-bordered error panel with copy and dismiss controls above the canvas.
|
||||||
|
/// inline editor for a single selected block label: material, circuit, group, max area, mag direction, turns.
|
||||||
|
fn label_edit_panel(idx: usize, b: &BlockLabel) -> Element<'_, Message> {
|
||||||
|
let header = text(format!("block label #{idx} at ({:.3}, {:.3})", b.x, b.y))
|
||||||
|
.size(12)
|
||||||
|
.color(Color::from_rgb(0.10, 0.15, 0.30));
|
||||||
|
|
||||||
|
let style = |_theme: &Theme, status: iced::widget::text_input::Status| {
|
||||||
|
let border = match status {
|
||||||
|
iced::widget::text_input::Status::Focused { .. } => Border {
|
||||||
|
color: Color::from_rgb(0.30, 0.45, 0.85),
|
||||||
|
width: 1.4,
|
||||||
|
radius: 3.0.into(),
|
||||||
|
},
|
||||||
|
_ => Border {
|
||||||
|
color: Color::from_rgb(0.55, 0.65, 0.85),
|
||||||
|
width: 1.0,
|
||||||
|
radius: 3.0.into(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
iced::widget::text_input::Style {
|
||||||
|
background: Background::Color(Color::WHITE),
|
||||||
|
border,
|
||||||
|
icon: Color::from_rgb(0.30, 0.35, 0.45),
|
||||||
|
placeholder: Color::from_rgb(0.60, 0.60, 0.65),
|
||||||
|
value: Color::from_rgb(0.05, 0.10, 0.15),
|
||||||
|
selection: Color::from_rgba(0.30, 0.45, 0.85, 0.35),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let lbl = |s: &'static str| text(s).size(11).color(Color::from_rgb(0.15, 0.20, 0.35));
|
||||||
|
|
||||||
|
let material = text_input("material", &b.block_type)
|
||||||
|
.id(iced::widget::Id::new(LABEL_MATERIAL_INPUT_ID))
|
||||||
|
.on_input(move |v| Message::LabelSetMaterial(idx, v))
|
||||||
|
.size(12)
|
||||||
|
.style(style)
|
||||||
|
.width(Length::Fixed(160.0));
|
||||||
|
let circuit = text_input("<None>", &b.in_circuit)
|
||||||
|
.on_input(move |v| Message::LabelSetCircuit(idx, v))
|
||||||
|
.size(12)
|
||||||
|
.style(style)
|
||||||
|
.width(Length::Fixed(120.0));
|
||||||
|
let group = text_input("0", &b.in_group.to_string())
|
||||||
|
.on_input(move |v| Message::LabelSetGroupText(idx, v))
|
||||||
|
.size(12)
|
||||||
|
.style(style)
|
||||||
|
.width(Length::Fixed(60.0));
|
||||||
|
let max_area = text_input("0", &format!("{}", b.max_area))
|
||||||
|
.on_input(move |v| Message::LabelSetMaxAreaText(idx, v))
|
||||||
|
.size(12)
|
||||||
|
.style(style)
|
||||||
|
.width(Length::Fixed(80.0));
|
||||||
|
let mag_dir = text_input("0", &format!("{}", b.mag_dir))
|
||||||
|
.on_input(move |v| Message::LabelSetMagDirText(idx, v))
|
||||||
|
.size(12)
|
||||||
|
.style(style)
|
||||||
|
.width(Length::Fixed(70.0));
|
||||||
|
let turns = text_input("1", &b.turns.to_string())
|
||||||
|
.on_input(move |v| Message::LabelSetTurnsText(idx, v))
|
||||||
|
.size(12)
|
||||||
|
.style(style)
|
||||||
|
.width(Length::Fixed(50.0));
|
||||||
|
|
||||||
|
let body_row = row![
|
||||||
|
lbl("Material:"), material,
|
||||||
|
lbl("Circuit:"), circuit,
|
||||||
|
lbl("Group:"), group,
|
||||||
|
lbl("MaxArea:"), max_area,
|
||||||
|
lbl("MagDir:"), mag_dir,
|
||||||
|
lbl("Turns:"), turns,
|
||||||
|
]
|
||||||
|
.spacing(6)
|
||||||
|
.align_y(Alignment::Center);
|
||||||
|
|
||||||
|
container(column![header, body_row].spacing(4).padding(8))
|
||||||
|
.style(|_t: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(Color::from_rgb(0.96, 0.98, 1.0))),
|
||||||
|
border: Border {
|
||||||
|
color: Color::from_rgb(0.55, 0.65, 0.85),
|
||||||
|
width: 1.0,
|
||||||
|
radius: 4.0.into(),
|
||||||
|
},
|
||||||
|
..container::Style::default()
|
||||||
|
})
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn error_panel(report: &ErrorReport) -> Element<'_, Message> {
|
fn error_panel(report: &ErrorReport) -> Element<'_, Message> {
|
||||||
let header = row![
|
let header = row![
|
||||||
text(format!("error: {}", report.title))
|
text(format!("error: {}", report.title))
|
||||||
|
|
@ -2225,6 +2512,149 @@ fn selection_bbox(doc: &FemmDoc) -> Option<(f64, f64, f64, f64)> {
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum Kind { Node, Segment, Arc, Label }
|
enum Kind { Node, Segment, Arc, Label }
|
||||||
|
|
||||||
|
/// ensures the doc has a MaterialProp with the given name, appending a unit-mu Air-like definition when absent.
|
||||||
|
fn ensure_material(doc: &mut FemmDoc, name: &str) {
|
||||||
|
if doc.materials.iter().any(|m| m.name == name) { return; }
|
||||||
|
doc.materials.push(femm_doc_mag::MaterialProp {
|
||||||
|
name: name.to_string(),
|
||||||
|
mu_x: 1.0, mu_y: 1.0,
|
||||||
|
h_c: 0.0, theta_m: 0.0,
|
||||||
|
j_src: Default::default(),
|
||||||
|
cduct: 0.0,
|
||||||
|
lam_d: 0.0,
|
||||||
|
theta_hn: 0.0, theta_hx: 0.0, theta_hy: 0.0,
|
||||||
|
lam_type: 0, lam_fill: 1.0,
|
||||||
|
n_strands: 0, wire_d: 0.0,
|
||||||
|
bh_curve: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// re-runs mesh + autolabel up to max_passes times, accumulating labels until no orphan regions remain or the mesher fails on a re-mesh.
|
||||||
|
fn autolabel_loop(doc: &mut FemmDoc, initial: Mesh, max_passes: usize) -> (Mesh, usize) {
|
||||||
|
let mut mesh = initial;
|
||||||
|
let mut total = 0;
|
||||||
|
for _ in 0..max_passes {
|
||||||
|
let added = autolabel_orphan_regions(doc, &mesh);
|
||||||
|
if added == 0 { break; }
|
||||||
|
total += added;
|
||||||
|
match catch_unwind(AssertUnwindSafe(|| run_mesh(doc))) {
|
||||||
|
Ok(Ok(m)) => { mesh = m; }
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(mesh, total)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// inserts an `Air` block label at the centroid of the largest triangle in every connected component of mesh triangles whose region attribute is zero, returning the number of labels added.
|
||||||
|
fn autolabel_orphan_regions(doc: &mut FemmDoc, mesh: &Mesh) -> usize {
|
||||||
|
let orphan: Vec<usize> = mesh.elements.iter().enumerate()
|
||||||
|
.filter_map(|(i, el)| if el.attribute == 0 { Some(i) } else { None })
|
||||||
|
.collect();
|
||||||
|
if orphan.is_empty() { return 0; }
|
||||||
|
|
||||||
|
let mut edge_to_tri: std::collections::HashMap<(u32, u32), Vec<usize>> = std::collections::HashMap::new();
|
||||||
|
for &i in &orphan {
|
||||||
|
let el = &mesh.elements[i];
|
||||||
|
for (a, b) in [(el.v0, el.v1), (el.v1, el.v2), (el.v2, el.v0)] {
|
||||||
|
let key = (a.min(b), a.max(b));
|
||||||
|
edge_to_tri.entry(key).or_default().push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parent: Vec<usize> = (0..mesh.elements.len()).collect();
|
||||||
|
fn find(p: &mut [usize], i: usize) -> usize {
|
||||||
|
let mut r = i;
|
||||||
|
while p[r] != r { r = p[r]; }
|
||||||
|
let mut cur = i;
|
||||||
|
while p[cur] != r {
|
||||||
|
let next = p[cur];
|
||||||
|
p[cur] = r;
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
for tris in edge_to_tri.values() {
|
||||||
|
for w in tris.windows(2) {
|
||||||
|
let ra = find(&mut parent, w[0]);
|
||||||
|
let rb = find(&mut parent, w[1]);
|
||||||
|
if ra != rb { parent[ra] = rb; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut groups: std::collections::HashMap<usize, (f64, f64, f64)> = std::collections::HashMap::new();
|
||||||
|
for &i in &orphan {
|
||||||
|
let el = &mesh.elements[i];
|
||||||
|
let n0 = &mesh.nodes[el.v0 as usize];
|
||||||
|
let n1 = &mesh.nodes[el.v1 as usize];
|
||||||
|
let n2 = &mesh.nodes[el.v2 as usize];
|
||||||
|
let cx = (n0.x + n1.x + n2.x) / 3.0;
|
||||||
|
let cy = (n0.y + n1.y + n2.y) / 3.0;
|
||||||
|
let area = ((n1.x - n0.x) * (n2.y - n0.y) - (n2.x - n0.x) * (n1.y - n0.y)).abs() * 0.5;
|
||||||
|
let root = find(&mut parent, i);
|
||||||
|
let e = groups.entry(root).or_insert((0.0, 0.0, 0.0));
|
||||||
|
if area > e.2 {
|
||||||
|
e.0 = cx;
|
||||||
|
e.1 = cy;
|
||||||
|
e.2 = area;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !groups.is_empty() { ensure_material(doc, "Air"); }
|
||||||
|
let mut added = 0;
|
||||||
|
for (_, (cx, cy, area)) in groups {
|
||||||
|
if area <= 0.0 { continue; }
|
||||||
|
doc.block_labels.push(BlockLabel {
|
||||||
|
x: cx, y: cy,
|
||||||
|
max_area: 0.0, mag_dir: 0.0,
|
||||||
|
mag_dir_fctn: String::new(),
|
||||||
|
turns: 0,
|
||||||
|
block_type: String::from("Air"),
|
||||||
|
in_circuit: String::new(),
|
||||||
|
in_group: 0,
|
||||||
|
is_external: false,
|
||||||
|
is_default: false,
|
||||||
|
selected: false,
|
||||||
|
});
|
||||||
|
added += 1;
|
||||||
|
}
|
||||||
|
added
|
||||||
|
}
|
||||||
|
|
||||||
|
/// shifts every selected node, label, and endpoint-of-selected-segment-or-arc by (dx, dy) in world units, returning the number of distinct nodes plus labels moved.
|
||||||
|
fn translate_selection(doc: &mut FemmDoc, dx: f64, dy: f64) -> usize {
|
||||||
|
let mut node_indices = std::collections::HashSet::<usize>::new();
|
||||||
|
for (i, n) in doc.nodes.iter().enumerate() {
|
||||||
|
if n.selected { node_indices.insert(i); }
|
||||||
|
}
|
||||||
|
for s in &doc.segments {
|
||||||
|
if s.selected {
|
||||||
|
node_indices.insert(s.n0 as usize);
|
||||||
|
node_indices.insert(s.n1 as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for a in &doc.arcs {
|
||||||
|
if a.selected {
|
||||||
|
node_indices.insert(a.n0 as usize);
|
||||||
|
node_indices.insert(a.n1 as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &i in &node_indices {
|
||||||
|
if let Some(n) = doc.nodes.get_mut(i) {
|
||||||
|
n.x += dx;
|
||||||
|
n.y += dy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut labels = 0;
|
||||||
|
for b in doc.block_labels.iter_mut() {
|
||||||
|
if b.selected {
|
||||||
|
b.x += dx;
|
||||||
|
b.y += dy;
|
||||||
|
labels += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node_indices.len() + labels
|
||||||
|
}
|
||||||
|
|
||||||
/// clears the selected flag on every entity in the doc.
|
/// clears the selected flag on every entity in the doc.
|
||||||
fn clear_selection(doc: &mut FemmDoc) {
|
fn clear_selection(doc: &mut FemmDoc) {
|
||||||
for n in &mut doc.nodes { n.selected = false; }
|
for n in &mut doc.nodes { n.selected = false; }
|
||||||
|
|
@ -2275,7 +2705,18 @@ fn apply_pick_at(doc: &mut FemmDoc, x: f64, y: f64, pick_radius_world: f64, op:
|
||||||
|
|
||||||
if matches!(op, PickOp::Replace) { clear_selection(doc); }
|
if matches!(op, PickOp::Replace) { clear_selection(doc); }
|
||||||
|
|
||||||
let hit = best.and_then(|(k, i, d)| if d <= pick_radius_world { Some((k, i)) } else { None });
|
let mut best_label: Option<(usize, f64)> = None;
|
||||||
|
for (i, b) in doc.block_labels.iter().enumerate() {
|
||||||
|
let d = (b.x - x).hypot(b.y - y);
|
||||||
|
if d <= pick_radius_world && best_label.map(|(_, bd)| d < bd).unwrap_or(true) {
|
||||||
|
best_label = Some((i, d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let hit = if let Some((i, _)) = best_label {
|
||||||
|
Some((Kind::Label, i))
|
||||||
|
} else {
|
||||||
|
best.and_then(|(k, i, d)| if d <= pick_radius_world { Some((k, i)) } else { None })
|
||||||
|
};
|
||||||
if let Some((kind, idx)) = hit {
|
if let Some((kind, idx)) = hit {
|
||||||
let (label, flag) = match kind {
|
let (label, flag) = match kind {
|
||||||
Kind::Node => ("node", &mut doc.nodes[idx].selected),
|
Kind::Node => ("node", &mut doc.nodes[idx].selected),
|
||||||
|
|
@ -2374,6 +2815,17 @@ fn text_button(label: &str, msg: Message) -> Element<'_, Message> {
|
||||||
button(text(label).size(13)).on_press(msg).into()
|
button(text(label).size(13)).on_press(msg).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Edit-tool toolbar button: click-drag to move selected entities, double-click a label to rename.
|
||||||
|
fn edit_tool_button<'a>(active: Tool) -> Element<'a, Message> {
|
||||||
|
let active_flag = active == Tool::Edit;
|
||||||
|
let style = if active_flag { button::primary } else { button::secondary };
|
||||||
|
let btn = button(text("Edit").size(13))
|
||||||
|
.on_press(Message::SelectTool(Tool::Edit))
|
||||||
|
.style(style);
|
||||||
|
tooltip(btn, text("Edit: drag to move, double-click a label to rename").size(11),
|
||||||
|
tooltip::Position::Bottom).into()
|
||||||
|
}
|
||||||
|
|
||||||
/// draws a thin vertical rule between toolbar groups.
|
/// draws a thin vertical rule between toolbar groups.
|
||||||
fn separator<'a>() -> Element<'a, Message> {
|
fn separator<'a>() -> Element<'a, Message> {
|
||||||
container(iced::widget::Space::new().width(Length::Fixed(1.0)).height(Length::Fixed(28.0)))
|
container(iced::widget::Space::new().width(Length::Fixed(1.0)).height(Length::Fixed(28.0)))
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ impl<'a, Msg> canvas::Program<Msg> for PlotProgram<'a> {
|
||||||
let buf = self.buffer_size.max(1);
|
let buf = self.buffer_size.max(1);
|
||||||
let mut points: Vec<(i64, f64)> = Vec::with_capacity(self.samples.len());
|
let mut points: Vec<(i64, f64)> = Vec::with_capacity(self.samples.len());
|
||||||
for (&fi, &(bx, by)) in self.samples {
|
for (&fi, &(bx, by)) in self.samples {
|
||||||
points.push((fi, self.probe.mode.extract(bx, by)));
|
points.push((fi, self.probe.mode.extract(bx, by, self.probe.angle_deg.to_radians())));
|
||||||
}
|
}
|
||||||
if points.is_empty() {
|
if points.is_empty() {
|
||||||
frame.fill_text(Text {
|
frame.fill_text(Text {
|
||||||
|
|
|
||||||
|
|
@ -8,35 +8,50 @@ pub enum ProbeMode {
|
||||||
Bx,
|
Bx,
|
||||||
By,
|
By,
|
||||||
Angle,
|
Angle,
|
||||||
|
Primary,
|
||||||
|
Perpendicular,
|
||||||
|
Differential,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProbeMode {
|
impl ProbeMode {
|
||||||
pub fn label(self) -> &'static str {
|
pub fn label(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
ProbeMode::Magnitude => "|B|",
|
ProbeMode::Magnitude => "|B|",
|
||||||
ProbeMode::Bx => "Bx",
|
ProbeMode::Bx => "Bx",
|
||||||
ProbeMode::By => "By",
|
ProbeMode::By => "By",
|
||||||
ProbeMode::Angle => "atan2(By,Bx)",
|
ProbeMode::Angle => "atan2(By,Bx)",
|
||||||
|
ProbeMode::Primary => "Pri",
|
||||||
|
ProbeMode::Perpendicular => "Per (CW 90)",
|
||||||
|
ProbeMode::Differential => "Pri - Per",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn extract(self, bx: f64, by: f64) -> f64 {
|
pub fn extract(self, bx: f64, by: f64, angle_rad: f64) -> f64 {
|
||||||
|
let c = angle_rad.cos();
|
||||||
|
let s = angle_rad.sin();
|
||||||
|
let pri = bx * c + by * s;
|
||||||
|
let per = bx * s - by * c;
|
||||||
match self {
|
match self {
|
||||||
ProbeMode::Magnitude => (bx * bx + by * by).sqrt(),
|
ProbeMode::Magnitude => (bx * bx + by * by).sqrt(),
|
||||||
ProbeMode::Bx => bx,
|
ProbeMode::Bx => bx,
|
||||||
ProbeMode::By => by,
|
ProbeMode::By => by,
|
||||||
ProbeMode::Angle => by.atan2(bx),
|
ProbeMode::Angle => by.atan2(bx),
|
||||||
|
ProbeMode::Primary => pri,
|
||||||
|
ProbeMode::Perpendicular => per,
|
||||||
|
ProbeMode::Differential => pri - per,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// one user-placed probe with a world position, display color, label, and plot-mode selector.
|
/// one user-placed probe with a world position, display color, label, plot-mode selector, and primary-axis orientation in degrees (CCW from +x).
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Probe {
|
pub struct Probe {
|
||||||
pub x: f64,
|
pub x: f64,
|
||||||
pub y: f64,
|
pub y: f64,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub color: [u8; 4],
|
pub color: [u8; 4],
|
||||||
pub mode: ProbeMode,
|
pub mode: ProbeMode,
|
||||||
|
pub angle_deg: f64,
|
||||||
|
pub angle_text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns a saturated palette color from a small cycling palette suitable for probe markers and matching plot lines.
|
/// returns a saturated palette color from a small cycling palette suitable for probe markers and matching plot lines.
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,13 @@ use crate::kinematic::{Axis, Expression, Track};
|
||||||
use crate::spice;
|
use crate::spice;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
/// parses a SPICE-format duration text into a positive tokio Duration; None on parse failure or non-positive value.
|
||||||
|
pub fn parse_interval(text: &str) -> Option<Duration> {
|
||||||
|
spice::parse_spice(text)
|
||||||
|
.filter(|s| *s > 0.0)
|
||||||
|
.map(Duration::from_secs_f64)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SimMeta {
|
pub struct SimMeta {
|
||||||
pub source_fem: Option<String>,
|
pub source_fem: Option<String>,
|
||||||
pub dt_text: String,
|
pub dt_text: String,
|
||||||
|
|
@ -250,7 +257,3 @@ fn axis_from_str(s: &str) -> Option<Axis> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// parses a SPICE-format duration text into a tokio Duration, falling back to 50 ms on parse failure.
|
|
||||||
pub fn parse_interval(text: &str) -> Duration {
|
|
||||||
spice::parse_spice(text).map(Duration::from_secs_f64).unwrap_or(Duration::from_millis(50))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -198,6 +198,220 @@ impl FemmDoc {
|
||||||
nodes_bbox_tolerance(&self.nodes)
|
nodes_bbox_tolerance(&self.nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// walks every pair of segments and arcs, computes their geometric intersections, inserts a node at each crossing, and splits the crossed primitives so they terminate at the new nodes. also splits any segment or arc whose interior passes within `tol` of an existing node (T-junction). preserves boundary markers and per-piece metadata.
|
||||||
|
pub fn split_at_intersections(&mut self) {
|
||||||
|
let tol = self.bbox_tolerance().max(1.0e-9);
|
||||||
|
let n_segs = self.segments.len();
|
||||||
|
let n_arcs = self.arcs.len();
|
||||||
|
|
||||||
|
let seg_endpoints: Vec<((f64, f64), (f64, f64))> = (0..n_segs).map(|i| {
|
||||||
|
let s = &self.segments[i];
|
||||||
|
((self.nodes[s.n0 as usize].x, self.nodes[s.n0 as usize].y),
|
||||||
|
(self.nodes[s.n1 as usize].x, self.nodes[s.n1 as usize].y))
|
||||||
|
}).collect();
|
||||||
|
let arc_endpoints: Vec<((f64, f64), (f64, f64), f64)> = (0..n_arcs).map(|i| {
|
||||||
|
let a = &self.arcs[i];
|
||||||
|
((self.nodes[a.n0 as usize].x, self.nodes[a.n0 as usize].y),
|
||||||
|
(self.nodes[a.n1 as usize].x, self.nodes[a.n1 as usize].y),
|
||||||
|
a.arc_length)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let mut seg_breaks: Vec<Vec<(f64, f64)>> = vec![Vec::new(); n_segs];
|
||||||
|
let mut arc_breaks: Vec<Vec<(f64, f64)>> = vec![Vec::new(); n_arcs];
|
||||||
|
|
||||||
|
for i in 0..n_segs {
|
||||||
|
for j in (i + 1)..n_segs {
|
||||||
|
if let Some(hit) = line_line_intersection(
|
||||||
|
seg_endpoints[i].0, seg_endpoints[i].1,
|
||||||
|
seg_endpoints[j].0, seg_endpoints[j].1,
|
||||||
|
) {
|
||||||
|
seg_breaks[i].push(hit);
|
||||||
|
seg_breaks[j].push(hit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in 0..n_segs {
|
||||||
|
for j in 0..n_arcs {
|
||||||
|
for hit in line_arc_intersection(
|
||||||
|
seg_endpoints[i].0, seg_endpoints[i].1,
|
||||||
|
arc_endpoints[j].0, arc_endpoints[j].1, arc_endpoints[j].2,
|
||||||
|
) {
|
||||||
|
seg_breaks[i].push(hit);
|
||||||
|
arc_breaks[j].push(hit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in 0..n_arcs {
|
||||||
|
for j in (i + 1)..n_arcs {
|
||||||
|
for hit in arc_arc_intersection(
|
||||||
|
arc_endpoints[i].0, arc_endpoints[i].1, arc_endpoints[i].2,
|
||||||
|
arc_endpoints[j].0, arc_endpoints[j].1, arc_endpoints[j].2,
|
||||||
|
) {
|
||||||
|
arc_breaks[i].push(hit);
|
||||||
|
arc_breaks[j].push(hit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let on_line_tol = tol * 100.0;
|
||||||
|
for i in 0..n_segs {
|
||||||
|
let (p0, p1) = seg_endpoints[i];
|
||||||
|
for n in &self.nodes {
|
||||||
|
let d = shortest_distance_from_segment((n.x, n.y), p0, p1);
|
||||||
|
let d0 = (n.x - p0.0).hypot(n.y - p0.1);
|
||||||
|
let d1 = (n.x - p1.0).hypot(n.y - p1.1);
|
||||||
|
if d < on_line_tol && d0 > on_line_tol && d1 > on_line_tol {
|
||||||
|
seg_breaks[i].push((n.x, n.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in 0..n_arcs {
|
||||||
|
let (p0, p1, sweep) = arc_endpoints[i];
|
||||||
|
for n in &self.nodes {
|
||||||
|
let d = shortest_distance_from_arc((n.x, n.y), p0, p1, sweep);
|
||||||
|
let d0 = (n.x - p0.0).hypot(n.y - p0.1);
|
||||||
|
let d1 = (n.x - p1.0).hypot(n.y - p1.1);
|
||||||
|
if d < on_line_tol && d0 > on_line_tol && d1 > on_line_tol {
|
||||||
|
arc_breaks[i].push((n.x, n.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for breaks in seg_breaks.iter().chain(arc_breaks.iter()) {
|
||||||
|
for &(x, y) in breaks {
|
||||||
|
self.add_node(x, y, tol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_segments = std::mem::take(&mut self.segments);
|
||||||
|
for (i, s) in old_segments.into_iter().enumerate() {
|
||||||
|
let (p0, p1) = seg_endpoints[i];
|
||||||
|
let dx = p1.0 - p0.0;
|
||||||
|
let dy = p1.1 - p0.1;
|
||||||
|
let len2 = dx * dx + dy * dy;
|
||||||
|
if len2 < tol * tol {
|
||||||
|
self.segments.push(s);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut ts: Vec<f64> = seg_breaks[i].iter()
|
||||||
|
.map(|&(bx, by)| ((bx - p0.0) * dx + (by - p0.1) * dy) / len2)
|
||||||
|
.filter(|&t| t > 1.0e-6 && t < 1.0 - 1.0e-6)
|
||||||
|
.collect();
|
||||||
|
ts.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
||||||
|
ts.dedup_by(|a, b| (*a - *b).abs() < 1.0e-9);
|
||||||
|
|
||||||
|
let mut prev_node = s.n0;
|
||||||
|
for &t in &ts {
|
||||||
|
let bx = p0.0 + t * dx;
|
||||||
|
let by = p0.1 + t * dy;
|
||||||
|
let Some(node_idx) = self.closest_node(bx, by) else { continue };
|
||||||
|
let node_idx = node_idx as i32;
|
||||||
|
if node_idx == prev_node || node_idx == s.n1 { continue; }
|
||||||
|
self.segments.push(Segment {
|
||||||
|
n0: prev_node, n1: node_idx,
|
||||||
|
max_side_length: s.max_side_length,
|
||||||
|
boundary_marker: s.boundary_marker.clone(),
|
||||||
|
hidden: s.hidden,
|
||||||
|
in_group: s.in_group,
|
||||||
|
selected: false,
|
||||||
|
});
|
||||||
|
prev_node = node_idx;
|
||||||
|
}
|
||||||
|
self.segments.push(Segment {
|
||||||
|
n0: prev_node, n1: s.n1,
|
||||||
|
max_side_length: s.max_side_length,
|
||||||
|
boundary_marker: s.boundary_marker.clone(),
|
||||||
|
hidden: s.hidden,
|
||||||
|
in_group: s.in_group,
|
||||||
|
selected: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_arcs = std::mem::take(&mut self.arcs);
|
||||||
|
for (i, a) in old_arcs.into_iter().enumerate() {
|
||||||
|
let (p0, p1, _) = arc_endpoints[i];
|
||||||
|
if a.arc_length.abs() < 1.0e-6 {
|
||||||
|
self.arcs.push(a);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (cx, cy, r) = circle_from_arc(p0, p1, a.arc_length);
|
||||||
|
if r < 1.0e-9 {
|
||||||
|
self.arcs.push(a);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let a0_rad = (p0.1 - cy).atan2(p0.0 - cx);
|
||||||
|
let sweep_rad = a.arc_length.to_radians();
|
||||||
|
let sign = if sweep_rad >= 0.0 { 1.0 } else { -1.0 };
|
||||||
|
let sweep_abs = sweep_rad.abs();
|
||||||
|
|
||||||
|
let two_pi = 2.0 * std::f64::consts::PI;
|
||||||
|
let mut ts: Vec<f64> = arc_breaks[i].iter()
|
||||||
|
.filter_map(|&(bx, by)| {
|
||||||
|
let ang = (by - cy).atan2(bx - cx);
|
||||||
|
let mut delta = (ang - a0_rad) * sign;
|
||||||
|
while delta < 0.0 { delta += two_pi; }
|
||||||
|
while delta >= two_pi { delta -= two_pi; }
|
||||||
|
let t = delta / sweep_abs;
|
||||||
|
if t > 1.0e-6 && t < 1.0 - 1.0e-6 { Some(t) } else { None }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
ts.sort_by(|x, y| x.partial_cmp(y).unwrap_or(std::cmp::Ordering::Equal));
|
||||||
|
ts.dedup_by(|x, y| (*x - *y).abs() < 1.0e-9);
|
||||||
|
|
||||||
|
let mut prev_node = a.n0;
|
||||||
|
let mut prev_t = 0.0;
|
||||||
|
for &t in &ts {
|
||||||
|
let ang = a0_rad + sign * t * sweep_abs;
|
||||||
|
let bx = cx + r * ang.cos();
|
||||||
|
let by = cy + r * ang.sin();
|
||||||
|
let Some(node_idx) = self.closest_node(bx, by) else { continue };
|
||||||
|
let node_idx = node_idx as i32;
|
||||||
|
if node_idx == prev_node || node_idx == a.n1 { continue; }
|
||||||
|
let piece_sweep = a.arc_length * (t - prev_t);
|
||||||
|
self.arcs.push(ArcSegment {
|
||||||
|
n0: prev_node, n1: node_idx,
|
||||||
|
arc_length: piece_sweep,
|
||||||
|
..a.clone()
|
||||||
|
});
|
||||||
|
prev_node = node_idx;
|
||||||
|
prev_t = t;
|
||||||
|
}
|
||||||
|
let last_sweep = a.arc_length * (1.0 - prev_t);
|
||||||
|
self.arcs.push(ArcSegment {
|
||||||
|
n0: prev_node, n1: a.n1,
|
||||||
|
arc_length: last_sweep,
|
||||||
|
..a
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dedup_segments_and_arcs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// removes exact-duplicate segments (same unordered endpoints) and exact-duplicate arcs (same directed endpoints and near-equal sweep).
|
||||||
|
fn dedup_segments_and_arcs(&mut self) {
|
||||||
|
let mut kept_segs: Vec<Segment> = Vec::with_capacity(self.segments.len());
|
||||||
|
for s in std::mem::take(&mut self.segments) {
|
||||||
|
let (a, b) = if s.n0 < s.n1 { (s.n0, s.n1) } else { (s.n1, s.n0) };
|
||||||
|
if a == b { continue; }
|
||||||
|
let dup = kept_segs.iter().any(|k| {
|
||||||
|
let (ka, kb) = if k.n0 < k.n1 { (k.n0, k.n1) } else { (k.n1, k.n0) };
|
||||||
|
ka == a && kb == b
|
||||||
|
});
|
||||||
|
if !dup { kept_segs.push(s); }
|
||||||
|
}
|
||||||
|
self.segments = kept_segs;
|
||||||
|
|
||||||
|
let mut kept_arcs: Vec<ArcSegment> = Vec::with_capacity(self.arcs.len());
|
||||||
|
for a in std::mem::take(&mut self.arcs) {
|
||||||
|
if a.n0 == a.n1 || a.arc_length.abs() < 1.0e-6 { continue; }
|
||||||
|
let dup = kept_arcs.iter().any(|k| {
|
||||||
|
k.n0 == a.n0 && k.n1 == a.n1 && (k.arc_length - a.arc_length).abs() < 1.0e-2
|
||||||
|
});
|
||||||
|
if !dup { kept_arcs.push(a); }
|
||||||
|
}
|
||||||
|
self.arcs = kept_arcs;
|
||||||
|
}
|
||||||
|
|
||||||
/// rebuilds every list through the PSLG-aware add primitives, catching crossings missed by incremental edits.
|
/// rebuilds every list through the PSLG-aware add primitives, catching crossings missed by incremental edits.
|
||||||
pub fn enforce_pslg(&mut self) {
|
pub fn enforce_pslg(&mut self) {
|
||||||
let old_nodes = std::mem::take(&mut self.nodes);
|
let old_nodes = std::mem::take(&mut self.nodes);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,395 @@
|
||||||
|
[Format] = 4.00000000000000000e0
|
||||||
|
[Frequency] = 0.00000000000000000e0
|
||||||
|
[Precision] = 1.00000000000000002e-8
|
||||||
|
[MinAngle] = 2.50000000000000000e1
|
||||||
|
[DoSmartMesh] = 0
|
||||||
|
[Depth] = 5.00000000000000000e0
|
||||||
|
[LengthUnits] = millimeters
|
||||||
|
[ProblemType] = planar
|
||||||
|
[Coordinates] = cartesian
|
||||||
|
[ACSolver] = 0
|
||||||
|
[PrevType] = 0
|
||||||
|
[PrevSoln] = ""
|
||||||
|
[Comment] = ""
|
||||||
|
[PointProps] = 0
|
||||||
|
[BdryProps] = 1
|
||||||
|
<BeginBdry>
|
||||||
|
<BdryName> = "A=0"
|
||||||
|
<BdryType> = 0
|
||||||
|
<A_0> = 0.00000000000000000e0
|
||||||
|
<A_1> = 0.00000000000000000e0
|
||||||
|
<A_2> = 0.00000000000000000e0
|
||||||
|
<Phi> = 0.00000000000000000e0
|
||||||
|
<c0> = 0.00000000000000000e0
|
||||||
|
<c0i> = 0.00000000000000000e0
|
||||||
|
<c1> = 0.00000000000000000e0
|
||||||
|
<c1i> = 0.00000000000000000e0
|
||||||
|
<Mu_ssd> = 0.00000000000000000e0
|
||||||
|
<Sigma_ssd> = 0.00000000000000000e0
|
||||||
|
<innerangle> = 0.00000000000000000e0
|
||||||
|
<outerangle> = 0.00000000000000000e0
|
||||||
|
<EndBdry>
|
||||||
|
[BlockProps] = 3
|
||||||
|
<BeginBlock>
|
||||||
|
<BlockName> = "Air"
|
||||||
|
<Mu_x> = 1.00000000000000000e0
|
||||||
|
<Mu_y> = 1.00000000000000000e0
|
||||||
|
<H_c> = 0.00000000000000000e0
|
||||||
|
<H_cAngle> = 0.00000000000000000e0
|
||||||
|
<J_re> = 0.00000000000000000e0
|
||||||
|
<J_im> = 0.00000000000000000e0
|
||||||
|
<Sigma> = 0.00000000000000000e0
|
||||||
|
<d_lam> = 0.00000000000000000e0
|
||||||
|
<Phi_h> = 0.00000000000000000e0
|
||||||
|
<Phi_hx> = 0.00000000000000000e0
|
||||||
|
<Phi_hy> = 0.00000000000000000e0
|
||||||
|
<LamType> = 0
|
||||||
|
<LamFill> = 1.00000000000000000e0
|
||||||
|
<NStrands> = 0
|
||||||
|
<WireD> = 0.00000000000000000e0
|
||||||
|
<BHPoints> = 0
|
||||||
|
<EndBlock>
|
||||||
|
<BeginBlock>
|
||||||
|
<BlockName> = "NdFeB"
|
||||||
|
<Mu_x> = 1.05000000000000004e0
|
||||||
|
<Mu_y> = 1.05000000000000004e0
|
||||||
|
<H_c> = 9.15000000000000000e5
|
||||||
|
<H_cAngle> = 0.00000000000000000e0
|
||||||
|
<J_re> = 0.00000000000000000e0
|
||||||
|
<J_im> = 0.00000000000000000e0
|
||||||
|
<Sigma> = 6.67000000000000037e-1
|
||||||
|
<d_lam> = 0.00000000000000000e0
|
||||||
|
<Phi_h> = 0.00000000000000000e0
|
||||||
|
<Phi_hx> = 0.00000000000000000e0
|
||||||
|
<Phi_hy> = 0.00000000000000000e0
|
||||||
|
<LamType> = 0
|
||||||
|
<LamFill> = 1.00000000000000000e0
|
||||||
|
<NStrands> = 0
|
||||||
|
<WireD> = 0.00000000000000000e0
|
||||||
|
<BHPoints> = 0
|
||||||
|
<EndBlock>
|
||||||
|
<BeginBlock>
|
||||||
|
<BlockName> = "Steel"
|
||||||
|
<Mu_x> = 2.50000000000000000e3
|
||||||
|
<Mu_y> = 2.50000000000000000e3
|
||||||
|
<H_c> = 0.00000000000000000e0
|
||||||
|
<H_cAngle> = 0.00000000000000000e0
|
||||||
|
<J_re> = 0.00000000000000000e0
|
||||||
|
<J_im> = 0.00000000000000000e0
|
||||||
|
<Sigma> = 5.79999999999999982e0
|
||||||
|
<d_lam> = 0.00000000000000000e0
|
||||||
|
<Phi_h> = 0.00000000000000000e0
|
||||||
|
<Phi_hx> = 0.00000000000000000e0
|
||||||
|
<Phi_hy> = 0.00000000000000000e0
|
||||||
|
<LamType> = 0
|
||||||
|
<LamFill> = 1.00000000000000000e0
|
||||||
|
<NStrands> = 0
|
||||||
|
<WireD> = 0.00000000000000000e0
|
||||||
|
<BHPoints> = 0
|
||||||
|
<EndBlock>
|
||||||
|
[CircuitProps] = 0
|
||||||
|
[NumPoints] = 108
|
||||||
|
8.24005999999999972e2 -5.52793000000000006e2 0 0
|
||||||
|
4.71225999999999999e2 -9.05572999999999979e2 0 0
|
||||||
|
1.18446000000000026e2 -5.52793000000000006e2 0 0
|
||||||
|
4.71225999999999999e2 -2.00013000000000034e2 0 0
|
||||||
|
6.46474000000000046e2 -5.52792500000000018e2 0 0
|
||||||
|
4.71225500000000011e2 -7.28041000000000054e2 0 0
|
||||||
|
2.95976999999999975e2 -5.52792500000000018e2 0 0
|
||||||
|
4.71225500000000011e2 -3.77543999999999983e2 0 0
|
||||||
|
-2.80048700000000008e3 -7.22574000000000069e2 0 0
|
||||||
|
-2.85275000000000000e3 -7.74837000000000103e2 0 0
|
||||||
|
-2.90501299999999992e3 -7.22574000000000069e2 0 0
|
||||||
|
-2.85275000000000000e3 -6.70311000000000035e2 0 0
|
||||||
|
-2.83670149999999967e3 -7.22574000000000069e2 0 0
|
||||||
|
-2.85269999999999982e3 -7.38572500000000105e2 0 0
|
||||||
|
-2.86869849999999997e3 -7.22574000000000069e2 0 0
|
||||||
|
-2.85269999999999982e3 -7.06575500000000034e2 0 0
|
||||||
|
-2.80048700000000008e3 -4.08918000000000006e2 0 0
|
||||||
|
-2.85275000000000000e3 -4.61181000000000040e2 0 0
|
||||||
|
-2.90501299999999992e3 -4.08918000000000006e2 0 0
|
||||||
|
-2.85275000000000000e3 -3.56654999999999973e2 0 0
|
||||||
|
-2.83680150000000003e3 -4.08918000000000006e2 0 0
|
||||||
|
-2.85280000000000018e3 -4.24916500000000042e2 0 0
|
||||||
|
-2.86879850000000033e3 -4.08918000000000006e2 0 0
|
||||||
|
-2.85280000000000018e3 -3.92919499999999971e2 0 0
|
||||||
|
-2.80041199999999981e3 -8.27125999999999976e2 0 0
|
||||||
|
-2.85269999999999982e3 -8.79413999999999987e2 0 0
|
||||||
|
-2.90498799999999983e3 -8.27125999999999976e2 0 0
|
||||||
|
-2.85269999999999982e3 -7.74837999999999965e2 0 0
|
||||||
|
-2.83670149999999967e3 -8.27125999999999976e2 0 0
|
||||||
|
-2.85269999999999982e3 -8.43124500000000012e2 0 0
|
||||||
|
-2.86869849999999997e3 -8.27125999999999976e2 0 0
|
||||||
|
-2.85269999999999982e3 -8.11127499999999941e2 0 0
|
||||||
|
-2.80048700000000008e3 -3.04365999999999985e2 0 0
|
||||||
|
-2.85275000000000000e3 -3.56628999999999962e2 0 0
|
||||||
|
-2.90501299999999992e3 -3.04365999999999985e2 0 0
|
||||||
|
-2.85275000000000000e3 -2.52102999999999980e2 0 0
|
||||||
|
-2.83670149999999967e3 -3.04365999999999985e2 0 0
|
||||||
|
-2.85269999999999982e3 -3.20364499999999964e2 0 0
|
||||||
|
-2.86869849999999997e3 -3.04365999999999985e2 0 0
|
||||||
|
-2.85269999999999982e3 -2.88367500000000007e2 0 0
|
||||||
|
-2.80048700000000008e3 -5.13470000000000027e2 0 0
|
||||||
|
-2.85275000000000000e3 -5.65733000000000061e2 0 0
|
||||||
|
-2.90501299999999992e3 -5.13470000000000027e2 0 0
|
||||||
|
-2.85275000000000000e3 -4.61207000000000050e2 0 0
|
||||||
|
-2.83680150000000003e3 -5.13470000000000027e2 0 0
|
||||||
|
-2.85280000000000018e3 -5.29468500000000063e2 0 0
|
||||||
|
-2.86879850000000033e3 -5.13470000000000027e2 0 0
|
||||||
|
-2.85280000000000018e3 -4.97471500000000049e2 0 0
|
||||||
|
-2.80041199999999981e3 -6.18021999999999935e2 0 0
|
||||||
|
-2.85269999999999982e3 -6.70309999999999945e2 0 0
|
||||||
|
-2.90498799999999983e3 -6.18021999999999935e2 0 0
|
||||||
|
-2.85269999999999982e3 -5.65733999999999924e2 0 0
|
||||||
|
-2.83670149999999967e3 -6.18021999999999935e2 0 0
|
||||||
|
-2.85269999999999982e3 -6.34020499999999970e2 0 0
|
||||||
|
-2.86869849999999997e3 -6.18021999999999935e2 0 0
|
||||||
|
-2.85269999999999982e3 -6.02023499999999899e2 0 0
|
||||||
|
-2.80230000000000018e3 -7.26054999999999950e2 0 0
|
||||||
|
1.64704000000000008e2 -7.26054999999999950e2 0 0
|
||||||
|
1.58929000000000002e2 -7.15385999999999967e2 0 0
|
||||||
|
-2.80229899999999998e3 -7.15385999999999967e2 0 0
|
||||||
|
-2.80509999999999991e3 -4.02382000000000005e2 0 0
|
||||||
|
1.52897999999999996e2 -4.02382000000000005e2 0 0
|
||||||
|
1.50802999999999997e2 -4.06911999999999978e2 0 0
|
||||||
|
-2.80509999999999991e3 -4.06911999999999978e2 0 0
|
||||||
|
-2.80080000000000018e3 -8.19880999999999972e2 0 0
|
||||||
|
2.41389999999999986e2 -8.19880999999999972e2 0 0
|
||||||
|
2.59242000000000019e2 -8.34370000000000005e2 0 0
|
||||||
|
-2.80080000000000018e3 -8.34370000000000005e2 0 0
|
||||||
|
-2.80230099999999993e3 -2.97177999999999997e2 0 0
|
||||||
|
2.28776999999999987e2 -2.97177999999999997e2 0 0
|
||||||
|
2.25158999999999992e2 -3.00680999999999983e2 0 0
|
||||||
|
-2.80230000000000018e3 -3.00680999999999983e2 0 0
|
||||||
|
-2.80509999999999991e3 -5.06934000000000026e2 0 0
|
||||||
|
1.22325000000000003e2 -5.06934000000000026e2 0 0
|
||||||
|
1.21597999999999999e2 -5.12947999999999979e2 0 0
|
||||||
|
-2.80509999999999991e3 -5.12947999999999979e2 0 0
|
||||||
|
-2.80080000000000018e3 -6.10777000000000044e2 0 0
|
||||||
|
1.24111000000000004e2 -6.10777000000000044e2 0 0
|
||||||
|
1.25593999999999994e2 -6.19129999999999995e2 0 0
|
||||||
|
-2.80080000000000018e3 -6.19129999999999995e2 0 0
|
||||||
|
-2.97580000000000018e3 1.03290000000000009e3 0 0
|
||||||
|
8.75769999999999982e2 1.03290000000000009e3 0 0
|
||||||
|
8.75769999999999982e2 -1.93863999999999987e3 0 0
|
||||||
|
-2.97580000000000018e3 -1.93863999999999987e3 0 0
|
||||||
|
-2.80060305562163785e3 -7.26054999999999950e2 0 0
|
||||||
|
1.63924832159717880e2 -7.26054999999999950e2 0 0
|
||||||
|
1.58148941193386122e2 -7.15385999999999967e2 0 0
|
||||||
|
-2.80098366127493409e3 -7.15385999999999967e2 0 0
|
||||||
|
-2.80089730601984138e3 -4.02382000000000005e2 0 0
|
||||||
|
1.52117461287854894e2 -4.02382000000000005e2 0 0
|
||||||
|
1.50021295428289278e2 -4.06911999999999978e2 0 0
|
||||||
|
-2.80052551213271863e3 -4.06911999999999978e2 0 0
|
||||||
|
2.40753162867295032e2 -8.19880999999999972e2 0 0
|
||||||
|
2.58698068962689092e2 -8.34370000000000005e2 0 0
|
||||||
|
-2.80080000000000018e3 -8.33484061339750951e2 0 0
|
||||||
|
-2.80080000000000018e3 -8.20767938660249001e2 0 0
|
||||||
|
-2.80098366127491727e3 -2.97177999999999997e2 0 0
|
||||||
|
2.28090852036980777e2 -2.97177999999999997e2 0 0
|
||||||
|
2.24460386803995789e2 -3.00680999999999983e2 0 0
|
||||||
|
-2.80061707428122872e3 -3.00680999999999983e2 0 0
|
||||||
|
-2.80089730601984138e3 -5.06934000000000026e2 0 0
|
||||||
|
1.21439378587746887e2 -5.06934000000000026e2 0 0
|
||||||
|
1.20703383932220476e2 -5.12947999999999979e2 0 0
|
||||||
|
-2.80048960691881257e3 -5.12947999999999979e2 0 0
|
||||||
|
1.23243839330807077e2 -6.10777000000000044e2 0 0
|
||||||
|
1.24739159223903599e2 -6.19129999999999995e2 0 0
|
||||||
|
-2.80042374076121632e3 -6.19129999999999995e2 0 0
|
||||||
|
-2.80080000000000018e3 -6.11663938660248959e2 0 0
|
||||||
|
[NumSegments] = 19
|
||||||
|
84 85 -1 0 0 0
|
||||||
|
86 87 -1 0 0 0
|
||||||
|
88 89 -1 0 0 0
|
||||||
|
90 91 -1 0 0 0
|
||||||
|
64 92 -1 0 0 0
|
||||||
|
93 67 -1 0 0 0
|
||||||
|
67 94 -1 0 0 0
|
||||||
|
95 64 -1 0 0 0
|
||||||
|
96 97 -1 0 0 0
|
||||||
|
98 99 -1 0 0 0
|
||||||
|
100 101 -1 0 0 0
|
||||||
|
102 103 -1 0 0 0
|
||||||
|
76 104 -1 0 0 0
|
||||||
|
105 106 -1 0 0 0
|
||||||
|
107 76 -1 0 0 0
|
||||||
|
80 81 -1 0 0 0
|
||||||
|
81 82 -1 0 0 0
|
||||||
|
82 83 -1 0 0 0
|
||||||
|
83 80 -1 0 0 0
|
||||||
|
[NumArcSegments] = 86
|
||||||
|
0 3 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
3 69 4.34857757270957705e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
69 97 8.08539229291915795e-2 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
97 98 8.19364700061769868e-1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
98 89 2.03772786851518397e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
89 90 8.10683064316110036e-1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
90 101 1.69568447092203130e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
101 102 9.84046273757427370e-1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
102 2 6.48515291746757683e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
2 104 9.46023528666818692e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
104 105 1.37822874154360875e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
105 86 1.66061297943698634e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
86 85 1.97050248335090927e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
85 92 1.97936746204819087e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
92 65 7.83988493135179265e-2 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
65 93 3.66815324705578405e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
93 66 7.05763358568378685e-2 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
66 1 3.69741006413593851e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
1 0 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
4 7 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
7 6 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
6 5 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
5 4 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
8 87 7.90524150172130735e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
87 11 8.20947584982786935e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
11 10 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
10 9 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
9 84 8.61809622364696821e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
84 8 3.81903776353032187e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
12 15 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
15 14 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
14 13 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
13 12 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
16 91 2.19971248551486598e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
91 88 4.98449632060359704e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
88 19 8.28157911938815374e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
19 18 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
18 17 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
17 16 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
20 23 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
23 22 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
22 21 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
21 20 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
24 95 6.98427608648830134e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
95 64 9.62595439387266216e-1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
64 27 8.20531284741244349e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
27 26 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
26 25 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
25 67 8.20542113402890294e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
67 94 9.61512573222417233e-1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
94 24 6.98427608648855092e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
28 31 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
31 30 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
30 29 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
29 28 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
32 99 4.04320996664804166e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
99 96 3.86203153507314179e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
96 35 8.20947584982788214e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
35 34 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
34 33 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
33 32 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
36 39 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
39 38 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
38 37 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
37 36 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
40 103 5.72276643139511543e-1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
103 100 6.61193216297895070e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
100 43 8.28157911938815374e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
43 42 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
42 41 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
41 40 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
44 47 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
47 46 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
46 45 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
45 44 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
48 107 6.98427608648830134e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
107 76 9.62595439387141427e-1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
76 51 8.20531284741245486e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
51 50 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
50 49 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
49 106 8.87769926454948006e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
106 48 1.22300735450519338e0 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
52 55 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
55 54 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
54 53 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
53 52 9.00000000000000000e1 1.00000000000000000e1 0 0 0 1.00000000000000000e1
|
||||||
|
[NumHoles] = 0
|
||||||
|
[NumBlockLabels] = 87
|
||||||
|
6.57912266504880449e2 -7.39479266504880457e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
2.84539733495119549e2 -7.39479266504880457e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
2.84539733495119549e2 -3.66106733495119556e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
6.57912266504880449e2 -3.66106733495119556e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
5.33185701371385335e2 -6.14752701371385342e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
4.09266298628614663e2 -6.14752701371385342e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
4.09266298628614663e2 -4.90833298628614671e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
5.33185701371385335e2 -4.90833298628614671e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.82861591522801746e3 -7.46708084771982840e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87688408477198254e3 -7.46708084771982840e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87688408477198254e3 -6.98439915228017298e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.82861591522801746e3 -6.98439915228017298e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.84709367608059347e3 -7.28230323919406601e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85840632391940653e3 -7.28230323919406601e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85840632391940653e3 -7.16917676080593537e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.84709367608059347e3 -7.16917676080593537e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.82861591522801746e3 -4.33052084771982777e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87688408477198254e3 -4.33052084771982777e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87688408477198254e3 -3.84783915228017236e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.82861591522801746e3 -3.84783915228017236e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.84709367608059347e3 -4.14574323919406481e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85840632391940653e3 -4.14574323919406481e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85840632391940653e3 -4.03261676080593531e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.84709367608059347e3 -4.03261676080593531e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.82855707639325237e3 -8.51268923606747535e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87684292360674726e3 -8.51268923606747535e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87684292360674726e3 -8.02983076393252418e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.82855707639325237e3 -8.02983076393252418e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.84704367608059329e3 -8.32782323919406508e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85835632391940635e3 -8.32782323919406508e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85835632391940635e3 -8.21469676080593445e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.84704367608059329e3 -8.21469676080593445e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.82861591522801746e3 -3.28500084771982756e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87688408477198254e3 -3.28500084771982756e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87688408477198254e3 -2.80231915228017215e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.82861591522801746e3 -2.80231915228017215e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.84709367608059347e3 -3.10022323919406460e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85840632391940653e3 -3.10022323919406460e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85840632391940653e3 -2.98709676080593511e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.84709367608059347e3 -2.98709676080593511e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.82861591522801746e3 -5.37604084771982798e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87688408477198254e3 -5.37604084771982798e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87688408477198254e3 -4.89335915228017257e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.82861591522801746e3 -4.89335915228017257e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.84709367608059347e3 -5.19126323919406559e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85840632391940653e3 -5.19126323919406559e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85840632391940653e3 -5.07813676080593552e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.84709367608059347e3 -5.07813676080593552e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.82855707639325237e3 -6.42164923606747493e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87684292360674726e3 -6.42164923606747493e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.87684292360674726e3 -5.93879076393252376e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.82855707639325237e3 -5.93879076393252376e2 2 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.84704367608059329e3 -6.23678323919406466e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85835632391940635e3 -6.23678323919406466e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.85835632391940635e3 -6.12365676080593403e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.84704367608059329e3 -6.12365676080593403e2 1 -1 0 0.00000000000000000e0 0 0 0
|
||||||
|
-2.50559960000000001e3 -7.20720499999999902e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.91219879999999989e3 -7.20720499999999902e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.31879800000000000e3 -7.20720499999999902e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-7.25397199999999884e2 -7.20720499999999902e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.31996399999999994e2 -7.20720499999999902e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.50930019999999968e3 -4.04646999999999991e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.91770059999999967e3 -4.04646999999999991e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.32610099999999989e3 -4.04646999999999991e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-7.34501399999999649e2 -4.04646999999999991e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.42901799999999639e2 -4.04646999999999991e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.49479580000000033e3 -8.27125499999999988e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.88278739999999993e3 -8.27125499999999988e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.27077900000000000e3 -8.27125499999999988e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-6.58770599999999831e2 -8.27125499999999988e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-4.67621999999996660e1 -8.27125499999999988e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.49919319999999971e3 -2.98929499999999962e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.89297759999999971e3 -2.98929499999999962e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.28676199999999994e3 -2.98929499999999962e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-6.80546399999999721e2 -2.98929499999999962e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-7.43307999999997264e1 -2.98929499999999962e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.51235750000000007e3 -5.09941000000000031e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.92687249999999995e3 -5.09941000000000031e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.34138750000000005e3 -5.09941000000000031e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-7.55902499999999691e2 -5.09941000000000031e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.70417500000000018e2 -5.09941000000000031e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-2.50816060000000016e3 -6.14953500000000076e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.92288180000000011e3 -6.14953500000000076e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.33760300000000007e3 -6.14953500000000076e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-7.52324200000000019e2 -6.14953500000000076e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.67045399999999972e2 -6.14953500000000076e2 3 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
-1.05001500000000010e3 -4.52869999999999891e2 1 -1 0 -0.00000000000000000e0 0 0 0
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-698.81 -405.59 1555.3 1179.68">
|
||||||
|
<g>
|
||||||
|
<rect x="-696.81" y="-403.59" width="1551.3" height="1175.68" style="stroke: rgb(0, 0, 0); fill: rgba(255, 255, 255, 0);" class="Block" role="contentinfo">
|
||||||
|
<title>Air</title>
|
||||||
|
</rect>
|
||||||
|
<path d="M -389.534 319.227 C -389.344 321.003 -388.48 322.965 -388.48 324.788 C -388.48 327.934 -389.728 330.695 -390.258 333.689 L 505.511 333.314 C 496.803 329.093 488.478 322.981 480.616 317.476 L -389.534 319.227 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1.001;" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<path d="M 583.569 -10.949 C 483.286 -10.949 401.99 70.347 401.99 170.631 C 401.99 270.914 483.286 352.21 583.569 352.21 C 683.852 352.21 765.148 270.914 765.148 170.631 C 765.148 70.347 683.852 -10.949 583.569 -10.949 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1.001;" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -394.27 30.432 C -393.88 27.734 -393.66 24.976 -393.66 22.169 C -393.66 20.215 -393.76 18.284 -393.95 16.38 L 488.584 17.391 C 481.707 21.645 475.509 27.019 469.284 32.12 L -394.27 30.432 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1.001;" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -393.4 192.382 C -393.04 189.904 -392.85 187.37 -392.85 184.792 C -392.85 182.997 -392.94 181.223 -393.12 179.474 L 403.534 179.401 C 403.743 183.745 403.938 187.942 404.448 192.201 L -393.4 192.382 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1.001;" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -388.55 324.832 C -388.55 353.703 -411.97 377.108 -440.84 377.108 C -469.7 377.108 -493.11 353.703 -493.11 324.832 C -493.11 295.961 -469.7 272.556 -440.84 272.556 C -411.97 272.556 -388.55 295.961 -388.55 324.832 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;" class="Toroid" role="contentinfo">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -390.11 185.413 C -390.11 214.284 -413.52 237.689 -442.39 237.689 C -471.26 237.689 -494.66 214.284 -494.66 185.413 C -494.66 156.542 -471.26 133.137 -442.39 133.137 C -413.52 133.137 -390.11 156.542 -390.11 185.413 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;" class="Toroid" role="contentinfo">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -390.11 23.406 C -390.11 52.277 -413.52 75.682 -442.39 75.682 C -471.26 75.682 -494.66 52.277 -494.66 23.406 C -494.66 -5.465 -471.26 -28.87 -442.39 -28.87 C -413.52 -28.87 -390.11 -5.465 -390.11 23.406 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;" class="Toroid" role="contentinfo">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-698.81 -405.59 1555.3 1179.68">
|
||||||
|
<g>
|
||||||
|
<rect x="-696.83" y="-403.59" width="1551.3" height="1175.68" style="stroke: rgb(0, 0, 0); fill: rgba(255, 255, 255, 0);" class="Block" role="contentinfo">
|
||||||
|
<title>Air</title>
|
||||||
|
</rect>
|
||||||
|
<path d="M -389.02 48.687 C -389.02 77.558 -412.43 100.963 -441.3 100.963 C -470.17 100.963 -493.57 77.558 -493.57 48.687 C -493.57 19.816 -470.17 -3.589 -441.3 -3.589 C -412.43 -3.589 -389.02 19.816 -389.02 48.687 Z M -441.3 32.69 C -450.13 32.69 -457.3 39.852 -457.3 48.687 C -457.3 57.522 -450.13 64.684 -441.3 64.684 C -432.46 64.684 -425.3 57.522 -425.3 48.687 C -425.3 39.852 -432.46 32.69 -441.3 32.69 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<path d="M 750.275 185.812 C 750.275 278.894 674.818 354.351 581.737 354.351 C 488.656 354.351 413.199 278.894 413.199 185.812 C 413.199 92.73 488.656 17.273 581.737 17.273 C 674.818 17.273 750.275 92.73 750.275 185.812 Z M 581.737 102.088 C 535.498 102.088 498.013 139.573 498.013 185.812 C 498.013 232.051 535.498 269.536 581.737 269.536 C 627.976 269.536 665.461 232.051 665.461 185.812 C 665.461 139.573 627.976 102.088 581.737 102.088 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -390.7 179.474 L 413.322 179.474 C 413.245 181.565 413.206 183.666 413.206 185.776 C 413.206 188.043 413.251 190.3 413.339 192.546 L -390.7 192.546 L -390.7 179.474 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1.001;" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -389.15 319.454 L 479.088 319.454 C 485.554 324.427 492.39 328.941 499.549 332.948 L 499.549 333.943 L -389.15 333.943 L -389.15 319.454 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1.001;" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -389.68 43.369 L 491.564 43.369 C 484.74 47.7 478.248 52.507 472.136 57.745 L -389.68 57.745 L -389.68 43.369 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1.001;" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -390.04 184.792 C -390.04 213.663 -413.45 237.068 -442.32 237.068 C -471.19 237.068 -494.59 213.663 -494.59 184.792 C -494.59 155.921 -471.19 132.516 -442.32 132.516 C -413.45 132.516 -390.04 155.921 -390.04 184.792 Z M -442.32 168.795 C -451.15 168.795 -458.32 175.957 -458.32 184.792 C -458.32 193.627 -451.15 200.789 -442.32 200.789 C -433.48 200.789 -426.32 193.627 -426.32 184.792 C -426.32 175.957 -433.48 168.795 -442.32 168.795 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -388.49 324.772 C -388.49 353.643 -411.9 377.048 -440.77 377.048 C -469.64 377.048 -493.04 353.643 -493.04 324.772 C -493.04 295.901 -469.64 272.496 -440.77 272.496 C -411.9 272.496 -388.49 295.901 -388.49 324.772 Z M -440.77 308.775 C -449.6 308.775 -456.77 315.937 -456.77 324.772 C -456.77 333.607 -449.6 340.769 -440.77 340.769 C -431.93 340.769 -424.77 333.607 -424.77 324.772 C -424.77 315.937 -431.93 308.775 -440.77 308.775 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2977.8 -1034.9 3855.57 2975.54">
|
||||||
|
<g>
|
||||||
|
<path d="M 824.006 552.792 C 824.006 747.629 666.059 905.573 471.225 905.573 C 276.39 905.573 118.446 747.629 118.446 552.792 C 118.446 357.956 276.39 200.013 471.225 200.013 C 666.059 200.013 824.006 357.956 824.006 552.792 Z M 471.225 377.544 C 374.439 377.544 295.977 456.006 295.977 552.792 C 295.977 649.578 374.439 728.041 471.225 728.041 C 568.01 728.041 646.474 649.578 646.474 552.792 C 646.474 456.006 568.01 377.544 471.225 377.544 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<g style="">
|
||||||
|
<path d="M -2800.5 722.574 C -2800.5 751.445 -2823.9 774.85 -2852.7 774.85 C -2881.6 774.85 -2905 751.445 -2905 722.574 C -2905 693.703 -2881.6 670.298 -2852.7 670.298 C -2823.9 670.298 -2800.5 693.703 -2800.5 722.574 Z M -2852.7 706.577 C -2861.6 706.577 -2868.7 713.739 -2868.7 722.574 C -2868.7 731.409 -2861.6 738.571 -2852.7 738.571 C -2843.9 738.571 -2836.7 731.409 -2836.7 722.574 C -2836.7 713.739 -2843.9 706.577 -2852.7 706.577 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -2800.5 408.918 C -2800.5 437.789 -2823.9 461.194 -2852.8 461.194 C -2881.6 461.194 -2905 437.789 -2905 408.918 C -2905 380.047 -2881.6 356.642 -2852.8 356.642 C -2823.9 356.642 -2800.5 380.047 -2800.5 408.918 Z M -2852.8 392.921 C -2861.6 392.921 -2868.8 400.083 -2868.8 408.918 C -2868.8 417.753 -2861.6 424.915 -2852.8 424.915 C -2843.9 424.915 -2836.8 417.753 -2836.8 408.918 C -2836.8 400.083 -2843.9 392.921 -2852.8 392.921 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -2800.4 827.126 C -2800.4 855.997 -2823.8 879.402 -2852.7 879.402 C -2881.6 879.402 -2905 855.997 -2905 827.126 C -2905 798.255 -2881.6 774.85 -2852.7 774.85 C -2823.8 774.85 -2800.4 798.255 -2800.4 827.126 Z M -2852.7 811.129 C -2861.5 811.129 -2868.7 818.291 -2868.7 827.126 C -2868.7 835.961 -2861.5 843.123 -2852.7 843.123 C -2843.9 843.123 -2836.7 835.961 -2836.7 827.126 C -2836.7 818.291 -2843.9 811.129 -2852.7 811.129 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -2800.5 304.366 C -2800.5 333.237 -2823.9 356.642 -2852.7 356.642 C -2881.6 356.642 -2905 333.237 -2905 304.366 C -2905 275.495 -2881.6 252.09 -2852.7 252.09 C -2823.9 252.09 -2800.5 275.495 -2800.5 304.366 Z M -2852.7 288.369 C -2861.6 288.369 -2868.7 295.531 -2868.7 304.366 C -2868.7 313.201 -2861.6 320.363 -2852.7 320.363 C -2843.9 320.363 -2836.7 313.201 -2836.7 304.366 C -2836.7 295.531 -2843.9 288.369 -2852.7 288.369 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -2800.5 513.47 C -2800.5 542.341 -2823.9 565.746 -2852.8 565.746 C -2881.6 565.746 -2905 542.341 -2905 513.47 C -2905 484.599 -2881.6 461.194 -2852.8 461.194 C -2823.9 461.194 -2800.5 484.599 -2800.5 513.47 Z M -2852.8 497.473 C -2861.6 497.473 -2868.8 504.635 -2868.8 513.47 C -2868.8 522.305 -2861.6 529.467 -2852.8 529.467 C -2843.9 529.467 -2836.8 522.305 -2836.8 513.47 C -2836.8 504.635 -2843.9 497.473 -2852.8 497.473 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -2800.4 618.022 C -2800.4 646.893 -2823.8 670.298 -2852.7 670.298 C -2881.6 670.298 -2905 646.893 -2905 618.022 C -2905 589.151 -2881.6 565.746 -2852.7 565.746 C -2823.8 565.746 -2800.4 589.151 -2800.4 618.022 Z M -2852.7 602.025 C -2861.5 602.025 -2868.7 609.187 -2868.7 618.022 C -2868.7 626.857 -2861.5 634.019 -2852.7 634.019 C -2843.9 634.019 -2836.7 626.857 -2836.7 618.022 C -2836.7 609.187 -2843.9 602.025 -2852.7 602.025 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0); stroke-width: 1;" role="contentinfo" class="Toroid">
|
||||||
|
<title>NdFeB</title>
|
||||||
|
</path>
|
||||||
|
</g>
|
||||||
|
<path d="M -2802.3 726.055 L 164.704 726.055 C 162.72 722.536 160.794 718.979 158.929 715.386 L -2802.299 715.386 L -2802.3 726.055 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -2805.1 402.382 L 152.898 402.382 C 152.189 403.886 151.491 405.396 150.803 406.912 L -2805.1 406.912 L -2805.1 402.382 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -2800.8 819.881 L 241.39 819.881 C 247.179 824.899 253.133 829.732 259.242 834.37 L -2800.8 834.37 L -2800.8 819.881 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -2802.301 297.178 L 228.777 297.178 C 227.563 298.337 226.357 299.505 225.159 300.681 L -2802.3 300.681 L -2802.301 297.178 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -2805.1 506.934 L 122.325 506.934 C 122.066 508.933 121.824 510.938 121.598 512.948 L -2805.1 512.948 L -2805.1 506.934 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<path d="M -2800.8 610.777 L 124.111 610.777 C 124.573 613.573 125.067 616.357 125.594 619.13 L -2800.8 619.13 L -2800.8 610.777 Z" style="fill: rgb(216, 216, 216); stroke: rgb(0, 0, 0);" class="Segment" role="contentinfo">
|
||||||
|
<title>Steel</title>
|
||||||
|
</path>
|
||||||
|
<rect x="-2975.8" y="-1032.9" width="3851.57" height="2971.54" style="fill: rgba(216, 216, 216, 0); stroke: rgb(0, 0, 0);" class="Block" role="contentinfo">
|
||||||
|
<title>Air</title>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 6.0 KiB |
Loading…
Reference in New Issue