September 2014

Sun Mon Tue Wed Thu Fri Sat
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30        










« Importing Photosynth point clouds into AutoCAD 2011 - Part 1 | Main | Adding to AutoCAD’s Application Menu and Quick Access Toolbar using .NET »

April 09, 2010

Importing Photosynth point clouds into AutoCAD 2011 - Part 2

In the last post we looked at a command to allow importing of Photosynth point clouds into AutoCAD. In this post we’ll put a GUI on the front end, to avoid people having to sniff network traffic to determine the location of the appropriate files on the Photosynth servers.

The application is actually relative simple: it hosts a browser control that gets pointed at the Photosynth web-site, allowing the user to browse through Photosynths. As point clouds are detected (as the browser has some handy events notifying of the HTTP traffic generated by the embedded Photosynth application, and we know that the first point cloud file is always named “points_0_0.bin”), they get added to a list on the right-hand side of the form. They initially get added with just the title (the URL is stored elsewhere) and we then start a timer which will fire each second and – for any items that don’t yet have one – download an image from the server at an appropriate level of detail which we use for a thumbnail. We don’t download the image directly from the HTTP event, as that causes re-entrancy issues (the event will get fired again, which causes the prior event to get cancelled).

Over time the list builds up. When the user feels like it, they can click (or right-click, as they prefer) an item from the list to import it into AutoCAD. This will then cause the command we saw previously to get launched with the URL and the title.

When I started this application I created an in-process form for the browser. I found a really cool control, called csExWb2, which provided the HTTP events for which I was looking. While pretty extensive, there are two main problems with this control: firstly, it’s 32-bit only, so it can’t be hosted inside an AutoCAD plugin on a 64-bit system. Secondly, under certain circumstances it seems to have trouble exiting (and I’m not the only person to hit this, by all accounts).

Thankfully the same approach appears to address both problems: hosting the browser in a separate executable should allow it to run as a 32-bit process on 64-bit systems, and it will allow us to kill the process from our command-implementation should it choose not to exit cleanly. There are ancillary benefits to this approach related to per-process memory consumption (the browser can quickly consume 100+ Mb of memory) and the ability to rebuild the browser without restarting AutoCAD, but those really are of secondary importance.

As we’re using a separate executable, there are clearly some Inter-Process Communication (IPC) issues to deal with. One option would have been to use COM for this, but I decided to go old school and just launch a process for the browser (passing in the handle of the AutoCAD instance as a command-line (string) parameter) and then use the SendMessage() Win32 API to communicate back to AutoCAD.

A couple of extra points to note… I ended up using WinForms for this UI (along with a fun OwnerDraw implementation to make the custom UI look consistent with Photosynth’s) but I could very easily imagine using WPF for this (and I’m sure it would look much better, too). That may be for version 2. I also found the WinForms ListView control to behave quite strangely: I had to jump through some hoops to get the items to centre, for instance, and I’m still not happy that hovering/selecting on the left-hand side of an item doesn’t cause it to be selected. Again, something a WPF version would resolve, I expect.

Now for some code. As there’s now some complexity to the project (including a dialog), you can get the complete source project here.

The C# code for the browser application is very simple:

using System;

using System.Windows.Forms;

using BrowsePhotosynth;

 

namespace Browser

{

  static class Program

  {

    [STAThread]

    static void Main()

    {

      Application.EnableVisualStyles();

      Application.SetCompatibleTextRenderingDefault(false);

      string[] args = Environment.GetCommandLineArgs();

 

      // Extract the handle passed as an argument.

      // This is AutoCAD's main window, and we'll use it

      // to pump messages. If not handle, set it to 0,

      // which means "standalone mode"

 

      int hwnd =

        (args.Length > 1 ? int.Parse(args[1]) : 0);

 

      Application.Run(

        new BrowserForm("http://photosynth.net", hwnd)

      );

    }

  }

}

Most of the heavy lifting is done by the main implementation file behind the BrowserForm class:

using System.Collections.Generic;

using System.ComponentModel;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.IO;

using System.Net;

using System.Runtime.InteropServices;

using System.Text;

