August 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
31            








August 26, 2014

AutoCAD integration samples for Kinect for Windows 2 –Part 2

Following on from the introduction to this series – and to the Kinect for Windows v2 sensor – it’s time to take a closer look at some of the AutoCAD integration samples.

At the core of the Kinect sensor’s capabilities are really two things: the ability to capture depth data and to detect people’s bodies in the field of view. There are additional bells and whistles such as audio support, Kinect Fusion and face tracking, but the foundation is really about RGB-D input and the additional runtime analysis required to track humans.

Let’s take a look at both of these. I’ve captured the below animated GIFs – keeping them as lightweight as possible, so the site still loads in a reasonable time – that demonstrate these two foundational capabilities.

Capturing point clouds in AutoCAD can be achieved with the original KINECT command (along with variants such as KINBOUNDS and KINSNAPS, which enable clipping and timelapse capture, respectively).

Here’s an example capture with a lot of the frames edited out: the point here is to show the approximate quality of the capture, rather than the smoothness of the processing.


The KINECT command

This is all from a single frame of RGB-D data – you can clearly capture much more with Kinect Fusion (which we’ll look at next time).

The above point cloud has 183,810 points (the theoretical maximum being 512 x 424 = 217,088 points, given the resolution of the depth camera). This is a fair bit higher than with KfW v1, which had a theoretical depth resolution of 320 x 240 (i.e. a max of 76,800 points), although it seemed to give you more when you mapped 640 x 480 RGB data to depth. You were presumably getting multiple adjacent – and potentially differently coloured – pixels with the same depth values, as the generated point clouds could indeed contain up to about 300K points. The quality of the depth data is certainly better with KfW v2 while the processing is also snappier.

For instance, let’s take a look at skeleton tracking, which can be shown inside AutoCAD using the KINSKEL command:


The KINSKEL command

(There’s also a command that displays point cloud data overlaid with the skeleton data – the KINBOTH command – but I’m not showing it in this post. You can imagine how it works. :-)

Skeleton tracking is much smoother and cleaner than before – it’s much less jerky and with fewer flailing limbs – even if we’re still a ways off doing industrial-strength Mo-cap. The joint count has increased to 25 from the previous 20: there’s an additional joint in the neck and two more in each of the hands. Which means you get hand tracking – you can see whether the hand is open or closed, between the fingertips and the thumb – although not finger tracking, even if the sensor resolution is probably now sufficient for that, too.

Aside from the increase in the number of joints, their placement is now more biologically accurate, too. The hips and neck are much closer to where they are in real life with this version of the device.

This more precise skeletal tracking can clearly enable tighter application integrations. For instance, as we saw with this previous post, the KINEXT2 command – which sweeps pipes along the path through the air that your hand takes – can now use the distance between your thumb and your fingertips to specify the diameter of the circular extrusion profile. Which is much more precise than using the distance between both hands. (In fact the KINNEAR system variable – which previously enabled the now-redundant “near mode” with KfW v1 – is now only used to choose between these two different approaches for specifying the profile radius.)

Other commands for drawing geometry are also still there in the application, of course, such as KINPOLY – for drawing 3D polylines in space – and KINEXT – which works in a similar way to KINEXT2, but this this time with a pre-specified profile radius.

That’s it for this run through of the basic commands integrating depth and skeleton data into AutoCAD. In the next post we’ll move on to two more advanced capabilities, face tracking and Kinect Fusion.

August 25, 2014

AutoCAD integration samples for Kinect for Windows 2 –Part 1

Last Friday Microsoft announced a preview SDK for Kinect for Windows 2. As the first public release of the SDK, it seems a good time to publish an initial set of samples for readers to play with. These are very much a work in progress – I tend to restart AutoCAD between Kinect Fusion captures, for instance, as otherwise I’ve been getting regular crashes – but they should give people a sense of what’s possible. And while I haven’t yet implemented certain capabilities we had before, I have gone ahead and snuck a few enhancements in (which you’ll see in particular with the Kinect Fusion and Face Tracking integrations). Also, as I’ve now realised I have a lot to say about these samples, I’m going to stagger them across a few posts during the course of this week, following on from this introduction.

Kinect for Windows v2

The latest KfW device is a big step up from the original Kinect for Xbox 360 and Kinect for Windows v1 devices: for starters you get about 3 times the depth data and high-definition colour. This round of the Kinect technology is based on a custom CMOS sensor that uses time-of-flight rather than structured light to perceive depth – Microsoft moved away from using PrimeSense as a technology provider some time ago, well before their acquisition by Apple.

KfW v2 has a better range than KfW v1 – it has no need for a tilt motor or near mode – and it’s much less sensitive to daylight (I haven’t yet tried it outside, but I will!). This is really an impressive piece of tech… in many ways you’re effectively getting a laser scanner at the ridiculously low price of $200.

There are definitely some things to be aware of, however. The latest SDK is now Windows 8/8.1 only, which I have will no doubt exclude a number of people wanting to use this on on Windows 7. (I run Windows 8.1 on the machine I use for Kinect work – as I need a native OS install with GPU usage for Kinect Fusion, even if the rest of the SDK can function from inside a VM, such as my day-to-day system running Windows 7 via Parallels Desktop on OS X – so I’m thankfully not impacted by that particular decision.) The device also requires USB 3 – naturally enough, given the data throughput needed – and requires additional, external power in much the same way as KfW v1 did.

One other important “platform” consideration when using these samples… I’m tending to use them on AutoCAD 2014 rather than 2015. They do work on 2015, but as with this release we’ve completed the shift across from PCG to RCS/RCP for our native point cloud format it’s not currently possible to index text files into native point cloud files (as we can do in AutoCAD 2014 using POINTCLOUDINDEX). Which is a bit of a gap for developers wanting to generate and programmatically import point cloud data into AutoCAD: there’s currently a manual step needed, where the user indexes an .xyz file into .rcs using ReCap Studio before attaching it inside AutoCAD 2015. (This isn’t the end of the story, hopefully: I’m working with the ReCap team to see what’s possible, moving forwards. If you have a specific need for custom point cloud import into AutoCAD that you’d like to see addressed, please do let me know.)

A few words on the Kinect SDK itself, before we wrap up for today (I won’t embed the code directly in this blog – there’s just way too much of it). The various APIs that comprise the Kinect SDK have changed a lot between v1.8 and v2 of the SDK. The changes are generally positive – code using the APIs now feels a lot cleaner, and I do believe the changes have been done for good reasons – but it does mean there’s a definite break in compatibility. You will need separate codebases for each of KfW v1 and KfW v2, if you need to support both.

On a related note, we’ve just received a Structure Sensor from Occipital in the Neuchatel office, so I’m going to start taking a look at that, when I get the chance. There is an iOS SDK – and it seems open source drivers and a “hacker” USB cable are on their way – so I’m curious about what I’ll be able to get out of it. The device appears to have a lot of potential, even if it’s apparently even more quirky than Kinect Fusion, in many ways. I do buy into the fact that this tech is getting more mobile, but we’ll see whether this particular device is an evolutionary step or a dead-end. Especially as Occipital have licensed PrimeSense tech for the sensor, and Apple isn’t exactly known for running an OEM licensing business.

I personally suspect that the “sweet spot” for mobile capture tech in the near-term is a lightweight mobile app allows you to ensure you have adequate coverage of your object before the data is crunched back at the office or up in the cloud to generate a decent model. Unless Project Tango reliably delivers the goods, of course (another tech I have mentally flagged with “we’ll have to see”).

Anyway, onwards… the next few posts with be looking at various AutoCAD-Kinect integration samples that have been ported to KfW v2.

August 22, 2014

Building a web-based viewer using the Autodesk View & Data API – Part 2

In the last post we saw the process for getting content uploaded to Autodesk storage and translated into the format required by the Autodesk 360 viewer. In this post we’re going to show the steps to take that data and embed it in a “simple” HTML page. (Any complex capability in this page it’s due to the UI code that Dan Wellman kindly allowed me to borrow for the sample: otherwise what it does is very simple indeed.)

There are, of course, more complex samples that the ADN team has developed to demonstrate the richness of the new View & Data API. You can, for example, isolate geometry and search for particular metadata.

As I’ve mentioned before, the model I’ve used for this sample is big and flat, so isolating sets of components was out of the question: I’ve opted to use simple view changes to highlight different sections of the model. And the latest functionality that was pushed live in late July provides some nice navigation capabilities, smoothly transitioning the view where possible.

