November 2014

Sun Mon Tue Wed Thu Fri Sat
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30            










« Identifying holes in an AutoCAD solid using .NET | Main | Creating a legend of AutoCAD drawings using .NET »

October 25, 2011

Using the Microsoft Kinect SDK to sweep segmented solids inside AutoCAD

This post follows on from this recent post which showed a flat port of the previous OpenNI/NITE code which swept a single solid along a spline path defined by the user being tracked by the Kinect device. As mentioned, the previous approach was ultimately flawed, as adding vertices to our spline path made the whole thing problematic (as the sweep operation became less and less likely to succeed).

The updated code adopts a slightly different approach: it creates the solid in segments, creating a new segment whenever there’s an error encountered or when the current segment exceeds a certain length.

I’ve also added the capability to resize the profile to be swept dynamically by moving one’s hands closer together or further apart.

I was having some trouble with solids being disposed of by a background thread, as I was implementing this new version, so I also decided to reduce the number of objects I actually kept in memory (the cursor sphere was one, for instance). The modified approach creates them when needed – probably incurring a modest overhead, but worth the increased stability, on balance.

Here’s the updated C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using AcGi = Autodesk.AutoCAD.GraphicsInterface;

using System.Runtime.InteropServices;

using System.Collections.Generic;

using System.Diagnostics;

using System.Reflection;

using System.IO;

using System;

using Microsoft.Research.Kinect.Nui;

 

namespace KinectIntegration

{

  // Our own class duplicating the one implemented by nKinect

  // to aid with porting

 

  public class ColorVector3

  {

    public double X, Y, Z;

    public int R, G, B;

  }

 

  public class KinectJig : DrawJig

  {

    [DllImport("acad.exe", CharSet = CharSet.Auto,

      CallingConvention = CallingConvention.Cdecl,

      EntryPoint = "?acedPostCommand@@YAHPB_W@Z"

     )]

    extern static private int acedPostCommand(string strExpr);

 

    // Our transient solids (cursor sphere & tube) are yellow

 

    const short transSolColor = 2;

 

    // Our final solids will be green

 

    const short finalSolColor = 3;

 

    // A transaction and database to add solids

 

    private Transaction _tr;

    private Document _doc;

 

    // We need our Kinect sensor

 

    private Runtime _kinect = null;

 

    // With the images collected by it

 

    private ImageFrame _depth = null;

    private ImageFrame _video = null;

 

    // A list of points captured by the sensor

    // (for eventual export)

 

    private List<ColorVector3> _vecs;

 

    // A list of points to be displayed

    // (we use this for the jig)

 

    private Point3dCollection _points;

 

    // A list of vertices to draw between

    // (we use this for the final polyline creation)

 

    private Point3dCollection _vertices;

    private int _lastDrawnVertex;

 

    // Entities to create our solid

 

    private DBObjectCollection _created;

 

    // The radius of the profile circle to create

 

    private double _profRad;

 

    // The location at which to draw a sphere when resizing

 

    private Point3d _resizeLocation;

 

    // The approximate length of each swept segment

    // (as a multiple of the radius)

 

    private double _segFactor;

 

    // An offset value we use to move the mouse back

    // and forth by one screen unit

 

    private int _offset;

 

    // Flags to indicate Kinect gesture modes

 

    private bool _resizing;     // Drawing mode active

    private bool _drawing;     // Drawing mode active

    private bool _finished;    // Finished - want to exit

 

    public bool Finished

    {

      get { return _finished; }

    }

 

    public KinectJig(

      Document doc, Transaction tr, double profRad, double factor

    )

    {

      // Initialise the various members

 

      _doc = doc;

      _tr = tr;

      _points = new Point3dCollection();

      _vertices = new Point3dCollection();

      _lastDrawnVertex = -1;

      _offset = 1;

      _resizing = false;

      _drawing = false;

      _finished = false;

      _created = new DBObjectCollection();

      _profRad = profRad;

      _segFactor = factor;

 

      // Create our sensor object - the constructor takes

      // three callbacks to receive various data:

      // - skeleton movement

      // - rgb data

      // - depth data

 

      _kinect = new Runtime();

 

      _kinect.SkeletonFrameReady +=

        new EventHandler<SkeletonFrameReadyEventArgs>(

          OnSkeletonFrameReady

        );

      _kinect.VideoFrameReady +=

        new EventHandler<ImageFrameReadyEventArgs>(

          OnVideoFrameReady

        );

      _kinect.DepthFrameReady +=

        new EventHandler<ImageFrameReadyEventArgs>(

          OnDepthFrameReady

        );

    }

 

