work on sims and and button functions, added some more examples too
This commit is contained in:
parent
749a9d3e43
commit
ffd04c1905
|
|
@ -83,10 +83,14 @@ pub enum CanvasMessage {
|
|||
DeleteSelected,
|
||||
/// drag delta in canvas pixels accumulating into app-side pan.
|
||||
PanBy { dx: f32, dy: f32 },
|
||||
/// wheel-zoom factor focused on a canvas-pixel point.
|
||||
ZoomAt { factor: f32, focus: Point },
|
||||
/// wheel-zoom factor centred on a cursor offset measured from canvas centre.
|
||||
ZoomAt { factor: f32, focus_rel: Point },
|
||||
/// resets pan and zoom to the natural fit.
|
||||
ResetView,
|
||||
/// canvas reports its current pixel bounds for app-side view math.
|
||||
ViewportSize { width: f32, height: f32 },
|
||||
/// canvas-computed view replacement, used by zoom-window and other bounds-aware operations.
|
||||
SetView { pan: Vector, zoom: f32 },
|
||||
}
|
||||
|
||||
/// pan offset and zoom factor owned by the app shell.
|
||||
|
|
@ -107,6 +111,7 @@ pub struct CanvasState {
|
|||
cursor_world: Option<(f64, f64)>,
|
||||
cursor_canvas: Option<Point>,
|
||||
modifiers: iced::keyboard::Modifiers,
|
||||
last_reported_size: Option<(f32, f32)>,
|
||||
}
|
||||
|
||||
/// constructs the canvas widget for a doc reference, optional mesh overlay, and optional solution.
|
||||
|
|
@ -118,8 +123,11 @@ pub fn view<'a>(
|
|||
render_mode: RenderMode,
|
||||
view_state: ViewState,
|
||||
show_grid: bool,
|
||||
zoom_window_active: bool,
|
||||
) -> Element<'a, CanvasMessage> {
|
||||
Canvas::new(DocCanvas { doc, tool, mesh, solution, render_mode, view_state, show_grid })
|
||||
Canvas::new(DocCanvas {
|
||||
doc, tool, mesh, solution, render_mode, view_state, show_grid, zoom_window_active,
|
||||
})
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
|
|
@ -133,6 +141,7 @@ struct DocCanvas<'a> {
|
|||
render_mode: RenderMode,
|
||||
view_state: ViewState,
|
||||
show_grid: bool,
|
||||
zoom_window_active: bool,
|
||||
}
|
||||
|
||||
impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
||||
|
|
@ -163,7 +172,7 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
if let Some(p) = cursor.position_in(bounds) {
|
||||
state.press_origin = Some(p);
|
||||
state.dragged = false;
|
||||
if self.tool == Tool::Select {
|
||||
if self.tool == Tool::Select || self.zoom_window_active {
|
||||
state.marquee_from = Some(p);
|
||||
}
|
||||
return Some(Action::capture());
|
||||
|
|
@ -175,6 +184,22 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
let was_dragged = std::mem::take(&mut state.dragged);
|
||||
let now_opt = cursor.position_in(bounds);
|
||||
|
||||
if self.zoom_window_active {
|
||||
if let (Some(start), Some(now)) = (marquee_start, now_opt) {
|
||||
if was_dragged {
|
||||
let view = ViewTransform::fit(self.doc, bounds, &self.view_state);
|
||||
let p0 = view.inverse_map(start);
|
||||
let p1 = view.inverse_map(now);
|
||||
let (xmin, xmax) = (p0.0.min(p1.0), p0.0.max(p1.0));
|
||||
let (ymin, ymax) = (p0.1.min(p1.1), p0.1.max(p1.1));
|
||||
if let Some((pan, zoom)) = zoom_window_view(self.doc, bounds, xmin, xmax, ymin, ymax) {
|
||||
return Some(Action::publish(CanvasMessage::SetView { pan, zoom }).and_capture());
|
||||
}
|
||||
}
|
||||
}
|
||||
return Some(Action::capture());
|
||||
}
|
||||
|
||||
if self.tool == Tool::Select {
|
||||
if let (Some(start), Some(now)) = (marquee_start, now_opt) {
|
||||
let view = ViewTransform::fit(self.doc, bounds, &self.view_state);
|
||||
|
|
@ -225,6 +250,13 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
|
||||
let current_size = (bounds.width, bounds.height);
|
||||
if state.last_reported_size != Some(current_size) {
|
||||
state.last_reported_size = Some(current_size);
|
||||
return Some(Action::publish(CanvasMessage::ViewportSize {
|
||||
width: bounds.width, height: bounds.height,
|
||||
}));
|
||||
}
|
||||
if let Some(now) = cursor.position_in(bounds) {
|
||||
let view = ViewTransform::fit(self.doc, bounds, &self.view_state);
|
||||
state.cursor_world = Some(view.inverse_map(now));
|
||||
|
|
@ -265,7 +297,11 @@ impl<'a> canvas::Program<CanvasMessage> for DocCanvas<'a> {
|
|||
mouse::ScrollDelta::Pixels { y, .. } => *y / 40.0,
|
||||
};
|
||||
let factor = ZOOM_STEP.powf(lines);
|
||||
return Some(Action::publish(CanvasMessage::ZoomAt { factor, focus }).and_capture());
|
||||
let focus_rel = Point::new(
|
||||
focus.x - bounds.width * 0.5,
|
||||
focus.y - bounds.height * 0.5,
|
||||
);
|
||||
return Some(Action::publish(CanvasMessage::ZoomAt { factor, focus_rel }).and_capture());
|
||||
}
|
||||
}
|
||||
Event::Keyboard(iced::keyboard::Event::KeyPressed { key, .. }) => {
|
||||
|
|
@ -567,6 +603,36 @@ impl ViewTransform {
|
|||
}
|
||||
}
|
||||
|
||||
/// computes the pan and zoom values that frame a world rectangle inside the canvas with default padding.
|
||||
pub fn zoom_window_view(
|
||||
doc: &FemmDoc,
|
||||
bounds: Rectangle,
|
||||
xmin: f64, xmax: f64, ymin: f64, ymax: f64,
|
||||
) -> Option<(Vector, f32)> {
|
||||
let (dxmin, dxmax, dymin, dymax) = doc_bounds(doc);
|
||||
let dx = (dxmax - dxmin).max(1e-9);
|
||||
let dy = (dymax - dymin).max(1e-9);
|
||||
let avail_w = (bounds.width as f64 - 2.0 * PADDING_PX as f64).max(1.0);
|
||||
let avail_h = (bounds.height as f64 - 2.0 * PADDING_PX as f64).max(1.0);
|
||||
let base_scale = (avail_w / dx).min(avail_h / dy);
|
||||
|
||||
let rw = (xmax - xmin).max(1e-9);
|
||||
let rh = (ymax - ymin).max(1e-9);
|
||||
if !rw.is_finite() || !rh.is_finite() { return None; }
|
||||
let target_scale = (avail_w / rw).min(avail_h / rh);
|
||||
let zoom = (target_scale / base_scale) as f32;
|
||||
let zoom = zoom.clamp(ZOOM_MIN, ZOOM_MAX);
|
||||
|
||||
let doc_cx = (dxmin + dxmax) * 0.5;
|
||||
let doc_cy = (dymin + dymax) * 0.5;
|
||||
let r_cx = (xmin + xmax) * 0.5;
|
||||
let r_cy = (ymin + ymax) * 0.5;
|
||||
let scale_effective = base_scale * (zoom as f64);
|
||||
let pan_x = (doc_cx - r_cx) * scale_effective;
|
||||
let pan_y = (r_cy - doc_cy) * scale_effective;
|
||||
Some((Vector::new(pan_x as f32, pan_y as f32), zoom))
|
||||
}
|
||||
|
||||
fn doc_bounds(doc: &FemmDoc) -> (f64, f64, f64, f64) {
|
||||
let mut xmin = f64::INFINITY;
|
||||
let mut xmax = f64::NEG_INFINITY;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use femm_doc_mag::{ArcSegment, BlockLabel, FemmDoc, Node, Segment};
|
|||
use femm_doc_mag::ans::MagSolution;
|
||||
use femm_doc_mag::mesh::Mesh;
|
||||
use iced::widget::{button, column, container, row, scrollable, svg, text, text_input, tooltip};
|
||||
use iced::{Alignment, Background, Border, Color, Element, Length, Point, Subscription, Task, Theme, Vector, clipboard, time};
|
||||
use iced::{Alignment, Background, Border, Color, Element, Length, Point, Rectangle, Subscription, Task, Theme, Vector, clipboard, time};
|
||||
use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
|
@ -51,6 +51,8 @@ enum Message {
|
|||
ZoomIn,
|
||||
ZoomOut,
|
||||
ZoomFit,
|
||||
ZoomWindowToggle,
|
||||
ZoomSelection,
|
||||
ToggleGrid,
|
||||
ToggleSnap,
|
||||
SelectTool(Tool),
|
||||
|
|
@ -117,6 +119,8 @@ struct App {
|
|||
view_state: ViewState,
|
||||
show_grid: bool,
|
||||
snap_to_grid: bool,
|
||||
zoom_window_active: bool,
|
||||
canvas_size: Option<(f32, f32)>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
|
@ -136,6 +140,8 @@ impl App {
|
|||
view_state: ViewState::default(),
|
||||
show_grid: false,
|
||||
snap_to_grid: false,
|
||||
zoom_window_active: false,
|
||||
canvas_size: None,
|
||||
};
|
||||
(app, Task::none())
|
||||
}
|
||||
|
|
@ -144,6 +150,16 @@ impl App {
|
|||
format!("femm42 - {}", self.source_label)
|
||||
}
|
||||
|
||||
/// applies a zoom multiplier around a focus offset measured from canvas centre, adjusting pan to keep that world point fixed.
|
||||
fn apply_zoom_factor(&mut self, factor: f32, focus_rel: Point) {
|
||||
let prev = if self.view_state.zoom <= 0.0 { 1.0 } else { self.view_state.zoom };
|
||||
let next = (prev * factor).clamp(doc_canvas::ZOOM_MIN, doc_canvas::ZOOM_MAX);
|
||||
let real = next / prev;
|
||||
self.view_state.pan.x = focus_rel.x * (1.0 - real) + real * self.view_state.pan.x;
|
||||
self.view_state.pan.y = focus_rel.y * (1.0 - real) + real * self.view_state.pan.y;
|
||||
self.view_state.zoom = next;
|
||||
}
|
||||
|
||||
/// rounds a click world coordinate to the 1-cm grid step under snap-to-grid mode.
|
||||
fn maybe_snap(&self, w: (f64, f64)) -> (f64, f64) {
|
||||
if self.snap_to_grid {
|
||||
|
|
@ -238,16 +254,44 @@ impl App {
|
|||
self.status = format!("duplicated {added} entities, offset +10mm");
|
||||
}
|
||||
}
|
||||
Message::ZoomIn => {
|
||||
let z = if self.view_state.zoom <= 0.0 { 1.0 } else { self.view_state.zoom };
|
||||
self.view_state.zoom = (z * 1.25).clamp(doc_canvas::ZOOM_MIN, doc_canvas::ZOOM_MAX);
|
||||
}
|
||||
Message::ZoomOut => {
|
||||
let z = if self.view_state.zoom <= 0.0 { 1.0 } else { self.view_state.zoom };
|
||||
self.view_state.zoom = (z * 0.8).clamp(doc_canvas::ZOOM_MIN, doc_canvas::ZOOM_MAX);
|
||||
}
|
||||
Message::ZoomIn => self.apply_zoom_factor(1.25, Point::ORIGIN),
|
||||
Message::ZoomOut => self.apply_zoom_factor(0.80, Point::ORIGIN),
|
||||
Message::ZoomFit => {
|
||||
self.view_state = ViewState::default();
|
||||
self.zoom_window_active = false;
|
||||
}
|
||||
Message::ZoomWindowToggle => {
|
||||
self.zoom_window_active = !self.zoom_window_active;
|
||||
self.status = if self.zoom_window_active {
|
||||
String::from("zoom-window mode: drag a rectangle on the canvas")
|
||||
} else {
|
||||
String::from("zoom-window cancelled")
|
||||
};
|
||||
}
|
||||
Message::ZoomSelection => {
|
||||
let Some((cw, ch)) = self.canvas_size else {
|
||||
self.error = Some(ErrorReport {
|
||||
title: String::from("canvas size unknown"),
|
||||
body: String::from("move the cursor over the canvas once, then try Zoom Selection again."),
|
||||
});
|
||||
return Task::none();
|
||||
};
|
||||
match selection_bbox(&self.doc) {
|
||||
Some((xmin, xmax, ymin, ymax)) => {
|
||||
let rect = Rectangle { x: 0.0, y: 0.0, width: cw, height: ch };
|
||||
if let Some((pan, zoom)) = doc_canvas::zoom_window_view(&self.doc, rect, xmin, xmax, ymin, ymax) {
|
||||
self.view_state.pan = pan;
|
||||
self.view_state.zoom = zoom;
|
||||
self.status = format!("zoomed to selection bbox: x [{xmin:.3}, {xmax:.3}], y [{ymin:.3}, {ymax:.3}]");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.error = Some(ErrorReport {
|
||||
title: String::from("nothing selected"),
|
||||
body: String::from("select at least one node, segment, arc, or label, then click Zoom Selection."),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::ToggleGrid => {
|
||||
self.show_grid = !self.show_grid;
|
||||
|
|
@ -379,16 +423,20 @@ impl App {
|
|||
Message::Canvas(CanvasMessage::PanBy { dx, dy }) => {
|
||||
self.view_state.pan = self.view_state.pan + Vector::new(dx, dy);
|
||||
}
|
||||
Message::Canvas(CanvasMessage::ZoomAt { factor, focus }) => {
|
||||
let prev = if self.view_state.zoom <= 0.0 { 1.0 } else { self.view_state.zoom };
|
||||
let next = (prev * factor).clamp(doc_canvas::ZOOM_MIN, doc_canvas::ZOOM_MAX);
|
||||
let real = next / prev;
|
||||
self.view_state.pan.x = focus.x - real * (focus.x - self.view_state.pan.x);
|
||||
self.view_state.pan.y = focus.y - real * (focus.y - self.view_state.pan.y);
|
||||
self.view_state.zoom = next;
|
||||
Message::Canvas(CanvasMessage::ZoomAt { factor, focus_rel }) => {
|
||||
self.apply_zoom_factor(factor, focus_rel);
|
||||
}
|
||||
Message::Canvas(CanvasMessage::ResetView) => {
|
||||
self.view_state = ViewState::default();
|
||||
self.zoom_window_active = false;
|
||||
}
|
||||
Message::Canvas(CanvasMessage::ViewportSize { width, height }) => {
|
||||
self.canvas_size = Some((width, height));
|
||||
}
|
||||
Message::Canvas(CanvasMessage::SetView { pan, zoom }) => {
|
||||
self.view_state.pan = pan;
|
||||
self.view_state.zoom = zoom;
|
||||
self.zoom_window_active = false;
|
||||
}
|
||||
Message::Canvas(CanvasMessage::PickAt { world, pick_radius_world, op, restrict }) => {
|
||||
let summary = apply_pick_at(&mut self.doc, world.0, world.1, pick_radius_world, op, restrict);
|
||||
|
|
@ -402,13 +450,13 @@ impl App {
|
|||
self.render_mode = mode;
|
||||
}
|
||||
Message::SimAddTrackFromSelection => {
|
||||
let selected: Vec<usize> = self.doc.nodes.iter().enumerate()
|
||||
.filter_map(|(i, n)| if n.selected { Some(i) } else { None })
|
||||
let sel_segs: Vec<usize> = self.doc.segments.iter().enumerate()
|
||||
.filter_map(|(i, s)| if s.selected { Some(i) } else { None })
|
||||
.collect();
|
||||
if selected.is_empty() {
|
||||
if sel_segs.is_empty() {
|
||||
self.error = Some(ErrorReport {
|
||||
title: String::from("no nodes selected"),
|
||||
body: String::from("select at least 3 nodes: two anchors and one or more members the track will displace."),
|
||||
title: String::from("no segments selected"),
|
||||
body: String::from("select at least one segment (left-click or marquee). the track moves every node along selected segments between their leftmost and rightmost endpoints."),
|
||||
});
|
||||
} else {
|
||||
let subdivisions = self.simulation.as_ref()
|
||||
|
|
@ -417,17 +465,14 @@ impl App {
|
|||
let expr_text = self.simulation.as_ref()
|
||||
.map(|s| s.expression_text.clone())
|
||||
.unwrap_or_else(|| String::from(SIM_DEFAULT_EXPRESSION));
|
||||
// subdivides every selected segment with both endpoints selected, producing interior nodes for chord displacement.
|
||||
let seg_indices: Vec<usize> = self.doc.segments.iter().enumerate()
|
||||
.filter_map(|(i, s)| {
|
||||
if self.doc.nodes.get(s.n0 as usize).map(|n| n.selected).unwrap_or(false)
|
||||
&& self.doc.nodes.get(s.n1 as usize).map(|n| n.selected).unwrap_or(false) {
|
||||
Some(i)
|
||||
} else { None }
|
||||
})
|
||||
.collect();
|
||||
let mut all_members: Vec<usize> = selected.clone();
|
||||
let mut to_subdivide = seg_indices;
|
||||
let mut endpoint_nodes: Vec<usize> = Vec::new();
|
||||
for &i in &sel_segs {
|
||||
let seg = &self.doc.segments[i];
|
||||
if !endpoint_nodes.contains(&(seg.n0 as usize)) { endpoint_nodes.push(seg.n0 as usize); }
|
||||
if !endpoint_nodes.contains(&(seg.n1 as usize)) { endpoint_nodes.push(seg.n1 as usize); }
|
||||
}
|
||||
let mut all_members: Vec<usize> = endpoint_nodes.clone();
|
||||
let mut to_subdivide = sel_segs;
|
||||
to_subdivide.sort_by(|a, b| b.cmp(a));
|
||||
for idx in to_subdivide {
|
||||
let new_nodes = self.doc.subdivide_segment(idx, subdivisions);
|
||||
|
|
@ -561,13 +606,29 @@ impl App {
|
|||
}
|
||||
}
|
||||
Message::SimToggleRun => {
|
||||
if let Some(sim) = self.simulation.as_mut() {
|
||||
sim.running = !sim.running;
|
||||
self.status = if sim.running {
|
||||
format!("running: t = {}", spice::format_spice_time(sim.t_now))
|
||||
} else {
|
||||
format!("paused: t = {}", spice::format_spice_time(sim.t_now))
|
||||
};
|
||||
let Some(sim) = self.simulation.as_mut() else {
|
||||
self.error = Some(ErrorReport {
|
||||
title: String::from("no simulation"),
|
||||
body: String::from("create at least one track before running. select a segment and click \"Track from Selection\"."),
|
||||
});
|
||||
return Task::none();
|
||||
};
|
||||
if sim.tracks.is_empty() {
|
||||
self.error = Some(ErrorReport {
|
||||
title: String::from("no tracks defined"),
|
||||
body: String::from("the simulation has no tracks to displace. select a segment, click \"Track from Selection\", then Run."),
|
||||
});
|
||||
return Task::none();
|
||||
}
|
||||
sim.running = !sim.running;
|
||||
let status = if sim.running {
|
||||
format!("running: t = {}", spice::format_spice_time(sim.t_now))
|
||||
} else {
|
||||
format!("paused: t = {}", spice::format_spice_time(sim.t_now))
|
||||
};
|
||||
self.status = status;
|
||||
if sim.running {
|
||||
return Task::done(Message::SimTick);
|
||||
}
|
||||
}
|
||||
Message::SimTick => {
|
||||
|
|
@ -735,14 +796,20 @@ impl App {
|
|||
|
||||
let grid_tip = if self.show_grid { "Toggle grid (on)" } else { "Toggle grid (off)" };
|
||||
let snap_tip = if self.snap_to_grid { "Toggle grid snap (on)" } else { "Toggle grid snap (off)" };
|
||||
let zoom_window_tip = if self.zoom_window_active {
|
||||
"Zoom to window: drag a rectangle (click again to cancel)"
|
||||
} else {
|
||||
"Zoom to window"
|
||||
};
|
||||
let view_strip = column![
|
||||
icon_button(ICON_ZOOM_IN, "Zoom in", Some(Message::ZoomIn)),
|
||||
icon_button(ICON_ZOOM_OUT, "Zoom out", Some(Message::ZoomOut)),
|
||||
icon_button(ICON_ZOOM_FIT, "Zoom to fit", Some(Message::ZoomFit)),
|
||||
icon_button(ICON_ZOOM_WINDOW, "Zoom to window (rubber-band not yet implemented)", None),
|
||||
strip_icon_button(ICON_ZOOM_IN, "Zoom in (scroll up)", Some(Message::ZoomIn)),
|
||||
strip_icon_button(ICON_ZOOM_OUT, "Zoom out (scroll down)", Some(Message::ZoomOut)),
|
||||
strip_icon_button(ICON_ZOOM_FIT, "Zoom to fit", Some(Message::ZoomFit)),
|
||||
strip_icon_button(ICON_ZOOM_WINDOW, zoom_window_tip, Some(Message::ZoomWindowToggle)),
|
||||
strip_icon_button(ICON_ZOOM_FIT, "Zoom to selection", Some(Message::ZoomSelection)),
|
||||
horizontal_separator(),
|
||||
icon_button(ICON_GRID, grid_tip, Some(Message::ToggleGrid)),
|
||||
icon_button(ICON_GRID_SNAP, snap_tip, Some(Message::ToggleSnap)),
|
||||
strip_icon_button(ICON_GRID, grid_tip, Some(Message::ToggleGrid)),
|
||||
strip_icon_button(ICON_GRID_SNAP, snap_tip, Some(Message::ToggleSnap)),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_x(Alignment::Center);
|
||||
|
|
@ -753,7 +820,7 @@ impl App {
|
|||
|
||||
let canvas = doc_canvas::view(
|
||||
&self.doc, self.tool, self.mesh.as_ref(), active_solution, self.render_mode,
|
||||
self.view_state, self.show_grid,
|
||||
self.view_state, self.show_grid, self.zoom_window_active,
|
||||
).map(Message::Canvas);
|
||||
|
||||
let canvas_row = row![canvas, view_strip].spacing(6);
|
||||
|
|
@ -1238,6 +1305,40 @@ fn offset_for_unit(unit: femm_doc_mag::LengthUnit) -> (f64, f64) {
|
|||
(v, v)
|
||||
}
|
||||
|
||||
/// computes the (xmin, xmax, ymin, ymax) bounding box of every selected entity, or None when nothing is selected.
|
||||
fn selection_bbox(doc: &FemmDoc) -> Option<(f64, f64, f64, f64)> {
|
||||
let mut xmin = f64::INFINITY;
|
||||
let mut xmax = f64::NEG_INFINITY;
|
||||
let mut ymin = f64::INFINITY;
|
||||
let mut ymax = f64::NEG_INFINITY;
|
||||
let mut hit = false;
|
||||
let mut grow = |x: f64, y: f64| {
|
||||
if x < xmin { xmin = x; } if x > xmax { xmax = x; }
|
||||
if y < ymin { ymin = y; } if y > ymax { ymax = y; }
|
||||
};
|
||||
for n in &doc.nodes {
|
||||
if n.selected { grow(n.x, n.y); hit = true; }
|
||||
}
|
||||
for s in &doc.segments {
|
||||
if s.selected {
|
||||
if let (Some(a), Some(b)) = (doc.nodes.get(s.n0 as usize), doc.nodes.get(s.n1 as usize)) {
|
||||
grow(a.x, a.y); grow(b.x, b.y); hit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for a in &doc.arcs {
|
||||
if a.selected {
|
||||
if let (Some(p0), Some(p1)) = (doc.nodes.get(a.n0 as usize), doc.nodes.get(a.n1 as usize)) {
|
||||
grow(p0.x, p0.y); grow(p1.x, p1.y); hit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for l in &doc.block_labels {
|
||||
if l.selected { grow(l.x, l.y); hit = true; }
|
||||
}
|
||||
if hit { Some((xmin, xmax, ymin, ymax)) } else { None }
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Kind { Node, Segment, Arc, Label }
|
||||
|
||||
|
|
@ -1345,6 +1446,19 @@ fn apply_pick_rect(doc: &mut FemmDoc, p0: (f64, f64), p1: (f64, f64), op: PickOp
|
|||
format!("{verb}: {n_nodes} nodes, {n_segs} segs, {n_arcs} arcs, {n_labs} labels")
|
||||
}
|
||||
|
||||
/// renders an SVG icon button with its tooltip placed to the left, suitable for a vertical right-edge strip.
|
||||
fn strip_icon_button<'a>(svg_bytes: &'static [u8], tip: &'a str, msg: Option<Message>) -> Element<'a, Message> {
|
||||
let handle = svg::Handle::from_memory(svg_bytes);
|
||||
let glyph = svg(handle)
|
||||
.width(Length::Fixed(ICON_PX))
|
||||
.height(Length::Fixed(ICON_PX));
|
||||
let mut btn = button(glyph).padding(4).style(button::secondary);
|
||||
if let Some(m) = msg {
|
||||
btn = btn.on_press(m);
|
||||
}
|
||||
tooltip(btn, text(tip).size(11), tooltip::Position::Left).into()
|
||||
}
|
||||
|
||||
/// renders an SVG icon inside a square button with a hover tooltip.
|
||||
fn icon_button<'a>(svg_bytes: &'static [u8], tip: &'a str, msg: Option<Message>) -> Element<'a, Message> {
|
||||
let handle = svg::Handle::from_memory(svg_bytes);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
[LengthUnits] = millimeters
|
||||
[ProblemType] = planar
|
||||
[Coordinates] = cartesian
|
||||
[Comment] = "guitar pickup geometry: 25.5-inch scale, 6 plain steel strings (0.010-0.046 inch, real gauges), NdFeB bar magnets above and below the string plane with opposite faces presented."
|
||||
[Comment] = "guitar pickup geometry: 25.5-inch scale, 6 plain steel strings at real gauges 0.010-0.046 inch, NdFeB bar magnets at the neck and bridge ends. Both magnets present their N face inward toward the strings so flux is injected into each end of every string and cancels at the midpoint."
|
||||
[PointProps] = 0
|
||||
[BdryProps] = 1
|
||||
<BeginBdry>
|
||||
|
|
@ -83,85 +83,97 @@
|
|||
<EndBlock>
|
||||
[CircuitProps] = 0
|
||||
[NumPoints] = 36
|
||||
-50 -80 0 0
|
||||
700 -80 0 0
|
||||
700 80 0 0
|
||||
-50 80 0 0
|
||||
0 30 0 0
|
||||
647.7 30 0 0
|
||||
647.7 35 0 0
|
||||
0 35 0 0
|
||||
0 -35 0 0
|
||||
647.7 -35 0 0
|
||||
647.7 -30 0 0
|
||||
-100 -100 0 0
|
||||
750 -100 0 0
|
||||
750 100 0 0
|
||||
-100 100 0 0
|
||||
-30 -30 0 0
|
||||
-30 30 0 0
|
||||
0 -30 0 0
|
||||
0 -26.377 0 0
|
||||
647.7 -26.377 0 0
|
||||
647.7 -26.123 0 0
|
||||
0 -26.123 0 0
|
||||
0 -15.915 0 0
|
||||
647.7 -15.915 0 0
|
||||
647.7 -15.585 0 0
|
||||
0 -15.585 0 0
|
||||
0 -5.466 0 0
|
||||
647.7 -5.466 0 0
|
||||
647.7 -5.034 0 0
|
||||
0 -5.034 0 0
|
||||
0 4.920 0 0
|
||||
647.7 4.920 0 0
|
||||
647.7 5.580 0 0
|
||||
0 5.580 0 0
|
||||
0 15.293 0 0
|
||||
647.7 15.293 0 0
|
||||
647.7 16.207 0 0
|
||||
0 16.207 0 0
|
||||
0 25.666 0 0
|
||||
0 26.834 0 0
|
||||
0 30 0 0
|
||||
647.7 -30 0 0
|
||||
647.7 -26.377 0 0
|
||||
647.7 -26.123 0 0
|
||||
647.7 -15.915 0 0
|
||||
647.7 -15.585 0 0
|
||||
647.7 -5.466 0 0
|
||||
647.7 -5.034 0 0
|
||||
647.7 4.920 0 0
|
||||
647.7 5.580 0 0
|
||||
647.7 15.293 0 0
|
||||
647.7 16.207 0 0
|
||||
647.7 25.666 0 0
|
||||
647.7 26.834 0 0
|
||||
0 26.834 0 0
|
||||
[NumSegments] = 36
|
||||
647.7 30 0 0
|
||||
677.7 -30 0 0
|
||||
677.7 30 0 0
|
||||
[NumSegments] = 48
|
||||
0 1 -1 1 0 0
|
||||
1 2 -1 1 0 0
|
||||
2 3 -1 1 0 0
|
||||
3 0 -1 1 0 0
|
||||
4 5 -1 0 0 0
|
||||
5 6 -1 0 0 0
|
||||
5 19 -1 0 0 0
|
||||
4 6 -1 0 0 0
|
||||
6 7 -1 0 0 0
|
||||
7 4 -1 0 0 0
|
||||
7 8 -1 0 0 0
|
||||
8 9 -1 0 0 0
|
||||
9 10 -1 0 0 0
|
||||
10 11 -1 0 0 0
|
||||
11 8 -1 0 0 0
|
||||
11 12 -1 0 0 0
|
||||
12 13 -1 0 0 0
|
||||
13 14 -1 0 0 0
|
||||
14 15 -1 0 0 0
|
||||
15 12 -1 0 0 0
|
||||
15 16 -1 0 0 0
|
||||
16 17 -1 0 0 0
|
||||
17 18 -1 0 0 0
|
||||
18 19 -1 0 0 0
|
||||
19 16 -1 0 0 0
|
||||
20 21 -1 0 0 0
|
||||
21 22 -1 0 0 0
|
||||
22 23 -1 0 0 0
|
||||
23 20 -1 0 0 0
|
||||
23 24 -1 0 0 0
|
||||
24 25 -1 0 0 0
|
||||
25 26 -1 0 0 0
|
||||
26 27 -1 0 0 0
|
||||
27 24 -1 0 0 0
|
||||
27 28 -1 0 0 0
|
||||
28 29 -1 0 0 0
|
||||
29 30 -1 0 0 0
|
||||
30 31 -1 0 0 0
|
||||
31 28 -1 0 0 0
|
||||
31 32 -1 0 0 0
|
||||
32 33 -1 0 0 0
|
||||
33 34 -1 0 0 0
|
||||
20 34 -1 0 0 0
|
||||
34 35 -1 0 0 0
|
||||
35 32 -1 0 0 0
|
||||
33 35 -1 0 0 0
|
||||
7 21 -1 0 0 0
|
||||
8 22 -1 0 0 0
|
||||
9 23 -1 0 0 0
|
||||
10 24 -1 0 0 0
|
||||
11 25 -1 0 0 0
|
||||
12 26 -1 0 0 0
|
||||
13 27 -1 0 0 0
|
||||
14 28 -1 0 0 0
|
||||
15 29 -1 0 0 0
|
||||
16 30 -1 0 0 0
|
||||
17 31 -1 0 0 0
|
||||
18 32 -1 0 0 0
|
||||
[NumArcSegments] = 0
|
||||
[NumHoles] = 0
|
||||
[NumBlockLabels] = 9
|
||||
325 70 1 -1 0 0 0 1 2
|
||||
325 32.5 2 5 0 270 0 1 0
|
||||
325 -32.5 2 5 0 270 0 1 0
|
||||
325 75 1 -1 0 0 0 1 2
|
||||
-15 0 2 5 0 0 0 1 0
|
||||
662.7 0 2 5 0 180 0 1 0
|
||||
325 -26.25 3 1 0 0 0 1 0
|
||||
325 -15.75 3 1 0 0 0 1 0
|
||||
325 -5.25 3 1 0 0 0 1 0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
[Format] = 4.0
|
||||
[Frequency] = 0
|
||||
[Precision] = 1e-008
|
||||
[MinAngle] = 25
|
||||
[Depth] = 5
|
||||
[LengthUnits] = millimeters
|
||||
[ProblemType] = planar
|
||||
[Coordinates] = cartesian
|
||||
[Comment] = "single low-E steel string (1.168 mm gauge, 25.5-inch scale). NdFeB bar magnets touch each end of the string with N faces facing inward. Flux is injected from both ends, the two flows cancel at the midpoint, producing a null point in the centre of the rod."
|
||||
[PointProps] = 0
|
||||
[BdryProps] = 1
|
||||
<BeginBdry>
|
||||
<BdryName> = "A=0"
|
||||
<BdryType> = 0
|
||||
<A_0> = 0
|
||||
<A_1> = 0
|
||||
<A_2> = 0
|
||||
<Phi> = 0
|
||||
<c0> = 0
|
||||
<c0i> = 0
|
||||
<c1> = 0
|
||||
<c1i> = 0
|
||||
<Mu_ssd> = 0
|
||||
<Sigma_ssd> = 0
|
||||
<EndBdry>
|
||||
[BlockProps] = 3
|
||||
<BeginBlock>
|
||||
<BlockName> = "Air"
|
||||
<Mu_x> = 1
|
||||
<Mu_y> = 1
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "NdFeB"
|
||||
<Mu_x> = 1.05
|
||||
<Mu_y> = 1.05
|
||||
<H_c> = 915000
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0.667
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "Steel"
|
||||
<Mu_x> = 2500
|
||||
<Mu_y> = 2500
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 5.8
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
[CircuitProps] = 0
|
||||
[NumPoints] = 16
|
||||
-100 -30 0 0
|
||||
750 -30 0 0
|
||||
750 30 0 0
|
||||
-100 30 0 0
|
||||
-30 -5 0 0
|
||||
-30 5 0 0
|
||||
0 -5 0 0
|
||||
0 -0.584 0 0
|
||||
0 0.584 0 0
|
||||
0 5 0 0
|
||||
647.7 -5 0 0
|
||||
647.7 -0.584 0 0
|
||||
647.7 0.584 0 0
|
||||
647.7 5 0 0
|
||||
677.7 5 0 0
|
||||
677.7 -5 0 0
|
||||
[NumSegments] = 18
|
||||
0 1 -1 1 0 0
|
||||
1 2 -1 1 0 0
|
||||
2 3 -1 1 0 0
|
||||
3 0 -1 1 0 0
|
||||
4 5 -1 0 0 0
|
||||
5 9 -1 0 0 0
|
||||
4 6 -1 0 0 0
|
||||
6 7 -1 0 0 0
|
||||
7 8 -1 0 0 0
|
||||
8 9 -1 0 0 0
|
||||
10 11 -1 0 0 0
|
||||
11 12 -1 0 0 0
|
||||
12 13 -1 0 0 0
|
||||
13 14 -1 0 0 0
|
||||
14 15 -1 0 0 0
|
||||
15 10 -1 0 0 0
|
||||
7 11 -1 0 0 0
|
||||
8 12 -1 0 0 0
|
||||
[NumArcSegments] = 0
|
||||
[NumHoles] = 0
|
||||
[NumBlockLabels] = 4
|
||||
325 20 1 -1 0 0 0 1 2
|
||||
-15 0 2 5 0 0 0 1 0
|
||||
662.7 0 2 5 0 180 0 1 0
|
||||
325 0 3 1 0 0 0 1 0
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
[Format] = 4.0
|
||||
[Frequency] = 0
|
||||
[Precision] = 1e-008
|
||||
[MinAngle] = 25
|
||||
[Depth] = 5
|
||||
[LengthUnits] = millimeters
|
||||
[ProblemType] = planar
|
||||
[Coordinates] = cartesian
|
||||
[Comment] = "single 18 AWG steel wire (1.024 mm diameter) acting as a solenoid core, wrapped above and below by counter-circulating coil regions at an exaggerated current density chosen to produce a polarising field on the order of NdFeB remanence."
|
||||
[PointProps] = 0
|
||||
[BdryProps] = 1
|
||||
<BeginBdry>
|
||||
<BdryName> = "A=0"
|
||||
<BdryType> = 0
|
||||
<A_0> = 0
|
||||
<A_1> = 0
|
||||
<A_2> = 0
|
||||
<Phi> = 0
|
||||
<c0> = 0
|
||||
<c0i> = 0
|
||||
<c1> = 0
|
||||
<c1i> = 0
|
||||
<Mu_ssd> = 0
|
||||
<Sigma_ssd> = 0
|
||||
<EndBdry>
|
||||
[BlockProps] = 4
|
||||
<BeginBlock>
|
||||
<BlockName> = "Air"
|
||||
<Mu_x> = 1
|
||||
<Mu_y> = 1
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "Steel"
|
||||
<Mu_x> = 2500
|
||||
<Mu_y> = 2500
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 5.8
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "J+"
|
||||
<Mu_x> = 1
|
||||
<Mu_y> = 1
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 50
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "J-"
|
||||
<Mu_x> = 1
|
||||
<Mu_y> = 1
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = -50
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
[CircuitProps] = 0
|
||||
[NumPoints] = 16
|
||||
-130 -20 0 0
|
||||
130 -20 0 0
|
||||
130 20 0 0
|
||||
-130 20 0 0
|
||||
-100 -0.512 0 0
|
||||
100 -0.512 0 0
|
||||
100 0.512 0 0
|
||||
-100 0.512 0 0
|
||||
-100 1 0 0
|
||||
100 1 0 0
|
||||
100 6 0 0
|
||||
-100 6 0 0
|
||||
-100 -6 0 0
|
||||
100 -6 0 0
|
||||
100 -1 0 0
|
||||
-100 -1 0 0
|
||||
[NumSegments] = 16
|
||||
0 1 -1 1 0 0
|
||||
1 2 -1 1 0 0
|
||||
2 3 -1 1 0 0
|
||||
3 0 -1 1 0 0
|
||||
4 5 -1 0 0 0
|
||||
5 6 -1 0 0 0
|
||||
6 7 -1 0 0 0
|
||||
7 4 -1 0 0 0
|
||||
8 9 -1 0 0 0
|
||||
9 10 -1 0 0 0
|
||||
10 11 -1 0 0 0
|
||||
11 8 -1 0 0 0
|
||||
12 13 -1 0 0 0
|
||||
13 14 -1 0 0 0
|
||||
14 15 -1 0 0 0
|
||||
15 12 -1 0 0 0
|
||||
[NumArcSegments] = 0
|
||||
[NumHoles] = 0
|
||||
[NumBlockLabels] = 4
|
||||
0 15 1 -1 0 0 0 1 2
|
||||
0 0 2 0.5 0 0 0 1 0
|
||||
0 3.5 3 2 0 0 0 1 0
|
||||
0 -3.5 4 2 0 0 0 1 0
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
[Format] = 4.0
|
||||
[Frequency] = 1
|
||||
[Precision] = 1e-008
|
||||
[MinAngle] = 30
|
||||
[Depth] = 39.370078740157481
|
||||
[LengthUnits] = inches
|
||||
[ProblemType] = planar
|
||||
[Coordinates] = cartesian
|
||||
[Comment] = "Add comments here."
|
||||
[PointProps] = 0
|
||||
[BdryProps] = 1
|
||||
<BeginBdry>
|
||||
<BdryName> = "A=0"
|
||||
<BdryType> = 0
|
||||
<A_0> = 0
|
||||
<A_1> = 0
|
||||
<A_2> = 0
|
||||
<Phi> = 0
|
||||
<c0> = 0
|
||||
<c1> = 0
|
||||
<Mu_ssd> = 0
|
||||
<Sigma_ssd> = 0
|
||||
<EndBdry>
|
||||
[BlockProps] = 4
|
||||
<BeginBlock>
|
||||
<BlockName> = "Air"
|
||||
<Mu_x> = 1
|
||||
<Mu_y> = 1
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 0
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "Iron"
|
||||
<Mu_x> = 1000
|
||||
<Mu_y> = 1000
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 10
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "Copper"
|
||||
<Mu_x> = 1
|
||||
<Mu_y> = 1
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 58
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 0
|
||||
<EndBlock>
|
||||
<BeginBlock>
|
||||
<BlockName> = "M-19 Steel"
|
||||
<Mu_x> = 4416
|
||||
<Mu_y> = 4416
|
||||
<H_c> = 0
|
||||
<H_cAngle> = 0
|
||||
<J_re> = 0
|
||||
<J_im> = 0
|
||||
<Sigma> = 3
|
||||
<d_lam> = 0
|
||||
<Phi_h> = 0
|
||||
<Phi_hx> = 0
|
||||
<Phi_hy> = 0
|
||||
<LamType> = 0
|
||||
<LamFill> = 1
|
||||
<NStrands> = 0
|
||||
<WireD> = 0
|
||||
<BHPoints> = 13
|
||||
0 0
|
||||
0.29999999999999999 39.78875
|
||||
0.80000000000000004 79.577500000000001
|
||||
1.1200000000000001 159.155
|
||||
1.3200000000000001 318.31
|
||||
1.46 795.77499999999998
|
||||
1.54 1591.55
|
||||
1.6187499999999999 3376.6669999999999
|
||||
1.74 7957.75
|
||||
1.8700000000000001 15915.5
|
||||
1.99 31831
|
||||
2.0459640000000001 55102.040000000001
|
||||
2.0800000000000001 79577.5
|
||||
<EndBlock>
|
||||
[CircuitProps] = 2
|
||||
<BeginCircuit>
|
||||
<CircuitName> = "Current"
|
||||
<TotalAmps_re> = 100
|
||||
<TotalAmps_im> = 0
|
||||
<CircuitType> = 0
|
||||
<EndCircuit>
|
||||
<BeginCircuit>
|
||||
<CircuitName> = "Zero Net Current"
|
||||
<TotalAmps_re> = 0
|
||||
<TotalAmps_im> = 0
|
||||
<CircuitType> = 0
|
||||
<EndCircuit>
|
||||
[NumPoints] = 6
|
||||
0.5 0 0 0
|
||||
1 0 0 0
|
||||
-0.5 6.1230317691118863e-017 0 0
|
||||
-1 1.2246063538223773e-016 0 0
|
||||
0.10000000000000001 0 0 0
|
||||
-0.10000000000000001 0 0 0
|
||||
[NumSegments] = 0
|
||||
[NumArcSegments] = 6
|
||||
0 2 180 5 0 0 0
|
||||
1 3 180 5 1 0 0
|
||||
2 0 180 5 0 0 0
|
||||
3 1 180 5 1 0 0
|
||||
4 5 180 10 0 0 0
|
||||
5 4 180 10 0 0 0
|
||||
[NumHoles] = 0
|
||||
[NumBlockLabels] = 3
|
||||
0 0.75 2 0.025000000000000001 2 0 0 1 0
|
||||
0 0.34999999999999998 1 0.025000000000000001 0 0 0 1 0
|
||||
0 0 3 0.025000000000000001 1 0 0 1 0
|
||||
Loading…
Reference in New Issue