September 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        










September 30, 2014

Displaying the area of the last AutoCAD entity in an HTML palette using JavaScript and .NET

The title of this one is a little specific – the post actually deals with the scenario of passing data from .NET to an HTML-defined palette, as well as some other tips & tricks – but it’s something I wanted to show.

Here’s the basic idea: whenever a closed curve gets added to the drawing, we want to display its area as the only item in an HTML palette. We also want the palette to update when objects get erased, etc., which makes life somewhat trickier.

To set the scene, here’s a quick screencast of the finished application in action (I didn’t record audio – it should be obvious what’s happening, though):




The “brief” came from Matthew Shaxted at SOM Chicago: they want to use an HTML palette to display more advanced area calculations – not just the area of the last entity added – but this is a good starting point for them. On a side note, these guys are doing some really cool stuff with WebGL inside AutoCAD, something similar to (but way beyond) the approach shown in this previous post. It’s amazing what can be done with this mechanism.

Right, back to the topic at hand. In most of the previous AutoCAD+JavaScript-related posts, we call through to .NET commands from HTML/JavaScript. We want to do this here, too, but we also want data to travel in the reverse direction, and not just passed back by .NET functions: we need to push data to the palette, updating it when (for instance) new entities are created.

During the research for this post, I iterated through a few different approaches which I feel are worth sharing for context:

  • Reload the palette, forcing the page load to call back through from JavaScript to .NET to get the required data
    • Very heavy-handed: I literally had to close and re-launch the palette, which caused some ugly UI flicker
  • Define a hidden JavaScript command (with “no history” and “no undo marker” defined) that we call from our event handler using SendStringToExecute()
    • It worked, but added complexity as we’re having to marshal data via Editor.GetString() (etc.) methods, which also meant setting NOMUTT
  • Register a JavaScript method using registerCallback() and call it using Application.Invoke() from .NET
    • This is the approach recommended by the online help, which it turns out is out-of-date… there is no connection between JavaScript and acedInvoke() and its equivalents
  • Use the same approach but invoke it by calling acjsInvokeAsync() via P/Invoke
    • We need to get a native .NET method exposed for this, but at least this approach works (hurray!)
    • Thanks to Albert Szilvasy for providing pointers that got me this far :-)

Aside from this there were some other structural issues to deal with: we want to detect when objects get added to and erased from the chosen drawing – ObjectAppended and ObjectErased events are great for this, of course – but we clearly need to wait until CommandEnded to do anything of significance.

Beyond that, though, we also need to be careful not to trample on the undo file. As explained in this very helpful DevBlog post, if you open anything for write during CommandEnded of an undo-related command, this will cause the famous “nothing to redo” problem when someone tries to redo the undone actions. While we don’t open anything directly for write, we do use Editor.SelectLast() to get the most recently created entity in the drawing. Even if we call this via SendStringToExecute() it causes the problem.

So the code implements a rudimentary object list – which tracks curves that are added and erased while it’s in operation – so that we can get the most recently added entity from there. I have no doubt there are complexities we’re not addressing with this fairly crude approach, but then we’re only using it during undo, which mitigates the risk somewhat. For instance, we don’t populate the history when we load the app into a drawing that contains geometry, so if we undo back past the time the app was loaded, we don’t display any area information.

I think that’s enough preamble. Here’s the HTML page defining our palette – I’ve embedded the various JavaScript code, including the extension to the shaping layer – for simplicity:

<!doctype html>

<html>

  <head>

    <title>Last Area</title>

    <style>

      html, body {

        height: 100%;

        width: 100%;

        margin: 0;

        padding: 0;

      }

 

      body {

        display: table;

      }

 

      .centered-on-page {

        text-align: center;

        display: table-cell;

        vertical-align: middle;

        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;

        font-size: xx-large;

        font-weight: bold;

      }

    </style>

    <script

      src="http://app.autocad360.com/jsapi/v2/Autodesk.AutoCAD.js">

    </script>

    <script type="text/javascript">

      function displayValue(prop, val) {

 

        // Display the specified value in our div for the specified

        // property

 

        var div = document.getElementById(prop);

        if (div != null) {

          div.innerHTML = val;

        }

      }

 

      function updatePalette(args) {

 

        // Simply unpack the arguments from JSON and pass

        // them through to the generic display function

 

        var obj = JSON.parse(args);

        displayValue(obj.propName, obj.propValue);

      }

 

      // Shaping layer extension

 

      function lastAreaFromAutoCAD() {

        var jsonResponse =

          exec(

            JSON.stringify({

              functionName: 'LastArea',

              invokeAsCommand: false,

              functionParams: undefined

            })

          );

        var jsonObj = JSON.parse(jsonResponse);

        if (jsonObj.retCode !== Acad.ErrorStatus.eJsOk) {

          throw Error(jsonObj.retErrorString);

        }

        return jsonObj.result;

      }

    </script>

  </head>

  <body>

    <div id="area" class="centered-on-page"/>

    <script type="text/javascript">

      (function () {

 

        registerCallback("updval", updatePalette);

 

        // On load we call through to .NET to get the area of

        // the last entity and then display it

        // (we could also have the .NET could reinvoke

        // JavaScript if we wanted to keep the display-specific

        // logic in one module)

 

        try {

          var area = lastAreaFromAutoCAD();

          displayValue(

            "area", area > 0.0 ? area.toFixed(2) : "No area"

          );

        }

        catch (ex) {

          displayValue("area", "No area");

        }

      })();

    </script>

  </body>

</html>

Here’s the loading C# code which gets called from the palette but also calls through to it:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Windows;

using System;

using System.IO;

using System.Reflection;

using System.Runtime.InteropServices;

 

namespace LastAreaPalette

{

  public class Commands

  {

    // Member variables

 

    private PaletteSet _areaps = null;          // Our palette

    private static Document _launchDoc = null// Doc launched from

    private static bool _update = false;        // Flag for refresh

    private static ObjectIdCollection _ids =    // List to help

      new ObjectIdCollection();                 // refresh on UNDO

 

    [DllImport(

      "AcJsCoreStub.crx", CharSet = CharSet.Auto,

      CallingConvention = CallingConvention.Cdecl,

      EntryPoint = "acjsInvokeAsync")]

    extern static private int acjsInvokeAsync(

      string name, string jsonArgs

    );

 

    // JavaScript-exposed method allowing our HTML page to query

    // the area of the last entity as it loads

 

    [JavaScriptCallback("LastArea")]

    public string LastArea(string jsonArgs)

    {

      // Default return value is failure

 

      var res = "{\"retCode\":1}";

 

      var doc = GetActiveDocument(Application.DocumentManager);

 

      // If we didn't find a document, return

 

      if (doc == null)

        return res;

 

      // We could probably get away without locking the document

      // - as we only need to read - but it's good practice to

      // do it anyway

 

      try {

        using (var dl = doc.LockDocument())

        {

          // Call our internal method and package the results

          // as JSON if successful

 

          var area = GetAreaOfLastEntity(doc, ObjectId.Null);

          if (area > 0.0)

          {

            res =

              String.Format(

                "{{\"retCode\":0, \"result\":{0}}}", area

              );

          }

        }

      }

      catch { }

 

      return res;

    }

 

    // Helper function to retrieve the area of the last entity

    // (assuming it's a closed curve). We have an "optional"

    // ObjectId argument (we couldn't define ObjectId.Null as the

    // default value, as that's not a compile-time constant)

    // which we use when undoing (as SelectLast() invalidates

    // the undo file if used then)

 

    private double GetAreaOfLastEntity(

      Document doc, ObjectId id

    )

    {

      var db = doc.Database;

      var ed = doc.Editor;

 

      var res = 0.0;

 

      if (id == ObjectId.Null)

      {

        // Get the last entity (which returns a selection set)

 

        var psr = ed.SelectLast();

 

        if (psr.Status != PromptStatus.OK || psr.Value.Count != 1)

          return res;

 

        id = psr.Value[0].ObjectId;

 

        // If our list of IDs doesn't yet contain it, append it

 

        if (!_ids.Contains(id))

        {

          _ids.Add(id);

        }

      }

 

      // Use open/close as we're often called in an event handler

 

      var tr = doc.TransactionManager.StartOpenCloseTransaction();

      using (tr)

      {

        var c = tr.GetObject(id, OpenMode.ForRead) as Curve;

        if (c.Closed)

        {

          res = c.Area;

        }

        tr.Commit();

      }

      return res;

    }

 

    // Helper to get the document a palette was launched from

    // in the case where the active document is null

 

    private Document GetActiveDocument(DocumentCollection dm)

    {

      var doc = dm.MdiActiveDocument;

      if (doc == null)

      {

        doc = _launchDoc;

      }

      return doc;

    }

 

    [CommandMethod("LAREA")]

    public void LastAreaPalette()

    {

      // We're storing the "launch document" as we're attaching

      // various event handlers to it

 

      _launchDoc =

        Application.DocumentManager.MdiActiveDocument;

 

      _areaps =

        ShowPalette(

          _areaps,

          new Guid("4169EEA9-E3BA-49C4-9197-265A2E42E4B5"),

          "LAREA",

          "Last Area",

          GetHtmlPathArea(),

          true

        );

 

      if (_launchDoc != null)

      {

        // When the document we're connected to is closed,

        // we want to close the palette

 

        _launchDoc.BeginDocumentClose +=

          (s, e) =>

          {

            if (_areaps != null)

            {

              _areaps.Close();

              _areaps.Dispose();

              _areaps = null;

            }

            _launchDoc = null;

          };

 

        // We're going to monitor when objects get added and

        // erased. We'll use CommandEnded to refresh the

        // palette at most once per command (might also use

        // DocumentManager.DocumentLockModeWillChange)

 

        _launchDoc.Database.ObjectAppended += OnObjectAppended;

        _launchDoc.Database.ObjectErased += OnObjectErased;

        _launchDoc.CommandEnded += OnCommandEnded;

 

        // When the PaletteSet gets destroyed we remove

        // our event handlers

 

        _areaps.PaletteSetDestroy += OnPaletteSetDestroy;

      }

    }

 