using System.Text.RegularExpressions;

using System.Windows.Forms;

using System.Xml;

using System.Xml.Serialization;

using System;

 

namespace BrowsePhotosynth

{

  public partial class BrowserForm : Form

  {

    // A Win32 function we'll use to send messages to AutoCAD

 

    [DllImport("user32.dll")]

    private static extern IntPtr SendMessageW(

      IntPtr hWnd, int Msg, IntPtr wParam,

      ref COPYDATASTRUCT lParam

    );

 

    // And the structure we'll require to do so

 

    private struct COPYDATASTRUCT

    {

      public IntPtr dwData;

      public int cbData;

      public IntPtr lpData;

    }

 

    // A class containing the browsing information about each

    // point-cloud. This is made serializable to XML, to

    // allow easy persistence

 

    [XmlRoot("Synth")]

    public class SynthInfo

    {

      // The name of our Photosynth

 

      private string _name;

 

      [XmlAttribute("Name")]

      public string Name

      {

        get { return _name; }       

        set { _name = value; }

      }

 

      // Its URL

 

      private string _url;

 

      [XmlElement( "Url" )]

      public string Url

      {

        get { return _url; }       

        set { _url = value; }

      }

 

      // The location of its image file

 

      private string _image;

 

      [XmlElement( "Image" )]

      public string Image

      {

        get { return _image; }       

        set { _image = value; }

      } 

    }

 

    // We store a central list of these SynthInfo objects.

 

    private List<SynthInfo> _synths = new List<SynthInfo>();

 

    // A timer to call back into the code to download images

    // (to avoid rentrancy)

 

    private Timer _timer = new Timer();

 

    // Public property for the URL loaded into the browser

    // (this is the URL of the browser, which is typically

    // "http://photosynth.net")

 

    private string _url = null;

    public string Url

    {

      set { _url = value; }

      get { return _url; }

    }

 

    // Public property for the handle of the AutoCAD application

    // we're connected to

 

    private int _hwnd = 0;

    public int HWnd

    {

      set { _hwnd = value; }

      get { return _hwnd; }

    }

 

    // Internal constants

 

    const string pointsName = "points_0_0.bin";

    const string suffix = " - Photosynth";

    const string historyXml = "BrowsingHistory.xml";

    const int imgWidth = 128;

    const int imgHeight = imgWidth;

 

    // Form constructor

 

    public BrowserForm(string url, int hwnd)

    {

      InitializeComponent();

 

      _url = url;

      _hwnd = hwnd;

 

      // Handlers for our various events

 

      // Form events for loading/closing

 

      Load += new EventHandler(BrowserForm_Load);

      FormClosing +=

        new FormClosingEventHandler(BrowserForm_FormClosing);

 

      // Our main browser event, telling us when a URL is being

      // accessed (this allows us to detect when point clouds

      // are being accessed by the Photosynth application)

 

      _browser.ProtocolHandlerBeginTransaction +=

        new csExWB.ProtocolHandlerBeginTransactionEventHandler(

          cEXWB1_ProtocolHandlerBeginTransaction

        );

 

      // Events for selection of items from the list (and

      // our owner-draw implementation)

 

      _cloudList.MouseClick +=

        new MouseEventHandler(cloudList_MouseClick);

      _cloudList.DrawItem +=

        new DrawListViewItemEventHandler(cloudList_DrawItem);

 

      // Event for selection from the the right-click menu

 

      _cloudsMenu.ItemClicked +=

        new ToolStripItemClickedEventHandler(

          ContextMenuStrip_ItemClicked

        );

 

      // Set the appropriate list display properties

 

      _cloudImages.ImageSize = new Size(imgWidth, imgHeight);

      _cloudList.OwnerDraw = true;

    }

 

    private void BrowserForm_Load(object sender, EventArgs e)