    void OnDepthFrameReady(

      object sender, ImageFrameReadyEventArgs e

    )

    {

      _depth = e.ImageFrame;

    }

 

    void OnVideoFrameReady(

      object sender, ImageFrameReadyEventArgs e

    )

    {

      _video = e.ImageFrame;

    }

 

    void OnSkeletonFrameReady(

      object sender, SkeletonFrameReadyEventArgs e

    )

    {

      SkeletonFrame s = e.SkeletonFrame;

 

      if (!_finished)

      {

        foreach (SkeletonData data in s.Skeletons)

        {

          if (SkeletonTrackingState.Tracked == data.TrackingState)

          {

            Point3d leftHip =

              PointFromVector(

                data.Joints[JointID.HipLeft].Position

              );

            Point3d leftHand =

              PointFromVector(

                data.Joints[JointID.HandLeft].Position

              );

            Point3d rightHand =

              PointFromVector(

                data.Joints[JointID.HandRight].Position

              );

 

            if (

              leftHand.DistanceTo(Point3d.Origin) > 0 &&

              rightHand.DistanceTo(Point3d.Origin) > 0 &&

              leftHand.DistanceTo(rightHand) < 0.05)

            {

              // Hands are less than 5cm from each other

 

              _drawing = false;

              _resizing = false;

              _finished = true;

            }

            else

            {

              // Hands are within 10cm of each other vertically and

              // both hands are above the waist, so we resize the

              // profile radius

 

              _resizing =

                (leftHand.Z > leftHip.Z &&

                 rightHand.Z > leftHip.Z &&

                 Math.Abs(leftHand.Z - rightHand.Z) < 0.1);

 

              // If the left hand is below the waist, we draw

 

              _drawing = (leftHand.Z < leftHip.Z);

            }

 

            if (_resizing)

            {

              // If resizing, set some data to help draw

              // a sphere where we're resizing

 

              Vector3d vec = (leftHand - rightHand) / 2;

              _resizeLocation = rightHand + vec;

              _profRad = vec.Length;

            }

 

            if (_drawing)

            {

              // If we have at least one prior vertex...

 

              if (_vertices.Count > 0)

              {

                // ... check whether we're a certain distance away

                // from the last one before adding it (this smooths

                // off the jitters of adding every point)

 

                Point3d lastVert = _vertices[_vertices.Count - 1];

                if (lastVert.DistanceTo(rightHand) > _profRad * 4)

                {

                  // Add the new vertex to our list

 

                  _vertices.Add(rightHand);

                }

              }

              else

              {

                // Add the first vertex to our list

 

                _vertices.Add(rightHand);

              }

            }

            break;

          }

        }

      }

    }

 

    public void StartSensor()

    {

      if (_kinect != null)

      {

        _kinect.Initialize(

          RuntimeOptions.UseDepth |

          RuntimeOptions.UseColor |

          RuntimeOptions.UseSkeletalTracking

        );

        _kinect.VideoStream.Open(

          ImageStreamType.Video, 2,

          ImageResolution.Resolution640x480,

          ImageType.Color

        );

        _kinect.DepthStream.Open(

          ImageStreamType.Depth, 2,

          ImageResolution.Resolution640x480,

          ImageType.Depth

        );

      }

    }

 

    public void StopSensor()

    {

      if (_kinect != null)

      {

        _kinect.Uninitialize();

        _kinect = null;

      }

    }

 

    public void Cleanup()

    {

      _vertices.Clear();

 

      foreach (DBObject obj in _created)

      {

        obj.Dispose();       

      }

      _created.Clear();

    }

 

    public void UpdatePointCloud()

    {

      _vecs = GeneratePointCloud(1, true);

    }

 

    private List<ColorVector3> GeneratePointCloud(

      int sampling, bool withColor = false

    )