    void OnObjectAppended(object s, ObjectEventArgs e)

    {

      // If we have a curve, flag the palette for refresh

      // and add the curve's ID to our list

 

      if (e != null && e.DBObject is Curve)

      {

        _update = true;

        _ids.Add(e.DBObject.ObjectId);

      }

    }

 

    void OnObjectErased(object s, ObjectErasedEventArgs e)

    {

      // If we have a curve, flag the palette for refresh

      // and then either add or remove the curve's ID to/from our

      // list, depending on whether we're erasing or unerasing

 

      if (e != null && e.DBObject is Curve)

      {

        _update = true;

 

        var id = e.DBObject.ObjectId;

        if (e.Erased)

        {

          if (_ids.Contains(id))

          {

            _ids.RemoveAt(_ids.IndexOf(id));

          }

        }

        else if (!e.Erased)

        {

          _ids.Add(id);

        }

      }

    }

 

    void OnCommandEnded(object s, CommandEventArgs e)

    {

      if (_update)

      {

        // When we need to update the display in the palette,

        // get the last area and pass it through to our hidden

        // UPDPAL command (specifying the "area" div)

 

        _update = false;

 

        // We don't want our refresh function to use

        // Editor.SelectLast() if we're undoing,

        // so check for that

 

        var isUndoing =

          (e.GlobalCommandName == "U" ||

           e.GlobalCommandName == "UNDO");

 

        var doc = (Document)s;

 

        var id = ObjectId.Null;

        var area = 0.0;

 

        if (!isUndoing || _ids.Count > 0)

        {

          // If we're undoing, pass the ID of the object at the

          // top of our "stack" (which should be valid as

          // OnObjectErased() will have popped any being erased)

 

          if (isUndoing)

          {

            id = _ids[_ids.Count - 1];

          }

          area = GetAreaOfLastEntity(doc, id);

        }

 

        // Invoke our JavaScript function to update the palette

 

        acjsInvokeAsync(

          "updval",

          "{\"propName\":\"area\",\"propValue\":" +

          (area > 0.0 ?

            Math.Round(area, 2).ToString() :

            "\"No area\"") + "}"

        );

      }

    }

 

    void OnPaletteSetDestroy(object s, EventArgs e)

    {

      // When our palette is closed, detach the various

      // event handlers

 

      if (_launchDoc != null)

      {

        _launchDoc.Database.ObjectAppended -= OnObjectAppended;

        _launchDoc.Database.ObjectErased -= OnObjectErased;

        _launchDoc.CommandEnded -= OnCommandEnded;

      }

    }

 

    // Helper function to show a palette

 

    private PaletteSet ShowPalette(

      PaletteSet ps, Guid guid, string cmd, string title, Uri uri,

      bool reload = false

    )

    {

      // If the reload flag is true we'll force an unload/reload

      // (this isn't strictly needed - given our refresh function -

      // but I've left it in for possible future use)

 

      if (reload && ps != null)

      {

        // Close the palette and make sure we process windows

        // messages, otherwise sizing is a problem

 

        ps.Close();

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

        ps.Dispose();

        ps = null;

      }

 

      if (ps == null)

      {

        ps = new PaletteSet(cmd, guid);

      }

      else

      {

        if (ps.Visible)

          return ps;

      }

 

      if (ps.Count != 0)

      {

        ps[0].PaletteSet.Remove(0);

      }

 

      ps.Add(title, uri);

      ps.Visible = true;

 

      return ps;

    }

 

    // Helper function to get the path to our HTML files

 

    private static string GetHtmlPath()

    {

      // Use this approach if loading the HTML from the same

      // location as your .NET module

 

      //var asm = Assembly.GetExecutingAssembly();

      //return Path.GetDirectoryName(asm.Location) + "\\";

 

      return "http://through-the-interface.typepad.com/files/";

    }

 

    private static Uri GetHtmlPathArea()

    {

      return new Uri(GetHtmlPath() + "lastarea.html");

    }

  }

}

Something else to note about this, before we finish: rather than create an updarea() function in JavaScript that only updates the area in the palette, I did my best to make this a bit more generic. The updval() method has both the property name and value passed as arguments to it, which means we can use it to update other “divs” in the HTML, should we need to. You can imagine a much more complex palette populated with various fields coming from AutoCAD, for instance, all updated using calls to updval().

September 25, 2014

TEDxCERN: Mind = Blown

I was expecting yesterday’s TEDx event at CERN to be good, but it was way beyond that.

The overall theme of the event was “Forward: Charting the Future with Science.” It comprised 17 separate sessions grouped into 3 sections – Adapt, Change & Create – with Brian Cox as host.

Brian Cox

The event was planned and executed very well: even when the occasional minor glitch occurred it only ended up adding fun to the proceedings.

The agenda was structured nicely, with a good progression of themes between sessions. I really liked the inclusion of 3 TED-Ed sessions on climate change, matter & anti-matter and cosmic rays, which apparently met the usual requirement for TEDx events also to show material from TED.com.

To give you a sense of the quality of the event, here’s a single sentence on each of the 17 live sessions.

Adapt

Robert CreaseRobert Crease talked about the challenges around using science to influence public policy and how best to succeed.

Tamsin Edwards

Tamsin Edwards talked about embracing and effectively communicating uncertainty around climate science.

Marcia Barbosa

Marcia Barbosa described the complexity of that most essential of resources – water – and how nano-tubes and materials inspired by African beetles can help make clean water more widely available.Nina Federoff

Nina Federoff talked about the role of GMOs in feeding a future global population of 10 billion.

Nitin Sawhney and Nicki Wells

One of my favourite musician’s, Nitin Sawhney, talked about the connection between music and mathematics (he even used vedic mathematics to solve the cube root of 132,651 live on stage… awesome!) and performed with the very talented Nicki Wells.

Change

Julia Greer

Julia Greer talked about her team’s research into lightweight 3D nanostructures, and how materials at the nanoscale can have surprising properties.

Sonia Trigueros

Sonia Trigueros talked about how nanomedecine holds the promise to fight cancer in a much more targeted way than existing therapies.

Srikumar Bannerjee

Srikumar Bannerjee described how modern reactors and spent fuel reprocessing make nuclear energy the best choice for clean, abundant energy.

John Mighton

John Mighton talked about the difficulties he had learning mathematics as a child and how that inspired him to launch JUMP Math.

Andrew Nemr

Andrew Nemr closed the second session with a tap dance accompanied by an explanation of why he dislikes labels.

Create

Hayat Sindi started the second session by describing the importance of marrying science with social innovation.

Arthur Zang's CardioPad

Arthur Zang, a 26-year-old entrepreneur, presented a device he developed to help Cameroun’s cardiologists diagnose patients remotely.

Topher White

Topher White talked about an innovative approach to alerting rangers in the rainforest to illegal logging activity – repurposing old mobile phones, powered by recycled solar panels, to detect humanly inaudible chainsaws.

Danielle Fong

Danielle Fong presented a technology her company has developed using compressed air to store energy harvested from intermittent, renewable sources.

Julien Lesgourges discussed how spectral analysis can be applied to pretty much anything – sound, images and even the universe – resulting in cosmologists having a better understanding of its composition.

Jamie Edwards

Jamie Edwards, a 14-year-old schoolboy from Lancashire, talked entertainingly about how he convinced his school to allow – and even fund – his creation of a small nuclear reactor in its science lab.

Tim Exile

Tim Exile remixed sounds from CERN and TEDxCERN itself as part of his live performance closing this fantastic event.

Oh yes, here’s a quick aerial shot showing where I was glued to my seat for the ~5 hours of presentations…

View from the roof

At the following social event I had a nice time chatting with Matteo Mazzeri, one of the organisers of TEDxGeneva. I'm looking forward to attending that event in April. And it seems I just missed TEDxBern, unfortunately... maybe it's time to think about helping organise a TEDxNeuchatel? :-).

All images © 2014 CERN

September 24, 2014

Off to TEDxCERN

TEDxCERN 2014I last went to CERN back in 1996 (although it may have been late 1995) for an ADGE conference. I think that ADGE stood for “Autodesk Developer Group Europe”, but the A might also have been for AUGI. Any old-timers out there who can confirm? (I’m sure Jeremy Tammik remembers. :-)

Anyway, the ADGE conference that year was held at CERN, which was really cool. As part of the entertainment we had a tour around the facilities, including the accelerator complex – although some time before construction of the Large Hadron Collider was started.

So here I find myself about to head back there this afternoon for TEDxCERN. This will be my second TEDx event this year, having attended TEDxLausanne back in February. Understandably, given the location, today’s programme looks to be a bit more “sciencey”, which is great. And there are at least a couple of names I already know from the list of speakers, which is another good sign.

September 23, 2014

123D Catch available for Android

I was very happy to read the news on Shaan’s blog: 123D Catch is now available on Android. I blogged about its availability via the web and on iOS, a couple of years ago, but there’s now an Android version and it’s pretty slick.123D Catch on Google Play

Some quick background info: I do my best to stay OS agnostic, as a rule. My primary desktop OS is Windows (7 or 8.1, depending) but I also use OS X Mavericks and play with a few different Linux distributions when I get time. I have an old iPad 2 that has a cracked – but still functional – screen and use a Nexus 4 phone currently running Android 4.4.4 (as you can see, I tend not to follow the expected 2-year mobile refresh cycle, if I can avoid it… there are too many other fun gadgets to spend money on :-).