    {

      // Navigate to the provided URL, if it's non-null and

      // not the one we're already pointed at

 

      if (!String.IsNullOrEmpty(_url) &&

          _browser.LocationUrl != _url)

          _browser.Navigate(_url);

 

      // Load our browsing history from the XML file.

 

      _synths = DeserializeHistory();

 

      // For each member in the history, re-create

      // entries in the list view

 

      foreach (SynthInfo sinf in _synths)

      {

        // Create a list view item with appropriate indentation

 

        ListViewItem lvi = new ListViewItem(sinf.Name);

        lvi.IndentCount = 10;

        _cloudList.Items.Add(lvi);

 

        // If we have a valid image file, load it into the list

        // (if one doesn't exist then it should be downloaded

        // from the server when the timer fires)

 

        if (!String.IsNullOrEmpty(sinf.Image))

        {

          string imgFile = GetOutputLocation() + sinf.Image;

          if (File.Exists(imgFile))

            lvi.ImageIndex =

              _cloudImages.Images.Add(

                System.Drawing.Image.FromFile(imgFile),

                Color.Black

              );

        }

      }

    }

 

    void BrowserForm_FormClosing(

      object sender, FormClosingEventArgs e

    )

    {

      // Store our browsing history to XML

 

      SerializeHistory(_synths);

    }

 

    private void StartTimer()

    {

      // Create a timer which will fire every second

      // (we use this to check for images to download and

      // add to our dialog, as we get re-entrancy problems

      // if we do so from our http monitoring callback)

 

      _timer.Interval = 100;

      _timer.Tick += new EventHandler(OnTick);

      _timer.Start();

    }

 

    private void StopTimer()

    {

      // Stop our timer

 

      _timer.Stop();

      _timer.Tick -= new EventHandler(OnTick);

    }

 

    private void cEXWB1_ProtocolHandlerBeginTransaction(

      object sender,

      csExWB.ProtocolHandlerBeginTransactionEventArgs e

    )

    {

      // If we detect the first point cloud file in a series...

 

      if (e.URL.Contains(pointsName))

      {

        csExWB.cEXWB wb = (csExWB.cEXWB)sender;

 

        // Get the page's title and extract the URL

 

        string title = wb.GetTitle(true);

 

        // If the point cloud was embedded in the main page,

        // let's extract its actual title from the HTML content

        // (this will change as the Photosynth page structure

        // changes, but if it doesn't find the relevant entries

        // then we just use the overall title)

 

        if (title.StartsWith("Photosynth"))

        {

          string src = wb.DocumentSource;

          if (src.Contains("title-block"))

          {

            src = src.Substring(src.IndexOf("title-block"));

            if (src.Contains("A href="))

            {

              src = src.Substring(src.IndexOf("A href="));

              if (src.Contains(">"))

              {

                src = src.Substring(src.IndexOf(">"));

                if (src.Contains("<"))

                {

                  int endPos = src.IndexOf("<");

                  if (endPos > 1)

                  {

                    title = src.Substring(1, endPos - 1);

                  }

                }

              }

            }

          }

        }

        else if (title.EndsWith(suffix))

        {

          // Strip off the common suffix, if it's there

 

          title =

            title.Substring(0, title.Length - suffix.Length);

        }

 

        // Extract the base URL, without the initial point-cloud

        // name

 

        string baseUrl =

          e.URL.Substring(0, e.URL.Length - pointsName.Length);

 

        // Use this info to create a new entry in our list

        // and start the timer to get the related image

 

        AddToPointCloudList(title, baseUrl);

        StartTimer();

      }

    }

 

    private void OnTick(object sender, EventArgs e)