    {

      // We will return a list of our ColorVector3 objects

 

      List<ColorVector3> res = new List<ColorVector3>();

 

      // Let's start by determining the dimensions of the

      // respective images

 

      int depHeight = _depth.Image.Height;

      int depWidth = _depth.Image.Width;

      int vidHeight = _video.Image.Height;

      int vidWidth = _video.Image.Width;

 

      // For the sake of this initial implementation, we

      // expect them to be the same size. But this should not

      // actually need to be a requirement

 

      if (vidHeight != depHeight || vidWidth != depWidth)

      {

        Application.DocumentManager.MdiActiveDocument.

        Editor.WriteMessage(

          "\nVideo and depth images are of different sizes."

        );

        return null;

      }

 

      // Depth and color data for each pixel

 

      Byte[] depthData = _depth.Image.Bits;

      Byte[] colorData = _video.Image.Bits;

 

      // Loop through the depth information - we process two

      // bytes at a time

 

      for (int i = 0; i < depthData.Length; i += (2 * sampling))

      {

        // The depth pixel is two bytes long - we shift the

        // upper byte by 8 bits (a byte) and "or" it with the

        // lower byte

 

        int depthPixel = (depthData[i + 1] << 8) | depthData[i];

 

        // The x and y positions can be calculated using modulus

        // division from the array index

 

        int x = (i / 2) % depWidth;

        int y = (i / 2) / depWidth;

 

        // The x and y we pass into DepthImageToSkeleton() need to

        // be normalised (between 0 and 1), so we divide by the

        // width and height of the depth image, respectively

 

        // As we're using UseDepth (not UseDepthAndPlayerIndex) in

        // the depth sensor settings, we also need to shift the

        // depth pixel by 3 bits

 

        Vector v =

          _kinect.SkeletonEngine.DepthImageToSkeleton(

            ((float)x) / ((float)depWidth),

            ((float)y) / ((float)depHeight),

            (short)(depthPixel << 3)

          );

 

        // A zero value for Z means there is no usable depth for

        // that pixel

 

        if (v.Z > 0)

        {

          // Create a ColorVector3 to store our XYZ and RGB info

          // for a pixel

 

          ColorVector3 cv = new ColorVector3();

          cv.X = v.X;

          cv.Y = v.Z;

          cv.Z = v.Y;

 

          // Only calculate the colour when it's needed (as it's

          // now more expensive, albeit more accurate)

 

          if (withColor)

          {

            // Get the colour indices for that particular depth

            // pixel. We once again need to shift the depth pixel

            // and also need to flip the x value (as UseDepth means

            // it is mirrored on X) and do so on the basis of

            // 320x240 resolution (so we divide by 2, assuming

            // 640x480 is chosen earlier), as that's what this

            // function expects. Phew!

 

            int colorX, colorY;

            _kinect.NuiCamera.

              GetColorPixelCoordinatesFromDepthPixel(

                _video.Resolution, _video.ViewArea,

                320 - (x/2), (y/2), (short)(depthPixel << 3),

                out colorX, out colorY

              );

 

            // Make sure both indices are within bounds

 

            colorX = Math.Max(0, Math.Min(vidWidth - 1, colorX));

            colorY = Math.Max(0, Math.Min(vidHeight - 1, colorY));

 

            // Extract the RGB data from the appropriate place

            // in the colour data

 

            int colIndex = 4 * (colorX + (colorY * vidWidth));

            cv.B = (byte)(colorData[colIndex + 0]);

            cv.G = (byte)(colorData[colIndex + 1]);

            cv.R = (byte)(colorData[colIndex + 2]);

          }

          else

          {

            // If we don't need colour information, just set each

            // pixel to white

 

            cv.B = 255;

            cv.G = 255;

            cv.R = 255;

          }

 

          // Add our pixel data to the list to return

 

          res.Add(cv);

        }

      }

      return res;

    }

 

    private bool GenerateTube(

      double profRad, Point3dCollection pts, out Solid3d sol

    )

