January 2015

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



Twitter





January 30, 2015

Using NFC to launch your Google Cardboard app

When I first got my Google Cardboard toolkit – in my case the DODOcase VR Toolkit V1 – I was fascinated by the NFC tag that came with it. I hadn’t really played with NFC, prior to that, and only had a very vague idea of what it was about.

It turns out that NFC is this important new(ish) technology that’s enabling all kinds of local (hence “near-field”) communications. Especially, it seems, in the area of mobile payments with technologies such as Apple Pay. Anyway, my first exposure to NFC was via this business card-sized sticker that came in the VR Toolkit.

Here’s that sticker held up to the light, so you can see its integrated circuit:

The DODOcase NFC sticker

As a technology NFC relies on some underlying RFID standards. Which is great because it allows me to digress and talk about how RFID was used recently at Autodesk University 2014. :-)

Here’s a picture of my AU2014 badge, held up to the same light as the above NFC chip:

My AU 2014 badge, held up to the light

Between the AU logo and my name, you’ll see another printed circuit. This was an RFID tag that allowed the event organizers to track the movement of people between the various AU sessions. People were able to opt out of this at registration, apparently, although I wasn’t aware it was possible until the topic came up during my SensorTag class (where we had a really good discussion in the room about possible uses for sensor technology). I was aware it was happening, though, and I even signed up for access to the internal dashboard.

I can tell you, for instance, that Wednesday’s exhibit hall was the best attended single event of the conference, followed closely by the opening keynote. And that 24% fewer people showed up to breakfast on Wednesday than on Tuesday. And 16% fewer on Thursday than Wednesday. Ah, big data. :-)

Anyway, let’s get back on track. The NFC tag in Google Cardboard is intended to launch an appropriate app on your (Android) device when its back comes into contact with the tag. This usually happens when you either insert the phone into the cardboard holder or you close the back flap.

These NFC tags are typically re-writable, which gives some interesting possibilities for application developers: you can very easily customize the behaviour of the DODOcase’s NFC tag, having it launch your own app or web-page when it comes in contact with someone’s phone.

The Google Play Store has a number of NFC-related apps, allowing you to read and write NFC tags. The first one I used – when I only wanted to write tags to launch web-pages – was NFC Writer by Tagstand. The tool I recommend now – assuming you want to both read and write all kinds of NFC tags – is NFC Tools.

Here’s what NFC Tools said when I used it against the standard DODOcase tag (I’ve merged a couple of screenshots to get all the data on one view):

NFC Tools reading our tag

The main items of interest are way at the bottom in Records 0 and 1. These contain the information that causes – when the tag touches the back of the phone when it isn’t in tag reading mode – Android to bring up the Google Play Store with the option to install the DODOcase app:

DODOcase app on the Play Store

If the app is installed already, it’ll simply launch, of course.


DODOcase VR Store

So how can we get another app to load, instead of this one? Let’s take a look at how NFC Tools can be used to have a specific web-page or app launched when it touches your device.

We’ll start with a web-page for which we need a URL record placed on the tag. Here’s how to have http://autode.sk/gcbv launch when the tag touches your device:


Using NFC Tools to write a custom URL tag

The problem I had with this approach on my phone was that it would launch the page repeatedly, presumably when the contact between the phone and the tag was interrupted for some reason. This apparently doesn’t happen with all devices, but it was certainly enough for me to give up on NFC when I first started working on web-based samples.

On creating a native Android app, however, NFC became more interesting again, because even if a re-launch occurs, it won’t create a new instance of the app. Here’s how you can write an AAR – an Android Application Record – to your NFC tag, causing the com.autodesk.a360cardboard app (specified in the manifest shown in the previous post) to either get installed or launched when the tag is detected. (Bear in mind that this app isn’t actually in the Google Play Store so it won’t attempt to install it.)


Using NFC Tools to write a custom AAR tag

When the tag comes into contact with your device, your custom app will launch:


NFC in action

That's it for this quick look at how you can customize NFC tags for use with Google Cardboard.

January 28, 2015

Integrating web-based VR samples into a native Google Cardboard app using the Cardboard SDK for Android

I mentioned this in last Friday’s post: after building an Android app to bring our web-based VR samples to Gear VR, it made sense to do the same for Google Cardboard. It made sense for 3 reasons:

  1. Most importantly, I wanted to see what the additional capabilities of the Android SDK would bring to the web-based VR samples, particularly around the magnetic trigger button.
  2. Until the Note 4 gets its Lollipop update in “early 2015” – and WebViews support WebGL – there isn’t much more to do with Gear VR. I’ve completed the plumbing but am waiting for the toilet to arrive. OK, bad analogy. :-) My Nexus 4, on the other hand, is running Android Lollipop, so at least that’s one way to see how the web samples work when loaded inside a WebView.
  3. The supported development environment for Google Cardboard, these days, is Android Studio. After wrestling with Eclipse to get my Gear VR built using the Oculus Mobile SDK, I was keen to give Android Studio a try.

The Cardboard SDK for Android is really easy to include in your Android Studio project. I started by cloning the primary sample from GitHub and imported that into Android Studio. Once I had it working on my device, I created a project from scratch, added the libs and copied across big chunks of the main activity.

Android Studio - a breath of fresh air after Eclipse

As we’re doing the stereo rendering of the model via an embedded web-page, we’re primarily using the Cardboard SDK for information on when the magnetic trigger on the side of the device gets pulled (something that we couldn’t get from HTML).

Google Cardboard and its single button

It would have been great to have had information regarding the speed or duration of the pull: you really only get told that it was pulled. But that’s fair enough… in the below Java file we make do with what we have by implementing some basic “double-click” logic:

Beyond that we need to make sure the AndroidManifest.xml had the appropriate entries…

<?xmlversion="1.0"encoding="utf-8"?>

<manifestxmlns:android="http://schemas.android.com/apk/res/android"

    package="com.autodesk.a360cardboard">

 

  <uses-permissionandroid:name="android.permission.CAMERA"/>

  <uses-permissionandroid:name="android.permission.NFC"/>

  <uses-permissionandroid:name="android.permission.VIBRATE"/>

  <uses-permissionandroid:name="android.permission.INTERNET"/>

 

  <application

      android:allowBackup="true"

      android:icon="@drawable/ic_launcher"

      android:label="@string/app_name"

      android:theme="@style/AppTheme">

    <activity

        android:name=".MainActivity"

        android:label="@string/app_name"

        android:launchMode="singleTask"

        android:screenOrientation="landscape"

        android:configChanges="orientation|keyboardHidden|keyboard">

      <intent-filter>

        <actionandroid:name="android.intent.action.MAIN" />

        <categoryandroid:name="android.intent.category.LAUNCHER"/>

        <category

          android:name="com.google.intent.category.CARDBOARD"/>

      </intent-filter>

      <intent-filter>

        <actionandroid:name="android.nfc.action.NDEF_DISCOVERED"/>

        <categoryandroid:name="android.intent.category.DEFAULT"/>

        <data

          android:mimeType="application/com.autodesk.a360cardboard"/>

      </intent-filter>

    </activity>

  </application>

</manifest>

The interface itself is fairly rudimentary: it uses the same HTML/JavaScript as the Gear VR sample, but advances the selection when a single pull on the magnetic trigger is detected. If a double-pull is detected, the selected model is opened. A trigger-pull from within the model will go back to the main list by reloading the page… to get the selection in the list back to what it was we send the number of “down” clicks we’ve counted since the app was loaded and pass that through. The JavaScript does a modulo remainder to determine the item to select. A little crude, but this avoids us having the JavaScript need to call back into our Java code.


The menu

Overall it works pretty well. The performance of the embedded WebView seems as good as with the web-based samples in Chrome for Android: they’ve done a good job of making sure the container itself doesn’t add overhead. Plus you get the benefits of being properly full-screen – without the need for some user input, as you have in HTML – and the “always on” is managed for you: no need to go and make sure your screen doesn’t turn off after 30 seconds (my Nexus has it set to 30 minutes, these days, which is good for VR but less good for normal usage).

The double-click takes a bit of practice to get right: at the beginning you single-click twice quite a bit, which means you have to loop back round to select the model you wanted (which is irritating). But it’s fairly useable, given the limited input options we have available.

Speaking of input options: I spent some time trying to work out how to enable speech recognition inside a WebView. This *should* be possible with Lollipop, as you can now pre-grant permissions for loaded web-pages as long as the app itself has compatible permissions granted by the user, but I wasn’t able to get it working. This is still at the bleeding edge, so I’m hopeful that will be enabled, at some point.

Next time we’re going to talk a little about NFC, and see how that can be used effectively with Google Cardboard to launch our custom Android app.

photo credit: othree via photopincc

January 26, 2015

Using environment variables inside AutoCAD file path options

Operating System-level environment variables are a handy way to reduce redundancy or to simplify providing support for per-user settings. (I’m sure they’re good for other things, too, but these are the ones that spring to my mind, at least. :-)