    {

      // When the timer fires, check if there are images to add...

 

      if (_cloudImages.Images.Count < _synths.Count)

      {

        Cursor old = this.Cursor;

        this.Cursor = Cursors.WaitCursor;

 

        // Stop the timer, just during processing

 

        StopTimer();

 

        // Add images for any items which don't yet have them

        // (realistically this will usually be just one image,

        // as we check every second and browsing takes time)

 

        for (

          int i = _cloudImages.Images.Count; i < _synths.Count; i++

        )

        {

          // This should be redundant, as _synths should have the

          // same number of items as _cloudList.Items, but anyway

 

          if (i < _cloudList.Items.Count)

          {

            // Get the information on the synth for which we need

            // to download the image

 

            SynthInfo sinf = _synths[i];

            string baseUrl = sinf.Url;

 

            // Get the list view item

 

            ListViewItem lvi = _cloudList.Items[i];

 

            // Transform our base URL to get the URL

            // to an appropriate image on the server

 

            if (baseUrl.Contains(".synth_files"))

            {

              string imageUrl =

                baseUrl.Substring(

                  0, baseUrl.LastIndexOf(".synth_files")

                )

                + "_files/6/0_0.jpg";

 

              // Create a web client to download the image

 

              WebClient wc = new WebClient();

              using (wc)

              {

                string locFile =

                  MakeValidFileName(lvi.Text) + ".jpg";

                string locImage = GetOutputLocation() + locFile;

 

                // Try to download our image file

 

                try

                {

                  wc.DownloadFile(imageUrl, locImage);

                }

                catch

                { }

 

                // If we were successful, load and add it

 

                if (File.Exists(locImage))

                {

                  lvi.ImageIndex =

                    _cloudImages.Images.Add(

                      System.Drawing.Image.FromFile(locImage),

                      Color.Black

                    );

 

                  // Make sure our browsing history reflects

                  // the existence of the downloaded image

 

                  sinf.Image = locFile;

                  _synths[i] = sinf;

                }

              }

            }

          }

        }

        this.Cursor = old;

      }

    }

 

    private void cloudList_DrawItem(

      object sender,

      DrawListViewItemEventArgs e

    )

    {

      // Restrict the bounds to no greater than the

      // visible width

 

      Rectangle bounds = e.Bounds;

      if (bounds.Width > _cloudList.ClientSize.Width)

        bounds.Width = _cloudList.ClientSize.Width;

 

      if ((e.State &

          (ListViewItemStates.Hot | ListViewItemStates.Selected)

        ) != 0)

      {

        // Draw the background for a selected or hovered item

 

        if ((e.State & ListViewItemStates.Hot) != 0)

          e.Item.Selected = true;

 

        // Create a linear gradient brush going between

        // "Photosynth green" and black, then draw the background

 

        LinearGradientBrush brush =

          new LinearGradientBrush(

            bounds,

            Color.FromArgb(255, 166, 203, 2),

            Color.Black,

            LinearGradientMode.Vertical

          );

        using (brush)

        {

          e.Graphics.FillRectangle(brush, e.Bounds);

        }

      }

      else

      {

        // Draw a black background for an unselected item

 

        e.Graphics.FillRectangle(Brushes.Black, e.Bounds);

      }

 

      // Draw the item text for views other than "Details"

 

      if (_cloudList.View != View.Details)

      {

        // Restrict the item bounds to no greater than the

        // visible width

 

        Rectangle itemSize = e.Item.Bounds;

        if (itemSize.Width > _cloudList.ClientSize.Width)

          itemSize.Width = _cloudList.ClientSize.Width;

 

        // Reduce the bounds further to the image size we want

 

        itemSize.Inflate(

          (itemSize.Width - imgWidth) / -2,

          (itemSize.Height - imgHeight) / -2

        );

 

        // Get the image from the list, if it's there, otherwise

        // create a blank image

 

        System.Drawing.Image img =

          (_cloudImages.Images.Count > e.ItemIndex ?

            _cloudImages.Images[e.ItemIndex] :

            new System.Drawing.Bitmap(imgWidth, imgHeight)

          );

 

        // Draw the image and the text

 

        e.Graphics.DrawImage(img, itemSize);

        e.DrawText(

          TextFormatFlags.Bottom | TextFormatFlags.HorizontalCenter

        );

      }

    }

 

    private void cloudList_MouseClick(

      object sender,

      MouseEventArgs e

    )

    {

      // If an item in the list is clicked, then either import

      // the associated point cloud directly or show the menu

      // (if the right mouse button was used)

 

      if (_cloudList.SelectedItems.Count == 1)

      {

        if (e.Button == MouseButtons.Left)

          ImportPointCloud();

        else if (e.Button == MouseButtons.Right)

          _cloudsMenu.Show(MousePosition);

      }

    }

 