    {

      bool readyToBreak;

 

      // Let's start by creating our spline path

 

      using (Spline path = new Spline(pts, 0, 0.0))

      {

        double pathLen = path.GetDistanceAtParameter(path.EndParam);

        readyToBreak = (pathLen > _profRad * _segFactor);

 

        // And our sweep profile

 

        Circle profile =

          new Circle(pts[0], pts[1] - pts[0], profRad);

        using (profile)

        {

          // Then our sweep options

 

          SweepOptionsBuilder sob = new SweepOptionsBuilder();

 

          // Align the entity to sweep to the path

 

          sob.Align =

            SweepOptionsAlignOption.AlignSweepEntityToPath;

 

          // The base point is the start of the path

 

          sob.BasePoint = path.StartPoint;

 

          // The profile will rotate to follow the path

 

          sob.Bank = true;

          using (SweepOptions sweepOpts = sob.ToSweepOptions())

          {

            sol = new Solid3d();

 

            // Sweep our profile along our path

 

            sol.CreateSweptSolid(profile, path, sweepOpts);

          }

        }

      }

      _lastDrawnVertex = pts.Count - 1;

 

      return readyToBreak;

    }

 

    private Point3d PointFromVector(Vector v)

    {

      // Rather than just return a point, we're effectively

      // transforming it to the drawing space: flipping the

      // Y and Z axes (which makes it consistent with the

      // point cloud, and makes sure Z is actually up - from

      // the Kinect's perspective Y is up), and reversing

      // the X axis (which is the result of choosing UseDepth

      // rather than UseDepthAndPlayerIndex)

 

      return new Point3d(-v.X, v.Z, v.Y);

    }

 

    protected override SamplerStatus Sampler(JigPrompts prompts)

    {

      // We don't really need a point, but we do need some

      // user input event to allow us to loop, processing

      // for the Kinect input

 

      PromptPointResult ppr =

        prompts.AcquirePoint("\nClick to capture: ");

      if (ppr.Status == PromptStatus.OK)

      {

        if (_finished)

        {

          acedPostCommand("CANCELCMD");

          return SamplerStatus.Cancel;

        }

 

        // If not finished, but stopped drawing, add the

        // geometry that was previously drawn to the database

 

        if (!_drawing &&

             (_created.Count > 0 || _vertices.Count > 0)

          )

        {

          AddSolidOrPath();

        }

 

        // Generate a point cloud

 

        try

        {

          if (_depth != null && _video != null)

          {

            // Use a sampling of one in 50 points for the jig

 

            _vecs = GeneratePointCloud(50);

 

            // We just need the point coordinates for jigging

            // (no colours)

 

            _points.Clear();

 

            foreach (ColorVector3 vec in _vecs)

            {

              _points.Add(

                new Point3d(vec.X, vec.Y, vec.Z)

              );

            }

 

            // Let's move the mouse slightly to avoid having

            // to do it manually to keep the input coming

 

            System.Drawing.Point pt =

              System.Windows.Forms.Cursor.Position;

            System.Windows.Forms.Cursor.Position =

              new System.Drawing.Point(

                pt.X, pt.Y + _offset

              );

            _offset = -_offset;

          }

        }

        catch {}

 

        return SamplerStatus.OK;

      }

      return SamplerStatus.Cancel;

    }

 

    // Helper functions to extract/blank portions of our

    // vertex list (when we want to draw the beginning of it)

 

    private void ClearAllButLast(Point3dCollection pts, int n)

    {

      while (pts.Count > n)

      {

        pts.RemoveAt(0);

      }

      _lastDrawnVertex = -1;

    }

 

    private Point3dCollection GetAllButLast(

      Point3dCollection pts, int n

    )

    {

      Point3dCollection res = new Point3dCollection();

      for (int i = 0; i < pts.Count - n; i++)

      {

        res.Add(pts[i]);

      }

      return res;

    }

 

    protected override bool WorldDraw(AcGi.WorldDraw draw)

