using OSCADSharp.Booleans; using OSCADSharp.Spatial; using OSCADSharp.Transforms; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace OSCADSharp { /// /// Represents any Object or collection of objects that becomes am /// an OpenSCAD script when converted to a string. /// public abstract class OSCADObject { #region Attributes private uint id = Ids.Get(); /// /// The unique Id of the object /// these values auto-increment /// public uint Id { get { return this.id; } } /// /// Name of this OSCADObject /// public string Name { get; set; } = null; #endregion #region Transforms /// /// Applies Color and/or Opacity to this object /// /// The name of the color to apply /// The opacity from 0.0 to 1.0 /// A colorized object public OSCADObject Color(string colorName, double opacity = 1.0) { return new ColoredObject(this, colorName, opacity); } /// /// Mirrors the object about a plane, as specified by the normal /// /// The normal vector of the plane intersecting the origin of the object, /// through which to mirror it. /// A mirrored object public OSCADObject Mirror(Vector3 normal) { return new MirroredObject(this, normal); } /// /// Mirrors the object about a plane, as specified by the normal /// described by the x/y/z components provided /// /// /// /// /// A mirrored object public OSCADObject Mirror(double x, double y, double z) { return this.Mirror(new Vector3(x, y, z)); } /// /// Resizes to a specified set of X/Y/Z dimensions /// /// The X/Y/Z dimensions /// A resized object public OSCADObject Resize(Vector3 newsize) { return new ResizedObject(this, newsize); } /// /// Resizes to a specified set of X/Y/Z dimensions /// /// /// /// /// A resized object public OSCADObject Resize(double x, double y, double z) { return this.Resize(new Vector3(x, y, z)); } /// /// Rotates about a specified X/Y/Z euler angle /// /// The angle(s) to rotate /// A rotated object public OSCADObject Rotate(Vector3 angle) { return new RotatedObject(this, angle); } /// /// Rotates about a specified X/Y/Z euler angle /// /// /// /// /// A rotated object public OSCADObject Rotate(double x, double y, double z) { return this.Rotate(new Vector3(x, y, z)); } /// /// Rescales an object by an X/Y/Z scale factor /// /// The scale to apply. For example 1, 2, 1 would yield 2x scale on the Y axis /// A scaled object public OSCADObject Scale(Vector3 scale) { return new ScaledObject(this, scale); } /// /// Rescales an object by an X/Y/Z scale factor /// /// /// /// /// A scaled object public OSCADObject Scale(double x, double y, double z) { return this.Scale(new Vector3(x, y, z)); } /// /// Translates an object by the specified amount /// /// The vector upon which to translate (move object(s)) /// A translated object public OSCADObject Translate(Vector3 translation) { return new TranslatedObject(this, translation); } /// /// Translates an object by the specified amount /// /// /// /// /// A translated object public OSCADObject Translate(double x, double y, double z) { return this.Translate(new Vector3(x, y, z)); } /// /// Creates a minkowski sum of child nodes (including this object) /// /// Nodes to sum with /// A minkowski sum public OSCADObject Minkowski(params OSCADObject[] objects) { return doBlockStatement("Minkowski", objects, (children) => { return new MinkowskiedObject(children); }); } /// /// Creates a conved hull from child nodes (including this object) /// /// Nodes to hull /// Hull of nodes public OSCADObject Hull(params OSCADObject[] objects) { return doBlockStatement("Hull", objects, (children) => { return new HulledObject(children); }); } #endregion #region Boolean Operations /// /// Creates a union of all its child nodes. This is the sum of all children (logical or). /// May be used with either 2D or 3D objects, but don't mix them. /// /// child nodes /// public OSCADObject Union(params OSCADObject[] objects) { return doBlockStatement("Union", objects, (children) => { return new Union(children); }); } /// /// Subtracts the 2nd (and all further) child nodes from the first one (logical and not). /// May be used with either 2D or 3D objects, but don't mix them. /// /// child nodes /// public OSCADObject Difference(params OSCADObject[] objects) { return doBlockStatement("Difference", objects, (children) => { return new Difference(children); }); } /// /// Creates the intersection of all child nodes. This keeps the overlapping portion (logical and). /// Only the area which is common or shared by all children is retained. /// May be used with either 2D or 3D objects, but don't mix them. /// /// child nodes /// public OSCADObject Intersection(params OSCADObject[] objects) { return doBlockStatement("Intersection", objects, (children) => { return new Intersection(children); }); } private OSCADObject doBlockStatement(string name, OSCADObject[] objects, Func, OSCADObject> factory) { if (objects == null || objects.Length < 1) { throw new ArgumentException(name + " requires at least one non-null entities"); } IEnumerable children = new List() { this }; children = children.Concat(objects); return factory(children); } #endregion #region Utility Methods /// /// Returns the computed position of this object. /// /// For some objects that are the aggregate of many operations or /// multiple children, this may be an approximation or average /// of the position. /// /// public abstract Vector3 Position(); /// /// Returns the approximate boundaries of this OpenSCAD object /// /// public abstract Bounds Bounds(); /// /// Creates a copy of this object and all of its children /// /// This is not a deep copy in the sense that all OSCADObjects will be new instances, /// but any complex objects used as parameters (Such as Vector3s) will be referenced by the copies /// /// Clone of this object public abstract OSCADObject Clone(); /// /// Indicates whether this OSCADObject is the same as another OSCADObject. /// This processes the scripts for both objects and is computationally expensive. /// DO NOT use on deeply nested structures. /// /// OSCADObject to compare to /// True if scripts are exactly the same, false otherwise public bool IsSameAs(OSCADObject other) { return this.ToString() == other.ToString(); } /// /// Internal collection of children for this object /// protected List children = new List(); /// /// Returns all chidren of this OSCADObject /// /// If true, returns all descendants. If false, returns only direct children. /// public IEnumerable Children(bool recursive = true) { if(recursive == false) { return new List(this.children); } // Initial children are reversed here because for objects with multiple children (such as boolean operations) // the natural collection order would yield opposite the expected order in a stack (first child would be the last popped) Stack toTraverse = new Stack(this.children.Reverse()); List allChildren = new List(); OSCADObject child = null; while(toTraverse.Count > 0) { child = toTraverse.Pop(); allChildren.Add(child); foreach (var subChild in child.children) { toTraverse.Push(subChild); } } return allChildren; } /// /// Retrieves children that match the filtering predicate /// /// An expression like Linq's .Where() clause /// public IEnumerable Children(Func predicate) { return this.Children().Where(predicate); } /// /// Writes the script for this OSCADObject to the file specified /// /// (This is just a shortcut for File.WriteAllLines) /// /// Path for the file to write. Including filename and (optionally) file extension public void ToFile(string filePath) { string path = filePath; if (!path.EndsWith(".scad")) { path += ".scad"; } File.WriteAllLines(path, new string[] { this.ToString() }); } #endregion #region Operators /// /// Adds two OSCADObjects together (unions them) /// /// /// /// public static OSCADObject operator +(OSCADObject left, OSCADObject right) { if(left.GetType() == typeof(Union)) { left.children.Add(right); return left; } else if(right.GetType() == typeof(Union)) { right.children.Add(left); return right; } else { return new Union(new OSCADObject[] {left, right }); } } /// /// Subtracts two OSCADObjects (differences them) /// /// /// /// public static OSCADObject operator -(OSCADObject left, OSCADObject right) { if (left.GetType() == typeof(Difference)) { left.children.Add(right); return left; } else if (right.GetType() == typeof(Difference)) { right.children.Add(left); return right; } else { return new Difference(new OSCADObject[] {left, right }); } } #endregion } }