    private void ContextMenuStrip_ItemClicked(

      object sender,

      ToolStripItemClickedEventArgs e

    )

    {

      // If the right-click menu item's "import" item

      // was used, import the selected point cloud

      if (e.ClickedItem.Name == "_importMenuItem")

        ImportPointCloud();

    }

 

    private void AddToPointCloudList(string title, string baseUrl)

    {

      // Add a point cloud with a certain title and URL to our list

 

      ListViewItem lvi;

      bool found = false;

 

      // First we check that it's not already in the list

 

      for (int idx = 0; idx < _cloudList.Items.Count; idx++)

      {

        if (_synths.Count > idx)

        {

          lvi = _cloudList.Items[idx];

          if (lvi.Text == title && _synths[idx].Url == baseUrl)

          {

            found = true;

            break;

          }

        }

      }

 

      // If it isn't add it to the list and to our browsing history

 

      if (!found)

      {

        lvi = new ListViewItem(title);

        lvi.IndentCount = 10;

        _cloudList.Items.Add(lvi);

 

        SynthInfo sinf = new SynthInfo();

        sinf.Url = baseUrl;

        sinf.Name = title;

        _synths.Add(sinf);

      }

    }

 

    private void ImportPointCloud()

    {

      // If we're not connected to an AutoCAD session (via

      // the handle we received as a command-line argument),

      // then we show a message and continue.

 

      if (_hwnd == 0)

      {

        MessageBox.Show(

          "This browser is not connected to an instance of " +

          "AutoCAD. Relaunch from AutoCAD to import Point " +

          "Clouds from your browsing history.",

          "Browse Photosynth",

          MessageBoxButtons.OK,

          MessageBoxIcon.Information

        );

      }

      else

      {

        // Get the selected items from the list

 

        ListView.SelectedListViewItemCollection sel =

          _cloudList.SelectedItems;

 

        // Get the index of the first (and only) selected item

 

        int idx = sel[0].Index;

 

        // Assume the item in the _synths list is at the same

        // location

 

        SynthInfo sinf = _synths[idx];

        string title = sinf.Name;

        string firstUrl = sinf.Url + pointsName;

 

        // Hide the form and stop the browsing operation

 

        Visible = false;

        _browser.NavToBlank();

        _browser.Stop();

 

        // Fire off our command to AutoCAD

 

        SendCommandToAutoCAD(

          "_.IMPORTPHOTOSYNTH \"" + firstUrl + "\" \"" +

          title + "\" "

        );

 

        // Exit the application

 

        Application.Exit();

      }

    }

 

    // Clear the point cloud history from the browser and

    // delete the XMl history file.

 

    private void ClearHistory_Click(object sender, EventArgs e)

    {

      _synths.Clear();

      _cloudImages.Images.Clear();

      _cloudList.Items.Clear();

 

      string histFile = GetOutputLocation() + historyXml;

      if (File.Exists(histFile))

        File.Delete(histFile);

    }

 

    // The location of our various output files (the PCGs, JPGs

    // and the browsing history)

 

    private string GetOutputLocation()

    {

      return

        Environment.GetFolderPath(

          Environment.SpecialFolder.MyDocuments

        ) + "\\Photosynth Point Clouds\\";

    }

 

    // Save the current state of our browsing history

    // to an XML file.

 

    private void SerializeHistory(List<SynthInfo> synths)

    {

      string outputPath = GetOutputLocation();

 

      if (!Directory.Exists(outputPath))

        Directory.CreateDirectory(outputPath);

 

      if (_synths.Count > 0)

      {

        XmlSerializer xs =

          new XmlSerializer(typeof(List<SynthInfo>));

        XmlTextWriter xw =

          new XmlTextWriter(outputPath + historyXml, Encoding.UTF8);

        xs.Serialize(xw, synths);

        xw.Close();

      }

    }

 

    // Read and return the previous browsing history from

    // our stored XML file.

 

    private List<SynthInfo> DeserializeHistory()