    {

      short origCol = draw.SubEntityTraits.Color;

 

      // This simply draws our points

 

      draw.Geometry.Polypoint(_points, null, null);

 

 

      if (_resizing)

      {

        using (Solid3d sphere = new Solid3d())

        {

          try

          {

            sphere.CreateSphere(_profRad);

 

            if (sphere != null)

            {

              sphere.TransformBy(

                Matrix3d.Displacement(

                  _resizeLocation - Point3d.Origin

                )

              );

 

              // Draw the cursor

 

              draw.SubEntityTraits.Color = transSolColor;

 

              sphere.WorldDraw(draw);

            }

          }

          catch { }

          finally

          {

            draw.SubEntityTraits.Color = origCol;

          }

        }

        return true;

      }

 

      // If we're currently drawing...

 

      if (_drawing)

      {

        Solid3d sol = null;

        try

        {

          // If we have vertices that haven't yet been drawn...

 

          if (_vertices.Count > 1 &&

              _vertices.Count - 1 > _lastDrawnVertex

            )

          {

            // ... generate a tube

 

            if (GenerateTube(_profRad, _vertices, out sol))

            {

              // If it was created, add it to our list to draw

 

              _created.Add(sol);

              sol = null;

 

              // Clear all but the last two vertices to draw from

              // next time

 

              ClearAllButLast(_vertices, 2);

            }

          }

        }

        catch

        {

          // If the tube generation failed...

 

          if (sol != null)

          {

            sol.Dispose();

          }

 

          // Loop, creating the most recent successful tube we can

 

          bool succeeded = false;

          int n = 1;

 

          do

          {

            try

            {

              // Generate the previous, working tube using all

              // but the last points (if it fails, one more is

              // excluded per iteration, until we get a working

              // tube)

 

              GenerateTube(

                _profRad, GetAllButLast(_vertices, n++), out sol

              );

 

              _created.Add(sol);

              sol = null;

              succeeded = true;

            }

            catch { }

          }

          while (!succeeded && n < _vertices.Count);

 

          if (succeeded)

          {

            ClearAllButLast(_vertices, n - 1);

 

            if (_vertices.Count > 1)

            {

              try

              {

                // And generate a tube for the remaining vertices

 

                GenerateTube(_profRad, _vertices, out sol);

              }

              catch

              {

                succeeded = false;

              }

            }

          }

 

          if (!succeeded && sol != null)

          {

            sol.Dispose();

            sol = null;

          }

        }

 

        // Draw our solid(s)

 

        draw.SubEntityTraits.Color = transSolColor;

 

        if (sol != null)

        {

          try

          {

            sol.WorldDraw(draw);

          }

          catch

          {}

        }

 

        foreach (DBObject obj in _created)

        {

          Entity ent = obj as Entity;

          if (ent != null)

          {

            try

            {

              ent.WorldDraw(draw);

            }

            catch

            {}

          }

        }

 

        if (_vertices.Count > 0)

        {

          Point3d lastPt = _vertices[_vertices.Count - 1];

 

          // Create a cursor sphere

 

          using (Solid3d cursor = new Solid3d())

          {

            try

            {

              cursor.CreateSphere(_profRad);

 

              if (cursor != null)

              {

                cursor.TransformBy(

                  Matrix3d.Displacement(lastPt - Point3d.Origin)

                );

 

                // Draw the cursor

 

                draw.SubEntityTraits.Color = transSolColor;

 

                cursor.WorldDraw(draw);

              }

            }

            catch { }

          }

        }

 

        if (sol != null)

        {

          sol.Dispose();

        }

      }

 

      draw.SubEntityTraits.Color = origCol;

 

      return true;

    }

 

    public void AddSolidOrPath()

    {

      Solid3d sol = null;

      try

      {

        GenerateTube(_profRad, _vertices, out sol);

      }

      catch

      {

        if (sol != null)

        {

          sol.Dispose();

          sol = null;

        }

      }

 

      if (_created.Count > 0 || sol != null)

      {

        if (sol != null)

        {

          _created.Add(sol);

        }

 

        BlockTableRecord btr =

          (BlockTableRecord)_tr.GetObject(

            _doc.Database.CurrentSpaceId,

            OpenMode.ForWrite

          );

 

        foreach (DBObject obj in _created)

        {

          Entity ent = obj as Entity;

          if (ent != null)

          {

            ent.ColorIndex = finalSolColor;

 

            btr.AppendEntity(ent);

            _tr.AddNewlyCreatedDBObject(ent, true);

          }

        }

        _created.Clear();

      }

 

      Cleanup();

 

      _vertices.Clear();

    }

 

    public void ExportPointCloud(string filename)