Firstly, though, here’s the code for the very basic HTML page:

<!DOCTYPE HTML>

<html>

  <head>

    <meta charset="utf-8">

    <title>Steampunk Morgan Viewer</title>

    <link rel="stylesheet" href="css/css-transforms-viewer.css">

    <meta

      name="viewport"

      content="width=device-width, minimum-scale=1.0, maximum-scale=1.0" />

    <meta charset="utf-8">

 

    <link

      rel="stylesheet"

      href="https://developer.api.autodesk.com/viewingservice/v1/viewers/style.css"

      type="text/css">

    <script

      src="https://developer.api.autodesk.com/viewingservice/v1/viewers/viewer3D.min.js">

    </script>

    <script src="js/jquery.js"></script>

    <script src="js/jquery.easing.1.3.js"></script>

    <script src="js/jquery.csstransform.pack.js"></script>

    <script src="js/steampunk.js"></script>

  </head>

  <body onload="initialize();" oncontextmenu="return false;">

    <div id="viewer">

      <div id="cog"><!-- --></div>

      <div id="window">

        <div id="viewer3d"

             style="width:468px; height:468px; overflow: hidden;">

        </div>

      </div>

      <div id="ui">

        <ul>

          <li id="label1">

            <a href="#entirety" title="Entirety">Entirety</a>

          </li>

          <li id="label2">

            <a href="#engine" title="Engine">Engine</a>

          </li>

          <li id="label3">

            <a href="#body" title="Body">Body</a>

          </li>

          <li id="label4">

            <a href="#interior" title="Interior">Interior</a>

          </li>

          <li id="label5">

            <a href="#wheels" title="Wheels">Wheels</a>

          </li>

        </ul>

        <div><!-- --></div>

      </div>

    </div>

  </body>

</html>

Here’s the main custom JavaScript it references (steampunk.js):

var viewer;

 

// Many thanks to Dan Wellman (@danwellman). Not only did he write

// the excellent post that formed the basis for this application's

// Steampunk UI, he provided the artwork to help build a custom

// version...

// http://www.dmxzone.com/go/18220/an-image-viewer-with-the-dmxzone-universal-css-transforms-library/

 

function initialize() {

 

  // Get our access token from the internal web-service API

 

  $.get("http://" + window.location.host + '/api/token',

    function (accessToken) {

 

      var options = {};

      options.env = "AutodeskProduction";

      options.accessToken = accessToken;

      options.document =       "urn:dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL1NwTTNXNy5mM2Q=";

 

      // Create and initialize our 3D viewer

 

      var elem = document.getElementById('viewer3d');

      viewer = new Autodesk.Viewing.Viewer3D(elem, {});

      viewer.initialize();

 

      // Go with the "Riverbank" lighting and background effect

 

      viewer.impl.setLightPreset(8);

 

      // We have a heavy model, so let's save some work during

      // navigation

 

      viewer.setOptimizeNavigation(true);

 

      // Let's zoom in and out of the pivot - the screen

      // real estate is fairly limited - and reverse the

      // zoom direction

 

      viewer.navigation.setZoomTowardsPivot(true);

      viewer.navigation.setReverseZoomDirection(true);

 

      Autodesk.Viewing.Initializer(options, function () {

        loadDocument(viewer, options.document);

      });

    }

  );

 

  // Set up some UI elements for the Steampunk UI

 

  $("#ui").find("div").attr("id", "over");

  $("#window").wrapInner("<div id=\"wrapper\">");

 

  // Store positions

 

  var overPositions =

        {

          entirety: 0, engine: 75, body: 152,

          interior: 230, wheels: 310

        },

      cogPositions =

        {

          entirety: 5, engine: 80, body: 154,

          interior: 235, wheels: 310

        },

      previousCogPosition = 0;

 

  // Animation function

 

  function animator(pointer, callback) {

 

    // Move cog

 

    $("#cog").animate({

      "translateY": parseInt(cogPositions[pointer]),

      "rotate":

        (parseInt(cogPositions[pointer]) < previousCogPosition) ?

        "-=365" : "+=365"

    }, function () {

      previousCogPosition = cogPositions[pointer];

    });

 

    // Move over

 

    $("#over").animate({

      "translateY": parseInt(overPositions[pointer])

    });

 

    // Add a delay so the camera changes after the cog has stopped

    // whirring

 

    if (callback) {

      setTimeout(function () { callback(); }, 400);

    }

  }

 

  // Is there a hash?

 

  if (window.location.hash) {

 

    // Store the hash

 

    var hash = window.location.hash.split("#")[1];

 

    // Position over   

 

    animator(hash);

  }

 

  // Add transitions

 

  $("#ui a").click(function (e) {

    e.preventDefault();

 

    // Store new pointer

 

    var pointer = $(this).attr("href").split("#")[1];

 

    // Call animation function

 

    animator(pointer, function () {

      if (pointer === "entirety") {

        zoomEntirety();

      } else if (pointer === "engine") {

        zoomEngine();

      } else if (pointer === "body") {

        zoomBody();

      } else if (pointer === "interior") {

        zoomInterior();

      } else if (pointer === "wheels") {

        zoomWheels();

      }

    });

  });

}

 

// Helper functions to zoom into a specific part of the model

 

function zoomEntirety() {

  zoom(-48722.5, -54872, 44704.8, 10467.3, 1751.8, 1462.8);

}

function zoomEngine() {

  zoom(-17484, -364, 4568, 12927, 173, 1952);

}

function zoomBody() {

  zoom(53143, -7200, 5824, 12870, -327.5, 1674);

}

function zoomInterior() {

  zoom(20459, -19227, 19172.5, 13845, 1228.6, 2906);

}

function zoomWheels() {

  zoom(260.3, 26327, 954, 371.5, 134, 2242.7);

}

 

// Set the camera based on a position and target location

 

function zoom(px, py, pz, tx, ty, tz) {

 

  // Make sure our up vector is correct for this model

 

  viewer.navigation.setWorldUpVector(new THREE.Vector3(0, 0, 1));

 

  // This performs a smooth view transition (we might also use

  // setView() to get there more directly)

 

  viewer.navigation.setRequestTransition(

    true,

    new THREE.Vector3(px, py, pz), new THREE.Vector3(tx, ty, tz),

    viewer.getFOV()

  );

}

 

// Progress listener to set the view once the data has started

// loading properly (we get a 5% notification early on that we

// need to ignore - it comes too soon)

 

function progressListener(param) {

 

  if (param.percent > 0.1 && param.percent < 5) {

 

    // Remove the listener once called - one-time operation

 

    viewer.removeEventListener("progress", progressListener);

 

    // Iterate the materials to change any red ones to grey

 

    for (var p in viewer.impl.materials) {

      var m = viewer.impl.materials[p];

      if (m.color.r >= 0.5 && m.color.g == 0 && m.color.b == 0) {

        m.color.r = m.color.g = m.color.b = 0.5;

        m.needsUpdate = true;

      }

    }

 

    // Zoom to the overall view initially

 

    zoomEntirety();

  }

}

 

function loadDocument(viewer, docId) {

 

  var path = VIEWING_URL + '/' + docId.substr(4);

 

  Autodesk.Viewing.Document.load(path,

    function (document) {

      var geometryItems = [];

 

      if (geometryItems.length == 0) {

        geometryItems =

          Autodesk.Viewing.Document.getSubItemsWithProperties(

            document.getRootItem(),

            { 'type': 'geometry', 'role': '3d' },

            true

          );

      }

      if (geometryItems.length > 0) {

        viewer.load(document.getViewablePath(geometryItems[0]));

      }

 

      viewer.addEventListener("progress", progressListener);

    },

    function (errorMsg, httpErrorCode) {

      var container = document.getElementById('viewer3d');

      if (container) {

        alert("Load error " + errorMsg);

      }

    }

  );

}

A couple of things to note: the document ID is the Base64-encoded URN we saw last time. Which is hosted on my storage, so you’ll need to change this to the equivalent URN on your own, in due course.

You’ll also note that the code calls a REST API on the server hosting the page (/api/token) in order to get an access token. This is the internal API that very simply calls into the Autodesk web-service that deals with authentication.

Here’s the JavaScript code – using the Node.js framework – that implements this API:

var CONSUMER_KEY = 'K8whhq86fnoYqw4GXAW0ID1hH';

var CONSUMER_SECRET = 'DC2cBoXIy8';

var BASE_URL = 'https://developer.api.autodesk.com';

 

