Kean Walmsley


  • About the Author
    Kean on Google+

April 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      







April 23, 2014

Per-document data in AutoCAD .NET applications – Part 3

I almost named this post to make it clear it’s about a new API capability in AutoCAD 2015, but then I wouldn’t have had the slightly perverse satisfaction of resurrecting a series of posts after 7.5 years. Here are the first parts in this series, in case you’ve forgotten them (and you’d be forgiven for doing so, after all ;-).

This post is introducing the new PerDocumentClassAttribute capability provided in AutoCAD 2015’s .NET API. We’re going to look at it over two posts: the first showing how it can be used with your own “manager” class to link the data to a Document and the second will show how the Document’s UserData property might also be used to take care of that.

[Thanks to the ADN team for providing the sample I used as a basis for today’s post (it may well have come from the AutoCAD team before then, but I first saw it on the ADN web-site).]

So what does this attribute do? Let’s see what the documentation says:

This custom attribute class is used to mark a type so that an instance of that type will be instantiated for each document open/opened in AutoCAD.

An application may designate as many types as desired with this attribute. When the application is loaded into AutoCAD, a new instance of the type will be instantiated for each document currently open and a reference to the document, and a new instance will be instantiated for each document opened thereafter. When a document is closed, the associated type instance will be disposed if it derives from IDisposable.

The type associated with a PerDocumentClass attribute must provide either a public constructor that takes a Document argument, or a public static Create method that takes a Document argument and returns an instance of the type. If the Create method exists, it will be used, otherwise the constructor will be used. The Document that the type instance is being created for will be passed as the Document argument so that the type instance knows which Document it is associated with.

To paraphrase, this attribute provides a way to specify that a particular type of object needs to be created – and optionally disposed of – for any Documents loaded in the editor (currently loaded Documents have objects created as the application loads, additional objects will be instantiated as Documents get opened or created).

It’s important to note that the mechanism doesn’t specify how the data should be held in memory: as we’ll see in this post and the next, you can choose to manage this via your own manager class or a Document’s UserData map.

To use the mechanism you need to define your class and point to it from an assembly-level attribute. Here’s some C# code showing how a DateTime can be associated with each Document via our custom PerDocData class:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.Runtime;

using System;

using System.Collections.Generic;

 

// The all-important attribute telling AutoCAD to instantiate

// the PerDocData class for each open or new document

 

[assembly: PerDocumentClass(typeof(PerDocSample.PerDocData))]

 

namespace PerDocSample

{

  public class Commands

  {

    // A simple command to write the contents of our per-document

    // data to the command-line

 

    [CommandMethod("PPDD")]

    public void PrintPerDocData()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      var perDocData = PerDocDataManager.GetData(doc);

      doc.Editor.WriteMessage(

        perDocData == null ?

        "\nNo user data found." :

        perDocData.OpenDateTime.ToString()

      );

    }

  }

 

  // We need a manager class that will keep our data mapped to

  // a document. This class can optionally care about when

  // documents are closed via the Document's BeginDocumentClose

  // event

 

  public class PerDocDataManager

  {

    // Create a mapping between documents and our data objects

 

    static Dictionary<Document, PerDocData> _data =

      new Dictionary<Document, PerDocData>();

 

    // We call this static method from our class' constructor,

    // so we get added to the map

 

    public static void AddPerDoc(Document doc, PerDocData att)

    {

      _data.Add(doc, att);

 

      doc.BeginDocumentClose +=

        (s, e) =>

        {

          var d = (Document)s;

          if (_data.ContainsKey(d))

            _data.Remove(d);

        };

    }

 

    // A static method to return the data associated with a

    // Document

 

    public static PerDocData GetData(Document doc)

    {

      return _data.ContainsKey(doc) ? _data[doc] : null;

    }

  }

 

  // Our per-document data class. This will get instanciated for

  // each existing or new document: we get the creation

  // notification via either the static Create() method or via

  // the public constructor that takes a Document argument

 

  public class PerDocData

  {

    // We will store the time the document was opened or created

 

    private DateTime _openDateTime;

 

    // Provide a public read-only property for that

 

    public DateTime OpenDateTime

    {

      get { return _openDateTime; }

    }

 

    // Public constructor taking a Document

 

    public PerDocData(Document doc)

    {

      _openDateTime = DateTime.Now;

      PerDocDataManager.AddPerDoc(doc, this);

    }

 

    // Static Create method: this is the first approach tried

    // (to differentiate we're adding an hour to the current

    // time, so it's clear this method is being called)

 

    public static PerDocData Create(Document doc)

    {

      var pdd = new PerDocData(doc);

      pdd._openDateTime += TimeSpan.FromHours(1);

      return pdd;

    }

  }

}