While living like this (embracing tech pluralism) causes some degree of pain – things don’t “just work” a lot of the time – it feels good to surf the waves caused by the ebb and flow of the tech industry. And, anyway, I hate the view from walled gardens – I want to see the horizon! ;-)

So yes, the main device I walk around with, these days, runs Android and has been waiting patiently for 123D Catch to show up on Google Play.

I started by creating a project and using the app to take snaps of a stone tea-light holder I received for my 30th birthday (I think it was, anyway).Photographing the stone

Then I went ahead and uploaded the project for processing:

Transmitting and processing photos

Once completed, I followed the prompts to review the 3D model and then frame it for publication.

Reviewing, framing and publishing

The results were decent, although I did miss a bit of photo coverage on the back of the candle, leading to a couple of small holes. Nothing that couldn’t be fixed using meshmixer, Project Memento or the 123D cleanup tool, though.


If you have an Android device, take the new app for a spin and see what you think!

September 19, 2014

Exploding nested AutoCAD blocks using .NET

Some time ago I posted about how to use Entity.Explode() to do something similar to AutoCAD’s EXPLODE command. At the time it was mentioned in the comments that BlockReference.ExplodeToOwnerSpace() had some relative benefits, but it’s taken me some time to code up a simple sample to show how you might use it (Patrick’s recent comment reminded me I ought to, though).

Anyway, to end the week I thought I’d throw together a quick sample. BlockReference.ExplodeToOwnerSpace() doesn’t return a list of created objects, so I opted to capture this using a Database.ObjectAppended event handler and then recursively call our custom ExplodeBlock() function for any nested blocks that get created. We also then erase the originating entity (or entities, if called recursively), just as the EXPLODE command might.

Here’s the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

 

namespace Explosions

{

  public class Commands

  {

    [CommandMethod("EB")]

    public void ExplodeBock()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

 

      // Ask the user to select the block

 

      var peo = new PromptEntityOptions("\nSelect block to explode");

      peo.SetRejectMessage("Must be a block.");

      peo.AddAllowedClass(typeof(BlockReference), false);

 

      var per = ed.GetEntity(peo);

 

      if (per.Status != PromptStatus.OK)

        return;

 

      using (var tr = db.TransactionManager.StartTransaction())

      {

        // Call our explode function recursively, starting

        // with the top-level block reference

        // (you can pass false as a 4th parameter if you

        // don't want originating entities erased)

 

        ExplodeBlock(tr, db, per.ObjectId);

 

        tr.Commit();

      }

    }

 

    private void ExplodeBlock(

      Transaction tr, Database db, ObjectId id, bool erase = true

    )

    {

      // Open out block reference - only needs to be readable

      // for the explode operation, as it's non-destructive

 

      var br = (BlockReference)tr.GetObject(id, OpenMode.ForRead);

 

      // We'll collect the BlockReferences created in a collection

 

      var toExplode = new ObjectIdCollection();

 

      // Define our handler to capture the nested block references

 

      ObjectEventHandler handler =

        (s, e) =>

        {

          if (e.DBObject is BlockReference)

          {

            toExplode.Add(e.DBObject.ObjectId);

          }

        };

 

      // Add our handler around the explode call, removing it

      // directly afterwards

 

      db.ObjectAppended += handler;

      br.ExplodeToOwnerSpace();

      db.ObjectAppended -= handler;

 

      // Go through the results and recurse, exploding the

      // contents

 

      foreach (ObjectId bid in toExplode)

      {

        ExplodeBlock(tr, db, bid, erase);

      }

 

      // We might also just let it drop out of scope

 

      toExplode.Clear();

 

      // To replicate the explode command, we're delete the

      // original entity

 

      if (erase)

      {

        br.UpgradeOpen();

        br.Erase();

        br.DowngradeOpen();

      }

    }

  }

}

That’s it for this week. Monday is a holiday in Neuchatel, so I’ll be back online on Tuesday. And then on Wednesday I’m heading to TEDxCERN – which promises to be really cool. Can’t wait!

September 17, 2014

Reminder: Exchange Apps Hackathon this weekend

There’s still time to participate in the Autodesk Exchange Apps Hackathon, a virtual event taking place this weekend (September 20-21). The point of this event is to encourage developers to post apps to the Autodesk Exchange Apps store, and we’re even paying cool, hard cash ($50 or $100, whether free or paid) for each app that gets published.

Hackathon

Presentations and discussions will include:

  • How to architect your app for Exchange:
    • How to build your AutoCAD® app
    • How to build your Autodesk® Revit® app
    • How to build your Autodesk® Inventor® app
    • How to build your Autodesk® 3ds Max® and Autodesk® Maya® apps
  • Handling IPN notifications
  • Implementing simple copy protection for your app
  • Architecting your app to sell on monthly subscription
  • Selling online web services on Exchange Apps
  • Making use of Autodesk ‘cloud’ services such as our new, web-based, zero-client 3D model viewer
  • Sustainability apps

Lots of great information to help kick-start your app development. Sign up now!

September 15, 2014

Exporting Minecraft data from AutoCAD

After last week’s post on importing Minecraft data – in this case from Tinkercad – into AutoCAD, in today’s post we’re going to focus on the ultimately more interesting use case of generating Minecraft data from AutoCAD. We’re going to see some code to dice up 3D AutoCAD geometry and generate blocks in a .schematics file using Substrate.

Our “dicing” process – a term I’ve just coined for iterating through a 3D space, chunk by chunk – is going to use a couple of different approaches for determining there’s any 3D geometry in each grid location. Firstly, though, we going generate a spatial index from the contents of the modelspace – a basic list of bounding boxes with the owning entity’s ObjectId (which could be optimised further by sorting based on location) – to decide whether we want to take a closer look at the geometry we find there.

If we get a “hit” from the spatial index, we can test the associated entity for whether the specific point we’re interested in actual does contain geometry. The specific test will vary based on the type of 3D object we find…

If it’s a Solid3d we can perform a simple test using the CheckInterference() method, passing in a cubic Solid3d occupying the location to test. This works fine, but will generate hits for internal cubes, too (i.e. we end up with a fully solid object, rather than just having blocks representing the shell). Ideally we would union the two solids and check the resultant volume to see if it’s an internal cube or not (if the volume doesn’t change then its internal), but that’s likely to be expensive. The ObjectARX equivalent, AcDb3dSolid::checkInterference() does allow this, but it’s more complicated from .NET. Right now we simply create blocks for internal locations, as well, which may also be what the user wants in many cases.

For Surface objects there’s a bit more to do: here we use ProjectOnToSurface(), passing in a DBPoint, to see whether there’s a point in the block that’s close to the surface. We do this for each of the location cube’s vertices, which may be overkill but seems to give the best results for complex surfaces. Needless to say, we stop checking for “clashes” the first time we get a hit in a particular location – there’s no need to keep looking (although we might want to if we wanted to get the best possible material for a block… for now we’re not worrying about materials at all).

To put the code through its paces, I went ahead and rebuilt a space shuttle model using the code in this previous post (although I performed the loft operations by hand – for some reason these don’t work for me anymore).

I then went and used the EMC command – changing the default block size to 0.1, to make the model more detailed – and then used IMC to reimport it into AutoCAD:

Shuttle export and reimport

You should bear in mind that AutoCAD’s representation of an imported model is a lot heavier that it would be in Minecraft – each block is a block reference or a 3D solid, depending – so complex models that have trouble being loaded back into AutoCAD will often import just fine into Minecraft.

Here’s the space shuttle schematics file imported into a new world in MCEdit. I chose “diamond” as the material (just to add a bit of a bling factor for my kids), but you could easily hardcode another choice of material or select it based on the geometry’s layer (etc.).

Diamond shuttle in MCEdit

The surface analysis algorithm could use some tweaking – you can see some holes on the sides where the surface is close to vertical, and the base is very thick – but it’s good enough for my purposes.

Here’s the C# code defining the EMC and IMC commands:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using AcDb = Autodesk.AutoCAD.DatabaseServices;

using Substrate;

using System;

using System.Collections.Generic;

 

namespace Minecraft

{

  public class GeometryIndex

  {

    // We need a list of extents vs. ObjectIds

    // (some kind of spatial sorting might help with

    // performance, but for now it's just a flat list)

 

    List<Tuple<Extents3d, ObjectId>> _extList;

 

    public GeometryIndex()

    {

      _extList = new List<Tuple<Extents3d, ObjectId>>();

    }

 

    public int Size

    {

      get { return _extList.Count; }

    }

 

    public void PopulateIndex(BlockTableRecord ms, Transaction tr)

    {

      foreach (var id in ms)

      {

        var ent =

          tr.GetObject(id, OpenMode.ForRead) as

            Autodesk.AutoCAD.DatabaseServices.Entity;

        if (ent != null)

        {

          _extList.Add(

            new Tuple<Extents3d, ObjectId>(

              ent.GeometricExtents, id

            )

          );

        }

      }

    }

 

    internal ObjectIdCollection PotentialClashes(

      Point3d pt, double step

    )

    {

      var res = new ObjectIdCollection();

 

      foreach (var item in _extList)

      {

        var ext = item.Item1;

 

        if (

          pt.X + step >= ext.MinPoint.X &&

          pt.X <= ext.MaxPoint.X + step &&

          pt.Y + step >= ext.MinPoint.Y &&

          pt.Y <= ext.MaxPoint.Y + step &&

          pt.Z + step >= ext.MinPoint.Z &&

          pt.Z <= ext.MaxPoint.Z + step

        )

        {

          res.Add(item.Item2);

        }

      }

 

      return res;

    }

  }

 

  public class Commands

  {

    // Members that will be set by the EMC command and

    // picked up by the IMC command

 

    private double _blockSize = 1.0;

    private Point3d _origin = Point3d.Origin;

 