var request = require('request');

 

exports.getToken = function (req, res) {

 

  var params = {

    client_id: CONSUMER_KEY,

    client_secret: CONSUMER_SECRET,

    grant_type: 'client_credentials'

  }

 

  request.post(BASE_URL + '/authentication/v1/authenticate',

    { form: params },

    function (error, response, body) {

      if (!error && response.statusCode == 200) {               

        var authResponse = JSON.parse(body);

        res.send(authResponse.access_token);

      }

    }

  );

};

The key and secret are the same (edited) ones we saw in the last post. The caller will receive an authorization token they can then use to call into the View & Data API directly, without requiring further API calls from this particular web-service. (Unless the viewer needs another token, in which case this should get called again.)

So how do you get this sample working? The simplest way would be to clone the sample’s public GitHub repository to your local system (this can be done either using a GitHub client app or the command-line) and then create a new, private repository on GitHub containing this sample with a couple of modified files: you should (of course) change the key and secret to be your own in api.js, but you will also need to update the document ID (the value of options.document in steampunk.js) to point to your own content that was uploaded and translated using the process we saw last time.

Once these changes are committed to GitHub, you can create a free app on Heroku, which connects directly to GitHub and pulls down the code from there in order to build it. Very, very easy to deploy an app in this way (I won’t go through the specific steps in this post, but if there’s demand I can certainly do so in a follow-up – just post a comment if you’d like to see that).

Et voilà, the working application (if you see white mudguards and interior leather, give it some time: some materials are still loading). Hopefully you can see that it’s possible to create a fairly impressive web-based viewer for your content without a great deal of code.

The working application

Of course there’s much more you can do with the View & Data API than I’ve shown in this sample, so I do recommend perusing the other samples on GitHub, as well as playing with the running samples themselves. And you’ll find more information on this interesting new API on the ADN team’s Cloud & Mobile DevBlog.

August 20, 2014

Building a web-based viewer using the Autodesk View & Data API – Part 1

Over the next few posts we’re going to take a look at the steps needed to build the Steampunk Morgan Viewer, my first sample using Autodesk’s new View & Data API. In today’s post we’re going to look at the steps needed to host content to be served up to instances of the viewer. In a subsequent post we’ll look at the client-side implementation, connecting to and streaming down content and controlling how it gets viewed.

So let’s start with some basics. The first thing you need to do when working with the new View & Data API is to get your access key. You get that from the new developer portal:

developer.autodesk.com

Clicking on the middle button will allow you to enter the information Autodesk needs to generate a key for you:

Requesting your access key

Once approved, you’ll be able to access your consumer key and secret via the developer portal. This is information your application will need to pass to the View & Data API when it needs to use it.

Your access key

(The above key and secret are several characters shorter than yours will be: I’ve gone and cut a chunk out of the middle of each: please feel free to apply for your own. :-)

We’ll talk more about this topic in the next post, but one thing that’s worth pointing out, right away, is that the key and secret should not be embedded as part of client-resident modules (local JavaScript files, DLLs, etc.). Best practice is to implement your own web-service that calls into Autodesk’s API to get an authorization code and return it to the caller to be used in subsequent calls into Autodesk’s API. But again – we’ll see this specifically in the next post.

We now have the access information we need to use the View & Data API. Content uploaded to Autodesk storage is only accessible by an application using the same access information that was used to upload it, so we need to use our key and secret to upload our content.

But let’s take a quick step back. Where does this content come from? In my case I used Fusion 360 to prepare the content for my application, but you can upload content in any of about 70 or so file formats:

ipt, neu, stla, stl, xlsx, jt, jpg, skp, prt, dwf, xls, png, sldasm, step, dwg, zip, nwc, model, sim, stp, ste, f3d, pdf, iges, dwt, catproduct, csv, igs, sldprt, cgr, 3dm, sab, obj, pptx, cam360, jpeg, bmp, exp, ppt, doc, wire, ige, rcp, txt, dae, x_b, 3ds, rtf, rvt, g, sim360, iam, asm, dlv3, x_t, pps, session, xas, xpr, docx, catpart, stlb, tiff, nwd, sat, fbx, smb, smt, ifc, dwfx, tif.

We’re going to want to upload our Fusion 360 export (an .F3D file) for it to be viewed in our application. Our application only wants to view a single model, which makes things easier. We’re going to use a command-line HTTP tool called cURL to make calls into the web-service we need to post content, performing this as a one-time process. If you have a more dynamic use case, then this process could very easily be replicated in code.

The steps we’re going to follow are all posted here, but anyway: it’s better to go through it twice than not at all. :-)

 

Getting an access token

To perform transactions with the View & Data API, we need to pass a valid access token. We get one based on the customer key (client_id) and the secret (client_secret). Access tokens typically only last 30 minutes, so you may well need multiple tokens during a session. Which is fine: they are transient, in any case, and the main thing is the key and secret they were based on is the same.

curl --data "client_id=K8whhq86fnoYqw4GXAW0ID1hH&client_secret=DC2cBoXIy8&grant_type=client_credentials" https://developer.api.autodesk.com/authentication/v1/authenticate --header "Content-Type: application/x-www-form-urlencoded" – -k

[You may need to copy and paste this into a text editor to see the whole thing, as my blog’s format doesn’t like the long first line.]

You should get a JSON fragment back containing the access token:

{"token_type":"Bearer","expires_in":1799,"access_token":"9RVLOhMtO29QoTkgPkZ6KBN3ywkx"}

Now that we have the token we can use to make API calls, there are a number of activities to perform. One optional activity is to check the valid file formats, but we’re going to skip that and assume the file we want to upload is in an accepted format.

In terms of the steps to perform, we need to: create a container for our data (also called a bucket), upload a file into that container and then register it with the viewing service.

 

Creating a bucket

Buckets can be transient (24 hour), temporary (30 day) or persistent in nature. The type you create will depend on your use case. For our application we want a persistent bucket, which means the data it contains will stay around as long as the service exists (although if it doesn’t get accessed for 2 or more years then it might get archived).

Bucket names need to be unique across the system – as they form the root of the URN that will be used to identify content – so you won’t be able to call yours “steambuck”:

curl -k --header "Content-Type: application/json" --header "Authorization: Bearer 9RVLOhMtO29QoTkgPkZ6KBN3ywkx" --data '{"bucketKey":"steambuck","policy":"persistent"}' https://developer.api.autodesk.com/oss/v1/buckets

In response we receive this confirmation:

{"key":"steambuck","owner":"K8whhq86fnoYqw4GXAW0ID1hH",

"createDate":1403681174529,"permissions":[{"serviceId":"K8whhq86fnoYqw4GXAW0ID1hH","access":"full"}],

"policyKey":"persistent"}

 

Uploading our file

Once we have a container for it, we can upload our data. The “tricky” bit here is to find out the size of the file we want to upload. I either use the dir command (if on Windows) or ls –l (if on OS X) for this. You will also need to provide the full file path or be in the folder containing it, of course. (The file I uploaded is named SpM3W7.f3d, as it’s the 7th version of the Steampunk Morgan 3 Wheeler, in case you’re wondering. :-)

curl --header "Authorization: Bearer 9RVLOhMtO29QoTkgPkZ6KBN3ywkx" --header "Content-Length: 82984198" -H "Content-Type:application/octet-stream" --header "Expect:" --upload-file "SpM3W7.f3d" -X PUT https://developer.api.autodesk.com/oss/v1/buckets/steambuck/objects/SpM3W7.f3d -k

We get back a confirmation of the upload which includes the URN and the hash of the file.

{

  "bucket-key" : "steambuck",

  "objects" : [ {

    "location" : "https://developer.api.autodesk.com/oss/v1/buckets/steambuck/objects/SpM3W7.f3d",

    "size" : 82984198,

    "key" : "SpM3W7.f3d",

    "id" : "urn:adsk.objects:os.object:steambuck/SpM3W.f3d",

    "sha-1" : "724a4a7353132bf803cb907248043ed5873f2c01",

    "content-type" : "application/octet-stream"

  } ]

}

Registering our file with the viewing service

Now that our file is uploaded, we can register it with the viewing service. This basically asks that the file gets translated into the internal format that will get streamed down to the WebGL viewer.

The inputs for this part of the process are the access token (of course) and the URN of the file we’ve uploaded. The URN needs to be Base64 encoded: there’s a handy website that allows you to encode and decode Base64.

