Layout Model Contours

This tutorial presents a process for laying out contour curves generated by slicing a solid model; a procedure that may be applied in various contexts such as producing plan drawings or fabrication profiles. We focus on structuring the process in a semantically clear way using first introducing the typical spaghetti approach and then a more appealing mode using object-oriented methodology.

layout

Technique

  1. Apply the standard operating procedure of defining a geometry bucket and in addition create two lists one for points and one for string such that we can create annotations.
  2. Compute the number of slices required by measuring the massing model’s bounding box and dividing its height by the slice size.
  3. Define the horizontal cutting plane and an additional layout plane in the XY World plane such that we can perform a plane to plane transformation later. Use modular arithmetic to set the target plane on a rectangular grid.
  4. Intersect the solid model with the source cutting plane and transform each curve to its target location (basis or coordinate system).
  5. Create an annotation for each target profile with a unique serial number and place its position and text into the annotation buckets.

lyout2

Procedure I

private void RunScript( List<Brep> Solids, double SliceSize, 
  int Columns, double Spacing, ref object Geometry, 
  ref object AnnoText, ref object AnnoPnts )
  {

    //-- Note: Annotation lists are for outputting text
    //-- Unfortuantely Grasshopper lacks native support
    //-- for standard Rhino Annotations, so we emit the
    //-- text and location to link to Text node.
    //--
    var geometry = new List<object>();
    var annotext = new List<string>();
    var annopnts = new List<Point3d>();


    //-- Calculate Bounding Box
    //--
    BoundingBox bounds = BoundingBox.Empty;
    foreach( Brep solid in Solids )
    {
      bounds.Union(solid.GetBoundingBox(true));
    }

    //-- Determine number of Slices Required
    //--
    var slices = (int) (bounds.Diagonal.Z / SliceSize);
    if( slices == 0)
    {
      Print("Nothing to Cut");
      return;
    }


    //-- To Chop the Solid & Layout the Slices
    //-- Note: Can be parameters
    //--
    for( var slice = 0; slice < slices; slice++ )
    {
      //-- Define Cutting & Layout Planes
      //-- Note: Using grid layout with integer division
      //--
      Plane cutter = new Plane(new Point3d(0, 0, slice * SliceSize), Vector3d.ZAxis);
      Plane layout = new Plane(new Point3d(
        ( slice % Columns ) * Spacing,
        ( slice / Columns ) * Spacing, 0), Vector3d.ZAxis);

      //-- Plane to Plane Transformation
      //--
      Transform xform = Transform.PlaneToPlane(cutter, layout);

      foreach( Brep solid in Solids)
      {
        //-- Perform Intersections
        //--
        Curve[] curves;
        Point3d[] points;

        Rhino.Geometry.Intersect.Intersection.BrepPlane(
          solid, cutter, 0.0001, out curves, out points);

        //-- Emit curves into geometrty dumpster
        //-- this is lazy and not advised!!!
        //--
        foreach( Curve curve in curves)
        {
          //-- Apply Transform and Emit
          //--
          curve.Transform(xform);
          geometry.Add(curve);


          //-- Add Annotation
          //--
          var position = layout.Origin;
          position.Transform(Transform.Translation(1, 1, 0));

          annotext.Add("Plane: " + slice);
          annopnts.Add(position);
        }
      }
    }

    Geometry = geometry;
    AnnoText = annotext;
    AnnoPnts = annopnts;
  }

Remarks

  1. The procedure is in some ways “three dimensional”, in that we iterate three times using a nested for-loop construct. The complexity of this procedure is cubic as it will take ( n x m x l ) steps to process all profiles. Where n is the number of planes, m is the number of solids and l is the number of profiles of intersection.
  2. Generally, writing deep nested procedures leads to confusion fairly fast. In particular when the amount of instructions exceed one page and it is no longer easily evident in which level of processing we are operating.
  3. There is a better way! We just need to store interim information into data containers, namely lists, and expand the three-loop construct into three flat-loops.
  4. In addition we can organize the instructions in such a manner that information has proper semantic logic instead of generic containers.