    [CommandMethod("IMC")]

    public void ImportMinecraft()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

 

      // Request the name of the file to import

 

      var opts =

        new PromptOpenFileOptions(

          "Import from Minecraft"

        );

      opts.Filter =

        "Minecraft schematic (*.schematic)|*.schematic|" +

        "All files (*.*)|*.*";

      var pr = ed.GetFileNameForOpen(opts);

 

      if (pr.Status != PromptStatus.OK)

        return;

 

      // Read in the selected  Schematic file

 

      var schem =

        Substrate.ImportExport.Schematic.Import(pr.StringResult);

 

      if (schem == null)

      {

        ed.WriteMessage("\nCould not find Minecraft schematic.");

        return;

      }

 

      // Let the user choose the location of the geometry

 

      ed.WriteMessage(

        "\nDefault insert is {0}", _origin

      );

      var ppo = new PromptPointOptions("\nInsertion point or ");

      ppo.Keywords.Add("Default");

      ppo.AllowNone = true;

 

      var ppr = ed.GetPoint(ppo);

 

      Vector3d offset;

 

      if (ppr.Status == PromptStatus.Keyword)

      {

        offset = _origin.GetAsVector();

      }

      else if (ppr.Status == PromptStatus.OK)

      {

        offset = ppr.Value.GetAsVector();

      }

      else

      {

        return;

      }

 

      // Let the user choose the size of the block

 

      var pdo = new PromptDoubleOptions("\nEnter block size");

      pdo.AllowNegative = false;

      pdo.AllowNone = true;

      pdo.DefaultValue = _blockSize;

      pdo.UseDefaultValue = true;

 

      var pdr = ed.GetDouble(pdo);

 

      if (pdr.Status != PromptStatus.OK)

        return;

 

      _blockSize = pdr.Value;

      var step = _blockSize;

 

      // We only really care about the blocks

 

      var blks = schem.Blocks;

 

      // We can either create Solid3d objects for each Minecraft

      // block, or we can create a BlockTableRecord containing

      // a single Solid3d that we reference for each block

      // (if useBlock is set to true)

 

      var blkId = ObjectId.Null;

      var useBlock = true;

 

      using (var tr = db.TransactionManager.StartTransaction())

      {

        var bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId, OpenMode.ForRead

          );

 

        if (useBlock)

        {

          bt.UpgradeOpen();

 

          // Create our block and add it to the db & transaction

 

          var btr = new BlockTableRecord();

          btr.Name = "Minecraft Block";

 

          blkId = bt.Add(btr);

          tr.AddNewlyCreatedDBObject(btr, true);

 

          // Create our cube and add it to the block & transaction

 

          var cube = new Solid3d();

          cube.CreateBox(step, step, step);

 

          btr.AppendEntity(cube);

          tr.AddNewlyCreatedDBObject(cube, true);

 

          bt.DowngradeOpen();

        }

 

        var ms =

          tr.GetObject(

            bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite

          ) as BlockTableRecord;

        if (ms != null)

        {

          using (var pm = new ProgressMeter())

          {

            pm.Start("Importing Minecraft schematic");

            pm.SetLimit(blks.XDim * blks.YDim * blks.ZDim);

 

            // Create a cubic solid for each block

 

            for (int x = 0; x < blks.XDim; ++x)

            {

              for (int y = 0; y < blks.YDim; ++y)

              {

                for (int z = 0; z < blks.ZDim; ++z)

                {

                  var blk = blks.GetBlock(x, y, z);

                  if (blk != null && blk.Info.Name != "Air")

                  {

                    // Minecraft has a right-handed coordinate

                    // system with Z & Y swapped and Z negated

 

                    var disp =

                      new Point3d(x * step, -z * step, y * step) +

                      offset;

 

                    AcDb.Entity ent;

 

                    if (useBlock)

                    {

                      ent = new BlockReference(disp, blkId);

                    }

                    else

                    {

                      var sol = new Solid3d();

                      sol.CreateBox(step, step, step);

                      sol.TransformBy(

                        Matrix3d.Displacement(disp.GetAsVector())

                      );

                      ent = sol;

                    }

 

                    // Assign the layer based on the material

 

                    ent.LayerId =

                      LayerForMaterial(tr, db, blk.Info.Name);

 

                    ms.AppendEntity(ent);

                    tr.AddNewlyCreatedDBObject(ent, true);

                  }

                  pm.MeterProgress();

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

                }

              }

            }

            pm.Stop();

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

          }

          tr.Commit();

        }

      }

 

      // Zoom to the model's extents

 

      ed.Command("_.ZOOM", "_EXTENTS");

    }

 

    private ObjectId LayerForMaterial(

      Transaction tr, Database db, string layname

    )

    {

      // If a layer with the material's name exists, return its id

 

      var lt =

        (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);

      if (lt.Has(layname))

      {

        return lt[layname];

      }

 

      // Otherwise create a new layer for this material

 

      var ltr = new LayerTableRecord();

      ltr.Name = layname;

 

      lt.UpgradeOpen();

      var ltrId = lt.Add(ltr);

      lt.DowngradeOpen();

 

      tr.AddNewlyCreatedDBObject(ltr, true);

 

      return ltrId;

    }

 

    [CommandMethod("EMC")]

    public void ExportMinecraft()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

      var msId = SymbolUtilityServices.GetBlockModelSpaceId(db);

 

      // Request the name of the file to export to

 

      var opts =

        new PromptSaveFileOptions(

          "Export to Minecraft"

        );

      opts.Filter =

        "Minecraft schematic (*.schematic)|*.schematic|" +

        "All files (*.*)|*.*";

      var pr = ed.GetFileNameForSave(opts);

 

      if (pr.Status != PromptStatus.OK)

        return;

 

      var idx = new GeometryIndex();

 

      var emin = db.Extmin;

      var emax = db.Extmax;

 

      // Let the user choose the size of the block - offer a

      // default of a 50th of the diagonal length of the 3D extents

 

      var defSize =

        emax.GetAsVector().Subtract(emin.GetAsVector()).Length / 50;

 

      var pdo = new PromptDoubleOptions("\nEnter block size");

      pdo.AllowNegative = false;

      pdo.AllowNone = true;

      pdo.DefaultValue = defSize;

      pdo.UseDefaultValue = true;

 

      var pdr = ed.GetDouble(pdo);

 

      if (pdr.Status != PromptStatus.OK)

        return;

 

      _blockSize = pdr.Value;

      var step = _blockSize;

 

      _origin = new Point3d(emin.X, emax.Y, emin.Z);

 

      ed.WriteMessage(

        "\nExporting with block size of {0} at {1}.",

        step, _origin

      );

 

      // Set up our empty schematic container

 

      var schem =

        new Substrate.ImportExport.Schematic(

          (int)Math.Ceiling((emax.X - emin.X) / step),

          (int)Math.Ceiling((emax.Z - emin.Z) / step),

          (int)Math.Ceiling((emax.Y - emin.Y) / step)

        );

 

      using (var tr = db.TransactionManager.StartTransaction())

      {

        // Get our modelspace

 

        var ms =

          tr.GetObject(msId, OpenMode.ForRead) as BlockTableRecord;

        if (ms != null)

        {

          // Start by populating the spatial index based on the

          // contents of the modelspace

 

          idx.PopulateIndex(ms, tr);

 

          // We'll just use two materials - air and the model

          // material (which for now is "diamond")

 

          var air = new AlphaBlock(BlockType.AIR);

          var diamond = new AlphaBlock(BlockType.DIAMOND_BLOCK);

 

          using (var cube = new Solid3d())

          {

            // We'll use a single cube to test interference

 

            cube.CreateBox(step, step, step);

 

            var std2 = step / 2.0;

            var vecs =

              new Vector3d[]

              {

                new Vector3d(std2,std2,std2),

                new Vector3d(std2,std2,-std2),

                new Vector3d(std2,-std2,std2),

                new Vector3d(std2,-std2,-std2),

                new Vector3d(-std2,std2,std2),

                new Vector3d(-std2,std2,-std2),

                new Vector3d(-std2,-std2,std2),

                new Vector3d(-std2,-std2,-std2)

              };

            var blks = schem.Blocks;

 

            using (var pm = new ProgressMeter())

            {

              pm.Start("Exporting Minecraft schematic");

              pm.SetLimit(blks.XDim * blks.YDim * blks.ZDim);

 

              for (int x = 0; x < blks.XDim; ++x)

              {

                for (int y = 0; y < blks.YDim; ++y)

                {

                  for (int z = 0; z < blks.ZDim; ++z)

                  {

                    // Get the WCS point to test modespace contents

 

                    var wcsX = emin.X + step * x;

                    var wcsY = emax.Y + step * -z;

                    var wcsZ = emin.Z + step * y;

 

                    var pt = new Point3d(wcsX, wcsY, wcsZ);

 

                    // Check our point against bounding boxes

                    // to detect potential clashes

 

                    var ents = idx.PotentialClashes(pt, step);

 

                    // If we have some, verify using a more precise,

                    // per-entity interference check

 

                    if (ents.Count > 0)

                    {

                      var disp = pt.GetAsVector();

 

                      // Displace our interference cube to the

                      // location we want to test

 

                      cube.TransformBy(Matrix3d.Displacement(disp));

 

                      bool found = false;

 

                      // Check each of the potentially clashing

                      // entities against our test cube

 

                      foreach (ObjectId id in ents)

                      {

                        // For Solid3ds we simply check interference

                        // with our cube

 

                        var obj = tr.GetObject(id, OpenMode.ForRead);

                        var sol = obj as Solid3d;

                        if (sol != null)

                        {

                          if (sol.CheckInterference(cube))

                          {

                            // When we've found one we don't need to

                            // test the others

 

                            found = true;

                            break;

                          }

                        }

                        else

                        {

                          // For Surfaces we don't use the cube:

                          // we create a point at the location

                          // and project it onto the surface. If

                          // the resulting point is less than a

                          // step away, we assume we create the

                          // block at this location

 

                          var sur = obj as AcDb.Surface;

                          if (sur != null)

                          {

                            foreach (var v in vecs)

                            {

                              found =

                                SurfaceClash(sur, pt + v, step);

                              if (found)

                                break;

                            }

                          }

                        }

                      }

 

                      // Whether we've found a clash will drive

                      // whether we set the block to be stone or air

 

                      blks.SetBlock(x, y, z, found ? diamond : air);

 

                      // Displace the cube back again, ready for the

                      // next test

 

                      cube.TransformBy(Matrix3d.Displacement(-disp));

                    }

                    pm.MeterProgress();

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

                  }

                }

              }

              pm.Stop();

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

            }

          }

 

          // Finally we write the block information to a schematics

          // file

 

          schem.Export(pr.StringResult);

        }

        tr.Commit();

      }

    }

 

    private static bool SurfaceClash(

      AcDb.Surface sur, Point3d pt, double step

    )

    {

      try

      {

        var found = false;

 

        using (var dbp = new DBPoint(pt))

        {

          var ps =

            sur.ProjectOnToSurface(

              dbp, Vector3d.ZAxis

            );

 

          if (ps.Length > 0)

          {

            foreach (var p in ps)

            {

              var dbp2 = p as DBPoint;

              if (!found && dbp2 != null)

              {

                // If a discovered point is within 2 block's width

                // we consider it a hit

 

                var dist = dbp2.Position - dbp.Position;

                found = (dist.Length < 2.0 * step);

              }

              p.Dispose();

            }

          }

 

          if (found)

          {

            return true;

          }

        }

      }

      catch { }

 

      return false;

    }

  }

}

