October 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  










October 09, 2014

New Memento build and webinar

As reported over on Scott’s blog, Project Memento v1.0.10.5 is now available on Autodesk Labs. I won’t repeat the specific new features in this release – Scott covers those thoroughly – but I will say that I’m personally most excited about trying the improved .OBJ and .FBX export and the workflows that they enable.

Project Memento

To find out more about Memento, there’s a webinar on Wednesday October 15 at 9am Pacific talking about the tool. During the webinar, Tatjana Dzambazova – whom you may have seen in her excellent TEDx session – will cover topics such from uploading photos, working with highly detailed meshes and 3D printing the results.

recap.autodesk.comAnd in somewhat related news, the ReCap website has received a welcome refresh. Head on over and check it out!

October 08, 2014

Autodesk software is free for students, teachers and schools (yes, really)

I mentioned this initiative a few months ago, but it turns it hadn’t been rolled out everywhere: there were regional exceptions meaning that students in certain countries weren’t eligible for the program at that point. So my apologies if it sounds like I’m repeating myself, but at least it’s good news that I’m announcing twice. :-)students.autodesk.com

The last kinks have been ironed out of the program, so now students, teachers and schools anywhere in the world can now download and use the following Autodesk software for freeFree software list

So if you’re a student who was expecting to be able to get free Autodesk software tools based on my previous post but it didn’t work out, check again on students.autodesk.com – this time it will!

October 06, 2014

Update on Spark, Autodesk’s 3D printing platform

There’s been a lot in the news about Spark – Autodesk’s entry into the 3D printing market – of late. Earlier in the year we announced this open platform and a reference design for it, but in the last few weeks things have become even more interesting: specific examples of partnerships with companies who are building their own printers based on Spark have started to emerge. I thought it worth aggregating a few of the more interesting articles for those who might have missed them.

I’m personally really interested in the approach Autodesk is taking here. It seems to me that the “additive manufacturing” space is currently dominated by vendors trying to monetize both the upfront hardware investment and the consumables, which are often proprietary (i.e. the razor and the blades). And they’re providing software that’s really an afterthought rather than being considered of prime importance to the customer.

Opening up the platform to people wanting to drive innovation in materials and/or software should have a positive impact on the industry. And presumably be a good thing for users connecting Autodesk design tools with Spark-powered devices, of course.

Autodesk's 1st 3D printerHere’s an interesting interview where Autodesk’s CTO, Jeff Kowalski, provides some useful background information, including how the Spark platform and the coming Autodesk-branded 3D printers are analogous to Android and Google’s Nexus devices, respectively. And those who have managed to get their hands on the first Spark-based DLP printer are suitably impressed.

As an example of the type of innovation that could conceivably end up in the Spark software platform (I have no idea whether it’s part of the plan or not, mind), check out an Autodesk Research project announced at this week’s UIST (User Interface Software and Technology) Syposium:




PipeDream allows you to create internal pipes and tubes in your 3D-printed models as conduits for wires or for air leading to sensors or even actuators providing haptic feedback.

Local Motors' Strati 
Local Motors was the first to announce a partnership with Autodesk, incorporating Spark into the process for creating the Strati, the first ever 3D-printed car.

Dremel's 3D Idea Builder

A household name in handheld tool systems, Dremel then announced their own 3D printer based on Spark (this one based on FDM).

3DPrintshow's 2014 Brand of the Year

It’s clearly been an interesting few months since Autodesk announced this new focus on 3D printing back in May. In recognition of this – and I have to admit to finding this pretty incredible, personally – 3D Printshow named Autodesk as their 2014 Brand of the Year.

Spark blog

If you find this kind of news interesting, be sure to check this new blog dedicated to Spark on a regular basis – or simply follow the Spark Twitter account. Developments are coming thick and fast!

photo credit: automobileitalia via photopin cc

October 03, 2014

Connecting Three.js to an AutoCAD model – Part 2

To follow on from yesterday’s post, today we’re going to look at two C# source files that work with the HTML page – and referenced JavaScript files – which I will leave online rather than reproducing here.

As a brief reminder of the functionality – if you haven’t yet watched the screencast shown last time – this version of the app shows an embedded 3D view that reacts to the creation – and deletion – of geometry from the associated AutoCAD model. You will see the bounding boxes for geometry appear in the WebGL view (powered by Three.js) as you’re modeling.


Three.js integration with AutoCAD 

The code is a bit different to the approach we took to display the last area, earlier in the week: we do look for entities that are added to/removed from the document we care about, but we pass through the list of those added/removed by each command, not just the area of the latest. On the JavaScript side of things we add the handle of the associated entity as the Three.js name, allowing us to retrieve the object again in case it gets erased.