One thing I only discovered recently – and thanks to Tekno and Dieter for discussing this – is that you can use environment variables in a number of places in the file path settings accessed via AutoCAD’s OPTIONS command. The topic came up in the specific context of the TRUSTEDPATHS settings, but it seems to have more general support than that.

In this post we’re going to look at how you can test this capability, to see for ourselves how it might be used. For many of you the information in this post will be considered very basic, but it seemed worth covering, nonetheless.

Let’s take a module – available at C:\Program Files\MyCompany\MyProduct\MyApplication.dll – for which we have demand-loading entries set up in the Registry:

Demand loading settings

This module gets loaded as AutoCAD launches, but always brings up the “secure load” dialog:

Secure load request

The right way to avoid this warning is to add the application’s folder – C:\Program Files\MyCompany\MyProduct – to the TRUSTEDPATHS system variable. We could do this explicitly, of course, but let’s see how we might also do this with an environment variable.

Inside a Command Prompt, we’re going to type “set myproddir=C:\Program Files\MyCompany\MyProduct”. We can then test this by echoing – or using dir to check the contents of – the directory referenced in %myproddir%.

Setting an environment variable

This will only be set for processes that are children of this command-prompt, so after this we go to the AutoCAD Program Files folder and launch acad.exe from there. We could, of course, set this as a system-wide setting either via the Control Panel or using setx from the command-line, but for testing purposes we’d actually like to see what happens when the variable isn’t defined.

Once AutoCAD has come up, we can reference the environment variable using %myproddir% in the Trusted Paths setting:

Using the environment variable inside our options

If you close the OPTIONS dialog and reopen it, you’ll see the path has been resolved and displays as the actual location:

Reopening the OPTIONS dialog shows the resolved value

When you relaunch AutoCAD via our Command Prompt, you should no longer see the load warning for our custom module. However, if you launch AutoCAD from the usual shortcut – without having set the variable at the system level – you’ll see %myproddir% in the list as it remains unresolved. Which means we’re storing the name of the variable – rather than the resolved value – and attempting to resolve it on launch.

In case you want to access this system variable from the command-line, you can do so using (getenv):

Command: (getenv "myproddir")

"C:\\Program Files\\MyCompany\\MyProduct"

That’s it for this basic introduction to using environment variables. I’d be curious to hear from people on how they use them in their own applications. Please post a comment, if you have scenarios you’d like to share.

Update:

Thanks to Glenn Ryan for mentioning the possibility to use the REG_EXPAND_SZ type to allow environment variables in Registry keys to be expanded. I tried it with the above demand-loading keys and it works perfectly (assuming you’ve used the Control Panel or setx to give the myproddir variable a broader scope):

Modified demand-loading settings

This is really what you want, to be able to use the environment variable for your application’s path in multiple places whether inside or outside AutoCAD. Thanks, Glenn!

January 23, 2015

HoloLens for CAD?

I’m down with some kind of stomach bug, so any thoughts I might have had of writing code today are out the window. But luckily there’s plenty of juicy technology news buzzing around – especially in the AR space – that’s worth reporting on. Interestingly this isn’t the first time this has happened. I wonder if my sickness-addled brain has a tendency to gravitate towards “out there” technologies such as AR & VR (especially since reading John C. Wright’s The Golden Age trilogy – a must for anyone interested in this domain)?

I spent a fair amount of time working with VR, this week: I took the web-based VR samples and created a native wrapper for the Samsung Gear VR, and yesterday I decided that Google Cardboard deserved similar treatment, so I went and created a simple Android app using the Google Cardboard SDK and much of the same code. More on that next week.

I believe the immersive experience of VR is going to be really important for the entertainment industry and some limited use-cases for design professionals, such as walk- and fly-throughs of a purely virtual space (perhaps in the context of digital cities). It’ll also be useful for other kinds of design review that aren’t anchored to the real world. Of course it could also be the pre-cursor to us all living in shoeboxes, only interacting inside virtualities, but I’m not ready to go there just yet. :-)

In my opinion the “big ticket” item for our industry in the coming years – assuming various tricky problems get solved – is going to be AR. And interesting details on a couple of promising technologies in this space have emerged over the last few days.

The first is from Magic Leap, a secretive start-up that has recently received upwards of half a billion dollars in venture capital from the likes of Google. Oh, and they’ve recently appointed another sci-fi (although a lot of his work is termed as speculative rather science fiction) hero of mine as their Chief Futurist: Neal Stephenson, the author of the incredibly prescient novel Snow Crash.

More details regarding the Magic Leap technology have been inferred by the tech press from a recent 180-page patent filing. Inevitably, most of their focus is on entertainment, i.e. the living room:

Entertainment use case

But lip-service is at least paid to professional activities, which will now be possible in both the office and the living room. ;-)

Architecture use case

All very exciting!

Perhaps surprisingly – although perhaps not, if you happen to believe that Nadella’s Microsoft has turned a corner for whatever reason – the Magic Leap excitement was displaced very quickly on the AR front pages by the Windows 10 (and related) announcements. Yes, Windows 10 will be a free upgrade, and Microsoft is marketing Surface Hub as a huge, interactive workspace (which I have no doubt will be very cool, especially for people who have to collaborate with teams in other sites, as I do), but the main bit of news from the recent announcement was another AR-related technology, Microsoft HoloLens.

HoloLens

So far this looks very, very impressive. I know it’s a marketing video, but this just blows me away:




This video provides some additional, helpful context, in case:




When looking into into the announcement – and I fully admit that I haven’t yet found time to sit through the 2+ hour recorded webcast – I came across this photo in an article:

HoloLens and AutoCAD

I do believe that’s AutoCAD! :-) Now this isn’t a frame from the launch video, so I can only assume they decided to create a more direct link between the modeling environment and the hologram, showing it in 3D in both places. But nonetheless it’s indicative of the way people are thinking about this technology: it definitely has widespread implications for the design industry.

Now I’m not privy to any strategic discussions between Autodesk and Microsoft about HoloLens, but I’m certainly excited about the potential. Windows 10 will apparently ship with HoloLens APIs and capabilities… there’s even been some mention of Oculus and Magic Leap being two possible devices that might be hooked into the underlying Windows Holographic infrastructure.

I’d be very curious to see what can be done with this technology, and also how the battle for AR-mindshare plays out from here.

What do you all think? Excited? Depressed? Too early to say? Post a comment!

HoloLens photo credit: Microsoft Sweden via photopin cc

January 21, 2015

Building a Gear VR app using the Oculus Mobile SDK

Today we’re following on from last week’s post introducing this project where we want to convert the Google Cardboard A360 samples to work in a more integrated manner with the Samsung Gear VR.

The main purpose of the project is to see how we can hook up the existing, web-based samples to take advantage of the Gear VR’s hardware. We definitely don’t want to re-implement any of the visualization stack to be native; if we can use UI hardware events to control the web-based samples in a meaningful way, that’ll be just fine for this proof-of-concept.

It took some work but I was able to get the Oculus software running on the Note 4 inside Gear VR. I had to downgrade the software for it to work, though – and to a version that has some quirks – but it’s definitely usable. And it’s impressive: the Oculus team have done a great job implementing a nausea-free experience. You can view 360-degree videos and panoramic photos, and even watch a movie while sitting in a virtual cinema. Very neat!

The next step, for this project to work, was to build an Android app using the Oculus Mobile SDK. As mentioned last time, until the Note 4 device I’m using runs Android Lollipop, I’m not actually going to be able to effectively load a page containing the A360 viewer – which is WebGL-based – inside a WebView control, but I could at least work on the UI plumbing in the meantime.