[If you take the below string, for instance - dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL1NwTTNXNy5mM2Q= – you’ll find that decoding it returns the string “urn:adsk.objects:os.object:steambuck/SpM3W7.f3d”… yes, the “urn:” is embedded, so do watch out for that.]

curl -k -H "Content-Type: application/json" -H "Authorization:Bearer 9RVLOhMtO29QoTkgPkZ6KBN3ywkx" -i -d '{"urn":"dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL1NwTTNXNy5mM2Q="}' https://developer.api.autodesk.com/viewingservice/v1/register

The response you get back just indicates the translation has been requested:

HTTP/1.1 200 OK

Access-Control-Allow-Credentials: true

Access-Control-Allow-Origin: *

Content-Type: application/json; charset=utf-8

x-ads-app-identifier: platform-viewing-1.6.2.788.069ca82-production

x-ads-duration: 390 ms

x-ads-error-id:

x-ads-startup-time: Wed Jun 25 06:28:52 UTC 2014

Content-Length: 20

Connection: keep-alive

{"Result":"Success"}

Which is fine, but we will also need to check the status of a particular translation job. We can do this using this call:

curl -k -i -H "Authorization: Bearer 9RVLOhMtO29QoTkgPkZ6KBN3ywkx" -X GET https://developer.api.autodesk.com/viewingservice/v1/dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL1NwTTNXNy5mM2Q=

This returns the status of the current job in a rather long-winded piece of JSON, the important pieces of which are the “progress” and “success” entries.

That’s the basic process for getting content hosted for use in the viewer. There’s an additional step needed for maintaining references between files, but having a self-contained model meant we didn’t need to worry about that, at least.

In the next post we’ll look at hooking this hosted content into our client-side viewer.

August 19, 2014

Kinect Fusion for Kinect for Windows 2

A few weeks ago I received the official retail version of Kinect for Windows 2 (having signed up for the pre-release program I had the right to two sensors: the initial developer version and the final retail version).

The Kinect for Windows v2 box

After some initial integration work to get the AutoCAD-Kinect samples working with the pre-release Kinect SDK, I hadn’t spent time looking at it in the meantime: the main reason being that I was waiting for Kinect Fusion to be part of the SDK. The good (actually, great) news is that it’s now there and it’s working really well.

For those of you who attended my AU 2013 session on Kinect Fusion, you’ll know that the results with KfW v1 were a bit “meh”: it seemed to have throughput issues when integrated into a standalone 3D application such as AutoCAD, and the processing lag meant that tracking dropped regularly and it was therefore difficult (although not impossible) to do captures of any reasonable magnitude.

Some design decisions with the original KfW device also made scanning harder: the USB cable was shorter than that of Kinect for Xbox 360, weighing in at about 1.6m of attached cable with an additional 45cm between the power unit and the PC. So even with power and USB extension cables, you felt fairly constrained. I suspect the original KfW device was really only intended to work on someone’s desk: it was only when Kinect Fusion came along that the need for a longer cable became evident (and Kinect Fusion only works with Kinect for Windows devices, of course).

Thankfully this has been addressed in KfW v2: the base cable is now 2.9m with an additional 2.2m between the box that integrates power and the USB connection. So there’s really a lot more freedom to move around when scanning.

I have to admit that I wasn’t very optimistic that Kinect Fusion would be “fixed” with KfW v2: apart from anything the increased resolution of the device – both in terms of colour and depth data – just means there’s more processing to do. But somehow it does work better: I suspect that improvements such as making use of a worker thread to integrate frames coming in from the device – as well as using Parallel.For to parallelize big parts of the processing – have helped speed things up. And these are just the change obvious from the sample code: there are probably many more optimisations under-the-hood. Either way the results are impressive: with Kinect Fusion integrated into AutoCAD I can now scan objects much more reliably than I could with KfW v1.

Here’s a sample shot of a section of my living room (with one of my offspring doing his best to stand very still):

Kinect Fusion in the living room

I was even able to do a fairly decent scan of a car (although it did take me some time to get a scan that looks this good, and it’s far from being complete).

Kinect Fusion on a car

You certainly still have to move the device quite slowly to make sure that tracking is maintained during the scanning process – I still get quirky results when I’m too optimistic when scanning a tricky volume – and I’d recommend scanning larger objects in sections and aggregating them afterwards. But Kinect Fusion works much better than it did, and now much more comparably inside a 3D system than with the standalone 2D samples (as Kinect Fusion also renders images of the volume being scanned for 2D host applications).

I still have some work to do to tidy up the code before posting, but it’s in fairly good shape, overall. I’m also hoping to provide the option to create a mesh – not just a point cloud – as Kinect Fusion does maintain a mesh of the capture volume internally, but we’ll see how feasible that is given the size of these captures (2-3 million points are typical).

There’s so much happening in this space, right now, with exciting mobile scanning solutions such as Project Tango on the horizon as well as seemingly every third project on Kickstarter (I exaggerate, but still). It’s hard to know what the future holds for 3D scanning technologies such as Kinect Fusion that require a desktop-class system with a decent GPU, but it’s certainly interesting to see how things are evolving.

August 14, 2014

Quite the road trip: 3DRV

Some of you are probably aware of this activity from Shaan’s and Scott’s blogs, but I thought it was overdue a mention here, too.

Autodesk is sponsoring the mother of all road trips: over the course of 8 months, TJ McCue is travelling the length and breadth of the United States in a brightly decorated recreational vehicle (RV), spreading the word about 3D technology. TJ is talking to people around the country about reality computing – capture, processing and fabrication processes such as 3D printing – as well as the availability of low-cost design tools.

TJ and the 3DRV

TJ is documenting his travels and experiences on the 3DRV.com website: right now they’re in Connecticut, which is their 25th state. The site makes for fascinating reading: this summary of the first 6 weeks is a good place to start, but there’s a great deal of other really interesting content there, discussing topics such as how the US Navy makes use of 3D printing and some fun with flying – and finding – UAVs.

I’ve always wanted to do a proper coast-to-coast road trip across the US, although I’m not sure I’d want to spend 8 months on the road (this one started in May and wraps up at the end of this year… totally hardcore). Looking at the schedule, it looks – perhaps unsurprisingly – as though the 3DRV will be in Las Vegas for AU2014, so perhaps I’ll get the chance to see both the RV and TJ in person then.

A couple of quick updates that relate to this overall topic…

On 3D printing… on Monday I spent a few hours working through an interesting MOOC provided by Deloitte University Press: 3D opportunity: The course on additive manufacturing for business leaders.

The course is unfortunately pretty much over – the last day is August 17th, if you want to earn a certificate, although the material will remain available until August 31st, it seems – but I didn’t have the chance to go through it sooner. The good news is that the video lectures can be ripped through really quickly – I burned through them at 1.75X speed… when I watched one at normal speed I was stunned at how slowly everyone seemed to talk – and so it only took about 3 hours to work through the whole thing. Hopefully there’s still time for some of you to get the benefit of this course.

There are some quizzes to complete if you want to get a certificate of completion, as well as some optional written exercises that at this stage it’s too late to submit, of course. I found the course really interesting both from a technology – they spent some time looking at each of the technologies commonly being used for additive manufacturing – and a business – whether related to opportunities for product design or supply chain optimization – perspective. Very interesting stuff.

On using UAVs for reality captureas reported last week, I received the Bluetooth module that will allow me to add telemetry – and enable the use of DroidPlanner for “one button 3D capture” – to my Quanum Nova drone. Sadly, though, it turns out that mine is part of a batch of misconfigured modules: 3D Robotics are sending a free replacement as soon as they have one in stock, but one hasn’t been shipped as yet. Presumably I could take the misconfigured one and configure it myself if I had an FTDI cable, but then it would probably take me as long to get the cable as the replacement module. More on this in due course…

August 12, 2014

An updated implementation of a CAD standards plugin for AutoCAD using .NET

Over the last few months I’ve had a number of people ask me for an update to this 6-year old post on implementing a CAD standards plugin for AutoCAD (which in turn was based on code from an older DevNote created from a much older VB6 sample).

Augusto Gonçalves from the ADN team very kindly made a start on this while I was out on vacation, providing a basic port that I’ve now just put a few finishing touches on. Very few changes were actually needed from the code in the original post, thankfully. Also, I did do my best to make sure the code still supports Win32 as well as x64, although admittedly I’ve only tested the code and process on 64-bit Windows. And as you need to reference a platform-specific COM component you won't (easily?) be able to build a single plugin that works on both.