One change I made to the sample was to flesh out the two approaches for creating new instances of your tagged class. The first is to provide a Create() factory function that returns a new instance, the second is to provide a constructor: both of these take a Document as an argument. I’ve implemented the Create() function to call through to the constructor – which sets the DateTime property to the current time – but then to add an hour to the time before returning it. This lets us see that the Create() method is called if it is found (you can comment simply it out to see the behaviour when the constructor is used instead).

Otherwise the sample is pretty much the same as the one on the ADN site. The separate manager is used to associate a Document with the created instance of our custom class as well as to retrieve the object associated with a particular document. If your class derives from IDisposable then its Dispose() method will be called when the associated Document gets destroyed (this isn’t a feature we need to implement in our manager, it happens automagically).

To try the code out, NETLOAD the module that includes the code into AutoCAD and run the PPDD command in a number of different documents, both existing and new.

In the next post, we’ll take a look at swapping out the custom manager to use the built-in UserData capability.

April 22, 2014

Implementing a custom AutoCAD object snap mode using .NET (redux)

I had an email from Martin Duke about this old post a couple of weeks ago. I started to update the original post but then realised that a) I couldn’t easily go that far back using Windows Live Writer and the built-in Typepad editor often messes up code in old posts when I edit them and b) there was some value in revisiting this topic again now that nearly 6 years has passed. I’ll hopefully manage to link to this updated post from the old one without things going awry.

Martin was having some trouble with text display in object snap glyphs: it worked well enough once some geometry has been drawn – even if it didn’t automatically use the colour of the other object snap glyph graphics – but it didn’t work well when a drawing has just been created or loaded. If the previously implemented object snap was used in a fresh drawing, the text didn’t get displayed.

No osnap glyph text in a fresh drawing

As soon as additional geometry was created in the drawing, however, the text did get displayed as expected.

Once more geometry has been created, it works

The answer, it turns out, was to use an SHX font: this allowed the transient text to be displayed properly from the start and with the colour the glyph markers should have.

Switching the text to use an SHX font makes it work consistently

I also took the opportunity to centre the text properly on the snap point, which is ultimately a matter of taste: I think I even moved it away from the snap point on purpose 6 years ago. ;-)

It would clearly be nice to have TrueType fonts display properly, but this is an artifact of the 2D graphics system (and is logged as an issue in our internal tracking system). The behaviour is different when using a 3D Visual Style, for whatever that’s worth.

Here’s the updated C# code that shows how this can be made to work in this way:

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using AcGi = Autodesk.AutoCAD.GraphicsInterface;

 

[assembly: ExtensionApplication(

  typeof(OsnapApp.CustomOSnapApp))

]

 

namespace OsnapApp

{

  // Register and unregister custom osnap

 

  public class CustomOSnapApp : IExtensionApplication

  {

    private QuarterOsnapInfo _info =

      new QuarterOsnapInfo();

    private QuarterGlyph _glyph =

      new QuarterGlyph();

    private CustomObjectSnapMode _mode;

 

    public void Initialize()

    {

      // Register custom osnap on initialize

 

      _mode =

        new CustomObjectSnapMode(

          "Quarter",

          "Quarter",

          "Quarter of length",

          _glyph

        );

 

      // Which kind of entity will use the osnap

 

      _mode.ApplyToEntityType(

        RXObject.GetClass(typeof(Polyline)),

        new AddObjectSnapInfo(_info.SnapInfoPolyline)

      );

      _mode.ApplyToEntityType(

        RXObject.GetClass(typeof(Curve)),

        new AddObjectSnapInfo(_info.SnapInfoCurve)

      );

      _mode.ApplyToEntityType(

        RXObject.GetClass(typeof(Entity)),

        new AddObjectSnapInfo(_info.SnapInfoEntity)

      );

 

      // Activate the osnap

 

      CustomObjectSnapMode.Activate("_Quarter");

    }

 