This is ultimately a more interesting approach for people wanting to track more detailed information about modeling operations (although admittedly we’re still only passing geometric extents and the handle – we’re not dealing with more complicated data in this “simple” sample).

Here’s the first of the C# source files, which defines the AutoCAD commands to create a palette or an HTML document inside AutoCAD (this latter one is now a bit boring in comparison: it creates a static snapshot of the launching document, but doesn’t track any changes afterwards… the palette is a lot more fun :-).

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Windows;

using Newtonsoft.Json;

using System;

using System.Runtime.InteropServices;

 

namespace JavaScriptSamples

{

  public class ThreeCommands

  {

    private PaletteSet _3ps = null;

    private static Document _curDoc = null;

    private static ObjectIdCollection _add =

      new ObjectIdCollection();

    private static ObjectIdCollection _remove =

      new ObjectIdCollection();

 

    [DllImport(

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

      CallingConvention = CallingConvention.Cdecl,

      EntryPoint = "acjsInvokeAsync")]

    extern static private int acjsInvokeAsync(

      string name, string jsonArgs

    );

 

    [CommandMethod("THREE")]

    public void ThreePalette()

    {

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

      // various event handlers to it

 

      _curDoc =

        Application.DocumentManager.MdiActiveDocument;

 

      // Only attach event handlers if the palette isn't already

      // there (in which case it will already have them)

 

      var attachHandlers = (_3ps == null);

 

      _3ps =

        Utils.ShowPalette(

          _3ps,

          new Guid("9CEE43FF-FDD7-406A-89B2-6A48D4169F71"),

          "THREE",

          "Three.js Examples",

          GetHtmlPathThree()

        );

 

      if (attachHandlers && _curDoc != null) {

 

        Application.DocumentManager.DocumentActivated +=

          OnDocumentActivated;

 

        _curDoc.BeginDocumentClose +=

          (s, e) =>

          {

            RemoveHandlers(_curDoc);

            _curDoc = null;

          };

 

        _3ps.SizeChanged += OnPaletteSizeChanged;

 

        // When the PaletteSet gets destroyed we remove

        // our event handlers

 

        _3ps.PaletteSetDestroy += OnPaletteSetDestroy;

      }

    }

 

    [CommandMethod("THREEDOC")]

    public void ThreeDocument()

    {

      _curDoc = Application.DocumentManager.MdiActiveDocument;

 

      if (_curDoc != null)

      {

        _curDoc.BeginDocumentClose +=

          (s, e) => _curDoc = null;

      }

 

      Application.DocumentWindowCollection.AddDocumentWindow(

        "Three.js Document", GetHtmlPathThree()

      );

    }

 

    [JavaScriptCallback("ViewExtents")]

    public string ViewExtents(string jsonArgs)

    {

      // Default return value is failure

 

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

 

      if (_curDoc != null)

      {

        var vw = _curDoc.Editor.GetCurrentView();

        var ext = Utils.ScreenExtents(vw);

        res =

          String.Format(

            "{{\"retCode\":0, \"result\":" +

            "{{\"min\":{0},\"max\":{1}}}}}",

            JsonConvert.SerializeObject(ext.MinPoint),

            JsonConvert.SerializeObject(ext.MaxPoint)

          );

      }

      return res;

    }

 

    [JavaScriptCallback("ThreeSolids")]

    public string ThreeSolids(string jsonArgs)

    {

      return Utils.GetSolids(_curDoc, Point3d.Origin);

    }

 

    private void OnPaletteSizeChanged(

      object s, PaletteSetSizeEventArgs e

    )

    {

      Refresh();

    }

 

    private void OnDocumentActivated(

      object s, DocumentCollectionEventArgs e

    )

    {

      if (_3ps != null && e.Document != _curDoc)

      {

        // 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)

 

        // The document is dead...

 

        RemoveHandlers(_curDoc);

        _add.Clear();

        _remove.Clear();

 

        // ... long live the document!

 

        _curDoc = e.Document;

        AddHandlers(_curDoc);

 

        Refresh();

      }

    }

 

    private void AddHandlers(Document doc)

    {

      if (doc != null)

      {

        if (doc.Database != null)

        {

          doc.Database.ObjectAppended += OnObjectAppended;

          doc.Database.ObjectErased += OnObjectErased;

        }

        doc.CommandEnded += OnCommandEnded;

      }

    }

 

    private void RemoveHandlers(Document doc)