Rather than repeating the bulk of the instructions from the previous post, here’s a Screencast in which I go through the process of building and loading the plugin (all it doesn’t show is downloading and extracting the plugin’s source project):




Here’s the updated C# code:

//

//  AutoCAD CAD Standards API Sample

//

//  CircleStandard.cs : CAD Standards Plugin Sample for C#

//

// This sample adds a custom plugin to the CAD Standards

// Drawing Checker.

//

// The sample plugin tests for a match between the color of a

// circle in the current drawing, and any of the colors of

// circles contained in the specified standards (.DWS) files.

// All the colors of the standard circles are considered as

// fix candidates of the circle being checked. The recommended

// fix object will be the standard circle having the nearest

// radius to the circle being checked.

 

using AcStMgr;

using AXDBLib;

using MSXML2;

using System;

using System.Collections.Generic;

using System.Runtime.InteropServices;

 

namespace CircleStandard

{

  [ProgId("CircleStandard.CircleStandard")]

  public class CircleStandard : IAcStPlugin2

  {

    // Declare variables

 

    private ContextList m_contexts =

      new ContextList();

    private AcStManager m_mgr;

    private CircleStandard m_plugin;

    private AcadDatabase m_checkDb;

    private AcadDatabase m_dwsDb;

    private AcStError m_err;

    private object m_fixArray;

    private CircleCache[] m_cirCacheArray;

    private int m_recFixIndex;

    private int m_curIndex;

    private int m_fixCnt;

    private string m_propName;

 

    // Initialize

 

    // Initializes the plugin

 

    public void Initialize(AcStManager mgr)

    {

      // This is the only member function in which

      // the interface is passed an IAcStManager interface.

 

      // Store pointer to Manager object

 

      m_mgr = mgr;

      m_plugin = this;

    }

 

    // GetObjectFilter

 

    // Plugin populates the provided array with class names

    // of objects that it can check

 

    public object GetObjectFilter()

    {

      // In this case we're only interested in circles

 

      return new string[] { "AcDbCircle" };

    }

 

    // SetupForAudit

 

    // Sets the context for a plugin to check a drawing

 

    public void SetupForAudit(

      AcadDatabase db,

      string pathName,

      object objNameArray,

      object objPathArray,

      object objDbArray)

    {

      // This method defines the context in which a plug-in

      // will operate, specifically the drawing to check and

      // the DWS files that should be used to check the drawing.

      // Here we cache our DWS standards definitions and make

      // an initial cache of circles in the DWG to be checked.

 

      // NOTE: AcadDatabase objects contained in objDbArray

      // are ***not*** guaranteed to be valid after this call.

      // They should not be cached!!!

 

      if (db != null)

      {

        // Cache a pointer to the database

 

        m_checkDb = db;

 

        // pDb is the DWG to be checked

        // Store list of circles in drawing in m_ObjIDArray

 

        if (m_checkDb != null)

        {

          // Cache list of all circles in the current drawing

 

          foreach (AcadObject obj in

            m_mgr.get_ModelSpaceProxy(m_checkDb))

          {

            if (obj.ObjectName == "AcDbCircle")

            {

              m_contexts.Add(obj.ObjectID, true);

            }

          }

        }

 

        var dbArray = (object[])objDbArray;

        var nameArray = (string[])objNameArray;

        var pathArray = (string[])objPathArray;

 

        int i = 0;

 

        // Iterate over the DWSes and cache properties (color

        // and radius) of standard circles

 

        for (int iDWS = 0; iDWS < dbArray.Length; iDWS++)

        {

          // Get the DWS database

 

          m_dwsDb = (AcadDatabase)dbArray[iDWS];

          foreach (AcadCircle stdCircle in

            m_mgr.get_ModelSpaceProxy(m_dwsDb))

          {

            CircleCache cirCache = new CircleCache();

 

            // CircleCache is utility object for storing

            // properties

 

            // Cache properties (color and radius) of all

            // circles in the DWS database

 

            cirCache.color = stdCircle.color;

            cirCache.radius = stdCircle.Radius;

            cirCache.standardFileName = nameArray[iDWS];

 

            // pFix contains fix information to be passed back

            // to the manager later

 

            var fix = new AcStFix();

            fix.Description = "Color fix";

            fix.StandardFileName =

              cirCache.standardFileName;

            fix.FixObjectName =

              "Color: " +

              StripAcPrefix(stdCircle.color.ToString());

 

            if (fix.PropertyCount == 0)

            {

              fix.PropertyValuePut(

                "Color",

                stdCircle.color

              );

            }

            cirCache.pFix = fix;

 

            Array.Resize<CircleCache>(

              ref m_cirCacheArray,

              i+1

            );

            m_cirCacheArray[i++] = cirCache;

          }

        }

      }

    }

 

    // SetContext

 

    // Sets the objects to examine when iterating over errors

 

    public void SetContext(object objIdArray, bool useDb)

    {

      // If useDb is set to "true" (default), or if

      // objIdArray is blank, we use the database (we get

      // all ids for the current drawing). Otherwise, we

      // set supplied list of objIdArrays as our list.

 

      m_contexts.SetContext(useDb, objIdArray);

    }

 

    // Start

 

    // Initializes the error iterator mechanism

 

    public void Start(AcStError err)

    {

      // If pStartError is set to an error object, we should

      // only start checking from that error, not from the

      // beginning. Mostly we will just go the Next item at

      // this point...

 

      if (err != null)

      {

        var badId = err.BadObjectId;

 

        // Find the index for BadObjectId in m_objIDArray

 

        for (

          m_curIndex = 0;

          m_curIndex < m_contexts.Count;

          m_curIndex++

        )

        {

          if (m_contexts[m_curIndex] == badId)

          {

            m_curIndex = (m_curIndex - 1);

            Next();

          }

        }

      }

      else

      {

        // No AcStError object was passed in. Start checking

        // from the very begining

 

        m_curIndex = -1;

        Next();

      }

    }

 

    // Next

 

    // Finds the next error in the current context

 

    public void Next()

    {

      m_err = null;

      if (m_contexts.Count > 0)

      {

        // Drawing contains AcDbCircle objects

 

        AcadCircle circle;

        bool foundErr;

 

        if (m_cirCacheArray.Length > 0)

        {

          // If we've not reached end of list, we first

          // increment current list index

 

          if (m_curIndex < m_contexts.Count - 1)

          {

            m_curIndex++;

            foundErr = false;

            while (m_curIndex < m_contexts.Count)

            {

              // Don't iterate beyond end of list

              // Retrieve object using its ObjectId

 

              try

              {

                circle =

                  (AcadCircle)m_checkDb.ObjectIdToObject(

                    m_contexts[m_curIndex]

                  );

 

                // Try to find a circle with the same color from

                // the cached standard circle (Iterate over cached

                // standards)

 

                for (

                  int iCache = 0;

                  iCache < m_cirCacheArray.Length;

                  iCache++

                )

                {

                  if (circle.color.CompareTo(

                        m_cirCacheArray[iCache].color

                      ) != 0)

                  {

                    // If it doesn't match, we've found a potential

                    // error

 

                    foundErr = true;

                  }

                  else

                  {

                    // If it matches any one standard, then we can

                    // stop checking

 

                    foundErr = false;

                    break;

                  }

                }

                // Check for color differences

 

                if (foundErr)

                {

                  // We found an error so create a local error

                  // object

 

                  var err = new AcStError();

                  err.Description = "Color is non-standard";

                  err.BadObjectId = circle.ObjectID;

                  err.BadObjectName =

                    StripAcPrefix(

                      circle.color.ToString()

                    );                 

                  err.Plugin = m_plugin;

                  err.ErrorTypeName = "Color ";

                  err.ResultStatus =

                    AcStResultStatus.acStResFlagsNone;

 

                  if (err.PropertyCount == 0)

                  {

                    err.PropertyValuePut(

                      "Color",

                      circle.color

                    );

                  }

                  m_err = err;

                  foundErr = false;

                  break;

                }

              }

              catch

              {

              }             

              m_curIndex = (m_curIndex + 1);

            }

          }

        }

      }

    }

 

    // Done

 

    // Returns true if there are no more errors

 

    public bool Done()

    {

      return (m_err == null);

    }

 

    // GetError -- Returns the current error

 

    public AcStError GetError()

    {

      return m_err;

    }

 

    // GetAllFixes

 

    // Returns an array of IAcStFix objects for the given

    // error (note: The caller is responsible for releasing