That’s all I currently have planned in terms of Minecraft-related posts, but if anyone has additional suggestions on where to take this, please do share them.

Unrelatedly, Minecraft is in the news a lot at the moment with the prospective acquisition of Mojang by Microsoft. It seems a lot of fans are concerned by this prospect, but one way or another it’ll be interesting to see how it all plays out…

September 11, 2014

Importing Minecraft data into AutoCAD

A mere 2 among 100 million registered users, my boys are crazy about Minecraft. I’ve been looking into how I might be able to help them use Autodesk tools (well, AutoCAD) to generate Minecraft content. In this post we’ll take a look at importing Minecraft data into AutoCAD, but ultimately the creation/export story is clearly more interesting (something we’ll look at in the next post, I expect).

To investigate dealing with Minecraft data – bearing in mind I didn’t actually know anything much about its file formats – I took a look at the Minecraft export you can perform from Tinkercad, which has been part of that product for just over a year. I took one of my algorithmically-created Tinkercad designs and clicked on “Download for Minecraft”:Algorithmic objects in Tinkercad

This created a local “more_knots.schematic” file, which presumably has information that Minecraft can make sense of. To check this out, I went and installed MCEdit and imported the schematic file into a new world. It was quite fun to see the Tinkercad geometry appear in a Minecraft-like environment:

Tinkercad geometry in MCEdit

Next step, then, was to work out how to get access to the “.schematics” format from .NET. A quick web-search led me to Substrate. I cloned it from GitHub and built it into an AutoCAD plug-in that uses the ImportExport capability to bring in a Schematic file.

It was then a reasonably simple matter to access the blocks and create cubic solids at the right locations to represent them. The only tricky piece, here, is that Minecraft uses a right-handed coordinate system with Z and Y swapped – from our perspective, anyway – and then the Y-axis negated… so it’s X, –Z, Y, I suppose. Because the Y axis is negated – and the geometry will be relative to an origin that isn’t specified in the .schematics file – the position of the model may well need to be moved if you want to check its overlap with source geometry. That’s why the user can select the position and the block size in the import command (which we will set in memory from our export command, making it really easy for the user to export and then reimport to check the quality).

Rather than just creating hundreds or thousands of cubic Solid3d objects, I’ve coded the (default) option to create a single Solid3d in a BlockTableRecord and then create a BlockReference for each Minecraft block. This has advantages both from a file size and memory consumption perspective (AutoCAD’s 3D graphics system is optimised for instanced geometry such as block references).

The code adds blocks to layers based on the names of their materials (I’ve also neglected adding “Air” blocks to the drawing, for obvious reasons). It’s then up to the user to assign appropriate colours to the various layers, as they see fit.

Here’s the Tinkercad data brought into AutoCAD (with my own layer colouring) using the IMC command:

Tinkercad geometry in AutoCAD

Here’s the C# code that implements the IMC command, performing a simple import of Minecraft data:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

 

namespace Minecraft

{

  public class Commands

  {

    // Members that will be set by the EMC command and

    // picked up by the IMC command

 

    private double _blockSize = 1.0;

    private Point3d _origin = Point3d.Origin;

 

    [CommandMethod("IMC")]

    public void ImportMinecraft()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

 

      // Request the name of the file to import

 

      var opts =

        new PromptOpenFileOptions(

          "Import from Minecraft"

        );

      opts.Filter =

        "Minecraft schematic (*.schematic)|*.schematic|" +

        "All files (*.*)|*.*";

      var pr = ed.GetFileNameForOpen(opts);

 

      if (pr.Status != PromptStatus.OK)

        return;

 

      // Read in the selected  Schematic file

 

      var schem =

        Substrate.ImportExport.Schematic.Import(pr.StringResult);

 

      if (schem == null)

      {

        ed.WriteMessage("\nCould not find Minecraft schematic.");

        return;

      }

 

      // Let the user choose the location of the geometry

 

      ed.WriteMessage(

        "\nDefault insert is {0}", _origin

      );

      var ppo = new PromptPointOptions("\nInsertion point or ");

      ppo.Keywords.Add("Default");

      ppo.AllowNone = true;

 

      var ppr = ed.GetPoint(ppo);

 

      Vector3d offset;

 

      if (ppr.Status == PromptStatus.Keyword)

      {

        offset = _origin.GetAsVector();

      }

      else if (ppr.Status == PromptStatus.OK)

      {

        offset = ppr.Value.GetAsVector();

      }

      else

      {

        return;

      }

 

      // Let the user choose the size of the block

 

      var pdo = new PromptDoubleOptions("\nEnter block size");

      pdo.AllowNegative = false;

      pdo.AllowNone = true;

      pdo.DefaultValue = _blockSize;

      pdo.UseDefaultValue = true;

 

      var pdr = ed.GetDouble(pdo);

 

      if (pdr.Status != PromptStatus.OK)

        return;

 

      _blockSize = pdr.Value;

      var step = _blockSize;

 

      // We only really care about the blocks

 

      var blks = schem.Blocks;

 

      // We can either create Solid3d objects for each Minecraft

      // block, or we can create a BlockTableRecord containing

      // a single Solid3d that we reference for each block

      // (if useBlock is set to true)

 

      var blkId = ObjectId.Null;

      var useBlock = true;

 

      using (var tr = db.TransactionManager.StartTransaction())

      {

        var bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId, OpenMode.ForRead

          );

 

        if (useBlock)

        {

          bt.UpgradeOpen();

 

          // Create our block and add it to the db & transaction

 

          var btr = new BlockTableRecord();

          btr.Name = "Minecraft Block";

 

          blkId = bt.Add(btr);

          tr.AddNewlyCreatedDBObject(btr, true);

 

          // Create our cube and add it to the block & transaction

 

          var cube = new Solid3d();

          cube.CreateBox(step, step, step);

 

          btr.AppendEntity(cube);

          tr.AddNewlyCreatedDBObject(cube, true);

 

          bt.DowngradeOpen();

        }

 

        var ms =

          tr.GetObject(

            bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite

          ) as BlockTableRecord;

        if (ms != null)

        {

          using (var pm = new ProgressMeter())

          {

            pm.Start("Importing Minecraft schematic");

            pm.SetLimit(blks.XDim * blks.YDim * blks.ZDim);

 

            // Create a cubic solid for each block

 

            for (int x = 0; x < blks.XDim; ++x)

            {

              for (int y = 0; y < blks.YDim; ++y)

              {

                for (int z = 0; z < blks.ZDim; ++z)

                {

                  var blk = blks.GetBlock(x, y, z);

                  if (blk != null && blk.Info.Name != "Air")

                  {

                    // Minecraft has a right-handed coordinate

                    // system with Z & Y swapped and Z negated

 

                    var disp =

                      new Point3d(x * step, -z * step, y * step) +

                      offset;

 

                    AcDb.Entity ent;

 

                    if (useBlock)

                    {

                      ent = new BlockReference(disp, blkId);

                    }

                    else

                    {

                      var sol = new Solid3d();

                      sol.CreateBox(step, step, step);

                      sol.TransformBy(

                        Matrix3d.Displacement(disp.GetAsVector())

                    );

                      ent = sol;

                    }

 

                    // Assign the layer based on the material

 

                    ent.LayerId =

                      LayerForMaterial(tr, db, blk.Info.Name);

 

                    ms.AppendEntity(ent);

                    tr.AddNewlyCreatedDBObject(ent, true);

                  }

                  pm.MeterProgress();

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

                }

              }

            }

            pm.Stop();

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

          }

          tr.Commit();

        }

      }

 

      // Zoom to the model's extents

 

      ed.Command("_.ZOOM", "_EXTENTS");

    }

 

    private ObjectId LayerForMaterial(

      Transaction tr, Database db, string layname

    )

    {

      // If a layer with the material's name exists, return its id

 

      var lt =

        (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead);

      if (lt.Has(layname))

      {

        return lt[layname];

      }

 

      // Otherwise create a new layer for this material

 

      var ltr = new LayerTableRecord();

      ltr.Name = layname;

 

      lt.UpgradeOpen();

      var ltrId = lt.Add(ltr);

      lt.DowngradeOpen();

 

      tr.AddNewlyCreatedDBObject(ltr, true);

 

      return ltrId;

    }

  }

}