It took me about a day of work to get the initial project building properly. I had to install a lot of software, including…

  • JDK – the Java SE Development Kit 8
  • Eclipse Juno (I first tried Indigo, but that didn’t work, and it seems Android Studio isn’t yet supported)
  • ADT Plugin for Eclipse (this is no longer easy to get hold of, now that Android Studio is the supported Android development IDE)
  • Android NDK (we’re not really using native C/C++ code in our sample, but we’re basing our project in a native – rather than Unity-based – sample
  • Android 4.4.2 SDK (API 19)

I then had to create a basic project using the VrTemplate project: there’s a handy script to stamp out versions of this template project with your own company and app name. Again, while this has a native component to it, we’re going to avoid using C/C++ code unless we really have to.

There were quite a few dead-ends I followed to get to this point. Getting an .apk built ended up being quite an achievement: getting it to install and load was another one…

I eventually managed to get the app to launch on the Note 4 via USB debugging, but clearly relying on a USB cable to deploy the app meant it couldn’t be mounted in the Gear VR headset at the same time. I could use this approach to work out how to get input from the Bluetooth gamepad, at least, but it was only going to get me so far.

One important step was to generate a device-specific signature file, but in the end it’s this forum thread that got me to the point where I can effectively deploy and run the app inside the Gear VR headset. I needed to enable wifi debugging for adb – which allowed me to get freedom from the USB cable – but also to make some changes to the application’s manifest to make sure it had the “vr_only” flag set (the docs stated that “vr_dual” was needed for testing as a normal Android app or as a VR app, but in reality “dual” really ended up meaning “vanilla Android only”. Setting “vr_only” meant that launching the app brought it up directly inside the VR environment. Perfect!

Now for a look at what’s now working, and where the app needs to go…

I modified the main activity to include a WebView control which then gets pointed at an updated set of samples for Gear VR. This version of the samples has some cosmetic differences: rather than having a single menu of models to pick from, this version has it duplicated, one for each eye. I’ve also adjusted the colours to make the page less visually jarring when viewed in an immersive environment.

Now for the main point of the post… I was able to hook up the UI events provided by the Oculus Mobile and Android SDKs – whether from the Bluetooth gamepad or from the touchpad on the side of the headset – and wire these into the HTML samples. Basically we call JavaScript functions in the WebView whenever we need to, and this controls the HTML page.

Here’s a quick video of the gamepad controlling the UI, as a “for instance”.




Here’s the current state of the main Java file in the sample, showing how the WebView is created and how the Gear VR hardware is able to communicate with it.

The next step is to enable the use of the controls once viewing a particular model: especially zoom and explode. But at this stage that’s a relatively simple problem to solve: the plumbing should now all be in place for this to happen.

At some point I’d like to look at changing the model-selection page away from HTML, integrating it more tightly with the Oculus menuing system (which is much easier on the eyes… having a menu that stays fixed in 3D space – at least as far as your brain is concerned – is much easier to look at than one that follows your eyes). But that would involve some duplication: right now having the models enumerated only in HTML is much simpler to update.

photo credit: TechStage via photopin cc

January 16, 2015

Modifying the contents of an AutoCAD xref using .NET

An interesting query came into my inbox, last week. A member of one of our internal product teams was looking to programmatically modify the contents of an external reference file. She was using the code in this helpful DevBlog post, but was running into issues. She was using WblockCloneObjects() to copy a block definition across from a separate drawing into a particular xref, but found some strange behaviour.

In this post I’m going to show the steps we ended up following to make this work. We’re going to implement a slightly different scenario, where we modify an external reference to load a linetype and apply that to all the circles in its modelspace.

The application is split into two separate commands: our CRX command (for CReate Xref… yes, I know this name’s a bit confusing) will create an external drawing file – consisting of a circle with a polyline boundary around it – and reference it in the current drawing. There’s nothing very special about this command: we just create an external Database, save it to disk, and then run the standard ATTACH command via ed.Command().

The second command is called CHX (for CHange Xref) and this is the more interesting one: it attempts to edit any external reference found in modelspace, manipulating each one to have all its circles given the “dashed” linetype. If that linetype isn’t loaded it’ll go ahead and load it.

Here are the two commands in action:


Modify external reference



The main “trick” we had to resolve when using WblockCloneObjects() was to set the WorkingDatabase to be that of the xref. This step wasn’t needed for loading linetypes, but I’ve left the code commented out for setting the WorkingDatabase appropriately: you may well need to uncomment it for your own more complex operations.

The other important step was to check whether the xref’s underlying file is accessible for write: if the drawing is open in the editor or is read-only on disk (unlikely in our scenario, as we’re creating it) then the XrefFileLock.LockFile() operation will throw an exception (but also keep a file lock in memory which will throw another exception when eventually finalized… not good). So we need to preempt the exception, avoiding the conditions under which it would be thrown.

We do this using this approach to check whether the file is in use, extended to also detect whether the file is read-only on disk. It can be extended with additional file access exceptions, if the two that I’ve included don’t end up covering the various scenarios.

Here’s the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using System.IO;

using System;

 

namespace XrefManipulation

{

  public class Commands

  {

    const string xloc = @"c:\temp\xref.dwg";

 

    [CommandMethod("CRX")]

    public void CreateXref()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

 

      var ed = doc.Editor;

 

      // Create our Database with the default dictionaries and

      // symbol tables

 

      using (var db = new Database(true, true))

      {

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

        {

          // We'll add entities to its modelspace

 

          var bt =

            (BlockTable)tr.GetObject(

              db.BlockTableId, OpenMode.ForRead

            );

          var ms =

            (BlockTableRecord)tr.GetObject(

              bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite

            );

 

          // Create a Circle inside a Polyline boundary

 

          var c = new Circle(Point3d.Origin, Vector3d.ZAxis, 1.0);

 

          ms.AppendEntity(c);

          tr.AddNewlyCreatedDBObject(c, true);

 

          var p = new Polyline(4);

          p.AddVertexAt(0, new Point2d(-1, -1), 0, 0, 0);

          p.AddVertexAt(1, new Point2d(-1, 1), 0, 0, 0);

          p.AddVertexAt(2, new Point2d(1, 1), 0, 0, 0);

          p.AddVertexAt(3, new Point2d(1, -1), 0, 0, 0);

          p.Closed = true;

 

          ms.AppendEntity(p);

          tr.AddNewlyCreatedDBObject(p, true);

 

          tr.Commit();

        }

 

        // We're going to save our file in the specified location,

        // after erasing the file if it already exists

 

        if (File.Exists(xloc))

        {

          try

          {

            File.Delete(xloc);

          }

          catch (System.Exception ex)

          {

            if (

              ex is IOException || ex is UnauthorizedAccessException

            )

            {

              ed.WriteMessage(

                "\nUnable to erase existing reference file. " +

                "It may be open in the editor or read-only."

              );

              return;

            }

            throw;

          }

        }

        db.SaveAs(xloc, DwgVersion.Current);

      }

 

      // The simplest way to attach the xref is via a command

 

      ed.Command("_.-ATTACH", xloc, "_A", "25,12.5,0", 5, 5, 0);

    }

 

    [CommandMethod("CHX")]

    public void ChangeXref()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

 

      var ed = doc.Editor;

      var db = doc.Database;

 

      // Get the database associated with each xref in the

      // drawing and change all of its circles to be dashed

 

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

      {

        var bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId, OpenMode.ForRead

          );

        var ms =

          (BlockTableRecord)tr.GetObject(

            bt[BlockTableRecord.ModelSpace], OpenMode.ForRead

          );

 

        // Loop through the contents of the modelspace

 

        foreach (var id in ms)

        {

          // We only care about BlockReferences

 

          var br =

            tr.GetObject(id, OpenMode.ForRead) as BlockReference;

          if (br != null)

          {

            // Check whether the associated BlockTableRecord is

            // an external reference

 

            var bd =

              (BlockTableRecord)tr.GetObject(

                br.BlockTableRecord, OpenMode.ForRead

              );

            if (bd.IsFromExternalReference)

            {

              // If so, get its Database and call the function

              // to change the linetype of its Circles

 

              var xdb = bd.GetXrefDatabase(false);

              if (xdb != null)

              {

                // Lock the xref database

 

                if (

                  IsFileLockedOrReadOnly(new FileInfo(xdb.Filename))

                )

                {

                  ed.WriteMessage(

                    "\nUnable to modify the external reference. " +

                    "It may be open in the editor or read-only."

                  );

                }

                else

                {

                  using (

                    var xf = XrefFileLock.LockFile(xdb.XrefBlockId)

                  )

                  {

                    // Make sure the original symbols are loaded

 

                    xdb.RestoreOriginalXrefSymbols();

 

                    // Depending on the operation you're performing,

                    // you may need to set the WorkingDatabase to

                    // be that of the Xref

 

                    //HostApplicationServices.WorkingDatabase = xdb;

 

                    ChangeEntityLinetype(

                      xdb, typeof(Circle), "DASHED"

                    );

 

                    // And then set things back, afterwards

 

                    //HostApplicationServices.WorkingDatabase = db;

 

                    xdb.RestoreForwardingXrefSymbols();

                  }

                }

              }

            }

          }

        }

        tr.Commit();

      }

    }

 

    internal virtual bool IsFileLockedOrReadOnly(FileInfo fi)

    {

      FileStream fs = null;

      try

      {

        fs =

          fi.Open(

            FileMode.Open, FileAccess.ReadWrite, FileShare.None

          );

      }

      catch (System.Exception ex)

      {

        if (ex is IOException || ex is UnauthorizedAccessException)

        {

          return true;

        }

        throw;

      }

      finally

      {

        if (fs != null)

          fs.Close();

      }

 

      // File is accessible

 

      return false;

    }

 

    // Change all the entities of the specified type in a Database

    // to the specified linetype

 

    private void ChangeEntityLinetype(

      Database db, System.Type t, string ltname

    )

    {

      using (

        var tr = db.TransactionManager.StartTransaction()

      )

      {

        // Check whether the specified linetype is already in the

        // specified Database

 

        var lt =

          (LinetypeTable)tr.GetObject(

            db.LinetypeTableId, OpenMode.ForRead

          );

        if (!lt.Has(ltname))

        {

          // If not, load it from acad.lin

 

          string ltpath =

            HostApplicationServices.Current.FindFile(

              "acad.lin", db, FindFileHint.Default

            );

          db.LoadLineTypeFile(ltname, ltpath);

        }

 

        // Now go through and look for entities of the specified

        // class, changing each of their linetypes

 

        var bt =

          (BlockTable)tr.GetObject(

            db.BlockTableId, OpenMode.ForRead

          );

        var ms =

          (BlockTableRecord)tr.GetObject(

            bt[BlockTableRecord.ModelSpace],

            OpenMode.ForRead

          );

 

        // Loop through the modelspace

 

        foreach (var id in ms)

        {

          // Get each entity

 

          var ent = tr.GetObject(id, OpenMode.ForRead) as Entity;

          if (ent != null)

          {

            // Check its type against the one specified

 

            var et = ent.GetType();

            if (et == t || et.IsSubclassOf(t))

            {

              // In the case of a match, change the linetype

 

              ent.UpgradeOpen();

              ent.Linetype = ltname;

            }

          }

        }

        tr.Commit();

      }

    }

  }

}

