From f10c20a1e16e341b921c9cb7cbd0edfabaf20a8e Mon Sep 17 00:00:00 2001 From: Sameer Puri Date: Thu, 25 Apr 2019 16:40:26 -0500 Subject: [PATCH] Use absolute positioning everywhere, correct mirroring implementation --- src/machine.rs | 63 +++++---- src/main.rs | 363 ++++++++++++------------------------------------ src/turtle.rs | 366 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 484 insertions(+), 308 deletions(-) create mode 100644 src/turtle.rs diff --git a/src/machine.rs b/src/machine.rs index 0a65013..8934728 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -1,8 +1,9 @@ -use super::code::*; +use crate::code::*; +/// Generic machine state simulation, assuming nothing is known about the machine when initialized. pub struct Machine { - tool_state: Tool, - distance_mode: Distance, + tool_state: Option, + distance_mode: Option, pub tool_on_action: Program, pub tool_off_action: Program, } @@ -10,8 +11,8 @@ pub struct Machine { impl Default for Machine { fn default() -> Self { Self { - tool_state: Tool::Off, - distance_mode: Distance::Absolute, + tool_state: None, + distance_mode: None, tool_on_action: vec![], tool_off_action: vec![], } @@ -19,45 +20,47 @@ impl Default for Machine { } impl Machine { - pub fn tool_on(&mut self, p: &mut Program) { - if self.tool_state == Tool::Off { - self.tool_on_action - .iter() - .map(Clone::clone) - .for_each(|x| p.push(x)); + pub fn tool_on(&mut self) -> Program { + if self.tool_state == Some(Tool::Off) || self.tool_state == None { + self.tool_state = Some(Tool::On); + self.tool_on_action.clone() + } else { + vec![] } - self.tool_state = Tool::On; } - pub fn tool_off(&mut self, p: &mut Program) { - if self.tool_state == Tool::On { - self.tool_off_action - .iter() - .map(Clone::clone) - .for_each(|x| p.push(x)); + pub fn tool_off(&mut self) -> Program { + if self.tool_state == Some(Tool::On) || self.tool_state == None { + self.tool_state = Some(Tool::Off); + self.tool_off_action.clone() + } else { + vec![] } - self.tool_state = Tool::Off; } - pub fn distance(&mut self, p: &mut Program, is_absolute: bool) { + pub fn distance(&mut self, is_absolute: bool) -> Program { if is_absolute { - self.absolute(p); + self.absolute() } else { - self.incremental(p); + self.incremental() } } - pub fn absolute(&mut self, p: &mut Program) { - if self.distance_mode == Distance::Incremental { - p.push(GCode::DistanceMode(Distance::Absolute)); + pub fn absolute(&mut self) -> Program { + if self.distance_mode == Some(Distance::Incremental) || self.distance_mode == None { + self.distance_mode = Some(Distance::Absolute); + vec![GCode::DistanceMode(Distance::Absolute)] + } else { + vec![] } - self.distance_mode = Distance::Absolute; } - pub fn incremental(&mut self, p: &mut Program) { - if self.distance_mode == Distance::Absolute { - p.push(GCode::DistanceMode(Distance::Incremental)); + pub fn incremental(&mut self) -> Program { + if self.distance_mode == Some(Distance::Absolute) || self.distance_mode == None { + self.distance_mode = Some(Distance::Incremental); + vec![GCode::DistanceMode(Distance::Incremental)] + } else { + vec![] } - self.distance_mode = Distance::Incremental; } } diff --git a/src/main.rs b/src/main.rs index 369e7c4..d58f2e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,17 +10,19 @@ use std::env; use std::fs::File; use std::io::{self, Read}; -use lyon_geom::math; +use lyon_geom::{math, euclid}; use svgdom::{AttributeId, AttributeValue, ElementId, ElementType, PathSegment}; mod code; mod machine; +mod turtle; use code::*; use machine::*; +use turtle::*; fn main() -> io::Result<()> { - if let Err(_) = env::var("RUST_LOG") { + if env::var("RUST_LOG").is_err() { env::set_var("RUST_LOG", "svg2gcode=info") } env_logger::init(); @@ -92,26 +94,22 @@ struct ProgramOptions { impl Default for ProgramOptions { fn default() -> Self { ProgramOptions { - tolerance: 0.1, + tolerance: 0.001, feedrate: 3000.0, dpi: 72.0, } } } -fn svg2program(doc: &svgdom::Document, opts: ProgramOptions, mut mach: Machine) -> Program { - let mut current_transform = lyon_geom::euclid::Transform2D::create_scale(1.0, -1.0) - .post_translate(math::vector(0.0, 2.0)); - let mut transform_stack = vec![]; - +fn svg2program(doc: &svgdom::Document, opts: ProgramOptions, mach: Machine) -> Program { let mut p = Program::new(); + let mut t = Turtle::default(); + t.mach = mach; + p.push(GCode::UnitsMillimeters); - mach.tool_off(&mut p); - p.push(GCode::RapidPositioning { - x: 0.0.into(), - y: 0.0.into(), - }); - mach.tool_on(&mut p); + p.extend(t.mach.tool_off()); + p.extend(t.move_to(true, 0.0, 0.0)); + p.extend(t.mach.tool_on()); for edge in doc.root().traverse() { let (node, is_start) = match edge { @@ -131,24 +129,26 @@ fn svg2program(doc: &svgdom::Document, opts: ProgramOptions, mut mach: Machine) ) { let width_in_mm = length_to_mm(width, opts.dpi); let height_in_mm = length_to_mm(height, opts.dpi); - current_transform = - current_transform.post_mul(&lyon_geom::euclid::Transform2D::create_scale( + t.set_scaling( + euclid::Transform2D::create_scale( width_in_mm / width.num, height_in_mm / height.num, - )); + ) + .post_translate(math::vector(0.0, height_in_mm)), + ); } } if let (ElementId::G, true) = (id, is_start) { p.push(GCode::Named(Box::new(node.id().to_string()))); } - if let Some(&AttributeValue::Transform(ref t)) = attrs.get_value(AttributeId::Transform) { + if let Some(&AttributeValue::Transform(ref trans)) = attrs.get_value(AttributeId::Transform) + { if is_start { - transform_stack.push(current_transform); - current_transform = current_transform.post_mul( - &lyon_geom::euclid::Transform2D::row_major(t.a, t.b, t.c, t.d, t.e, t.f), - ); + t.push_transform(lyon_geom::euclid::Transform2D::row_major( + trans.a, trans.b, trans.c, trans.d, trans.e, trans.f, + )); } else { - current_transform = transform_stack.pop().unwrap(); + t.pop_transform(); } } if node.is_graphic() && is_start { @@ -156,121 +156,24 @@ fn svg2program(doc: &svgdom::Document, opts: ProgramOptions, mut mach: Machine) ElementId::Path => { if let Some(&AttributeValue::Path(ref path)) = attrs.get_value(AttributeId::D) { p.push(GCode::Named(Box::new(node.id().to_string()))); - let mut curpos = math::point(0.0, 0.0); - curpos = current_transform.transform_point(&curpos); - let mut prev_ctrl = curpos; + t.reset(); for segment in path.iter() { match segment { PathSegment::MoveTo { abs, x, y } => { - mach.tool_off(&mut p); - mach.distance(&mut p, *abs); - let mut to = math::point(*x, *y); - to = current_transform.transform_point(&to); - if !*abs { - to -= math::vector( - current_transform.m31, - current_transform.m32, - ); - } - p.push(GCode::RapidPositioning { - x: to.x.into(), - y: to.y.into(), - }); - if *abs { - curpos = to; - } else { - curpos += to.to_vector(); - } - prev_ctrl = curpos; + p.extend(t.move_to(*abs, *x, *y)) } PathSegment::ClosePath { abs } => { - mach.tool_off(&mut p); + // Ignore abs, should have identical effect: https://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand + p.extend(t.line(true, 0.0, 0.0, None, opts.feedrate)) } PathSegment::LineTo { abs, x, y } => { - mach.tool_on(&mut p); - mach.distance(&mut p, *abs); - let mut to = math::point(*x, *y); - to = current_transform.transform_point(&to); - if !*abs { - to -= math::vector( - current_transform.m31, - current_transform.m32, - ); - } - p.push(GCode::LinearInterpolation { - x: to.x.into(), - y: to.y.into(), - z: None, - f: opts.feedrate.into(), - }); - if *abs { - curpos = to; - } else { - curpos += to.to_vector(); - } - prev_ctrl = curpos; + p.extend(t.line(*abs, *x, *y, None, opts.feedrate)); } PathSegment::HorizontalLineTo { abs, x } => { - mach.tool_on(&mut p); - mach.distance(&mut p, *abs); - let mut to = if *abs { - let inv_transform = current_transform - .inverse() - .expect("could not invert transform"); - math::point(*x, inv_transform.transform_point(&curpos).y) - } else { - math::point(*x, 0.0) - }; - to = current_transform.transform_point(&to); - if !*abs { - to -= math::vector( - current_transform.m31, - current_transform.m32, - ); - } - p.push(GCode::LinearInterpolation { - x: to.x.into(), - y: to.y.into(), - z: None, - f: opts.feedrate.into(), - }); - if *abs { - curpos = to; - } else { - curpos += to.to_vector(); - } - prev_ctrl = curpos; + p.extend(t.line(*abs, *x, None, None, opts.feedrate)); } PathSegment::VerticalLineTo { abs, y } => { - mach.tool_on(&mut p); - mach.distance(&mut p, *abs); - let mut to = if *abs { - let inv_transform = current_transform - .inverse() - .expect("could not invert transform"); - math::point(inv_transform.transform_point(&curpos).x, *y) - } else { - math::point(0.0, *y) - }; - to = current_transform.transform_point(&to); - if !*abs { - to -= math::vector( - current_transform.m31, - current_transform.m32, - ); - } - p.push(GCode::LinearInterpolation { - x: to.x.into(), - y: to.y.into(), - z: None, - f: opts.feedrate.into(), - }); - if *abs { - curpos = to; - } else { - curpos += to.to_vector(); - } - prev_ctrl = curpos; + p.extend(t.line(*abs, None, *y, None, opts.feedrate)); } PathSegment::CurveTo { abs, @@ -281,122 +184,52 @@ fn svg2program(doc: &svgdom::Document, opts: ProgramOptions, mut mach: Machine) x, y, } => { - mach.tool_on(&mut p); - mach.absolute(&mut p); - let from = curpos; - let mut ctrl1 = math::point(*x1, *y1); - ctrl1 = current_transform.transform_point(&ctrl1); - let mut ctrl2 = math::point(*x2, *y2); - ctrl2 = current_transform.transform_point(&ctrl2); - let mut to = math::point(*x, *y); - to = current_transform.transform_point(&to); - if !*abs { - ctrl1 += curpos.to_vector(); - ctrl2 += curpos.to_vector(); - to += curpos.to_vector(); - } - let cbs = lyon_geom::CubicBezierSegment { - from, - ctrl1, - ctrl2, - to, - }; - let last_point = std::cell::Cell::new(curpos); - cbs.flattened(opts.tolerance).for_each(|point| { - p.push(GCode::LinearInterpolation { - x: point.x.into(), - y: point.y.into(), - z: None, - f: opts.feedrate.into(), - }); - last_point.set(point); - }); - curpos = last_point.get(); - prev_ctrl = ctrl1; + p.extend(t.cubic_bezier( + *abs, + *x1, + *y1, + *x2, + *y2, + *x, + *y, + opts.tolerance, + None, + opts.feedrate, + )); } PathSegment::SmoothCurveTo { abs, x2, y2, x, y } => { - mach.tool_on(&mut p); - mach.absolute(&mut p); - let from = curpos; - let mut ctrl1 = prev_ctrl; - let mut ctrl2 = math::point(*x2, *y2); - ctrl2 = current_transform.transform_point(&ctrl2); - let mut to = math::point(*x, *y); - to = current_transform.transform_point(&to); - if !*abs { - ctrl1 += curpos.to_vector(); - ctrl2 += curpos.to_vector(); - to += curpos.to_vector(); - } - let cbs = lyon_geom::CubicBezierSegment { - from, - ctrl1, - ctrl2, - to, - }; - let last_point = std::cell::Cell::new(curpos); - cbs.flattened(opts.tolerance).for_each(|point| { - p.push(GCode::LinearInterpolation { - x: point.x.into(), - y: point.y.into(), - z: None, - f: opts.feedrate.into(), - }); - last_point.set(point); - }); - curpos = last_point.get(); - prev_ctrl = ctrl1; + p.extend(t.smooth_cubic_bezier( + *abs, + *x2, + *y2, + *x, + *y, + opts.tolerance, + None, + opts.feedrate, + )); } PathSegment::Quadratic { abs, x1, y1, x, y } => { - mach.tool_on(&mut p); - mach.absolute(&mut p); - let from = curpos; - let mut ctrl = math::point(*x1, *y1); - ctrl = current_transform.transform_point(&ctrl); - let mut to = math::point(*x, *y); - to = current_transform.transform_point(&to); - if !*abs { - ctrl += curpos.to_vector(); - to += curpos.to_vector(); - } - let qbs = lyon_geom::QuadraticBezierSegment { from, ctrl, to }; - let last_point = std::cell::Cell::new(curpos); - qbs.flattened(opts.tolerance).for_each(|point| { - p.push(GCode::LinearInterpolation { - x: point.x.into(), - y: point.y.into(), - z: None, - f: opts.feedrate.into(), - }); - last_point.set(point); - }); - curpos = last_point.get(); - prev_ctrl = ctrl; + p.extend(t.quadratic_bezier( + *abs, + *x1, + *y1, + *x, + *y, + opts.tolerance, + None, + opts.feedrate, + )); } PathSegment::SmoothQuadratic { abs, x, y } => { - mach.tool_on(&mut p); - mach.absolute(&mut p); - let from = curpos; - let mut ctrl = prev_ctrl; - let mut to = math::point(*x, *y); - to = current_transform.transform_point(&to); - if !*abs { - ctrl += curpos.to_vector(); - to += curpos.to_vector(); - } - let qbs = lyon_geom::QuadraticBezierSegment { from, ctrl, to }; - let last_point = std::cell::Cell::new(curpos); - qbs.flattened(opts.tolerance).for_each(|point| { - p.push(GCode::LinearInterpolation { - x: point.x.into(), - y: point.y.into(), - z: None, - f: opts.feedrate.into(), - }); - last_point.set(point); - }); - curpos = last_point.get(); - prev_ctrl = ctrl; + p.extend(t.smooth_quadratic_bezier( + *abs, + *x, + *y, + opts.tolerance, + None, + opts.feedrate, + )); } PathSegment::EllipticalArc { abs, @@ -408,45 +241,19 @@ fn svg2program(doc: &svgdom::Document, opts: ProgramOptions, mut mach: Machine) x, y, } => { - mach.tool_on(&mut p); - mach.absolute(&mut p); - let from = curpos; - let mut to = math::point(*x, *y); - to = current_transform.transform_point(&to); - if !*abs { - to += curpos.to_vector(); - } - - let mut radii = math::vector(*rx, *ry); - radii = current_transform.transform_vector(&radii); - - let sarc = lyon_geom::SvgArc { - from, - to, - radii, - x_rotation: lyon_geom::euclid::Angle { - radians: *x_axis_rotation, - }, - flags: lyon_geom::ArcFlags { - large_arc: *large_arc, - sweep: *sweep, - }, - }; - let last_point = std::cell::Cell::new(curpos); - sarc.for_each_flattened( + p.extend(t.elliptical( + *abs, + *rx, + *ry, + *x_axis_rotation, + *large_arc, + *sweep, + *x, + *y, + None, + opts.feedrate, opts.tolerance, - &mut |point: math::F64Point| { - p.push(GCode::LinearInterpolation { - x: point.x.into(), - y: point.y.into(), - z: None, - f: opts.feedrate.into(), - }); - last_point.set(point); - }, - ); - curpos = last_point.get(); - prev_ctrl = curpos; + )); } } } @@ -459,13 +266,13 @@ fn svg2program(doc: &svgdom::Document, opts: ProgramOptions, mut mach: Machine) } } - mach.tool_off(&mut p); - mach.absolute(&mut p); + p.extend(t.mach.tool_off()); + p.extend(t.mach.absolute()); p.push(GCode::RapidPositioning { x: 0.0.into(), y: 0.0.into(), }); - mach.tool_on(&mut p); + p.extend(t.mach.tool_on()); p.push(GCode::ProgramEnd); p diff --git a/src/turtle.rs b/src/turtle.rs new file mode 100644 index 0000000..3d4774a --- /dev/null +++ b/src/turtle.rs @@ -0,0 +1,366 @@ +use crate::code::{GCode, Program}; +use crate::machine::Machine; +use lyon_geom::euclid::{Angle, Transform2D}; +use lyon_geom::math::{point, vector, F64Point}; +use lyon_geom::{ArcFlags, CubicBezierSegment, QuadraticBezierSegment, SvgArc}; + +pub struct Turtle { + curpos: F64Point, + curtran: Transform2D, + scaling: Option>, + transtack: Vec>, + pub mach: Machine, + prev_ctrl: F64Point, +} + +impl Default for Turtle { + fn default() -> Self { + Self { + curpos: point(0.0, 0.0), + curtran: Transform2D::identity(), + scaling: None, + transtack: vec![], + mach: Machine::default(), + prev_ctrl: point(0.0, 0.0), + } + } +} + +impl Turtle { + pub fn move_to(&mut self, abs: bool, x: X, y: Y) -> Program + where + X: Into>, + Y: Into>, + { + let invtran = self.curtran.inverse().unwrap(); + let origcurpos = invtran.transform_point(&self.curpos); + let x = x + .into() + .map(|x| if abs { x } else { origcurpos.x + x }) + .unwrap_or(origcurpos.x); + let y = y + .into() + .map(|y| if abs { y } else { origcurpos.y + y }) + .unwrap_or(origcurpos.y); + + let mut to = point(x, y); + to = self.curtran.transform_point(&to); + self.curpos = to; + self.prev_ctrl = self.curpos; + + vec![ + self.mach.tool_off(), + self.mach.absolute(), + vec![GCode::RapidPositioning { + x: to.x.into(), + y: to.y.into(), + }], + ] + .drain(..) + .flatten() + .collect() + } + + pub fn line(&mut self, abs: bool, x: X, y: Y, z: Z, f: F) -> Program + where + X: Into>, + Y: Into>, + Z: Into>, + F: Into>, + { + let invtran = self.curtran.inverse().unwrap(); + let origcurpos = invtran.transform_point(&self.curpos); + let x = x + .into() + .map(|x| if abs { x } else { origcurpos.x + x }) + .unwrap_or(origcurpos.x); + let y = y + .into() + .map(|y| if abs { y } else { origcurpos.y + y }) + .unwrap_or(origcurpos.y); + + let mut to = point(x, y); + to = self.curtran.transform_point(&to); + self.curpos = to; + self.prev_ctrl = self.curpos; + + vec![ + self.mach.tool_on(), + self.mach.absolute(), + vec![GCode::LinearInterpolation { + x: to.x.into(), + y: to.y.into(), + z: z.into(), + f: f.into(), + }], + ] + .drain(..) + .flatten() + .collect() + } + + fn bezier>, F: Into>>( + &mut self, + cbs: CubicBezierSegment, + tolerance: f64, + z: Z, + f: F, + ) -> Program { + let z = z.into(); + let f = f.into(); + let last_point = std::cell::Cell::new(self.curpos); + let mut cubic = vec![]; + cbs.flattened(tolerance).for_each(|point| { + cubic.push(GCode::LinearInterpolation { + x: point.x.into(), + y: point.y.into(), + z, + f, + }); + last_point.set(point); + }); + self.curpos = last_point.get(); + self.prev_ctrl = cbs.ctrl1; + + vec![self.mach.tool_on(), self.mach.absolute(), cubic] + .drain(..) + .flatten() + .collect() + } + + pub fn cubic_bezier( + &mut self, + abs: bool, + x1: f64, + y1: f64, + x2: f64, + y2: f64, + x: f64, + y: f64, + tolerance: f64, + z: Z, + f: F, + ) -> Program + where + Z: Into>, + F: Into>, + { + let from = self.curpos; + let mut ctrl1 = point(x1, y1); + let mut ctrl2 = point(x2, y2); + let mut to = point(x, y); + if !abs { + let invtran = self.curtran.inverse().unwrap(); + let origcurpos = invtran.transform_point(&self.curpos); + ctrl1 += origcurpos.to_vector(); + ctrl2 += origcurpos.to_vector(); + to += origcurpos.to_vector(); + } + ctrl1 = self.curtran.transform_point(&ctrl1); + ctrl2 = self.curtran.transform_point(&ctrl2); + to = self.curtran.transform_point(&to); + let cbs = lyon_geom::CubicBezierSegment { + from, + ctrl1, + ctrl2, + to, + }; + + self.bezier(cbs, tolerance, z, f) + } + + pub fn smooth_cubic_bezier( + &mut self, + abs: bool, + x2: f64, + y2: f64, + x: f64, + y: f64, + tolerance: f64, + z: Z, + f: F, + ) -> Program + where + Z: Into>, + F: Into>, + { + let from = self.curpos; + let ctrl1 = self.prev_ctrl; + let mut ctrl2 = point(x2, y2); + let mut to = point(x, y); + if !abs { + let invtran = self.curtran.inverse().unwrap(); + let origcurpos = invtran.transform_point(&self.curpos); + ctrl2 += origcurpos.to_vector(); + to += origcurpos.to_vector(); + } + ctrl2 = self.curtran.transform_point(&ctrl2); + to = self.curtran.transform_point(&to); + let cbs = lyon_geom::CubicBezierSegment { + from, + ctrl1, + ctrl2, + to, + }; + + self.bezier(cbs, tolerance, z, f) + } + + pub fn smooth_quadratic_bezier( + &mut self, + abs: bool, + x: f64, + y: f64, + tolerance: f64, + z: Z, + f: F, + ) -> Program + where + Z: Into>, + F: Into>, + { + let from = self.curpos; + let ctrl = self.prev_ctrl; + let mut to = point(x, y); + if !abs { + let invtran = self.curtran.inverse().unwrap(); + let origcurpos = invtran.transform_point(&self.curpos); + to += origcurpos.to_vector(); + } + to = self.curtran.transform_point(&to); + let qbs = QuadraticBezierSegment { from, ctrl, to }; + + self.bezier(qbs.to_cubic(), tolerance, z, f) + } + + pub fn quadratic_bezier( + &mut self, + abs: bool, + x1: f64, + y1: f64, + x: f64, + y: f64, + tolerance: f64, + z: Z, + f: F, + ) -> Program + where + Z: Into>, + F: Into>, + { + let from = self.curpos; + let mut ctrl = point(x1, y1); + let mut to = point(x, y); + if !abs { + let invtran = self.curtran.inverse().unwrap(); + let origcurpos = invtran.transform_point(&self.curpos); + to += origcurpos.to_vector(); + ctrl += origcurpos.to_vector(); + } + ctrl = self.curtran.transform_point(&ctrl); + to = self.curtran.transform_point(&to); + let qbs = QuadraticBezierSegment { from, ctrl, to }; + + self.bezier(qbs.to_cubic(), tolerance, z, f) + } + + pub fn elliptical( + &mut self, + abs: bool, + rx: f64, + ry: f64, + x_axis_rotation: f64, + large_arc: bool, + sweep: bool, + x: f64, + y: f64, + z: Z, + f: F, + tolerance: f64, + ) -> Program + where + Z: Into>, + F: Into>, + { + let z = z.into(); + let f = f.into(); + + let from = self.curpos; + let mut to = point(x, y); + to = self.curtran.transform_point(&to); + if !abs { + to -= vector(self.curtran.m31, self.curtran.m32); + to += self.curpos.to_vector(); + } + + let mut radii = vector(rx, ry); + radii = self.curtran.transform_vector(&radii); + + let sarc = SvgArc { + from, + to, + radii, + x_rotation: Angle { + radians: x_axis_rotation, + }, + flags: ArcFlags { + large_arc: large_arc, + sweep: sweep, + }, + }; + let last_point = std::cell::Cell::new(self.curpos); + + let mut ellipse = vec![]; + sarc.for_each_flattened(tolerance, &mut |point: F64Point| { + ellipse.push(GCode::LinearInterpolation { + x: point.x.into(), + y: point.y.into(), + z, + f, + }); + last_point.set(point); + }); + self.curpos = last_point.get(); + self.prev_ctrl = self.curpos; + + vec![self.mach.tool_on(), self.mach.absolute(), ellipse] + .drain(..) + .flatten() + .collect() + } + + pub fn set_scaling(&mut self, scaling: Transform2D) { + if let Some(ref scaling) = self.scaling { + self.curtran = self.curtran.post_mul(&scaling.inverse().unwrap()); + } + self.scaling = Some(scaling); + self.curtran = self + .curtran + .post_mul(&scaling); + } + + pub fn push_transform(&mut self, trans: Transform2D) { + self.transtack.push(self.curtran); + if let Some(ref scaling) = self.scaling { + println!("{:?}", trans); + self.curtran = self + .curtran + .post_mul(&scaling.inverse().unwrap()) + .post_mul(&trans) + .post_mul(&scaling); + } + } + + pub fn pop_transform(&mut self) { + self.curtran = self + .transtack + .pop() + .expect("popped when no transforms left"); + } + + pub fn reset(&mut self) { + self.curpos = point(0.0, 0.0); + self.curpos = self.curtran.transform_point(&self.curpos); + self.prev_ctrl = self.curpos; + } +}