    // Unregister custom osnap on terminate

 

    public void Terminate()

    {

      CustomObjectSnapMode.Deactivate("_Quarter");

    }

  }

 

  // Create new quarter object snap

 

  public class QuarterGlyph : AcGi.Glyph

  {

    private Point3d _pt;

 

    public override void SetLocation(Point3d point)

    {

      _pt = point;

    }

 

    protected override void SubViewportDraw(AcGi.ViewportDraw vd)

    {

      int glyphPixels = CustomObjectSnapMode.GlyphSize;

      var glyphSize = vd.Viewport.GetNumPixelsInUnitSquare(_pt);

 

      // Calculate the size of the glyph in WCS

      //  (use for text height factor)

 

      // We'll add 10% to the size, as otherwise

      //  it looks a little too small

 

      double glyphHeight =

        (glyphPixels / glyphSize.Y) * 1.1;

 

      string text = "¼";

 

      // Translate the X-axis of the DCS to WCS

      //  (for the text direction) and the snap

      //  point itself (for the text location)

 

      var dist = -glyphHeight / 2.0;

      var offset = new Vector3d(dist, dist, 0);

      var e2w = vd.Viewport.EyeToWorldTransform;

      var dir = Vector3d.XAxis.TransformBy(e2w);

      var pt = (_pt + offset).TransformBy(e2w);

 

      //  Draw the centered text representing the glyph

 

      var style = new AcGi.TextStyle();

      var fd =

        new AcGi.FontDescriptor("txt.shx", false, false, 0, 0);

      style.Font = fd;

      style.TextSize = glyphHeight;

 

      vd.Geometry.Text(

        pt,

        vd.Viewport.ViewDirection,

        dir,

        text,

        false,

        style

      );

    }

  }

 

  // OSnap info

 

  public class QuarterOsnapInfo

  {

    public void SnapInfoEntity(

      ObjectSnapContext context,

      ObjectSnapInfo result)

    {

      // Nothing here

    }

 

    public void SnapInfoCurve(

      ObjectSnapContext context,

      ObjectSnapInfo result

    )

    {

      // For any curve

 

      var cv = context.PickedObject as Curve;

      if (cv == null)

        return;

 

      double startParam = cv.StartParam;

      double endParam = cv.EndParam;

 

      // Add osnap at first quarter

 

      double param =

        startParam + ((endParam - startParam) * 0.25);

      var pt = cv.GetPointAtParameter(param);

 

      result.SnapPoints.Add(pt);

 

      // Add osnap at third quarter

 

      param =

        startParam + ((endParam - startParam) * 0.75);

      pt = cv.GetPointAtParameter(param);

 

      result.SnapPoints.Add(pt);

      if (cv.Closed)

      {

        pt = cv.StartPoint;

        result.SnapPoints.Add(pt);

      }

    }

 

    public void SnapInfoPolyline(

      ObjectSnapContext context,

      ObjectSnapInfo result)

    {

      // For polylines

 

      var pl = context.PickedObject as Polyline;

      if (pl == null)

        return;

 

      // Get the overall start and end parameters

 

      double plStartParam = pl.StartParam;

      double plEndParam = pl.EndParam;

 

      // Get the local

 

      double startParam = plStartParam;

      double endParam = startParam + 1.0;

 

      while (endParam <= plEndParam)

      {

        // Calculate the snap point per vertex...

 

        // Add osnap at first quarter

 

        double param =

          startParam + ((endParam - startParam) * 0.25);

        var pt = pl.GetPointAtParameter(param);

 

        result.SnapPoints.Add(pt);

 

        // Add osnap at third quarter

 

        param =

          startParam + ((endParam - startParam) * 0.75);

        pt = pl.GetPointAtParameter(param);

 

        result.SnapPoints.Add(pt);

 

        startParam = endParam;

        endParam += 1.0;

      }

    }

  }

}

I’m easing back into the swing of things after my 10+ days out of the office. I’ll hopefully get the time to post on more new AutoCAD 2015 APIs during the coming few days.

April 18, 2014