    {

      string histFile = GetOutputLocation() + historyXml;

 

      if (File.Exists(histFile))

      {

        XmlSerializer xs =

          new XmlSerializer(typeof(List<SynthInfo>));

        XmlTextReader xr = new XmlTextReader(histFile);

        if (xs.CanDeserialize(xr))

        {

          List<SynthInfo> synths =

            (List<SynthInfo>)xs.Deserialize(xr);

          xr.Close();

          return synths;

        }

      }

      return new List<SynthInfo>();

    }

 

    // Just use the Win32 API to communicate with AutoCAD.

    // We simply need to send a command string, so this

    // approach avoids a dependency on AutoCAD's COM

    // interface.

 

    private void SendCommandToAutoCAD(string toSend)

    {

      const int WM_COPYDATA = 0x4A;

 

      COPYDATASTRUCT cds = new COPYDATASTRUCT();

      cds.dwData = new IntPtr(1);

      string data = toSend + "\0";

      cds.cbData = data.Length * Marshal.SystemDefaultCharSize;

      cds.lpData = Marshal.StringToCoTaskMemAuto(data);

 

      SendMessageW(

        new IntPtr(_hwnd), WM_COPYDATA, this.Handle, ref cds

      );

 

      Marshal.FreeCoTaskMem(cds.lpData);

    }

 

    // Function to create a valid filename from a string.

    // This has been duplicated from the plugin project.

 

    private static string MakeValidFileName(string name)

    {

      string invChars =

        Regex.Escape(new string(Path.GetInvalidFileNameChars()));

      string invRegEx = string.Format(@"[{0}]", invChars + ".");

      return Regex.Replace(name, invRegEx, "-");

    }

  }

}

Here is the code we’ve added to the original plugin code to define our new BP command:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Colors;

using System.Diagnostics;

using System.Text.RegularExpressions;

using System.Reflection;

using System.IO;

using System.Net;

using System;

using DemandLoading;

 

namespace ImportPhotosynth

{

  public class Appl : IExtensionApplication

  {

    public void Initialize()

    {

      try

      {

        RegistryUpdate.RegisterForDemandLoading();

      }

      catch

      { }

    }

 

    public void Terminate()

    {

      Commands.Cleanup();

    }

  }

 

  public class Commands

  {

    static Process _p = null;

 

    static public void Cleanup()

    {

      if (_p != null)

      {

        if (!_p.HasExited)

          _p.Kill();

 

        _p.Dispose();

        _p = null;

      }

    }

 

    [CommandMethod("BP", CommandFlags.Session)]

    public void BrowsePhotosynth()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      Cleanup();

 

      string exePath =

        Path.GetDirectoryName(

          Assembly.GetExecutingAssembly().Location

        ) + "\\";

 

      if (!File.Exists(exePath + "ADNPlugin-BrowsePhotosynth.exe"))

      {

        ed.WriteMessage(

          "\nCould not find the ADNPlugin-BrowsePhotosynth " +

          "tool: please make sure it is in the same folder " +

          "as the application DLL."

        );

        return;

      }

 

      // Launch our browser window with the AutoCAD's handle

      // so that we can receive back command strings

 

      ProcessStartInfo psi =

        new ProcessStartInfo(

          exePath + "ADNPlugin-BrowsePhotosynth",

          " " + Application.MainWindow.Handle

        );

      _p = Process.Start(psi);

    }

 

    [CommandMethod("IMPORTPHOTOSYNTH", CommandFlags.NoHistory)]

    public void ImportPhotosynth()