    {

      if (_vecs.Count > 0)

      {

        using (StreamWriter sw = new StreamWriter(filename))

        {

          // For each pixel, write a line to the text file:

          // X, Y, Z, R, G, B

 

          foreach (ColorVector3 pt in _vecs)

          {

            sw.WriteLine(

              "{0}, {1}, {2}, {3}, {4}, {5}",

              pt.X, pt.Y, pt.Z, pt.R, pt.G, pt.B

            );

          }

        }

      }

    }

  }

 

  public class Commands

  {

    [CommandMethod("ADNPLUGINS", "KINEXT", CommandFlags.Modal)]

    public void ImportFromKinect()

    {

      Document doc =

        Autodesk.AutoCAD.ApplicationServices.

          Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

 

      Transaction tr =

        doc.TransactionManager.StartTransaction();

 

      // Pass in a default radius of 5cm and a segment length

      // of 10 times that

 

      KinectJig kj = new KinectJig(doc, tr, 0.05, 10);

      try

      {

        kj.StartSensor();

      }

      catch (System.Exception ex)

      {

        ed.WriteMessage(

          "\nUnable to start Kinect sensor: " + ex.Message

        );

        tr.Dispose();

        return;

      }

 

      PromptResult pr = ed.Drag(kj);

 

      if (pr.Status != PromptStatus.OK && !kj.Finished)

      {

        kj.StopSensor();

        kj.Cleanup();

        tr.Dispose();

        return;

      }

 

      // Generate a final point cloud with color before stopping

      // the sensor

 

      kj.UpdatePointCloud();

      kj.StopSensor();

 

      kj.AddSolidOrPath();

      tr.Commit();

 

      // Manually dispose to avoid scoping issues with

      // other variables

 

      tr.Dispose();

 

      // We'll store most local files in the temp folder.

      // We get a temp filename, delete the file and

      // use the name for our folder

 

      string localPath = Path.GetTempFileName();

      File.Delete(localPath);

      Directory.CreateDirectory(localPath);

      localPath += "\\";

 

      // Paths for our temporary files

 

      string txtPath = localPath + "points.txt";

      string lasPath = localPath + "points.las";

 

      // Our PCG file will be stored under My Documents

 

      string outputPath =

        Environment.GetFolderPath(

          Environment.SpecialFolder.MyDocuments

        ) + "\\Kinect Point Clouds\\";

 

      if (!Directory.Exists(outputPath))

        Directory.CreateDirectory(outputPath);

 

      // We'll use the title as a base filename for the PCG,

      // but will use an incremented integer to get an unused

      // filename

 

      int cnt = 0;

      string pcgPath;

      do

      {

        pcgPath =

          outputPath + "Kinect" +

          (cnt == 0 ? "" : cnt.ToString()) + ".pcg";

        cnt++;

      }

      while (File.Exists(pcgPath));

 

      // The path to the txt2las tool will be the same as the

      // executing assembly (our DLL)

 

      string exePath =

        Path.GetDirectoryName(

          Assembly.GetExecutingAssembly().Location

        ) + "\\";

 

      if (!File.Exists(exePath + "txt2las.exe"))

      {

        ed.WriteMessage(

          "\nCould not find the txt2las tool: please make sure " +

          "it is in the same folder as the application DLL."

        );

        return;

      }

 

      // Export our point cloud from the jig

 

      ed.WriteMessage(

        "\nSaving TXT file of the captured points.\n"

      );

 

      kj.ExportPointCloud(txtPath);

 

      // Use the txt2las utility to create a .LAS

      // file from our text file

 

      ed.WriteMessage(

        "\nCreating a LAS from the TXT file.\n"

      );

 

      ProcessStartInfo psi =

        new ProcessStartInfo(

          exePath + "txt2las",

          "-i \"" + txtPath +

          "\" -o \"" + lasPath +

          "\" -parse xyzRGB"

        );

      psi.CreateNoWindow = false;

      psi.WindowStyle = ProcessWindowStyle.Hidden;

 

      // Wait up to 20 seconds for the process to exit

 

      try

      {

        using (Process p = Process.Start(psi))

        {

          p.WaitForExit();

        }

      }

      catch

      { }

 

      // If there's a problem, we return

 

      if (!File.Exists(lasPath))

      {

        ed.WriteMessage(

          "\nError creating LAS file."

        );

        return;

      }

 

      File.Delete(txtPath);

 

      ed.WriteMessage(

        "Indexing the LAS and attaching the PCG.\n"

      );

 

      // Index the .LAS file, creating a .PCG

 

      string lasLisp = lasPath.Replace('\\', '/'),

              pcgLisp = pcgPath.Replace('\\', '/');

 

      doc.SendStringToExecute(

        "(command \"_.POINTCLOUDINDEX\" \"" +

          lasLisp + "\" \"" +

          pcgLisp + "\")(princ) ",

        false, false, false

      );

 

      // Attach the .PCG file

 

      doc.SendStringToExecute(

        "_.WAITFORFILE \"" +

        pcgLisp + "\" \"" +

        lasLisp + "\" " +

        "(command \"_.-POINTCLOUDATTACH\" \"" +

        pcgLisp +

        "\" \"0,0\" \"1\" \"0\")(princ) ",

        false, false, false

      );

 

      doc.SendStringToExecute(

        "_.-VISUALSTYLES _C _Realistic ",

        false, false, false

      );

    }

 