Autodesk ReCap Connect: a partner program for ReCap

It’s Good Friday, so much of the world will be offline today, but I thought I’d post quickly on some (good) news that may be of interest to people.

The ReCap team has officially launched a new partner program. This provides access to a number of mechanisms for integrating with or harnessing Autodesk ReCap products and services.

The Capture Codec Kit is primarily for importing custom point cloud formats – something important for hardware manufacturers, particularly – into Autodesk ReCap on the desktop.

Embedded ReCap OEM allows generated or post-processed point cloud data to be brought into Autodesk’s design products that support the RCS format. This looks like it would be very handy for my Kinect integration with AutoCAD 2015, for instance.

The third option is the ReCap Photo Web API, a web-service API into the back-end photo processing system that drives Photo on ReCap 360 and 123D Catch. You can use this RESTful API to create and manage projects reconstructing 3D models from photographs. Being able to process these projects programmatically – and using additional information such as camera positions – brings some really interesting capabilities for application developers. I’d like to see how it might be used in conjunction with Augmented Reality applications, for instance. Interesting times!

If you’re interested in finding out more about being part of this program, be sure to contact Dominique Pouliquen.

April 16, 2014

Fun in the UK

It’s been a great week of holidays, so far. There’s been a lot of driving: we booked our flights in and out of Stansted – as our main purpose was to attend a good friend’s wedding in Suffolk – but fairly late on we decided to drive up to Cumbria to visit my grandmother.

Best fish and chips in ages

My birthday dinner at The Windmill Inn in Redmile – a pub used to film certain episodes of the classic UK series, Auf Wiedersehen Pet.

As my old friend Charles McAuley used to say, the difference between England and America is that in England 100 miles is a long way and in America 100 years is a long time. So the 4-5 hour drive up north was pretty tiring, but at least we really got lucky with the accommodation – the Oakwood Park Hotel in Brampton was incredible.

The weather was also a little fresh for the start of our week, mainly due to the biting wind. But we got to stand on Hadrian’s Wall, visiting Birdoswald and Lanercost Priory and refuelling with lots of cups of tea in various teashops.

We drove back down south on Monday, staying over (once again lucking out) at Peacock Farm in Redmile before we drove through to Suffolk. We stopped off briefly in the town where I grew up – mainly to show the kids how lucky they are to live in Switzerland – and then visited Grimes Graves, as they all have a thing about flint (and this is very much flint country).

Today is the wedding day, so I’m starting to think about getting suited and booted. I’m really looking forward to catching up with some of my oldest and dearest friends (some of whom have travelled across from New Zealand and Kuala Lumpur to be here).

Also the sun has come out, so it’s warming up a little, thankfully. And when the sun is shining in the UK, it’s really a wonderful place to be. :-)

April 14, 2014

One month to go until the AEC Technology Symposium

As Jeremy has pointed out on his blog, in lieu of a dedicated AEC DevCamp – as has been run every other year for some years – Autodesk and the ADN team will be supporting the upcoming AEC Technology Symposium being held in New York on May 16-18.

As with the DevCamps, this is a great opportunity to get to know the people behind our AEC products – whether product management or software engineering – so you can build relationships and get a better sense of where Autodesk is heading in the AEC industry.

There will also be a Hackathon held on May 17-18, after the symposium itself on May 16, so you’ll be able to play around with cutting edge Autodesk technology and have hands-on assistance from some highly knowledgeable Autodesk personnel.

[As a reminder, this post has been queued up in advance of my holidays in the UK, so please don’t expect responses to any blog comments until next week.]

April 11, 2014

Call for proposals for Autodesk University 2014 approaching

The “call for proposals” for AU2014 is going out on April 23rd and will remain open for about a month. This is approaching quickly, so get your thinking hats on if you’re considering submitting a class proposal.

From my side I’m thinking about a few possible topics:

I’m also toying around with the idea of doing a kind of “meta” session, during which we’d look at some of my favourite blog posts/areas of research over the years. Something like “8 years of Through the Interface”, possibly including a brief section on the motivations behind the blog and how I manage it.

These are just preliminary thoughts, though. If any of you have any suggestions, I’d really like to hear them – even if you’re not planning on attending the conference in person (anything I prepare for AU tends to make it onto this site first, of course :-).