This technique is really useful: I can imagine lots of scenarios where in-place xref modification could be used to develop a valuable application feature. If you agree and have a particular scenario you think is worth sharing – or perhaps would like to see some sample code developed for – please post a comment!

January 15, 2015

A360 viewer & Samsung Gear VR

Back in October, I had a lot of fun developing some Virtual Reality samples using Autodesk’s View & Data API. The samples instanced the A360 viewer component twice in a web-page and controlled the views on these two instances – maintaining a stereoscopic effect – as the mobile viewing device changed orientation.

Here’s a video we saw in a previous post to give a sense for how these demos work…




The original samples were developed for Google Cardboard – which many of you will have received at the recent DevDays events around the world – but they’re just as applicable for other mobile VR platforms.

One such platform that’s been getting a lot of interest over the last couple of months is the Samsung Gear VR. This is similar in concept to Google Cardboard, but goes way above and beyond in terms of comfort and visual quality. Gear VR currently works with only one model of smartphone, the Galaxy Note 4, although you can get a sense for the quality of the optics by holding other devices in place.

The ADN team has bought at least one of these to play with. Philippe Leefsma had one shipped across to the Neuchâtel office, but then went on vacation for the best part of January. I hate to see toys going unplayed with, so with Philippe’s permission I cracked the box and got stuck in. :-)

Geared up

It’s very important to note that the samples we’ve developed, so far, are for web-based VR: they make use of Three.js and WebGL via the View & Data API’s viewing stack to create a VR experience in the browser. This has certain benefits in terms of portability, of course – the samples work well on certain iPhones, although interestingly seem to have issues we so far haven’t been able to resolve on the iPhone 6 – but there are downsides in terms of native device integration: for Google Cardboard this only means you don’t get access to the information from the magnet-based switch on the side of the headset (as that’s only available in the Android-native Google Cardboard SDK) but for Gear VR the impact is more pronounced.

Gear VR has a micro-USB plug that connects the headset with the Note 4 when you mount it. This allows VR applications running on the Note 4 to use the Oculus Mobile SDK (yes, the Gear VR software was developed in conjunction with Oculus) to gain access to some additional capabilities. Rather than a simple toggle switch, Gear VR has volume & back buttons as well as a separate gamepad with which it connects via Bluetooth. There’s also a proximity sensor so that the device can turn off when the headset is taken off (at least that’s what I think it does).

I’d like to use to the SDK to add some additional capabilities to the web-based samples, at some point, but right now there are a few things getting in the way. Firstly, at the time of writing the Note 4 is still running Android KitKat (4.4.4), while the Gear VR software stack is all based on Lollipop (5.x). [It turns out this isn’t true: it’s just that not all Note 4 devices have a compatible chipset & firmware. More on this at the end of the post.] So even plugging the phone into the Gear VR device tells me I need to install the software… and then trying to do so results in this message.


Coming soon


Secondly, Android’s WebView component apparently doesn’t support WebGL in versions prior to Lollipop, either. This wouldn’t necessarily be an issue if we were using a native viewer component – which would no doubt give better performance/higher framerates – but doesn’t make sense for a prototype implementation. Better to take advantage of the existing web-based viewer, building a native wrapper around it, for now.

So really we’re stuck with trying out the existing web-based samples with Gear VR. These actually work surprisingly well, although I had to jump through some hoops to get them working.

The initial hoop related to WebGL support. After finding out that the samples didn’t load – and then using USB debugging to determine that WebGL wasn’t being found – I worked out that I could check on the WebGL status of the device by using chrome://gpu and then override the setting by using chrome://flags, as per the below animation (I think it’s only missing a “re-open the browser” step):


Chrome flags


The second hoop was more physical: I had to mount the Note 4 in the device without inserting the micro-USB plug. This turned out to be easy enough to do: it seems to be designed for the plug to flap up and allow the phone to be mounted – but remain disconnected – underneath.

One final hack – rather than a hoop, in this case – was to tape across the speaker on the back. With the Nexus the microphone beep is controlled by the system volume, but this isn’t the case with Samsung devices: and – as you will know if you’ve played the above video – the speech recognition sub-system does cause a beep at regular intervals. A physical block seems the only way to address this, right now.

In terms of the overall experience… the visual quality and field of view were excellent: you can also adjust the focal length, which is great for people who wear glasses. The fit is comfortable, and the level of immersion is much higher than with Google Cardboard.

Here’s a photo of my colleague Simon Freihart having fun with the demo:

Simon and the Gear VR

Ultimately, though, for the use-cases that interest me most – i.e. using the technology for design visualization, rather than for immersive games where you may stay for hours at a time – it may end up being somewhat redundant. The flexibility you get with Google Cardboard – the ability to use pretty much any smartphone – does make it very valuable, and the quality is probably “good enough”.

Which leads me to the next big debate. Is the Gear VR experience good enough to make me want to buy a Note 4 as my next smartphone? I’m a big fan of stock Android, à la Nexus line of smartphones & tablets… I really don’t like the Samsung software layer. Maybe the native experience with the Oculus Mobile SDK will blow me away, when I get to try it, but for now I’m not quite feeling compelled to take the plunge and order a Note 4 as my primary device (I suspect that’ll be the Nexus 6, but we’ll see). If I can persuade someone to buy it for me as a secondary device, that’ll be a-whole-nother story, of course. :-)

Update:

It turns out it’s possible to run Gear VR with an KitKat-fuelled Note 4. I may just have to upgrade (or downgrade?) the firmware for it to work. The Over-The-Air upgrade process certainly doesn’t recognise an update as pending… will post another update if/when I get it working.

Update 2:

Interestingly I have (or rather Philippe has ;-) an unlocked Asia-Pacific Note 4 with the Exynos (rather than Snapdragon) processor. This page allowed me to install an older version of the Oculus runtime that supports this CPU. This should keep me going until a properly compatible firmware is available. Many thanks to HomerS66 for helping figure this all out!

January 14, 2015

Last few days to apply for the Autodesk Cloud Accelerator

I mentioned this event – an excellent opportunity to kickstart your web or mobile application development efforts – late last year.

The original submission deadline was last week, but this has been extended to January 17 – the end of this week! If you can put together a proposal (which shouldn’t be more than 1,000 words) by then, you may still be able to participate.

As a reminder, Autodesk is hosting the 2-week workshop in our downtown San Francisco offices from March 9-20, and will pay the hotel costs for 1-2 people per company. Get more information here.

January 12, 2015

Selecting a linetype and applying it to a set of AutoCAD entities using .NET

I received this request from Mateusz Andrzejczak, over the weekend:

I have problem with LineTypeDialog. Your part of the code is working perfectly, but i have problem with modifying the values. I have a SelectionSet that holds all object that are selected with using a filter. I want to use LineTypeDialog to select linetype and then accept so all the object in selection set will change to selected linetype. I'm working with this for a few hours and it's not working. Any tip for me?

The question related to this old post. I started by sending Mateusz a link to this follow-up post, which shows how to display various AutoCAD dialogs and then apply the chosen properties to a selected entity.

But then I thought about it, and decided to go ahead and implement Mateusz’s request, showing a couple of additional “nice-to-have” features beyond just dealing with a selection set:

  1. Make sure the command works with the pickfirst selection set (so it can be called using the noun-verb interaction paradigm)
  2. Check the selection set to see whether the selected entities have the same linetype ID: if so, select that as the default in the dialog

Nothing really earth-shattering, but it does show the use of the linetype dialog in a somewhat more realistic scenario.

Here’s the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Windows;

 

namespace EntityProperties

{

  public class Commands

  {

    [CommandMethod("SLT", CommandFlags.UsePickSet)]

    public void SetLineType()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (doc == null)

        return;

 

      var ed = doc.Editor;

 

      // Get the pickfirst selection set or ask the user to

      // select some entities

 