    {

      if (doc != null)

      {

        if (doc.Database != null)

        {

          doc.Database.ObjectAppended -= OnObjectAppended;

          doc.Database.ObjectErased -= OnObjectErased;

        }

        doc.CommandEnded -= OnCommandEnded;

      }

    }

 

    private void OnObjectAppended(object s, ObjectEventArgs e)

    {

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

      {       

        _add.Add(e.DBObject.ObjectId);

      }

    }

 

    private void OnObjectErased(object s, ObjectErasedEventArgs e)

    {

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

      {

        var id = e.DBObject.ObjectId;

        if (e.Erased)

        {

          if (!_remove.Contains(id))

          {

            _remove.Add(id);

          }

        }

        else

        {

          if (!_add.Contains(id))

          {

            _add.Add(e.DBObject.ObjectId);

          }

        }

      }

    }

 

    private void OnCommandEnded(object s, CommandEventArgs e)

    {

      // Invoke our JavaScript functions to update the palette

 

      if (_add.Count > 0)

      {

        if (_3ps != null)

        {

          var sols =

            Utils.SolidInfoForCollection(

              (Document)s, Point3d.Origin, _add

            );

          acjsInvokeAsync("addsols", Utils.SolidsString(sols));

        }

        _add.Clear();

      }

 

      if (_remove.Count > 0)

      {

        if (_3ps != null)

        {

          acjsInvokeAsync("remsols", Utils.GetHandleString(_remove));

          _remove.Clear();

        }

      }

    }

 

    private void OnPaletteSetDestroy(object s, EventArgs e)

    {

      // When our palette is closed, detach the various

      // event handlers

 

      if (_curDoc != null)

      {

        RemoveHandlers(_curDoc);

        _curDoc = null;

      }

    }

 

    private void Refresh()

    {

      if (_3ps != null && _3ps.Count > 0)

      {

        acjsInvokeAsync("refsols", "{}");

      }

    }

 

    private static Uri GetHtmlPathThree()

    {

      return new Uri(Utils.GetHtmlPath() + "threesolids2.html");

    }

  }

}

This file depends on a shared Utils.cs file:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Windows;

using Newtonsoft.Json;

using System;

using System.Collections.Generic;

using System.IO;

using System.Reflection;

using System.Text;

 

namespace JavaScriptSamples

{

  internal class Utils

  {

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

    // in the case where the active document is null

 

    internal static Document GetActiveDocument(

      DocumentCollection dm, Document launchDoc = null

    )

    {

      // If we're called from an HTML document, the active

      // document may be null

 

      var doc = dm.MdiActiveDocument;

      if (doc == null)

      {

        doc = launchDoc;

      }

      return doc;

    }

 

    internal static string GetSolids(

      Document launchDoc, Point3d camPos, bool sort = false

    )

    {

      var doc =

        Utils.GetActiveDocument(

          Application.DocumentManager,

          launchDoc

        );

 

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

 

      if (doc == null)

        return "";

 

      // We could probably get away without locking the document

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

      // do it anyway

 

      using (var dl = doc.LockDocument())

      {

        var db = doc.Database;

        var ed = doc.Editor;

 

        var ids = new ObjectIdCollection();

 

        using (

          var tr = doc.TransactionManager.StartOpenCloseTransaction()

        )

        {

          // Start by getting the modelspace

 

          var ms =

            (BlockTableRecord)tr.GetObject(

              SymbolUtilityServices.GetBlockModelSpaceId(db),

              OpenMode.ForRead

            );

 

          // If in palette mode we can get the camera from the

          // Editor, otherwise we rely on what was provided when

          // the HTML document was launched

 

          if (launchDoc == null)

          {

            var view = ed.GetCurrentView();

            camPos = view.Target + view.ViewDirection;

          }

 

          // Get each Solid3d in modelspace and add its extents

          // to the sorted list keyed off the distance from the

          // closest face of the solid (not necessarily true,

          // but this only really is a crude approximation)

 

          foreach (var id in ms)

          {

            ids.Add(id);

          }

          tr.Commit();

        }

 

        var sols = SolidInfoForCollection(doc, camPos, ids, sort);

 

        return SolidsString(sols);

      }

    }

 

    internal static List<Tuple<double,string, Extents3d>>

    SolidInfoForCollection(

      Document doc, Point3d camPos, ObjectIdCollection ids,

      bool sort = false

    )