    {

The rest of the code in this source file is identical to that from the previous post.

A few comments on getting the application to work…

  • Pre-built versions of the files you need can be found in the project’s bin folder. txt2las.exe is the original one provided on the lastools website, csExWB.dll is built exactly from the application source, and ComUtilities.dll is the pre-built module provided along with it.
  • You will need to extract the various files into a single folder on your hard drive (I recommend a folder under your main AutoCAD 2011 program files folder – I would normally put them straight into the program files folder, but as some of them have generic names that aren’t prefixed by an RDS, this is a little risky).
  • You will need to use regsvr32 to register the ComUtilities.dll file: you can either open a command-prompt window and browse to the folder, entering “regsvr32 ComUtilities.dll” or – and this is the approach I tend to use – create a desktop shortcut to the regsvr32.exe file in your Windows\System32 folder and drag & drop the ComUtilities.dll file from Windows Explorer onto that shortcut.
  • I haven’t tried this out on a 64-bit system, but I believe it will work (if it doesn’t please let me know).

The best way to run the application is directly from inside AutoCAD: you NETLOAD the ADNPlugins-ImportPhotosynth.dll into AutoCAD 2011, which should create demand-loading entries for future, automatic loading. You can then use the BP command to launch the browser dialog. [It’s also possible to launch the executable application directly, but this will only allow you to populate the browsing history for later use inside an AutoCAD session – without being “connected” to AutoCAD, it won’t do anything more).

When the application loads, if there’s an embedded Photosynth in the main page, this should get added to the history (bear in mind that Photosynth now hosts panoramas, so not all items have a point cloud behind):

Browsing Photosynth 1

And as you browse (there’s not yet a “back” button on the form, so you will need to right-click on a non-Silverlight part of the page to get access to this via a context menu) you will see additional Photosynths get added to our history:

Browsing Photosynth 2

Browsing Photosynth 3

Browsing Photosynth 4

And then when you find a Photosynth that interests you, hovering over the item in the right-hand side of the dialog should select it:

Browsing Photosynth 5

And, if you’re inside AutoCAD, you can then left- or right-click it to import it:

Point cloud of a Mini Cooper imported into AutoCAD

If you’re interested in getting at the data generated by this application, open up the “Photosynth Point Clouds” folder under your “My Documents”. In here you’ll find JPGs and PCGs for the various point clouds, as well as an XML file containing your browsing history:

<?xml version="1.0" encoding="utf-8"?>

<ArrayOfSynthInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <SynthInfo Name="Point Wilson Lighthouse">

    <Url>http://mslabs-354.vo.llnwd.net/d7/photosynth/M6/collections/3d/7d/fe/3d7dfec4-e184-4c96-831f-c4667ed88ed9.synth_files/</Url>

    <Image>Point Wilson Lighthouse.jpg</Image>

  </SynthInfo>

  <SynthInfo Name="Девичья башня">

    <Url>http://mslabs-361.vo.llnwd.net/d4/photosynth/m6/collections/89/19/b0/8919b044-95c5-4bce-8147-2149d1d5342e.synth_files/</Url>

    <Image>Девичья башня.jpg</Image>

  </SynthInfo>

  <SynthInfo Name="Cincinnati Art Museum Exterior - West Wing">

    <Url>http://mslabs-606.vo.llnwd.net/d7/photosynth/M6/collections/f7/5d/71/f75d71e8-7624-4ff8-8037-0106ee59eec4.synth_files/</Url>

    <Image>Cincinnati Art Museum Exterior - West Wing.jpg</Image>

  </SynthInfo>

  <SynthInfo Name="Moskvich">

    <Url>http://mslabs-866.vo.llnwd.net/d4/photosynth/m6/collections/93/20/cb/9320cb03-6d22-48d7-b697-b897d75d2435.synth_files/</Url>

    <Image>Moskvich.jpg</Image>

  </SynthInfo>

  <SynthInfo Name="Mini Cooper 2009 rubensanchez">

    <Url>http://mslabs-488.vo.llnwd.net/d3/photosynth/M6/collections/5b/93/87/5b93877f-e15a-4310-a9f4-11100e101e88.synth_files/</Url>

    <Image>Mini Cooper 2009 rubensanchez.jpg</Image>

  </SynthInfo>

</ArrayOfSynthInfo>

There are some really cool Photosynths out there – knock yourselves out! :-)

In a future post I’m going to show a little more on the “front-end”: to describe an attempt at capturing a 3D point cloud from a set of 2D images. If you’re interested in seeing a first try at this, search Photosynth for “kean notebook”: the first item in the list should be a relatively simple Photosynth of a notebook PC my manager (Jim Quanci) captured using the camera in his Blackberry while we were in the Tokyo office a couple of weeks ago. Nothing very exciting, just the early results of a very basic test.

blog comments powered by Disqus

Feed/Share

10 Random Posts