    // Return whether a file is accessible

 

    private bool IsFileAccessible(string filename)

    {

      // If the file can be opened for exclusive access it means

      // the file is accesible

      try

      {

        FileStream fs =

          File.Open(

            filename, FileMode.Open,

            FileAccess.Read, FileShare.None

          );

        using (fs)

        {

          return true;

        }

      }

      catch (IOException)

      {

        return false;

      }

    }

 

    // A command which waits for a particular PCG file to exist

 

    [CommandMethod(

      "ADNPLUGINS", "WAITFORFILE", CommandFlags.NoHistory

     )]

    public void WaitForFileToExist()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      HostApplicationServices ha =

        HostApplicationServices.Current;

 

      PromptResult pr = ed.GetString("Enter path to PCG: ");

      if (pr.Status != PromptStatus.OK)

        return;

      string pcgPath = pr.StringResult.Replace('/', '\\');

 

      pr = ed.GetString("Enter path to LAS: ");

      if (pr.Status != PromptStatus.OK)

        return;

      string lasPath = pr.StringResult.Replace('/', '\\');

 

      ed.WriteMessage(

        "\nWaiting for PCG creation to complete...\n"

      );

 

      // Check the write time for the PCG file...

      // if it hasn't been written to for at least half a second,

      // then we try to use a file lock to see whether the file

      // is accessible or not

 

      const int ticks = 50;

      TimeSpan diff;

      bool cancelled = false;

 

      // First loop is to see when writing has stopped

      // (better than always throwing exceptions)

 

      while (true)

      {

        if (File.Exists(pcgPath))

        {

          DateTime dt = File.GetLastWriteTime(pcgPath);

          diff = DateTime.Now - dt;

          if (diff.Ticks > ticks)

            break;

        }

        System.Windows.Forms.Application.DoEvents();

        if (HostApplicationServices.Current.UserBreak())

        {

          cancelled = true;

          break;

        }

      }

 

      // Second loop will wait until file is finally accessible

      // (by calling a function that requests an exclusive lock)

 

      if (!cancelled)

      {

        int inacc = 0;

        while (true)

        {

          if (IsFileAccessible(pcgPath))

            break;

          else

            inacc++;

          System.Windows.Forms.Application.DoEvents();

          if (HostApplicationServices.Current.UserBreak())

          {

            cancelled = true;

            break;

          }

        }

        ed.WriteMessage("\nFile inaccessible {0} times.", inacc);

 

        try

        {

          CleanupTmpFiles(lasPath);

        }

        catch

        { }

      }

    }

 

    internal void CleanupTmpFiles(string txtPath)

    {

      if (File.Exists(txtPath))

        File.Delete(txtPath);

      Directory.Delete(

        Path.GetDirectoryName(txtPath)

      );

    }

  }

}

When we run the KINEXT command and move our hands when at the same level (we’ll drop the left hand to draw with the right), we see a sphere being displayed representing the size of the tube we’ll be sweeping:

Resizing the cursor

Enlarging the cursor

And after dropping our left hand we can proceed with drawing with our right, culminating in a series of connected (and disjoint) tubes:

A set of tubes created using the Kinect

blog comments powered by Disqus

Feed/Share

10 Random Posts