So far so good! It’s not the best way to bring data from Tinkercad into AutoCAD, but then that’s not the point, of course. This is just about getting access to Minecraft data before we look at the more interesting use case of dicing the current AutoCAD model and generating a .schematic output file.

September 09, 2014

SINDEX 2014 – Switzerland’s industrial automation trade show

Last week I spent a morning at SINDEX 2014, which is apparently Switzerland’s largest technology exhibition. Its main focus is on industrial electronics and automation: not exactly fields I know a lot about, but ones that I do find to be extremely interesting. There were 400 or so exhibitors, many focused on providing sensors and electrical equipment, others providing complete automation solutions.

SINDEX 2014

There was a serious focus on robotics, for instance, something in which regular readers will know I have a strong interest. This post contains a few examples of the robots that were on show.

This robot, from Stäubli, is moving boxes of Tic Tacs around at high speed. Yes, the video is in no way sped up – it really moved that quickly.




Increasing the ability for robots to function more autonomously typically involves computer vision and image processing techniques, a very cool – and increasingly important – part of the technology landscape. Some of the most interesting exhibits, for me, were the ones using integrated depth cameras (e.g. Kinect), laser scanners or just basic image processing to enhance a robot’s ability to deal with varying situations.

Take this system from Isra Vision




… where the robot is picking parts out of a bin based on input from a laser scanner:

The ceiling-mounted laser scanner

The system scans the top level of the bin and matches portions of the point cloud against a known 3D model of the part. That way the robot can effectively position itself to pick up the “best” part from the bin and then go ahead and do something with it.

The Isra system dashboard

If you look at the above video – at around the 45 second mark – you can see a physical debugging console showing the code the robot’s controller is stepping through. Apparently most manufacturers have their own (often quite arcane) languages for robotic control (again – not a field I have experience with… I’d be very happy to get comments from people who do). It was certainly very interesting to see the code in action.

Of course there were a number of fun technology demonstrations that have no serious, practical purpose – and yet demonstrate the technology’s potential – something I admit I like a great deal (check my next post for one more example of this ;-).

For instance, here’s the Xylobot – created at HEIG-VD – playing the Dance of the Sugar Plum Fairy:




It was programmed to play a number of other tunes, too. Fun stuff!

Update:

Martin Müller made a very valid comment that the Xylobot is in some ways quite ugly: not necessarily in terms of its physical appearance but in terms of its perfect precision. I expect it’d be possible to program in a little imperfection, of course – if the goal was to make the performance more “human” – but that clearly wasn’t the point of this particular technology demonstration.

This reminded me of another video I took of some more “sensitive” robots, though, this time made by KUKA. These are not only elegant from a design perspective but from the “care” with which they can perform intricate tasks. Perhaps not beautiful in the way they replicate human imperfection, but nonetheless impressive…


September 08, 2014

Translating between AutoCAD drawing points and geographic locations using .NET – Part 3

I hadn’t planned on writing a third part to this series, but then Mads Paulin – who I mentioned in the last post – got back to me with some information that’s really worth sharing.

I’d pinged Mads while looking for a way to list the available coordinate systems to the user (another question from Coralie Jacobi, who had originally kicked off the series). I’d found out that the coordinate system definitions are primarily stored in this folder…

C:\ProgramData\Autodesk\Geospatial Coordinate Systems 14.01

… but I hadn’t worked out to extract any information from the contained CSD files.

Mads delivered the goods: he explained there’s a GeoCoordinateSystem class that contains a static method CreateAll(). The returned array of GeoCoordinateSystem classes can then be iterated, accessing the information you want from each one.

There’s also a static Create() method which lets you instantiate a single GeoCoordinateSystem class based on the XML you retrieve from the GeoLocation object. So we can now rip out our previous DynamicXML implementation (I’m glad we went through it, though: it’s still an interesting technique for people needing to retrieve data from XML). Mads will be happy: as the architect for the feature he was temporarily embarrassed by the hoops I ended up jumping through to get at this simple bit of information. :-)

Here’s the updated C# code. Look in particular for the new LCS (ListCoordinateSystems) command and the greatly simplified PrintCoordinateSystem() helper function. (The LLPF and PFLL commands should work just as they did, last time around, of course.)

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using System;

 

namespace GeoLocationAPI

{

  public class Commands

  {

    [CommandMethod("IGR")]

    public void InsertGeoRef()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

      var msId = SymbolUtilityServices.GetBlockModelSpaceId(db);

 

      if (HasGeoData(db))

      {

        // Report and return: could also open the object for

        // write and modify its properties, of course

 

        ed.WriteMessage("\nDrawing already has geo-location data!");

        return;

      }

 

      // Let's create some geolocation data for this drawing,

      // using a handy method to add it to the modelspace

      // (it gets added to the extension dictionary)

 

      var data = new GeoLocationData();

      data.BlockTableRecordId = msId;

      data.PostToDb();

 

      // We're going to define our geolocation in terms of

      // latitude/longitude using the Mercator projection

      // http://en.wikipedia.org/wiki/Mercator_projection

 

      data.CoordinateSystem = "WORLD-MERCATOR";

      data.TypeOfCoordinates = TypeOfCoordinates.CoordinateTypeGrid;

 

      // Use the lat-long for La Tene, my local "beach"

      // (it's on a lake, after all :-)     

 

      var geoPt = new Point3d(7.019438, 47.005247, 0);

 

      // Transform from a geographic to a modelspace point

      // and add the information to our geolocation data

 

      var wcsPt = data.TransformFromLonLatAlt(geoPt);

      data.DesignPoint = wcsPt;

      data.ReferencePoint = geoPt;

 

      // Let's launch the GEOMAP command to show our geographic

      // overlay

 

      ed.Command("_.GEOMAP", "_AERIAL");

 

      // Now we'll add a circle around our location

      // and that will provide the extents for our zoom

 

      using (var tr = db.TransactionManager.StartTransaction())

      {

        var ms =

          tr.GetObject(msId, OpenMode.ForWrite) as BlockTableRecord;

        if (ms != null)

        {

          // Add a red circle of 7K units radius

          // centred on our point

 

          var circle = new Circle(wcsPt, Vector3d.ZAxis, 7000);

          circle.ColorIndex = 1;

          ms.AppendEntity(circle);

          tr.AddNewlyCreatedDBObject(circle, true);

        }

        tr.Commit();

      }

 

      // And we'll zoom to the circle's extents

 

      ed.Command("_.ZOOM", "_OBJECT", "_L", "");

    }

 

    [CommandMethod("CGI")]

    public void CreateGeoMapImage()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

 

      // Get the first corner of our area to convert to a

      // GeomapImage

 

      var ppo = new PromptPointOptions("\nSpecify first corner");

      var ppr = ed.GetPoint(ppo);

      if (ppr.Status != PromptStatus.OK)

        return;

 

      var first = ppr.Value;

 

      // And get the second point as a corner (to rubber-band

      // the selection)

 

      var pco =

        new PromptCornerOptions("\nSpecify second corner", first);

      ppr = ed.GetCorner(pco);

 

      if (ppr.Status != PromptStatus.OK)

        return;

 

      var second = ppr.Value;

 

      // We'll use an event handler on the Database to check for

      // GeomapImage entities being added

      // (we'll use a lambda but assigned to a variable to be

      // able to remove it, afterwards)

 

      ObjectId giId = ObjectId.Null;

      ObjectEventHandler handler =

        (s, e) =>

        {

          if (e.DBObject is GeomapImage)

          {

            giId = e.DBObject.ObjectId;

          }

        };

 

      // Simply call the GEOMAPIMAGE command with the two points

 

      db.ObjectAppended += handler;

      ed.Command("_.GEOMAPIMAGE", first, second);

      db.ObjectAppended -= handler;

 

      // Only continue if we've collected a valid ObjectId

 

      if (giId == ObjectId.Null)

        return;

 

      // Open the entity and change some values

 

      try

      {

        using (var tr = doc.TransactionManager.StartTransaction())

        {

          // Get each object and check if it's a GeomapImage

 

          var gi =

            tr.GetObject(giId, OpenMode.ForWrite) as GeomapImage;

          if (gi != null)

          {

            // Let's adjust the brightmess/contrast/fade of the

            // GeomapImage

 

            gi.Brightness = 90;

            gi.Contrast = 40;

            gi.Fade = 20;

 

            // And make sure it's at the right resolution and

            // shows both aerial and road information

 

            gi.Resolution = GeomapResolution.Optimal;

            gi.MapType = GeomapType.Hybrid;

 

            gi.UpdateMapImage(true);

          }

 

          tr.Commit();

        }

      }

      catch (Autodesk.AutoCAD.Runtime.Exception)