Procedure II

 private void RunScript(List<Brep> Solids, double SliceSize, 
  int Columns, double Spacing, ref object Geometry, 
  ref object AnnoText, ref object AnnoPnts)
  {

    //-- Generally it is NOT advisable to code
    //-- with numerous nested loops. Instead:
    //-- Create semanticaly reasonable objects and
    //-- capture logic in there. See below...
    //--
    //-- Golden rule: You can convert nested loops
    //-- to collection objects and function calls
    //--
    //-- Observe below that the code is more readable
    //-- but it did get a bit longer. There are no
    //-- nested loops however.
    //--
    //-- Also note the law of preservation of information
    //-- complexity. 3D collections = 3D loops = 3D objects
    //--

    var geometry = new List<object>();

    Mold mold = new Mold(Solids, SliceSize);

    mold.Layout(Columns, Spacing);

    mold.Emit(geometry);

    Geometry = geometry;

  }

  // <Custom additional code> 

  //-- Slice Object Definition ----------------------------------------------------------
  //--
  public class Slice
  {
    //-- Storage for Curves
    //--
    public List<Curve> Curves;
    public Plane Source;

    //-- Create a Slice from a Solid and a Plane
    //--
    public Slice(Brep solid, Plane plane)
    {
      //-- Save Cutting Plane
      //--
      Source = plane;

      //-- Perform Intersection
      //--
      Curve[] curves;
      Point3d[] points;

      Rhino.Geometry.Intersect.Intersection.BrepPlane(
        solid, plane, 0.0001, out curves, out points);

      //-- Store Curves
      //--
      Curves = new List<Curve>(curves);
    }

    //-- Emit all Curves in Geometry List
    //--
    public void Emit(List<object> geometry)
    {
      foreach( var curve in Curves )
      {
        geometry.Add(curve);
      }
    }

    //-- Transform Layout
    //--
    public void Layout(Plane target)
    {
      Transform xform = Transform.PlaneToPlane(Source, target);
      foreach( var curve in Curves )
      {
        curve.Transform(xform);
      }
    }
  }

  //-- Slices per Level Definition -----------------------------------------------------
  //--
  public class Level
  {
    //-- Storage of Slices
    //--
    public List<Slice> Slices;

    //-- Create a Slice List from a List of Solids and a Plane
    //--
    public Level(List<Brep> solids, Plane plane)
    {
      Slices = new List<Slice>();

      foreach( var solid in solids)
      {
        Slices.Add(new Slice(solid, plane));
      }
    }

    //-- Emit all Slices in Geometry List
    //--
    public void Emit(List<object> geometry)
    {
      foreach( var slice in Slices )
      {
        slice.Emit(geometry);
      }
    }

    //-- Transform Layout
    //--
    public void Layout(Plane plane)
    {
      foreach( var slice in Slices )
      {
        slice.Layout(plane);
      }
    }
  }

  //-- Stack of Slices Definition -------------------------------------------------------
  //--
  public class Mold
  {
    public List<Level> Levels;

    public Mold(List<Brep> solids, double height)
    {
      Levels = new List<Level>();

      var bounds = Bounds(solids);
      var levels = NumberOfLevels(bounds, height);

      for( var level = 0; level < levels; level++ )
      {
        Plane plane = new Plane(new Point3d(0, 0, level * height), Vector3d.ZAxis);
        Levels.Add(new Level(solids, plane));
      }
    }

    //-- Compute Bounding Box of Solids
    //--
    public BoundingBox Bounds(List<Brep> solids)
    {
      BoundingBox bounds = BoundingBox.Empty;
      foreach( Brep solid in solids )
      {
        bounds.Union(solid.GetBoundingBox(true));
      }
      return bounds;
    }

    //-- Compute Number of Slices from Bounding Box
    //--
    public int NumberOfLevels(BoundingBox bounds, double height)
    {
      return (int) (bounds.Diagonal.Z / height);
    }

    //-- Emit all Levels in Geometry List
    //--
    public void Emit(List<object> geometry)
    {
      foreach( var level in Levels )
      {
        level.Emit(geometry);
      }
    }

    //-- Transform Layout
    //--
    public void Layout(int columns, double spacing)
    {
      var index = 0;
      foreach( var level in Levels )
      {
        Plane plane = new Plane(new Point3d(
          (index % columns) * spacing,
          (index / columns) * spacing, 0), Vector3d.ZAxis);

        level.Layout(plane);

        ++index;
      }
    }
  }


  // </Custom additional code> 

Remarks

  1. This is rather a large leap from spaghetti to fully organized code but the general logic is rather obvious. We need to define as many levels of abstraction as the number of nested constructs. There is some universal law of conservation of complexity playing out here. Thus we need to capture the notions of a:
    1. SLICE which is an entity that contains a list of profile curves (one or many depends on the complexity of the mass) that belong to one unique solid model.
    2. LEVEL which is an entity that contains a number of slices that are produced by a single plane and multiple solid bodies.
    3. MOLD which is an entity that contains a collection of levels.
  2. We then need to take all the sub-processes from the original mono-body code and distribute them in the entities that they logically belong. For example computing the number of slices is the job of the mold.
  3. We also perform what is know as cascading delegation of tasks, such that when we ask the mold to contour itself and layout its parts, it, the mold, asks its levels to contour and layout themselves and each of the levels it asks its slices to be computed and layout.
  4. The important questions for structuring processes in this fashion are: (a) What IS what and (b) what HAS what. For example a slice can be thought of as either being (IS) a collection of curves or an object which contains (HAS) a collection of curves. Both are valid with a smaller conceptual preference towards the former. In addition is it rather more clear that a slice contains (HAS) a cutting plane, rather then being (IS) a cutting plane.