Please post a comment with your suggestions (bearing in mind I’ll (hopefully) be offline until April 22nd, enjoying travelling around the UK with my family).

April 09, 2014

AutoCAD 2015’s updated JavaScript API

We first introduced a JavaScript API into AutoCAD as a “preview” in the 2014 release. Take a look at this post for more details on this implementation. In the 2015 release the API has matured in a number of key ways, making it really ready for primetime usage.

Firstly it’s been made much more robust: rather than having a single instance of the browser executable hosting the various bits of JavaScript code running inside AutoCAD, we now use separate instances of the browser – which in turn hosts the Chromium Embedded Framework (CEF) – for each application. Which means that if one gets brought down, for whatever reason, you don’t need to reboot AutoCAD for Design Feed (for instance) to start working again. This kind of process isolation is important but complimented by a fair amount of stability-related work in the JavaScript API, making it much less likely you’ll get a browser crash in the first place.

It’s also much easier to debug JavaScript in AutoCAD 2015. Rather than having to enable debugging using a Registry key, you can now bring up the Developer Tools window from any HTML-defined palette or dialog inside AutoCAD by pressing the F12 key. Here’s what you see when you hit F12 in the New Tab page, for instance:

JavaScript debugging integrated in AutoCAD 2015

Before looking at the enhancements at an API level, let’s look at some links.

Firstly, the URL for the AutoCAD 2015 JavaScript API is: http://app.autocad360.com/jsapi/v2/Autodesk.AutoCAD.js

That’s the reference you’ll need to add in a <script> element in your HTML page.

If using XML to transfer data to AutoCAD via the API – such as when using transient graphics – you’ll probably want to take a look at the XML schema for this data.

Otherwise, in terms of documentation, here’s the landing page for the JavaScript API documentation and a handy Getting Started guide.

Now for a quick look at the various API enhancements.

As considerable work was done in the core of AutoCAD during this release as part of the fiber removal project, we also needed to update the JavaScript API. You are now able to call Acad.Editor.executeCommand() and executeCommandAsync() from JavaScript. The editor also provides a doEvents() function in case you need to process events (typically to repaint the screen) after calling commands synchronously from your JavaScript code.

This version of the API also includes access to information via the Bindable Object Layer (BOL). You can access information that’s provided via the BOL – such as layers, linetypes, hatch patterns, etc. – using Acad.DataItemCollectionManager.getKnownCollection(). You can also use the manager object to get notified when a collection has items added, removed or changed.

As well as accessing data via the BOL, it’s now possible to get read-only access to AutoCAD properties via JavaScript. I’ll have to look into creating a modeless, HTML property palette, at some point, to better understand the capabilities of this particular mechanism.

That’s it for today’s brief look at the updated JavaScript API in AutoCAD 2015. I’m looking forward to playing with this some more – it feels like the API is coming of age, in this release, with the future promise of bringing the ability to have apps that work in both AutoCAD and AutoCAD 360 (we’re not there yet, though, in case you were wondering about that side of things).

Tomorrow I’ll be heading off to the UK on vacation, visiting friends and family until the Easter weekend. I’ll try to queue up a blog post or two to keep things moving, but please don’t expect a response to any comments or emails for the next 10 days or so.

April 08, 2014

Supporting AutoCAD 2015’s dark theme

After a little fun last Tuesday around a pink theme for AutoCAD – for which I got at least a few “we should totally do that”s from internal folk in response, so watch this space ;-) – today we’re going to talk more seriously about what’s needed to support the dark theme in your applications.

But before that, a big thanks to Lee Ambrosius, who owns – and does a fantastic job with – AutoCAD’s developer documentation. Lee pointed out the online documentation which helped me create this post. If you’re interested in the work Lee and team has been doing, please check out his blog post on the developer documentation updates in the 2015 release.

So, how best to support the dark theme? Firstly, after the nonsense I spouted last week it’s important to make clear that there’s no shortcut to supporting the dark theme: you will need to convert dialogs and user interface elements – even down to the icon level – to look good in this environment.