      {

        ed.WriteMessage(

          "\nUnable to update geomap image entity." +

          "\nPlease check your internet connectivity and call " +

          "GEOMAPIMAGEUPDATE."

        );

      }

    }

 

    [CommandMethod("LCS")]

    public void ListCoordinateSystems()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

 

      //

      var css = GeoCoordinateSystem.CreateAll();

 

      ed.WriteMessage("\nAvailable coordinate systems:\n");

 

      int i=1;

      foreach (var cs in css)

      {

        ed.WriteMessage("\n{0} {1}", i, cs.ID);

        ++i;

      }

    }

 

    [CommandMethod("LLFP")]

    public void LatLongFromPoint()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

 

      if (!HasGeoData(db))

      {

        ed.WriteMessage(

          "\nCurrent drawing has no geo-location information."

        );

        return;

      }

 

      // Get the drawing point to be translated into a lat-lon

 

      var ppo = new PromptPointOptions("\nSpecify point");

      var ppr = ed.GetPoint(ppo);

      if (ppr.Status != PromptStatus.OK)

        return;

 

      var dwgPt = ppr.Value;

 

      // Translate the drawing point to a lat-lon

 

      var res = TranslateGeoPoint(db, dwgPt, true);

 

      // Print any coordinate system information

 

      PrintCoordinateSystem(ed, res.Item2);

 

      // And then the point itself

 

      var lonlat = res.Item1;

      ed.WriteMessage(

        "\nLatitude-longitude is {0},{1}", lonlat.Y, lonlat.X

      );

    }

 

    [CommandMethod("PFLL")]

    public void PointFromLatLong()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

      var ed = doc.Editor;

      var db = doc.Database;

 

      if (!HasGeoData(db))

      {

        ed.WriteMessage(

          "\nCurrent drawing has no geo-location information."

        );

        return;

      }

 

      // Get the latitude and longitude to be translated

      // to a drawing point

 

      var pdo = new PromptDoubleOptions("\nEnter latitude");

      var pdr = ed.GetDouble(pdo);

      if (pdr.Status != PromptStatus.OK)

        return;

 

      var lat = pdr.Value;

 

      pdo.Message = "\nEnter longitude";

      pdr = ed.GetDouble(pdo);

      if (pdr.Status != PromptStatus.OK)

        return;

 

      var lon = pdr.Value;

 

      var lonlat = new Point3d(lon, lat, 0.0);

 

      // Translate the lat-lon to a drawing point

 

      var res = TranslateGeoPoint(db, lonlat, false);

 

      // Print any coordinate system information

 

      ed.WriteMessage(res.Item2);

 

      PrintCoordinateSystem(ed, res.Item2);

 

      // And then the point itself

 

      var dwgPt = res.Item1;

      ed.WriteMessage(

        "\nDrawing point is {0},{1},{2}", dwgPt.X, dwgPt.Y, dwgPt.Z

      );

    }

 

    private static void PrintCoordinateSystem(Editor ed, string xml)

    {

      var gcs = GeoCoordinateSystem.Create(xml);

      ed.WriteMessage("\nCoordinate system: {0}", gcs.ID);

    }

 

    private Tuple<Point3d, string> TranslateGeoPoint(

      Database db, Point3d inPt, bool fromDwg

    )

    {

      using (

        var tr = db.TransactionManager.StartOpenCloseTransaction()

      )

      {

        // Get the drawing's GeoLocation object

 

        var gd =

          tr.GetObject(db.GeoDataObject, OpenMode.ForRead)

            as GeoLocationData;

 

        // Get the output point...

        // dwg2lonlat if fromDwg is true,

        // lonlat2dwg otherwise

 

        var outPt =

          (fromDwg ?

            gd.TransformToLonLatAlt(inPt) :

            gd.TransformFromLonLatAlt(inPt)

          );

 

        var cs = gd.CoordinateSystem;

 

        tr.Commit();

 

        return new Tuple<Point3d, string>(outPt, cs);

      }

    }

 

    private static bool HasGeoData(Database db)

    {

      // Check whether the drawing already has geolocation data

 

      bool hasGeoData = false;

      try

      {

        var gdId = db.GeoDataObject;

        hasGeoData = true;

      }

      catch { }

      return hasGeoData;

    }

  }

}

When we run the LCS command, we actually see the tail end of a looong list of coordinate systems:

6446 WGS72be/b.UTM-16N

6447 WGS72be/b.UTM-16S

6448 WGS72be/b.UTM-17N

6449 WGS72be/b.UTM-17S

6450 WGS72be/b.UTM-18N

6451 WGS72be/b.UTM-18S

6452 WGS72be/b.UTM-19N

6453 WGS72be/b.UTM-19S

6454 WGS72be/b.UTM-1N

6455 WGS72be/b.UTM-1S

6456 WGS72be/b.UTM-20N

6457 WGS72be/b.UTM-20S

6458 WGS72be/b.UTM-21N

6459 WGS72be/b.UTM-21S

6460 WGS72be/b.UTM-22N

6461 WGS72be/b.UTM-22S

6462 WGS72be/b.UTM-23N

6463 WGS72be/b.UTM-23S

6464 WGS72be/b.UTM-24N

6465 WGS72be/b.UTM-24S

6466 WGS72be/b.UTM-25N

6467 WGS72be/b.UTM-25S

6468 WGS72be/b.UTM-26N

6469 WGS72be/b.UTM-26S

6470 WGS72be/b.UTM-27N

6471 WGS72be/b.UTM-27S

6472 WGS72be/b.UTM-28N

6473 WGS72be/b.UTM-28S

6474 WGS72be/b.UTM-29N

6475 WGS72be/b.UTM-29S

6476 WGS72be/b.UTM-2N

6477 WGS72be/b.UTM-2S

6478 WGS72be/b.UTM-30N

6479 WGS72be/b.UTM-30S

6480 WGS72be/b.UTM-31N

6481 WGS72be/b.UTM-31S

6482 WGS72be/b.UTM-32N

6483 WGS72be/b.UTM-32S

6484 WGS72be/b.UTM-33N

6485 WGS72be/b.UTM-33S

6486 WGS72be/b.UTM-34N

6487 WGS72be/b.UTM-34S

6488 WGS72be/b.UTM-35N

6489 WGS72be/b.UTM-35S

6490 WGS72be/b.UTM-36N

6491 WGS72be/b.UTM-36S

6492 WGS72be/b.UTM-37N

6493 WGS72be/b.UTM-37S

6494 WGS72be/b.UTM-38N

6495 WGS72be/b.UTM-38S

6496 WGS72be/b.UTM-39N

6497 WGS72be/b.UTM-39S

6498 WGS72be/b.UTM-3N

6499 WGS72be/b.UTM-3S

6500 WGS72be/b.UTM-40N

6501 WGS72be/b.UTM-40S

6502 WGS72be/b.UTM-41N

6503 WGS72be/b.UTM-41S

6504 WGS72be/b.UTM-42N

6505 WGS72be/b.UTM-42S

6506 WGS72be/b.UTM-43N

6507 WGS72be/b.UTM-43S

6508 WGS72be/b.UTM-44N

6509 WGS72be/b.UTM-44S

6510 WGS72be/b.UTM-45N

6511 WGS72be/b.UTM-45S

6512 WGS72be/b.UTM-46N

6513 WGS72be/b.UTM-46S

6514 WGS72be/b.UTM-47N

6515 WGS72be/b.UTM-47S

6516 WGS72be/b.UTM-48N

6517 WGS72be/b.UTM-48S

6518 WGS72be/b.UTM-49N

6519 WGS72be/b.UTM-49S

6520 WGS72be/b.UTM-4N

6521 WGS72be/b.UTM-4S

6522 WGS72be/b.UTM-50N

6523 WGS72be/b.UTM-50S

6524 WGS72be/b.UTM-51N

6525 WGS72be/b.UTM-51S

6526 WGS72be/b.UTM-52N

6527 WGS72be/b.UTM-52S

6528 WGS72be/b.UTM-53N

6529 WGS72be/b.UTM-53S

6530 WGS72be/b.UTM-54N

6531 WGS72be/b.UTM-54S

6532 WGS72be/b.UTM-55N

6533 WGS72be/b.UTM-55S

6534 WGS72be/b.UTM-56N

6535 WGS72be/b.UTM-56S

6536 WGS72be/b.UTM-57N

6537 WGS72be/b.UTM-57S

6538 WGS72be/b.UTM-58N

6539 WGS72be/b.UTM-58S

6540 WGS72be/b.UTM-59N

6541 WGS72be/b.UTM-59S

6542 WGS72be/b.UTM-5N

6543 WGS72be/b.UTM-5S

6544 WGS72be/b.UTM-60N

6545 WGS72be/b.UTM-60S

6546 WGS72be/b.UTM-6N

6547 WGS72be/b.UTM-6S

6548 WGS72be/b.UTM-7N

6549 WGS72be/b.UTM-7S

6550 WGS72be/b.UTM-8N

6551 WGS72be/b.UTM-8S

6552 WGS72be/b.UTM-9N

6553 WGS72be/b.UTM-9S

6554 WGS84.AusAntarctic/LM

6555 WGS84.BLM-14NF

6556 WGS84.BLM-15NF

6557 WGS84.BLM-16NF

6558 WGS84.BLM-17NF

6559 WGS84.CanLam.3

6560 WGS84.CRTM05

6561 WGS84.PlateCarree

6562 WGS84.PseudoMercator

6563 WGS84.SCAR-SP19-20

6564 WGS84.SCAR-SP21-22

6565 WGS84.SCAR-SP23-24

6566 WGS84.SCAR-SQ01-02

6567 WGS84.SCAR-SQ19-20

6568 WGS84.SCAR-SQ21-22

6569 WGS84.SCAR-SQ37-38

6570 WGS84.SCAR-SQ39-40

6571 WGS84.SCAR-SQ41-42

6572 WGS84.SCAR-SQ43-44

6573 WGS84.SCAR-SQ45-46

6574 WGS84.SCAR-SQ47-48

6575 WGS84.SCAR-SQ49-50

6576 WGS84.SCAR-SQ51-52

6577 WGS84.SCAR-SQ53-54

6578 WGS84.SCAR-SQ55-56

6579 WGS84.SCAR-SQ57-58

6580 WGS84.SCAR-SR13-14

6581 WGS84.SCAR-SR15-16

6582 WGS84.SCAR-SR17-18

6583 WGS84.SCAR-SR19-20

6584 WGS84.SCAR-SR27-28

6585 WGS84.SCAR-SR29-30