    // the objects in this array)

 

    public void GetAllFixes(

      AcStError err,

      ref object fixArray,

      ref int recommendedFixIndex

    )

    {

      if (err != null)

      {

        var arr = new IAcStFix[m_cirCacheArray.Length];

        ACAD_COLOR vErrorVal;

        recommendedFixIndex = -1;

        m_fixCnt = 0;

 

        // If we have a cache of fixes, then use that

 

        if (m_cirCacheArray.Length > 0)

        {

          for (int i = 0; i < m_cirCacheArray.Length; i++)

          {

            vErrorVal =

              (ACAD_COLOR)err.PropertyValueGet("Color");

            if (vErrorVal.CompareTo(

                  m_cirCacheArray[i].color

                ) != 0)

            {

              // If color property of fix matches error, then

              // add to list of fixes.

 

              arr[i] = m_cirCacheArray[i].pFix;

            }

          }

          fixArray = arr;

          m_fixArray = fixArray;

 

          // Find the recommendedFixIndex

          // (we call this function to retrieve the index -

          // we don't need the returned fix object here)

 

          GetRecommendedFix(err);

          recommendedFixIndex = m_recFixIndex;

        }

 

        // Did we find a recommended fix along the way?

 

        if (recommendedFixIndex == -1)

        {

          // No recomended fix, so set the proper flag on the

          // error object

 

          err.ResultStatus =

            AcStResultStatus.acStResNoRecommendedFix;

        }

      }

    }

 

    // GetRecommendedFix

 

    // Retrieves a fix object that describes the

    // recommended fix

 

    public AcStFix GetRecommendedFix(AcStError err)

    {

      var recFix = new AcStFix();

 

      if (m_cirCacheArray.Length == 0)

      {

        err.ResultStatus =

          AcStResultStatus.acStResNoRecommendedFix;

      }

      else

      {

        // Get the objectId for this error

 

        var tmpObjID = err.BadObjectId;

 

        // Retrieve the object to fix from the DWG

 

        var tmpCircle =

          (AcadCircle)m_checkDb.ObjectIdToObject(tmpObjID);

        double radiusToBeChecked = tmpCircle.Radius;

 

        CircleCache cirCache = m_cirCacheArray[0];

        double diff =

          Math.Abs(radiusToBeChecked - cirCache.radius);

        m_recFixIndex = 0;

 

        // Attempt to get a fix color from the cached

        // m_CircleCacheArray

 

        // Rule: the color of the standard circle with the

        // nearest radius as the one to be fixed

 

        for (int i = 0; i < m_cirCacheArray.Length; i++)

        {

          if (diff >

              Math.Abs(

                radiusToBeChecked - m_cirCacheArray[i].radius

              )

            )

          {

            cirCache = m_cirCacheArray[i];

            diff =

              Math.Abs(radiusToBeChecked - cirCache.radius);

            m_recFixIndex = i;

          }

        }

 

        // Populate properties of the recommended fix object

 

        recFix.Description = "Color fix";

        recFix.StandardFileName =

          m_cirCacheArray[m_recFixIndex].

          standardFileName;

        recFix.FixObjectName = "Color";

        if (recFix.PropertyCount == 0)

        {

          recFix.PropertyValuePut(

            "Color",

            m_cirCacheArray[m_recFixIndex].color

          );

        }

      }

      return recFix;

    }

 

    // GetPropertyDiffs

 

    // Populates the provided arrays with the names of

    // properties that are present in the provided

    // error and fix objects (used to populate the fix

    // dialog with 'property name, current value, fix value')

 

    public void GetPropertyDiffs(

      AcStError err,

      AcStFix fix,

      ref object objPropNames,

      ref object objErrorValues,

      ref object objFixValues,

      ref object objFixableStatuses)

    {

      if (err != null)

      {

        var propNames = new string[0];

        var propName = "";

        var errorValues = new string[0];

        var objErrorVal = new object();

        var fixValues = new string[0];

        var objFixVal = new object();

        var fixableStatuses = new bool[0];

 

        // Iterate error properties

 

        for (int i = 0; i < err.PropertyCount; i++)

        {

          err.PropertyGetAt(i, ref propName, ref objErrorVal);

          m_propName = propName;

 

          // Retrieve corresponding Fix property value

 

          try

          {

            fix.PropertyValueGet(propName, ref objFixVal);

 

            var errVal = (ACAD_COLOR)objErrorVal;

            var fixVal = (ACAD_COLOR)objFixVal;

 

            // Fix object has the same prop, so see if they match

 

            if (errVal.CompareTo(fixVal) != 0)

            {

              // Store error and fix properties in array ready to

              // pass back to caller

 

              Array.Resize<string>(ref propNames, i+1);

              propNames[i] = propName;

              Array.Resize<string>(ref errorValues, i+1);

              errorValues[i] = StripAcPrefix(errVal.ToString());

              Array.Resize<string>(ref fixValues, i+1);

              fixValues[i] = StripAcPrefix(fixVal.ToString());

              Array.Resize<bool>(ref fixableStatuses, i+1);

              fixableStatuses[i] = true;

            }

          }

          catch

          {

          }

        }

 

        // Initialize the arrays supplied by caller

 

        objPropNames = propNames;

        objErrorValues = errorValues;

        objFixValues = fixValues;

        objFixableStatuses = fixableStatuses;

        m_fixCnt++;

      }

    }

 

    // StripAcPrefix

 

    // Helper function to make color names prettier

 

    private string StripAcPrefix(string p)

    {

      if (p.StartsWith("ac"))

        return p.Substring(2);

      else

        return p;

    }

 

    // FixError

 

    // Takes an error and a fix object and attempts

    // to fix the error

 

    public void FixError(

      AcStError err,

      AcStFix fix,

      out string failureReason)

    {

      failureReason = "";

      if (err != null)

      {

        var badObjID = err.BadObjectId;

 

        // Retrieve object to fix from DWG

 

        var badObj =

          (AcadCircle)m_checkDb.ObjectIdToObject(badObjID);

        if (fix == null)

        {

          // If the fix object is null then attempt to get

          // the recommended fix

 

          var tmpFix = GetRecommendedFix(err);

 

          if (tmpFix == null)

          {

            // Set the error's result status to failed and

            // noRecommendedFix

 

            err.ResultStatus =

              AcStResultStatus.acStResNoRecommendedFix;

          }

          else

          {

            fix = tmpFix;

          }

        }

 

        if (fix != null)

        {

          // Fix the bad circle

 

          var sFixVal = new object();

          fix.PropertyValueGet(m_propName, ref sFixVal);

          var fixVal = (ACAD_COLOR)sFixVal;

          try

          {

            badObj.color = fixVal;

            err.ResultStatus =

              AcStResultStatus.acStResFixed;

          }

          catch

          {

            err.ResultStatus =

              AcStResultStatus.acStResFixFailed;

          }

        }

      }

    }

 

    // Clear

 

    // Clears the plugin state and releases any cached

    // objects

 

    public void Clear()

    {

      // Called just before a plugin is released.

      // Use this function to tidy up after yourself

 

      m_plugin = null;

      m_curIndex = -1;

      m_recFixIndex = -1;

      m_fixCnt = 0;

      m_propName = "";

      m_mgr = null;

      m_dwsDb = null;

      m_checkDb = null;

 

      if (m_err != null)

      {

          m_err.Reset();

          m_err = null;

      }

      if (m_cirCacheArray != null)

      {

        for (int i = 0; i < m_cirCacheArray.Length; i++)

        {

          if (m_cirCacheArray[i].pFix != null)

          {

            m_cirCacheArray[i].pFix.Reset();

            m_cirCacheArray[i].pFix = null;

          }

        }

      }

 

      m_contexts.Clear();

    }

 

    // CheckSysvar

 

    // Checks a system variable

 

    public void CheckSysvar(

      string sysvarName,

      bool getAllFixes,

      ref bool passFail)

    {

    }

 

    // StampDatabase

 

    // Returns whether the plugin uses information

    // from the database for checking

 

    public void StampDatabase(AcadDatabase db, ref bool stampIt)

    {

      // If the DWS contains circles, we stamp it by

      // returning stampIt as true, otherwise, returning

      // stampIt as false

 

      stampIt = false;

      foreach (

        AcadObject obj in

        m_mgr.get_ModelSpaceProxy(db)

      )

      {

        if (obj.ObjectName == "AcDbCircle")

        {

          stampIt = true;

          break;

        }

      }

    }

 