      var psr = ed.GetSelection();

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

        return;

 

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

 

      {

        // Get the IDs of the selected objects

 

        var ids = psr.Value.GetObjectIds();

 

        // Loop through in read-only mode, checking whether the

        // selected entities have the same linetype

        // (if so, it'll be set in ltId, otherwise different will

        // be true)

 

        var ltId = ObjectId.Null;

        bool different = false;

 

        foreach (ObjectId id in ids)

        {

          // Get the entity for read

 

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

 

          // On the first iteration we store the linetype Id

 

          if (ltId == ObjectId.Null)

            ltId = ent.LinetypeId;

          else

          {

            // On subsequent iterations we check against the

            // first one and set different to be true if they're

            // not the same

 

            if (ltId != ent.LinetypeId)

            {

              different = true;

              break;

            }

          }

        }

 

        // Now we can display our linetype dialog with the common

        // linetype selected (if they have the same one)

 

        var ltd = new LinetypeDialog();

        if (!different)

          ltd.Linetype = ltId;

        var dr = ltd.ShowDialog();

        if (dr != System.Windows.Forms.DialogResult.OK)

          return; // We might also commit before returning

 

        // Assuming we have a different linetype selected

        // (or the entities in the selected have different

        // linetypes to start with) then we'll loop through

        // to set the new linetype

 

        if (different || ltId != ltd.Linetype)

        {

          foreach (ObjectId id in ids)

          {

            // This time we need write access

 

            var ent = (Entity)tr.GetObject(id, OpenMode.ForWrite);

 

            // Set the linetype if it's not the same

 

            if (ent.LinetypeId != ltd.Linetype)

              ent.LinetypeId = ltd.Linetype;

          }

        }

 

        // Finally we commit the transaction

 

        tr.Commit();

      }

    }

  }

}

When you run the SLT command, you’ll see the linetype dialog has a default entry highlighted in the case where all the selected entities have the same linetype:

Selecting a linetype for a set of AutoCAD entities

January 08, 2015

Recreating the Star Wars opening crawl in AutoCAD using F# – Part 4

Happy Friday! It’s time to unveil the completed Star Wars opening crawl inside AutoCAD…

After an intro and seeing various pieces implemented, in today’s post we’re going to add the crawl text and animate its movement into the distance.

  1. The initial blue text
  2. The theme music
  3. The star field
  4. The disappearing Star Wars logo
  5. The crawling text

As the surprise “bonus” item 6, I decided to add a planet and – at the end of the crawl – shift the view downwards to show its surface: an effect I’ve seen in the opening crawl for at least one of the films (I forget which). The effect took me some work to get it looking right. If you run the code, try orbiting the view to see the relative positions… I ended up having to both pan and orbit to get effect I wanted. The planet itself could look better: I would have liked to give the surface some texture and even a translucent atmosphere, and some of the stars appear in the foreground… but you have to stop somewhere.

It’s in this post that we’ll finally use the Star Wars API to get the opening crawl text for the various episodes. We’ll ask the user to choose the episode to display, and get the text associated with that (although as the API understandably lists the films in order of cinematic release rather than the internal chronology, we get the data for all the films and select the text for the one with the matching episode number).

There isn’t very much to the whole thing: we create the crawl text based on what’s provided by the API, adding in some text for the episode number (in roman numerals) and title. The effect is OK: I struggled for some time to get the exact angle as used in the films, but didn’t quite manage it. Once again the speed of the crawl is likely to vary per-system – I haven’t taken the time or effort to get this to synchronise the timing perfectly for different hardware profiles.

Without further ado, here are videos of all six opening crawls. Yes, during a lull in my day I went ahead and captured all of them. The only difference between them is the crawl text contents: feel free to choose your favourite episode and just watch that. :-)

[Apologies for any advertising that pops up as you're watching: YouTube cleverly noticed the use of the (copyrighted) Star Wars theme music in the video, so I acknowledged the 3rd party content, giving the owner the right to remunerate people watching the videos. Fair enough, considering (I'm not using YouTube ads to generate revenue for myself). As long as they don't decide to pull the videos, which could still happen.]


Episode I: The Phantom Menace




Episode II: Attack of the Clones




Episode III: Revenge of the Sith




Episode IV: A New Hope




Episode V: The Empire Strikes Back




Episode VI: Return of the Jedi




Episode VII: The Force Awakens

Star Wars Episode VII - The Force Awakens

Here’s the final F# code. The code for accessing SWAPI is really greatly simplified thanks to the use of F#’s JSON Type Provider (although in a real application I’d have added code to catch the exception thrown when the service is inaccessible).

module StarWars.Crawler

 

open Autodesk.AutoCAD.ApplicationServices

open Autodesk.AutoCAD.ApplicationServices.Core

open Autodesk.AutoCAD.DatabaseServices

open Autodesk.AutoCAD.EditorInput

open Autodesk.AutoCAD.Geometry

open Autodesk.AutoCAD.Runtime

open FSharp.Data

open System

open System.Windows.Media

 

// Our JSON Type Provider providing access to SWAPI

 

type Film = JsonProvider<"http://swapi.co/api/films">

 

// The intro music MP3 file

 

let mp3 =

  "http://s.cdpn.io/1202/Star_Wars_original_opening_crawl_1977.mp3"

 

// The layers we want to create as a list of (name, (r, g, b))

 

let layers =

  [

    ("Stars", (255, 255, 255));

    ("Intro", (75, 213, 238));

    ("Crawl", (229, 177, 58));

    ("Planet", (238, 232, 170))

  ]

 

// Create layers based on the provided names and colour values

// (only creates layers if they don't already exist... could be

// updated to make sure the layers are on/thawed and have the

// right colour values)

 

let createLayers (tr:Transaction) (db:Database) =

  let lt =

    tr.GetObject(db.LayerTableId, OpenMode.ForWrite) :?> LayerTable

  layers |>

  List.iter (fun (name, (r, g, b)) ->

    if not(lt.Has(name)) then

      let lay = new LayerTableRecord()

      lay.Color <-

        Autodesk.AutoCAD.Colors.Color.FromRgb(byte r, byte g, byte b)

      lay.Name <- name

      lt.Add(lay) |> ignore

      tr.AddNewlyCreatedDBObject(lay, true)

    )

 

// Get a view by name

 

let getView (tr:Transaction) (db:Database) (name:string) =

  let vt =

    tr.GetObject(db.ViewTableId, OpenMode.ForRead) :?> ViewTable

  if vt.Has(name) then

    tr.GetObject(vt.[name], OpenMode.ForRead) :?> ViewTableRecord

  else

    null

 

// Add an entity to a block and a transaction

 

let addToDatabase (tr:Transaction) (btr:BlockTableRecord) o =

  btr.AppendEntity(o) |> ignore

  tr.AddNewlyCreatedDBObject(o, true)

 

// Flush the graphics for a particular document

 

let refresh (doc:Document) =

  doc.TransactionManager.QueueForGraphicsFlush()

  doc.TransactionManager.FlushGraphics()

 

// Transform between the Display and World Coordinate Systems

 

let dcs2wcs (vtr:AbstractViewTableRecord) =

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

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

  Matrix3d.PlaneToWorld(vtr.ViewDirection)

 

// Poll until a music file has downloaded fully

// (could sleep or use a callback to avoid this being too

// CPU-intensive, but hey)

 

let rec waitForComplete (mp:MediaPlayer) =

  if mp.DownloadProgress < 1. then

    System.Windows.Forms.Application.DoEvents()

    waitForComplete mp

 

// Poll until a specified delay has elapsed since start

// (could sleep or use a callback to avoid this being too

// CPU-intensive, but hey)

 

let rec waitForElapsed (start:DateTime) delay =

  let elapsed = DateTime.Now - start

  if elapsed.Seconds < delay then

    System.Windows.Forms.Application.DoEvents()

    waitForElapsed start delay

 

// Run operation f n times and then wait until

// a specified delay has elapsed since start

 

let rec performNTimesOrUntilElapsed (start:DateTime) delay f n =

  let elapsed = DateTime.Now - start

  if n > 0 || elapsed.Seconds < delay then

    if n > 0 then f()

    System.Windows.Forms.Application.DoEvents()

    performNTimesOrUntilElapsed start delay f (n-1)

 

// Get the roman numerals for an integer

 

let roman n =

  let numerals =

    [(1000, "M"); (900, "CM"); (500, "D"); (400, "CD"); (100, "C");

     (90, "XC"); (50, "L"); (40, "XL"); (10, "X"); (9, "IX");

     (5, "V"); (4, "IV"); (1, "I")]

  let rec acc (v, r) (m, s) =

    if (v < m) then (v, r) else acc (v-m, r+s) (m, s)

  List.fold acc (n, "") numerals |> snd

 

// Get info on all 6 Star Wars episodes

 

let allFilms =

  let films = Film.Load("http://swapi.co/api/films")

  films.Results

 

// Create the intro text as an MText object relative to the view