Let’s start by looking at CUIx: the simplest way to get this working is to create resource-only DLLs for your icons. If you haven’t done this before, the steps in this thread over on The Swamp are likely to be of help (be sure to heed Owen’s advice at the end – that’s an important step). One DLL should be named as your CUIx file – i.e. MyRibbon.cuix would have MyRibbon.dll containing the dark theme icons – while the other DLL should have the “_light” suffix – i.e. MyRibbon_light.dll.

As the themes change, the appropriate set of icons should be loaded and used for your various CUI-hosted elements without any custom code being needed (check the top-left of the below image to see our ribbon item get a different icon depending on the value of COLORTHEME).

Themes

For your custom dialogs it’s going to be a little more tricky: for a modal dialog you should check the COLORTHEME system variable on launch and choose an appropriate theme for it. Unless you’re modifying the sysvar from your code, directly or indirectly, it should be safe enough to assume that it won’t change while your dialog is displayed.

For modeless dialogs you’ll need to work a little harder: a sysvar reactor attached to the COLORTHEME system variable will notify your application when the value changes, at which point you can take appropriate action.

Here’s an example of how you might have a flag in your code that gets set when you application launches (in our case when we run the WTC command) and that gets modified when you the sysvar changes. You’d ideally have this variable notify your UI for an update – perhaps via a dependency variable in WPF, or otherwise sending some kind of refresh notification – but in our case we’re simply checking the flag via the GT command.

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.Runtime;

 

namespace ThemeWatching

{

  public class Commands

  {

    // Maintain a flag in our code to save checking the sysvar

 

    private bool _isDark = false;

 

    public void CheckTheme()

    {

      _isDark =

        (short)Application.GetSystemVariable("COLORTHEME") == 0;

    }

 

    [CommandMethod("WTC")]

    public void WatchThemeChanges()

    {

      // Let's check the theme when we install the event handler

 

      CheckTheme();

 

      // And we'll check again whenever COLORTHEME changes

      // (we could toggle, but this way seems safer, and the

      // marginal cost difference shouldn't matter during a

      // sysvar change)

 

      Application.SystemVariableChanged +=

        (s, e) =>

        {

          if (e.Name == "COLORTHEME" && e.Changed)

          {

            CheckTheme();

          }

        };

    }

 

    [CommandMethod("GT")]

    public void GetTheme()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null) return;

 

      doc.Editor.WriteMessage(

        "\nCurrent theme this is {0}.", _isDark ? "dark" : "light"

      );

    }

  }

}

If you’re using a UI framework that supports theming – WPF is a great example – then adjusting the look of your custom dialogs will be straightforward. It’ll be more work if you don’t currently use such a framework, but now might be a good time to consider doing so (a number exist for WinForms development, too, so WPF is not the only option if you don’t want to throw away your existing WinForms UI investment). If you have experience to share around your use of UI frameworks that support theming, please do post a comment.

You can, of course, choose to have certain dialogs not adjust according to the theme – there are a number in AutoCAD, the CUI dialog being a prime example – and to then ask your users what’s important to them in terms of UI aesthetics.

April 04, 2014

Conceptual modeling in a drone-captured environment

I thought I was done with my posts for the week, but this was too good not to share. The below video shows a scene being captured using a GoPro Hero 3 Black edition on a 5-second timelapse setting mounted on a DJI Phantom drone and processed with Photo on ReCap 360 before being used as a basis for a conceptual model created in Revit 2014. Phew.




This is very much a proof-of-concept rather than a genuine project – no architect was involved, for instance – but it does a good job of showing the possibilities of this kind of workflow.

April 03, 2014

An update to Project Memento

I’ve talked about this tool – which was developed by my good friend and colleague Murali Pappoppula and his team – a number of times in the past. I’m really enjoying seeing it mature, over time.

This latest version has the capability to send photos directly to the Photo on ReCap 360 web-service, letting you pull the mesh down directly into Memento. Presumably it makes use of the new API that we have a number of ADN members piloting, right now.

Here’s a demo of the capabilities, taking you through from photo import to 3D printing:




And here’s a more detailed description of the improvements in this release (you need an Autodesk account to log in to the Beta site to access this, as it's currently an Autodesk Labs technology preview). If you haven't already joined the project, you can sign up here. And once you've signed up, go to Resources to download the latest update.

Give it a try and let us know what you think!

10 Random Posts