    // UpdateStatus

 

    // Updates the result status of the provided error

 

    public void UpdateStatus(AcStError err)

    {

    }

 

    // WritePluginInfo

 

    // Takes an AcStPluginInfoSection node and creates a

    // new AcStPluginInfo node below it (note: used by the

    // Batch Standards Checker to get information about the

    // plugin)

 

    public void WritePluginInfo(object objSectionNode)

    {

      var section = (IXMLDOMNode)objSectionNode;

      var xml =

        section.ownerDocument.createElement("AcStPluginInfo");

      var info = (IXMLDOMElement)section.appendChild(xml);

 

      info.setAttribute("PluginName", Name);

      info.setAttribute("Version", Version);

      info.setAttribute("Description", Description);

      info.setAttribute("Author", Author);

      info.setAttribute("HRef", HRef);

      info.setAttribute("DWSName", "");

      info.setAttribute("Status", "1");

    }

 

    // Author

 

    // Returns the name of the plugin's author

 

    public string Author

    {

      get { return "Kean Walmsley, Autodesk, Inc."; }

    }

 

    // Description

 

    // Returns a description of what the plugin checks

 

    public string Description

    {

      get

      {

        return

          "Checks that circles in a drawing have a color " +

          "that matches those of a similar radius in an " +

          "associated standards file.";

      }

    }

 

    // HRef

 

    // Returns a URL where the plugin can be obtained

 

    public string HRef

    {

      get

      {

        return

          "http://blogs.autodesk.com/through-the-interface";

      }

    }

 

    // Icon

 

    // Returns the HICON property Icon

 

    public int Icon

    {

      get { return 1; }

    }

 

    // Name

 

    // Returns the name of the plugin

 

    public string Name

    {

      get { return "Circle color checker"; }

    }

 

    // Version

 

    // Returns the version of the plugin

 

    public string Version

    {

      get { return "2.0"; }

    }

 

    // CircleCache

 

    // Caches "standard" circle properties (Color, Radius)

    // from .DWS files, as well as pointers to the circle's

    // relevant AcStFix object

 

    private class CircleCache

    {

      public double radius;

      public ACAD_COLOR color;

      public string standardFileName;

      public AcStFix pFix;

    }

 

    // ContextList

 

    // Manages list of objects to check - either all in

    // database, or just those recently added or modified

 

    private class ContextList

    {

      // List of objects to use when not in database context

 

      List<long> m_altIdArray = new List<long>();

 

      List<long> m_dbIdArray = new List<long>();

 

      // All objects in database

 

      private bool m_useDb;

 

      // Return item from correct context list

 

      public long this[int index]

      {

        get

        {

          if (m_useDb)

            return m_dbIdArray[index];

          else

            return m_altIdArray[index];

        }

      }

 

      // Number of items in current list

 

      public int Count

      {

        get

        {

          if (m_useDb)

            return m_dbIdArray.Count;

          else

            return m_altIdArray.Count;

        }

      }

 

      // Flag to determine from which context list to return element

      // Select all database or just modified items for checking

      // (but also add any new ids to database array

 

      public void SetContext(bool useDb, object objContextArray)

      {

        if (!useDb && objContextArray != null)

        {

          m_useDb = false;

          long[] idArray = (long[])objContextArray;

          for (int i = 0; i < idArray.Length; i++)

          {

            long val = (long)idArray[i];

            m_altIdArray.Add(val);

 

            // Have to keep database list up to date

 

            m_dbIdArray.Add(val);

          }

        }

        else

        {

          // Clear

 

          m_useDb = true;

          m_altIdArray.Clear();

        }

      }

 

      public void Add(long id, bool useDb)

      {

        if (useDb)

          m_dbIdArray.Add(id);

        else

          m_altIdArray.Add(id);

      }

 

      // Clear both lists

 

      public void Clear()

      {

        m_altIdArray.Clear();

        m_dbIdArray.Clear();

      }

    }

 

    long IAcStPlugin2.Icon

    {

      get { return 0; }

    }

  }

}

Thanks to Augusto for his help getting this ready!

August 11, 2014

DogeSharp in AutoCAD

Sometimes you just need to ease back into work after a few weeks off. So today I decided to have a go at integrating DogeSharp – a programming language on top of of .NET inspired by Dogescript – into AutoCAD. There was absolutely no reason to do so, other than to cause myself some amusement. Which is actually a pretty good reason, now that I think about it.

Doge

For those of you who – like me – are almost completely unaware of Internet memes such as Doge, this was apparently a big one in 2013 and all started with this blog post. Although as the meme has since grown big enough for advertisers and politicians to jump on the bandwagon, at this point it seems to be well past its prime.

But programmers don’t necessarily care about such mundane things as external validation. And we often look at things on a different timescale. Code can end up having a surprising lifespan – it can be used once and then disappear or it can stay around forever – and as we all know this sadly isn’t necessarily based on its quality. Who knows what tomorrow will bring… why not write code in a language that causes you and the people around you some entertainment? (I’m not actually advocating this, of course… apart from anything else, the ADN team won’t thank me if they end up having to provide support for Doge[script|Sharp]. :-)

The Dogescript and DogeSharp languages make use of the usual Dogesque words: so, such, much, many, very, wow, amaze, plz, etc. Here’s some code I ended up getting working to builds a functional AutoCAD command (I saved this code in a file named AutoCAD.ds):

many System

many Autodesk.AutoCAD.Runtime

many Autodesk.AutoCAD.ApplicationServices

many Autodesk.AutoCAD.EditorInput

 

much MyClass

    [such CommandMethod many "DOGE"]

    very TestCommand so void much static much public

        such doc Application.DocumentManager.MdiActiveDocument

        such ed doc.Editor

 

        such num plz NumFunc;

 

        plz ed.WriteMessage many "wow – such amaze ... ";

        plz ed.WriteMessage many plz num.ToString;

 

    very NumFunc so long much static

        such rand gimme Random;

        such num plz rand.Next;

        amaze num;

I did have to work around one minor quirk: I ended up making multiple calls to WriteMessage() as the formatting string didn’t get passed through as expected. No big deal, though.

Just as Dogescript compiles to JavaScript, DogeSharp gets translated to C# which then builds a .NET executable (whether a .EXE or .DLL). The DogeSharp project on GitHub basically implements a command-line executable that passes a .ds file and generates an appropriate assembly as per the command-line arguments. (I did have to make a minor change to the DogeSharp code to allow classes to be public and so visible to AutoCAD, but I’m told this will be fixed soon. This has now been fixed. Thanks, Ruan!)

Here are the command-line arguments I added to the DogeSharp samples’ build.bat to build the desired AutoCAD-DS.dll (after copying AcMgd.dll, AcDbMgd.dll and AcCoreMgd.dll from the ObjectARX SDK’s inc folder into DogeSharp’s Samples\Libs folder):

echo Building AutoCAD sample

%dsc% AutoCAD.ds /target:library /out:AutoCAD-DS.dll /reference:Libs\AcDbMgd.dll /reference:Libs\AcCoreMgd.dll

/reference:Libs\AcMgd.dll /reference:System.dll /preservetranslated

The %dsc% system variable resolves to the dsc.exe executable built by the DogeSharp project… the DSC tool isn’t truly integrated into the VS project environment, but as build.bat gets executed as a post-build step (or rather a pre-build step on the chained Samples project), everything gets launched from within Visual Studio.

I mean to say, what’s not to like about a command-line tool that populates the VS output with such cool ASCII art? :-)

1>  Building AutoCAD sample

1> 

1>           ▄              ▄

1>          ▌▒█           ▄▀▒▌

1>          ▌▒▒█        ▄▀▒▒▒▐

1>         ▐▄▀▒▒▀▀▀▀▄▄▄▀▒▒▒▒▒▐

1>       ▄▄▀▒░▒▒▒▒▒▒▒▒▒█▒▒▄█▒▐

1>     ▄▀▒▒▒░░░▒▒▒░░░▒▒▒▀██▀▒▌

1>    ▐▒▒▒▄▄▒▒▒▒░░░▒▒▒▒▒▒▒▀▄▒▒▌

1>    ▌░░▌█▀▒▒▒▒▒▄▀█▄▒▒▒▒▒▒▒█▒▐

1>   ▐░░░▒▒▒▒▒▒▒▒▌██▀▒▒░░░▒▒▒▀▄▌

1>   ▌░▒▄██▄▒▒▒▒▒▒▒▒▒░░░░░░▒▒▒▒▌