// (has a parameter to the function doesn't execute when loaded...

// also has hardcoded values that make it view-specific)

 

let createIntro _ =

 

  let mt = new MText()

  mt.Contents <-

    "{\\fFranklin Gothic Book|b0|i0|c0|p34;" +

    "A long time ago, in a galaxy far,\\Pfar away...}"

  mt.Layer <- "Intro"

  mt.TextHeight <- 0.5

  mt.Width <- 10.

  mt.Normal <- Vector3d.ZAxis

  mt.TransformBy(Matrix3d.Displacement(new Vector3d(1., 6., 0.)))

  mt

 

// Generate a quantity of randomly located stars... a list of (x,y)

// tuples where x and y are between 0 and 1. These will later

// get transformed into the relevant space (on the screen, etc.)

 

let locateStars quantity =

 

  // Create our random number generator

 

  let ran = new System.Random()

 

  // Note: _ is only used to make sure this function gets

  // executed when it is called... if we have no argument

  // it's a value that doesn't require repeated execution

 

  let randomPoint _ =

 

    // Get random values between 0 and 1 for our x and y coordinates

 

    (ran.NextDouble(), ran.NextDouble())

 

  // Local recursive function to create n stars at random

  // locations (in the plane of the screen)

 

  let rec randomStars n =

    match n with

    | 0 -> []

    | _ -> (randomPoint 0.) :: randomStars (n-1)

 

  // Create the specified number of stars at random locations

 

  randomStars quantity

 

// Take locations from 0-1 in X and Y and place them

// relative to the screen

 

let putOnScreen wid hgt dcs (x, y) =

 

  // We want to populate a space that's 2 screens high (so we

  // can pan/rotate downwards at the end of the crawl), hence

  // the additional multiplier on y

 

  let pt = new Point3d(wid * (x - 0.5), hgt * ((y * -1.5) + 0.5), 0.)

  pt.TransformBy(dcs)   

 

// Create a polyline from a vertex list

 

let polyFromVerts n w m lay verts =

  let pl = new Polyline(List.length verts)

  pl.Normal <- n

  List.iteri

    (fun i v ->

      pl.AddVertexAt(i, new Point2d(fst v, snd v), 0., w, w)

    )

    verts

  pl.Closed <- true

  pl.TransformBy(m)

  pl.Layer <- lay

  pl

 

// Create the Star Wars logo from a list of vertex lists

 