6586 WGS84.SCAR-SR31-32

6587 WGS84.SCAR-SR33-34

6588 WGS84.SCAR-SR35-36

6589 WGS84.SCAR-SR37-38

6590 WGS84.SCAR-SR39-40

6591 WGS84.SCAR-SR41-42

6592 WGS84.SCAR-SR43-44

6593 WGS84.SCAR-SR45-46

6594 WGS84.SCAR-SR47-48

6595 WGS84.SCAR-SR49-50

6596 WGS84.SCAR-SR51-52

6597 WGS84.SCAR-SR53-54

6598 WGS84.SCAR-SR55-56

6599 WGS84.SCAR-SR57-58

6600 WGS84.SCAR-SR59-60

6601 WGS84.SCAR-SS04-06

6602 WGS84.SCAR-SS07-09

6603 WGS84.SCAR-SS10-12

6604 WGS84.SCAR-SS13-15

6605 WGS84.SCAR-SS16-18

6606 WGS84.SCAR-SS19-21

6607 WGS84.SCAR-SS25-27

6608 WGS84.SCAR-SS28-30

6609 WGS84.SCAR-SS31-33

6610 WGS84.SCAR-SS34-36

6611 WGS84.SCAR-SS37-39

6612 WGS84.SCAR-SS40-42

6613 WGS84.SCAR-SS43-45

6614 WGS84.SCAR-SS46-48

6615 WGS84.SCAR-SS49-51

6616 WGS84.SCAR-SS52-54

6617 WGS84.SCAR-SS55-57

6618 WGS84.SCAR-SS58-60

6619 WGS84.SCAR-ST01-04

6620 WGS84.SCAR-ST05-08

6621 WGS84.SCAR-ST09-12

6622 WGS84.SCAR-ST13-16

6623 WGS84.SCAR-ST17-20

6624 WGS84.SCAR-ST21-24

6625 WGS84.SCAR-ST25-28

6626 WGS84.SCAR-ST29-32

6627 WGS84.SCAR-ST33-36

6628 WGS84.SCAR-ST37-40

6629 WGS84.SCAR-ST41-44

6630 WGS84.SCAR-ST45-48

6631 WGS84.SCAR-ST49-52

6632 WGS84.SCAR-ST53-56

6633 WGS84.SCAR-ST57-60

6634 WGS84.TM-116SE

6635 WGS84.TM-132SE

6636 WGS84.TM-36SE

6637 WGS84.TM-6NE

6638 WGS84.UPSNorth

6639 WGS84.UPSSouth

6640 WGS84.USGS-AntarticMtn

6641 WGS84.Winkel

6642 WI-C

6643 WI-N

6644 WI-S

6645 WI27-TM

6646 WI83-C

6647 WI83-CF

6648 WI83-N

6649 WI83-NF

6650 WI83-S

6651 WI83-SF

6652 WI83-TM

6653 WIHP-C

6654 WIHP-CF

6655 WIHP-N

6656 WIHP-NF

6657 WIHP-S

6658 WIHP-SF

6659 WilkinMN-F

6660 WilkinMN-IF

6661 WilkinMN-M

6662 WinnebagoWI-F

6663 WinnebagoWI-IF

6664 WinnebagoWI-M

6665 WinonaMN-F

6666 WinonaMN-IF

6667 WinonaMN-M

6668 WisconsinTM-HP

6669 WoodWI-F

6670 WoodWI-IF

6671 WoodWI-M

6672 WORLD-EQDIST-CYL

6673 WORLD-LL

6674 WORLD-LM-CONIC

6675 WORLD-LM-TAN

6676 WORLD-MERCATOR

6677 WORLD-MILLER

6678 WORLD-ROBINSON

6679 WORLD-SINUSOIDAL

6680 WORLD-VDGRNTN

6681 WrightMN-F

6682 WrightMN-IF

6683 WrightMN-M

6684 WSIG

6685 WV-N

6686 WV-S

6687 WV83-N

6688 WV83-NF

6689 WV83-S

6690 WV83-SF

6691 WVHP-N

6692 WVHP-NF

6693 WVHP-S

6694 WVHP-SF

6695 WY-E

6696 WY-EC

6697 WY-W

6698 WY-WC

6699 WY83-E

6700 WY83-EC

6701 WY83-ECF

6702 WY83-EF

6703 WY83-W

6704 WY83-WC

6705 WY83-WCF

6706 WY83-WF

6707 WYHP-E

6708 WYHP-EC

6709 WYHP-ECF

6710 WYHP-EF

6711 WYHP-W

6712 WYHP-WC

6713 WYHP-WCF

6714 WYHP-WF

6715 Xian80.GK-13

6716 Xian80.GK-14

6717 Xian80.GK-15

6718 Xian80.GK-16

6719 Xian80.GK-17

6720 Xian80.GK-18

6721 Xian80.GK-19

6722 Xian80.GK-20

6723 Xian80.GK-21

6724 Xian80.GK-22

6725 Xian80.GK-23

6726 Xian80.GK/CM-105E

6727 Xian80.GK/CM-111E

6728 Xian80.GK/CM-117E

6729 Xian80.GK/CM-123E

6730 Xian80.GK/CM-129E

6731 Xian80.GK/CM-135E

6732 Xian80.GK/CM-75E

6733 Xian80.GK/CM-81E

6734 Xian80.GK/CM-87E

6735 Xian80.GK/CM-93E

6736 Xian80.GK/CM-99E

6737 Xian80.GK3d-25

6738 Xian80.GK3d-26

6739 Xian80.GK3d-27

6740 Xian80.GK3d-28

6741 Xian80.GK3d-29

6742 Xian80.GK3d-30

6743 Xian80.GK3d-31

6744 Xian80.GK3d-32

6745 Xian80.GK3d-33

6746 Xian80.GK3d-34

6747 Xian80.GK3d-35

6748 Xian80.GK3d-36

6749 Xian80.GK3d-37

6750 Xian80.GK3d-38

6751 Xian80.GK3d-39

6752 Xian80.GK3d-40

6753 Xian80.GK3d-41

6754 Xian80.GK3d-42

6755 Xian80.GK3d-43

6756 Xian80.GK3d-44

6757 Xian80.GK3d-45

6758 Xian80.GK3d/CM-102E

6759 Xian80.GK3d/CM-105E

6760 Xian80.GK3d/CM-108E

6761 Xian80.GK3d/CM-111E

6762 Xian80.GK3d/CM-114E

6763 Xian80.GK3d/CM-117E

6764 Xian80.GK3d/CM-120E

6765 Xian80.GK3d/CM-123E

6766 Xian80.GK3d/CM-126E

6767 Xian80.GK3d/CM-129E

6768 Xian80.GK3d/CM-132E

6769 Xian80.GK3d/CM-135E

6770 Xian80.GK3d/CM-75E

6771 Xian80.GK3d/CM-78E

6772 Xian80.GK3d/CM-81E

6773 Xian80.GK3d/CM-84E

6774 Xian80.GK3d/CM-87E

6775 Xian80.GK3d/CM-90E

6776 Xian80.GK3d/CM-93E

6777 Xian80.GK3d/CM-96E

6778 Xian80.GK3d/CM-99E

6779 Xian80.LL

6780 XY-BC

6781 XY-BL

6782 XY-BR

6783 XY-CA

6784 XY-CC

6785 XY-CF

6786 XY-CG

6787 XY-CL

6788 XY-CM

6789 XY-DAM

6790 XY-DK

6791 XY-DM

6792 XY-FT

6793 XY-FU

6794 XY-GC

6795 XY-GL

6796 XY-GM

6797 XY-HM

6798 XY-IFT

6799 XY-IIN

6800 XY-IMI

6801 XY-IN

6802 XY-IYD

6803 XY-KM

6804 XY-KT

6805 XY-M

6806 XY-MI

6807 XY-ML

6808 XY-MM

6809 XY-NM

6810 XY-PE

6811 XY-PO

6812 XY-RD

6813 XY-RO

6814 XY-SC

6815 XY-SL

6816 XY-SY

6817 XY-UI

6818 XY-YD

6819 Yacare.LL

6820 Yacare/E.LL

6821 YAP

6822 YellowMedicineMN-F

6823 YellowMedicineMN-IF

6824 YellowMedicineMN-M

6825 YEM-E

6826 YEM-GK7

6827 YEM-GK8

6828 YEM-GK9

6829 YEM-U37

6830 YEM-U38

6831 YEM-U39

6832 YEM-W

6833 Yemen96.UTM-38N

6834 Yemen96.UTM-39N

6835 YemenNtl96.LL

6836 ZAIRE

6837 Zanderij.LL

6838 Zanderij.SurinameTM

6839 Zanderij.SurinameTMOld

6840 Zanderij.TM-54NW

6841 Zanderij.UTM-21N

6842 ZIM-35

6843 ZIM-35/01

6844 ZIM-36

6845 ZIM-36/01

So we can see we have 6845 available to us (many more than the list shown inside AutoCAD 2015). I’m not sure how best they’d be filtered or categorised – this isn’t my specialist field, of course – but if someone has a request/suggestion for how to do so, please do post a comment. A lot more can clearly be done with this list than just printing the ID of each entry.

Update:

Mads tells me the library we use contains more coordinate systems than AutoCAD supports: AutoCAD only currently works with “projected” coordinate systems (the others in the list are used by other Autodesk products). You can modify the LCS command to filter the list by wrapping the contents of its foreach loop in an if statement, e.g.:

if (cs.Type == GeoCSType.Projected)

{

  ed.WriteMessage("\n{0} {1}", i, cs.ID);

  ++i;

}

That reduces the list down to 6132 coordinate systems that can be used by AutoCAD. Which should still be plenty, I would think. Thanks, Mads!

Feed/Share

10 Random Posts