    {

      // We'll sort our list of extents objects based on a

      // distance value

 

      var sols =

        new List<Tuple<double, string, Extents3d>>();

 

      using (

        var tr = doc.TransactionManager.StartOpenCloseTransaction()

      )

      {

        foreach (ObjectId id in ids)

        {

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

          var sol = obj as Entity;//Solid3d;

          if (sol != null)

          {

            var ext = sol.GeometricExtents;

            var tmp =

              ext.MinPoint + 0.5 * (ext.MaxPoint - ext.MinPoint);

            var mid = new Point3d(ext.MinPoint.X, tmp.Y, tmp.Z);

            var dist = camPos.DistanceTo(mid);

            sols.Add(

              new Tuple<double, string, Extents3d>(

                dist, sol.Handle.ToString(), ext

              )

            );

          }

        }

      }

 

      if (sort)

      {

        sols.Sort((sol1,sol2)=>sol2.Item1.CompareTo(sol1.Item1));

      }

      return sols;

    }

 

    // Helper function to build a JSON string containing a

    // sorted extents list

 

    internal static string SolidsString(

      List<Tuple<double, string, Extents3d>> lst)

    {

      var sb = new StringBuilder("{\"retCode\":0, \"result\":[");

 

      var first = true;

      foreach (var tup in lst)

      {

        if (!first)

          sb.Append(",");

 

        first = false;

        var hand = tup.Item2;

        var ext = tup.Item3;

 

        sb.AppendFormat(

          "{{\"min\":{0},\"max\":{1},\"handle\":\"{2}\"}}",

          JsonConvert.SerializeObject(ext.MinPoint),

          JsonConvert.SerializeObject(ext.MaxPoint),

          hand

        );

      }

      sb.Append("]}");

 

      return sb.ToString();

    }

 

    // Helper function to build a JSON string containing a

    // list of handles

 

    internal static string GetHandleString(ObjectIdCollection _ids)

    {

      var sb = new StringBuilder("{\"handles\":[");

      bool first = true;

      foreach (ObjectId id in _ids)

      {

        if (!first)

        {

          sb.Append(",");

        }

 

        first = false;

 

        sb.AppendFormat(

          "{{\"handle\":\"{0}\"}}",

          id.Handle.ToString()

        );

      }

      sb.Append("]}");

      return sb.ToString();

    }

 

    // Helper function to show a palette

 

    internal static 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.Remove(0);

      }

 

      ps.Add(title, uri);

      ps.Visible = true;

 

      return ps;

    }

 

    internal static Matrix3d Dcs2Wcs(AbstractViewTableRecord v)

    {

      return

        Matrix3d.Rotation(-v.ViewTwist, v.ViewDirection, v.Target) *

        Matrix3d.Displacement(v.Target - Point3d.Origin) *

        Matrix3d.PlaneToWorld(v.ViewDirection);

    }

 

    internal static Extents3d ScreenExtents(

      AbstractViewTableRecord vtr

    )

    {

      // Get the centre of the screen in WCS and use it

      // with the diagonal vector to add the corners to the

      // extents object

 

      var ext = new Extents3d();

      var vec = new Vector3d(0.5 * vtr.Width, 0.5 * vtr.Height, 0);

      var ctr =

        new Point3d(vtr.CenterPoint.X, vtr.CenterPoint.Y, 0);

      var dcs = Utils.Dcs2Wcs(vtr);

      ext.AddPoint((ctr + vec).TransformBy(dcs));

      ext.AddPoint((ctr - vec).TransformBy(dcs));

 

      return ext;

    }

 

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

 

    internal 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/";

    }

  }

}

I’ve been banging away at the app to get it to fail: the latest version seems fairly solid, but do let me know if you come across any issues with it.

If I’m right, the kind of responsiveness this sample shows should enable all kinds of interesting HTML palette-based applications inside AutoCAD.

October 02, 2014

Connecting Three.js to an AutoCAD model – Part 1

As part of my preparations for AU, I’ve been extending this Three.js integration sample to make it more responsive to model changes: I went ahead and implemented event handlers in .NET – much as we saw in the last post – to send interaction information through to JavaScript so that it can update the HTML palette view.

The code is in pretty good shape, but I still need to decide whether to post it separately or with the other JavaScript samples I’m working on (I’ll also be showing Paper.js and Isomer integrations during my AU talk, as well as a special demo bringing ShapeShifter models into AutoCAD).

In the meantime, here’s a screencast of the Three.js updated integration in action.




My apologies for the sound quality: I’ve managed to lose my external mic and my new MacBook’s internal one pics up a lot of noise from the fan, once it gets going.

Also, if the command-list provided by Screencast is getting in the way, if you go to full-screen mode it should be easier to see what’s going on.

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!

Feed/Share

10 Random Posts