let createLogo n w (p:Point3d) =

  let verts =

    [

      [

        (3.44258, 3.55679214868466);

        (3.44258, 3.17553);

        (2.8459, 3.17553);

        (2.8459, 2.26434);

        (2.40686, 2.26434);

        (2.40686, 3.17612262061896);

        (1.75655462335279, 3.17429248415801);

        (1.72128333960472, 3.16669182023216);

        (1.70763551483587, 3.15445862216978);

        (1.70053119527792, 3.13782570874412);

        (1.69872472722736, 3.12280350074472);

        (1.69905468222644, 3.1191373340882);

        (1.70087251722259, 3.11283393221843);

        (1.70607593564196, 3.10180962306061);

        (1.71621799674036, 3.08484068174517);

        (1.73226620616926, 3.06151479594735);

        (1.75494342528422, 3.03081841447927);

        (1.78484748611009, 2.99191745820677);

        (1.82294826291299, 2.94341738705363);

        (1.87617804512918, 2.87460815638392);

        (1.92246977710667, 2.81233246534401);

        (1.95733932573181, 2.76278154528292);

        (1.97888966827266, 2.72730122581048);

        (1.99798396461003, 2.61953152517619);

        (1.97570333908842, 2.50537461239132);

        (1.91940141039899, 2.40109844384521);

        (1.83646768287547, 2.32393324739609);

        (1.80352751564867, 2.30404166777099);

        (1.77133382731068, 2.28892985436458);

        (1.73039688525943, 2.27813889539216);

        (1.6754267050528, 2.27134897179126);

        (1.59812878352077, 2.26745012799826);

        (1.48927961179153, 2.26536230567172);

        (1.33992701030606, 2.26450009692313);

        (1.14136357854396, 2.26434001353564);

        (1.04351, 2.26434);

        (0.353207, 2.26434);

        (0.353207, 2.68233);

        (1.40159102170787, 2.68233);

        (1.41748209051223, 2.69338144406387);

        (1.43267712171974, 2.70939240011938);

        (1.44055338604423, 2.73157466234716);

        (1.44119451950013, 2.7367666633397);

        (1.44105860097375, 2.73978455802721);

        (1.43997063735183, 2.74322220229697);

        (1.43439829934938, 2.75301581981413);

        (1.4213959120805, 2.77071589918082);

        (1.39979463633981, 2.7968603003749);

        (1.36874714824015, 2.83262747962015);

        (1.32697107584659, 2.87983734880692);

        (1.23683919774444, 2.98529715322583);

        (1.18339822046774, 3.06424778707422);

        (1.16026089514802, 3.13669377471704);

        (1.15987518361763, 3.21768150787052);

        (1.18779743410553, 3.32756187144587);

        (1.25599700588018, 3.43654449583423);

        (1.36395601807001, 3.52111523123897);

        (1.50483659505006, 3.55390286925382)

      ];

      [

        (4.39994870002626, 3.54702);

        (4.84743214991296, 2.25278501118727);

        (4.41662097329373, 2.25517027344357);

        (4.36118115868732, 2.43645696748207);

        (3.81392247714979, 2.4388933038331);

        (3.75959503424206, 2.26439);

        (3.32291291955518, 2.26439);

        (3.76562469596367, 3.54702)

      ];

      [

        (3.93947639266304, 2.78028246119739);

        (4.0036225829752, 2.96145971580626);

        (4.09333768797878, 3.21205266525835);

        (4.17869162137176, 2.9611015523335);

        (4.23989035006252, 2.78062648416092)

      ];

      [

        (5.54586280271403, 3.54702);

        (5.73207467832675, 3.54636901457996);

        (5.88112842531212, 3.54435138355133);

        (5.98711334518623, 3.54111056421452);

        (6.04631520398901, 3.53650379705603);

        (6.11453578628832, 3.51811946058964);

        (6.17810911285892, 3.48782673131194);

        (6.23583237180861, 3.44644129584782);

        (6.28596484458466, 3.39522327740142);

        (6.32595493380338, 3.3422331096743);

        (6.35270689747694, 3.29157143345469);

        (6.3680867123442, 3.23557047460095);

        (6.37643733628869, 3.16568660578167);

        (6.36992931993028, 3.05606170860937);

        (6.33210762885031, 2.95400067046692);

        (6.26513711406835, 2.86555263333169);

        (6.17366080953793, 2.79497351541438);

        (6.14309800665153, 2.7769730033941);

        (6.11700705528388, 2.76203194437172);

        (6.1127843266014, 2.7596756036369);

        (6.13885495853737, 2.73241774032095);

        (6.17040343063679, 2.70456002005355);

        (6.19335603450253, 2.68673568368009);

        (6.86633739559967, 2.67707780533635);

        (6.8742792314147, 2.26439);

        (6.43526528939969, 2.26439);

        (6.2418481178589, 2.26499101663952);

        (6.11292604672176, 2.26701660378081);

        (6.03406510582276, 2.27102207373412);

        (5.9876655077232, 2.2786416155789);

        (5.93407000168671, 2.30917788766821);

        (5.85931623340334, 2.36822655452807);

        (5.75524877978626, 2.46058692597491);

        (5.61622131796047, 2.59075075972907);

        (5.53661647932702, 2.66649674082322);

        (5.5380039627306, 2.26439);

        (5.0687, 2.26439);

        (5.0687, 3.54702)

      ];

      [

        (5.53061, 3.22193);

        (5.67562434528338, 3.22193);

        (5.76549694080276, 3.22143652653738);

        (5.8227611211413, 3.21969654059112);

        (5.85910049504742, 3.21603930873005);

        (5.8835168373915, 3.20907338608929);

        (5.91186718137154, 3.19493342317029);

        (5.93449705925347, 3.17520079608985);

        (5.9461460801979, 3.14851545023844);

        (5.94993368385409, 3.1219164675812);

        (5.95063789889132, 3.09471968284322);

        (5.94382358152697, 3.06635439430217);

        (5.92659250539508, 3.04172336065536);

        (5.90396433067426, 3.02224480067035);

        (5.89094323070014, 3.01257677365093);

        (5.85401438673261, 3.00939777579999);

        (5.81195751173521, 3.00827403940718);

        (5.75772390976083, 3.00788023741928);

        (5.69388269742043, 3.00778);

        (5.53061, 3.00778)

      ];

      [

        (1.57896862502186, 2.10589);

        (1.67880828853815, 1.81260822369826);

        (1.71379594628957, 1.71227377415791);

        (1.73527827819659, 1.65374851057995);

        (1.75920847744905, 1.72143678846549);

        (1.79238473443157, 1.81629031452661);

        (1.82529369636899, 1.9108498644361);

        (1.85381147382987, 1.99319569627039);

        (1.87388905179331, 2.05149770143778);

        (1.8912822178088, 2.10154);

        (2.32183990237932, 2.10154);

        (1.87988984918951, 0.813874);

        (1.58239936659396, 0.813874);

        (1.56171424001044, 0.873466415317032);

        (1.53295553769481, 0.956661589981072);

        (1.49226696124087, 1.07432847254541);

        (1.44570537030483, 1.20935203857834);

        (1.39914465363298, 1.34447313546997);

        (1.35858792560323, 1.46260098506068);

        (1.35731504835852, 1.46634287831389);

        (1.12278635463374, 0.823096);

        (0.828087008371704, 0.823096);

        (0.391580354628849, 2.10592925972778);

        (0.820152580844014, 2.10564013321748);

        (0.837989023926543, 2.05516759780397);

        (0.859154236649471, 1.99519000485767);

        (0.889137620919735, 1.91045548589063);

        (0.92363489102879, 1.81330725414169);

        (0.95817174136269, 1.71621235357281);

        (0.979127616594254, 1.65771496109079);

        (1.13684321996318, 2.10589)

      ];

      [

        (3.28904785776228, 2.11550813590997);

        (3.73761724891686, 0.824937551963215);

        (3.30662126941653, 0.822954990623427);

        (3.24971008910397, 1.00471601224713);

        (2.69993521628902, 1.00914873793685);

        (2.64593333868056, 0.827492143980233);

        (2.21818710344123, 0.831947254825196);

        (2.6611172170532, 2.11491182100224)

      ];

      [

        (2.83075227905127, 1.35366041729816);

        (2.87923452796852, 1.48620742282014);

        (2.91710514665256, 1.58972911414478);

        (2.98271654250203, 1.76897755710787);

        (3.04320129635346, 1.59028356566733);

        (3.05209063261682, 1.56402552642664);

        (3.06700400017455, 1.52004510187737);

        (3.0854341923654, 1.46582399091646);

        (3.12362427253239, 1.35375);

        (2.96565, 1.35375)

      ];

      [

        (6.86738701658834, 2.11055);

        (6.86636282954738, 1.71143316113769);

        (6.80968306747308, 1.71166033453278);

        (6.76879036498295, 1.71182022759786);

        (6.70799358315783, 1.71199016269997);

        (6.63307325593112, 1.7122001636159);

        (6.55001968254444, 1.71242014609799);

        (6.46487686643307, 1.71259008296755);

        (6.38353162001043, 1.71274);

        (6.3119, 1.71274);

        (6.27257783369629, 1.71274);

        (6.24205274248879, 1.71264056973548);

        (6.22660212251127, 1.7125107967416);

        (6.2251332275232, 1.71152567832907);

        (6.21826693670348, 1.69895889104225);

        (6.21368777582741, 1.67971131922799);

        (6.21435024322066, 1.66802861722527);

        (6.21739642079077, 1.6617633178101);

        (6.23123505854596, 1.64006984102296);

        (6.25133503104544, 1.61172470022028);

        (6.27506976405328, 1.58087731010094);

        (6.30343445446908, 1.54536194073766);

        (6.33613016667173, 1.50446982038211);

        (6.36882765268814, 1.46364795338937);

        (6.39759648916047, 1.42768942522637);

        (6.42698279823146, 1.39058529555372);

        (6.45061281453046, 1.35962111552111);

        (6.46946283661554, 1.33294897542795);

        (6.48427168722091, 1.30886051606526);

        (6.49558070778541, 1.2857968585775);

        (6.50373858922054, 1.26288773947885);

        (6.50912773260329, 1.23975035055556);

        (6.51327721344814, 1.21302571797147);

        (6.51175336049161, 1.12736501317236);

        (6.48652470110405, 1.04165809320606);

        (6.44008974336086, 0.963007688635264);

        (6.37622624858673, 0.897126531506992);

        (6.34136126462528, 0.869847315902196);

        (6.30454078761051, 0.848196296706673);

        (6.25735068769598, 0.832813348711309);

        (6.1946926284406, 0.82334703105736);

        (6.10791480816548, 0.817863341131013);

        (5.98694154482626, 0.815070019097485);

        (5.82230156071494, 0.814036130538472);

        (5.60480157525874, 0.813819020750331);

        (5.57579464995636, 0.813819);

        (5.31033615083933, 0.814524554163534);

        (5.1147019020476, 0.816483302483575);

        (4.987492484323, 0.819719154126808);

        (4.92211642121057, 0.824809172133531);

        (4.86255265351012, 0.851353630113647);

        (4.78599624454765, 0.907412194416292);

        (4.67949985067332, 1.00068616876809);

        (4.53106852156296, 1.14052267830742);

        (4.47120745637841, 1.19730089863496);

        (4.43722713582279, 1.22872889310876);

        (4.43479568627557, 0.813638441379537);

        (3.97790059619618, 0.815626482435268);

        (3.9754088950639, 2.09644);

        (4.85435350955452, 2.09644);

        (4.9524384120744, 2.08116590914619);

        (5.0421132756076, 2.04495127286902);

        (5.11968748422517, 1.9926567681356);

        (5.18396561975463, 1.92855365816238);

        (5.23369876113581, 1.8565584931861);

        (5.26760265295347, 1.78012486674058);

        (5.28395979085181, 1.70241419345942);

        (5.28004510702221, 1.62839637688364);

        (5.25918823660479, 1.55956085936412);

        (5.23129767989095, 1.49769758838801);

        (5.19287713035147, 1.44636773420326);

        (5.14201539837177, 1.39663698792505);

        (5.08439692602106, 1.35247865701708);

        (5.02763448373853, 1.32012290369814);

        (5.01926153493527, 1.31567062139799);

        (5.0201401696421, 1.31465755077974);

        (5.03635825437325, 1.29789855762624);

        (5.05404454395319, 1.28126513332677);

        (5.07123638095395, 1.26662725426366);

        (5.08545995358084, 1.25609066762876);

        (5.09155854789262, 1.25261446887105);

        (5.11520296200932, 1.24773262641588);

        (5.1839384665362, 1.2437210842384);

        (5.31499237416825, 1.24167909762829);

        (5.52681922465014, 1.24108996855417);

        (5.63722185856806, 1.24103002684722);

        (5.75066647981753, 1.24129939671563);

        (5.83424919549954, 1.24231918109318);

        (5.89196733024185, 1.24477834974135);

        (5.92703259711752, 1.248815584514);

        (5.94186413459469, 1.25312246895092);

        (5.94452720971008, 1.2550352046588);

        (5.94806585844389, 1.26045738811548);

        (5.95485974741274, 1.27515168783283);

        (5.95764005574495, 1.28273863090887);

        (5.95785068079404, 1.28531001171654);

        (5.95662977011935, 1.29060599364051);

        (5.95018212309273, 1.30349479459504);

        (5.9355344205503, 1.32495058423466);

        (5.9118829225398, 1.3549665197346);

        (5.87849452603907, 1.39425562054069);

        (5.83444545149862, 1.44431638015785);

        (5.73724255457614, 1.5576766597549);

        (5.68852920511552, 1.65032448217977);

        (5.6739520592985, 1.72315250284916);

        (5.67339629718887, 1.7776616505618);

        (5.67582917215106, 1.81532530917471);

        (5.6869349467792, 1.86440938045866);

        (5.70875633588307, 1.91894581413262);

        (5.74384006043308, 1.97499767954953);

        (5.79476342169442, 2.02753710966927);

        (5.86330983842433, 2.07103004485607);

        (5.95041130228872, 2.10023544319704);

        (6.0548074435266, 2.11055)

      ];

      [

        (4.4345937394936, 1.56212643206149);

        (4.43518042620646, 1.67044148437216);

        (4.43556787492364, 1.77298062965549);

        (4.59383378518243, 1.77217924888779);

        (4.64881620553342, 1.77178759329077);

        (4.70616920376935, 1.77085699790493);

        (4.75610194813188, 1.76893449843586);

        (4.79998632985362, 1.76368084421198);

        (4.84719213337829, 1.71746157785606);

        (4.8610886482744, 1.66244080023347);

        (4.85668777505293, 1.63727739146715);

        (4.84628131477829, 1.61325711967745);

        (4.82603724633321, 1.58950045930602);

        (4.79564620691826, 1.57081593809471);

        (4.76918190987008, 1.56332632934342);

        (4.73276899886416, 1.5595967363527);

        (4.67535969661275, 1.55779395371568);

        (4.58510256995686, 1.55724810481441);

        (4.52224299866422, 1.5581940498193)

      ]

    ]

 

  // Displace our vertices to the specified point and then

  // apply a hardcoded, view-specific scaling

 

  let m1 = Matrix3d.Displacement(p.GetAsVector())

  let m = m1 * Matrix3d.Scaling(1.4, p + new Vector3d(3.5,0.,0.))

  verts |> List.map (polyFromVerts n w m "Crawl")

 