1>  ▀▒▀▐▄█▄█▌▄░▀▒▒░░░░░░░░░░▒▒▒▐

1>  ▐▒▒▐▀▐▀▒░▄▄▒▄▒▒▒▒▒▒░▒░▒░▒▒▒▒▌

1>  ▐▒▒▒▀▀▄▄▒▒▒▄▒▒▒▒▒▒▒▒░▒░▒░▒▒▐

1>   ▌▒▒▒▒▒▒▀▀▀▒▒▒▒▒▒░▒░▒░▒░▒▒▒▌

1>   ▐▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▒░▒░▒▒▄▒▒▐

1>    ▀▄▒▒▒▒▒▒▒▒▒▒▒░▒░▒░▒▄▒▒▒▒▌

1>      ▀▄▒▒▒▒▒▒▒▒▒▒▄▄▄▀▒▒▒▒▄▀

1>        ▀▄▄▄▄▄▄▀▀▀▒▒▒▒▒▄▄▀

1>           ▒▒▒▒▒▒▒▒▒▒▀▀

1> 

1> 

1>                                many D#

1> 

1>          very c Ruan Pearce-Authers

1> 

1>           much programming

1> 

1>                                     such language

1> 

1>                 very translating...

1> 

1>        much translation: AutoCAD.ds

1> 

1>                                    so forwarding: /target:library

1> 

1>               very references: Libs\AcDbMgd.dll, Libs\AcCoreMgd.dll, Libs\AcMgd.dll, System.dll

1> 

1>              such translated, wow

1> 

1>             many compiling...

1> 

1>                                       such compiled, wow

1> 

1>              many time, such elapsed, so milliseconds: 451

1> 

1>                      many success: AutoCAD-DS.dll

1> 

1>  Samples -> Z:\GitHub\DogeSharp\Samples\bin\Debug\Samples.dll

When NETLOADed into AutoCAD, AutoCAD-DS.dll defines the DOGE command that prints a message and a random number:

Command: DOGE

wow - such amaze . . .1391841392

If you do end up messing around with DogeSharp, please do pass on any feedback/suggestions to the author, Ruan Pearce-Authers.

August 08, 2014

Autodesk Exchange Apps Hackathon 2014

After the initial Autodesk Exchange Apps Portathon was held last year, the ADN team is running a similar – but expanded – event, the Autodesk Exchange Apps Hackathon, on September 20-21.

Autodesk Exchange Apps Hackathon

There’s more information available on both Jeremy’s blog and the ADN DevBlog, but here’s a quick summary of the salient points:

  • Free to attend
  • Open to anyone, anywhere (it’s a virtual event)
  • Lots of great presentations on app development
    • The ADN team will also be available to provide support
  • You earn money for apps you get published
    • $50/$100 for published free/paid apps, up to 5 per company
  • Additional Cloud/Sustainability App competitions, with iPad minis to win

This is really a great opportunity to get your apps in front of lots of Autodesk customers – there have been upwards of the 2.25 million unique visitors to the store since its inception – and to earn money from Autodesk for doing so! Find out more from the Hackathon site.

August 06, 2014

Top 5 beaches on the island of Andros

As mentioned in the last post, we recently got back from our first trip to the beautiful Greek island of Andros. While we managed to visit 8 beaches during our 12 days on the island – some multiple times – there are more to see: the list is far from exhaustive.

I’ve done my best to state my (admittedly subjective) criteria, in case you’re looking for something different from a beach holiday. I’ve rated the various beaches in 5 different areas:

Access [easy or hard to get to, state of access road, parking relative to the beach]

Facilities [umbrellas, shade, whether there’s a “taverna”, windsurfing, etc.]

Cleanliness [do people clean up after themselves or does someone do it for them]

Natural Beauty

Peacefulness

I’ve given a heavier weighting (3x) to the last two, as that’s what I’ve found I valued most highly (i.e. when I summed it all up it gave me the order that felt right).

So without further ado, the winners are…
 
 

5. Kypri

Kypri beach

One of the last beaches we visited, this one really impressed us (especially the Hotel Perrakis beach bar – excellent service & great mojitos! – and the windsurf/kayak rental). Not the prettiest location, but then it has stiff competition.

It narrowly beat the neighbouring Gold Beach onto this list, which lost out in terms of facilities and having a less laid-back (i.e. more twentysomething) vibe.

Access ★★★★★ Close to a major road, convenient parking
Facilities ★★★★★ Great bar & facilities, credit card payment!
Cleanliness ★★★★★ Very clean
Natural beauty ★★★★☆ Still pretty amazing
Peacefulness ★★★☆☆ Could lose the music for my aging ears
Overall ★★★★☆ 4.0

 

4. Achla

Achla beach

Achla beach is one of the top-rated attractions on Andros, according to TripAdvisor. Some rate it as the best beach on Andros, in Greece or even the world. It’s seriously that beautiful.

But unless you have your own boat, it’s a disaster: the last 8km of access road is worse than unpaved, it’s basically off-roading along a scary ridge that seems to go on forever. (Driving up and out is easier, as you have much more control.)

Any beach is going to look amazing after that drive. The fresh peach daiquiris at the beach bar also helped calm some nerves in our party.

Access ★★☆☆☆ Insane 8km of off-road driving
Facilities ★★☆☆☆ Bring your own shade
Cleanliness ★★★★☆ People seem to take care, here
Natural beauty ★★★★★ Incredibly beautiful
Peacefulness ★★★★★ Perfect (unless a big boat unloads)
Overall ★★★★☆ 4.2 (joint with Zorkos)

 

3. Ζόρκος (Zorkos)

Zorkos beach

This was a fantastic beach, with a much more tolerable access road. Yes, it was still dirt, but it really wasn’t so bad when compared with Achla. The restaurant was very good (even slightly cheaper than elsewhere) and having beach umbrellas at the end of the drive in was a nice bonus. (The price across Andros is a fairly uniform €3 per sunbed – in a few places with competition – such as Kypri and Agios Petros beach – you might find them at €2.50 or get a free bottle of water thrown in.)

Access ★★★★☆ Still with dirt-road access towards the end
Facilities ★★★★☆ Very decent restaurant, umbrellas on beach
Cleanliness ★★★☆☆ Trash blew from overfull bins late in the day
Natural beauty ★★★★☆ Really beautiful
Peacefulness ★★★★★ Calm, peaceful, relaxing
Overall ★★★★☆ 4.2 (joint with Achla)

 

2. Γριάς το Πήδημα (Grias to Pidima)

Grias to Pidima beach

Another Andros landmark, Grias to Pidima means (I believe) “The Old Lady’s Leap” and is named after the natural formation of an old lady just off the beach. Yes, you do have to squint and use your imagination, but it’s still pretty cool.

You have a bit of a drive up from Ormos, on Korthi bay, and then a modest ~10 minute hike along the top of a small cliff (which is a bit scary at first with a 5 year-old in flip flops) until you get down to the beach. But it’s find once you know the path.

We only stopped by at the end of the afternoon, so didn’t really experience the full day there. But it was a great few hours, nonetheless.

Access ★★★☆☆ A bit of a hike down close to a cliff
Facilities ★★☆☆☆ No facilities of any kind but natural shade
Cleanliness ★★★★★ Very clean
Natural beauty ★★★★★ Wow
Peacefulness ★★★★★ Very peaceful when we went
Overall ★★★★☆ 4.4

 

1. Vitali

Vitali beach 2

This was hands-down our favourite beach on the island. When we arrived there on the first full day on Andros – by a dirt road but nothing bad when compared with Achla and even Zorkos – friends led us to a cave on the far left of the beach where we camped out in the shade as the older kids (and braver adults) jumped off rocks into the sea. Incredible.

Vitali beach 1

The second time we went there just as a family and did the sunbed and umbrella thing. Our umbrella was right on the beach and our kids had a blast playing in the waves with the late-afternoon sun glowing on their faces (this one was east-facing).

If I had to visit a beach every day for a month, this would be it, and that’s really saying a lot.

Access ★★★★☆ Dirt road at the end, but parking on beach
Facilities ★★★★★ Great restaurant, perfect beach service
Cleanliness ★★★★★ Very clean
Natural beauty ★★★★☆ Not quite as impressive, but still fantastic
Peacefulness ★★★★★ Calm, peace, zen… [drops off to sleep]
Overall ★★★★★ 4.6

Feed/Share

10 Random Posts