// Format the "opening crawl" text appropriately for MText

 

let crawlText num (title:string) (text:string) =

  let lines =

    text.Split([|"\r\n"|], StringSplitOptions.None) |>

    Array.toList

  let rec convert (lns:string list) =

    match lns with

    | [] -> ""

    | (x::xs) ->

      let ln =

        if xs = [] || (xs <> [] && xs.Head = "") then

          "\\pql;" + x + "\\P"

        else

          "\\pqd;" + x + "\\P"

      ln + convert xs

 

  String.Format(

    "\\pxsm1.5,qc;{{\\fFranklin Gothic Demi|b0|i0|c0|p34;" +

    "Episode {0}\\P\\P\\fFranklin Gothic Medium Cond|b0|i0|c0|p34;" +

    "\\H1.3333x;\\W0.95;{1}\\P\\fFranklin Gothic Demi|b0|i0|c0|" +

    "p34;\\H0.74999x;\\W1;\\P\\pqd;\\H0.83334x;{2}}}",

    roman num, title.ToUpper(), convert lines

  )

 

// Create the crawl text object with the provided text contents

// (again with hardcoded values that are view-specific)

 

let createCrawlText text =

 

  // Create our MText object

 

  let mt = new MText()

 

  // Set its contents and other properties

 

  mt.Contents <- text   

  mt.Layer <- "Crawl"

  mt.TextHeight <- 0.3

  mt.Width <- 6.

  mt

 

// Move the crawl text through space in a number of increments

 

let moveThroughSpace (doc:Document) (ent:Entity) steps delay =

 

  // Record our start time, so we stop when delay has elapsed

 

  let start = DateTime.Now

 

  // Specify our displacement increment

 

  let mat = Matrix3d.Displacement(new Vector3d(0.,0.2,0.))

 

  // Define a local recursive function to move the object

  // n times (but to check for user escape and terminate, as

  // needed)

 

  let rec moveLoop n =

    ent.TransformBy(mat)

    refresh doc

    let elapsed = DateTime.Now - start

    if n > 0 &&

        not(

          (HostApplicationServices.Current.UserBreak() ||

            elapsed.TotalSeconds >= delay)

        ) then

          moveLoop (n-1)

 

  // Move the MText a number of times along the Y axis

 

  moveLoop steps

 

// Commands to recreate the open crawl experience for a selected

// Star Wars episode

 

[<CommandMethod("EPISODE")>]

let episode() =

 

  // Make sure the active document is valid before continuing

 

  let doc = Application.DocumentManager.MdiActiveDocument

  if doc <> null then

 

    let db = doc.Database

    let ed = doc.Editor

 

    // Ask for the episode number

 

    let pso = new PromptIntegerOptions("\nEnter episode")

    pso.LowerLimit <- 1

    pso.UpperLimit <- 6 // Change to 7 in December 2015... :-)

    let psr = ed.GetInteger(pso)

    if psr.Status = PromptStatus.OK then

 

      // Start our transaction and create the required layers

 

      use tr = doc.TransactionManager.StartTransaction()

      createLayers tr db

 

      // Get our special Initial and Crawl views

 

      let ivtr = getView tr db "Initial"

      let cvtr = getView tr db "Crawl"

 

      if ivtr = null || cvtr = null then

        ed.WriteMessage(

          "\nPlease load StarWarsCrawl.dwg before running command.")

 

      doc.TransactionManager.EnableGraphicsFlush(true)

      let btr =

        tr.GetObject(doc.Database.CurrentSpaceId, OpenMode.ForWrite)

          :?> BlockTableRecord

 

      // Set the initial view: this gives us higher quality text

 

      ed.SetCurrentView(ivtr)

 

      // First we create the intro text

 

      let intro = createIntro ()

      intro |> addToDatabase tr btr

 

      // Make sure the intro text is visible

 

      doc |> refresh

      ed.UpdateScreen()

 

      // We'll now perform a number of start-up tasks, while our

      // initial intro text is visible... we'll start vy recording

      // our start time, so we can synchronise our delay

 

      let start = DateTime.Now

 

      // Get our view's DCS matrix

 

      let dcs = dcs2wcs(cvtr)

 

      // Create our planet with hardcoded, view-specific values

      // (do it here as demand-loading ASM may take time)

 

      let planet = new Solid3d()

      planet.CreateSphere(3.)

      planet.Layer <- "Planet"

      let bot =

        Point3d.Origin.TransformBy(dcs) +

        new Vector3d(0.,-1.1,-3.2)

      planet.TransformBy(Matrix3d.Displacement(bot.GetAsVector()))

      planet |> addToDatabase tr btr

 

      // Create a host of stars at random screen positions

 

      locateStars 1000 |>

      List.iter

        (fun xy ->

          let p = putOnScreen cvtr.Width cvtr.Height dcs xy

          let dbp = new DBPoint(p)

          dbp.Layer <- "Stars"

          dbp |> addToDatabase tr btr)

 

      // Open the intro music over the web

 

      let mp = new MediaPlayer()

      mp.Open(new Uri(mp3))

 

      // Wait for the download to complete before playing it

 

      waitForComplete mp

 

      // Get the data associated with the specified episode

      // (this can take some time)

 

      let epNum = psr.Value

      let epInfo =

        allFilms |>

        Array.pick

          (fun e -> if e.EpisodeId = epNum then Some(e) else None)

 

      // Have a minimum delay of 5 seconds showing the intro text

 

      waitForElapsed start 5

 

      // Start the audio at 8.5 seconds in

 

      mp.Position <- new TimeSpan(0, 0, 0, 8, 500)

      mp.Play()

 

      // Switch to the crawl view: this will also change the

      // visual style from 2D Wireframe to Realistic

 

      ed.SetCurrentView(cvtr)

 

      // Remove the intro text

 

      intro.Erase()

 

      // Draw the SW logo and "move it away" from the camera,

      // once again with hardcoded, view-specific values

 

      let es =

        createLogo cvtr.ViewDirection 0.0833

          (new Point3d(-1.,-5.,0.))

      es |> List.iter (addToDatabase tr btr)

 

      // Use a simple scaling to give the impression of movement

      // (yes, with hardcoded, view-specific values)

 

      let rec moveAway (es:Polyline list) n =

        let m = Matrix3d.Scaling(0.91, new Point3d(3.,12.,0.))

        es |>

        List.iter

          (fun e ->

            e.TransformBy(m)

            refresh doc

            System.Threading.Thread.Sleep(15))

        if n > 0 &&

          not(HostApplicationServices.Current.UserBreak()) then

            moveAway es (n-1)

      moveAway es 40

 

      // Remove the polylines making up the logo

 

      es |> List.iter (fun e -> e.Erase())

 

      if not(HostApplicationServices.Current.UserBreak()) then

 

        // Create the crawl text

 

        let crawl =

          crawlText epNum epInfo.Title epInfo.OpeningCrawl |>

          createCrawlText

        crawl |> addToDatabase tr btr

 

        // Start moving it through space, with 300 steps or for 68

        // seconds (whichever comes first)

 

        moveThroughSpace doc crawl 300 68.

 

        crawl.Erase()

 

        // After the crawl is done, rotate the view down onto

        // the surface of the planet

 

        if not(HostApplicationServices.Current.UserBreak()) then

 

          // We'll display the target by a portion of the distance

          // between the top set of stars and the bottom

 

          let pt2 = new Point3d(0., -0.5 * cvtr.Height, 0.)

          let dpt1 = Point3d.Origin.TransformBy(dcs)

          let dpt2 = pt2.TransformBy(dcs)

          let del = (dpt2 - dpt1) / 100.

          let disp = Matrix3d.Displacement(del)

 

          // We'll rotate the view direction at the same time,

          // so that the planet appears at the bottom

 

          let rot =

            Matrix3d.Rotation(-0.005, Vector3d.XAxis, cvtr.Target)

 

          // Our view table record needs to be writeable, of course

 

          cvtr.UpgradeOpen()

 

          // Perform the specified view changes 100 times,

          // waiting until 12 seconds have elapsed

 

          performNTimesOrUntilElapsed DateTime.Now 12

            (fun f ->

              cvtr.Target <- cvtr.Target.TransformBy(disp)

              cvtr.ViewDirection <-

                cvtr.ViewDirection.TransformBy(rot)

              ed.SetCurrentView(cvtr)

              System.Threading.Thread.Sleep(50)

            )

            100

 

      tr.Commit() // Commit the transaction

      mp.Stop() // Stop the music

I hope you’ve found this series entertaining, if not particularly useful. It was certainly a fun diversion – for me, at least – to work through the various challenges around getting this to work with a tolerable level of quality. It never ceases to amaze me what you can do inside AutoCAD with the right programming language, a bit of time and a mildly obsessive personality. :-)

Feed/Share

10 Random Posts