Tuesday, May 21, 2013

Deploying RPS scripts with DeployRpsAddin

I know nobody is checking the code I check into the RevitPythonShell code repository, because nobody bothered to ask me about the new killer feature i sneaked in. For a project at work that I used the RevitPythonShell for, the need arose to deploy a bunch of scripts on a customers computer. Now, RPS itself has a simple installer that you can execute and go next, next, next, finish and voilĂ , you have RPS installed on your computer. But... any scripts you write need to be copied to the customers computer, then RPS has to be installed and configured to pick up the script. Oh, and don't forget to install cpython and any modules you make use of.

Have you tried writing instructions for such a manual installation procedure? I have. It is not fun at all! I have also tried to get people to execute these instructions. It just doesn't fly.

This is where the new feature of RPS comes in: In the Ribbon, there is now an additional button called "Deploy RpsAddin" that is located under the button for starting the shell, just above the one for customizing RPS.

Deploying an RPS script as a standalone addin involves choosing an xml file that acts as a manifest or package description of the addin you are about to create. Basically, you describe which python scripts to assign to which buttons and it creates a new dll for you that includes all these scripts and can be used as an addin in Revit. You just need to write an addin manifest and place it in the right position.

The source comes with an example addin called HelloWorld. That really is all it does: a Button that prints "Hello, World!" to the screen. But I include the xml deployment file and also an InnoSetup script to get you started on deploying your own addins.

When you want to include a cpython library, you will need to make sure that this is also in the search path of the addin's bundled interpreter. So, the addin includes a bunch of files, dlls, a version of IronPython (2.7) and also the new RpsRuntime dll that handles the parts of RPS that get used by both the standard RPS version and deployed addins.

You can include cpython modules in your setup program, copying them for instance into your installation directory and then go from there. There is an equivalent of the RevitPythonShell2013.xml file that gets deployed with your addin to the %AppData% folder that you can use for setting up stuff.

The structure of the deployment xml file looks like this: (I will call it RpsAddin xml file from now on)

<?xml version=" 1.0" encoding=" utf-8" ?>
<RpsAddin>
  <RibbonPanel text=" Hello World">
    <!-- the script is always searched relative to the location of the RpsAddin xml file -->
    < PushButton text ="Hello World! " script ="helloworld.py "/>
  </RibbonPanel>
</RpsAddin>

You can add as many RibbonPanel tags as you would like. Each PushButton is then placed on that panel and assigned to the script. The path to the script is relative to the RpsAddin xml file. The DLL that gets created is placed in a folder "Output_YOUR_RPSADDIN_NAME" relative to the RpsAddin xml. The name of your addin is taken from the name you call the RpsAddin xml file. In this case, the file is called "HelloWorld.xml", so the Addin will be called "HelloWorld", a folder "Output_HelloWorld" is created with the dll "HelloWorld.dll", "RpsRuntime.dll" and a bunch of IronPython dlls.

You can then use an InnoSetup file to create an installer for this. The HelloWorld example comes with this file:

[Files]
Source: Output_HelloWorld\RpsRuntime.dll; DestDir: {app};
Source: Output_HelloWorld\IronPython.dll; DestDir: {app};
Source: Output_HelloWorld\IronPython.Modules.dll; DestDir: {app};
Source: Output_HelloWorld\Microsoft.Scripting.Metadata.dll; DestDir: {app};
Source: Output_HelloWorld\Microsoft.Dynamic.dll; DestDir: {app};
Source: Output_HelloWorld\Microsoft.Scripting.dll; DestDir: {app};

; this is the main dll with the script embedded
Source: Output_HelloWorld\HelloWorld.dll; DestDir: {app};

; add a similar line, if your addin requires a configuration file (search paths or predefined variables)
;Source: HelloWorld.xml; DestDir: {userappdata}\HelloWorld; Flags: onlyifdoesntexist;

[code]
{ install revit manifest file }
procedure CurStepChanged(CurStep: TSetupStep);
var
  AddInFilePath: String;
  AddInFileContents: String;
begin

  if CurStep = ssPostInstall then
  begin

  { GET LOCATION OF USER AppData (Roaming) }
  AddInFilePath := ExpandConstant('{userappdata}\Autodesk\Revit\Addins\2013\HelloWorld.addin');

  { CREATE NEW ADDIN FILE }
  AddInFileContents := '<?xml version="1.0" encoding="utf-8" standalone="no"?>' + #13#10;
  AddInFileContents := AddInFileContents + '<RevitAddIns>' + #13#10;
  AddInFileContents := AddInFileContents + '  <AddIn Type="Application">' + #13#10;
    AddInFileContents := AddInFileContents + '    <Name>HelloWorld</Name>' + #13#10;
  AddInFileContents := AddInFileContents + '    <Assembly>'  + ExpandConstant('{app}') + '\HelloWorld.dll</Assembly>' + #13#10;

  { NOTE: create your own GUID here!!! }
  AddInFileContents := AddInFileContents + '    <AddInId>276D41F2-CCC4-4B55-AF2A-47D30227F289</AddInId>' + #13#10;

  AddInFileContents := AddInFileContents + '    <FullClassName>HelloWorld</FullClassName>' + #13#10;

  { NOTE: you should register your own VendorId with Autodesk }
  AddInFileContents := AddInFileContents + '  <VendorId>RIPS</VendorId>' + #13#10;
  AddInFileContents := AddInFileContents + '  </AddIn>' + #13#10;
  AddInFileContents := AddInFileContents + '</RevitAddIns>' + #13#10;
  SaveStringToFile(AddInFilePath, AddInFileContents, False);

  end;
end;

{ uninstall revit addin manifest }
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
  AddInFilePath: String;
begin
  if CurUninstallStep = usPostUninstall then
  begin
    AddInFilePath := ExpandConstant('{userappdata}\Autodesk\Revit\Addins\2013\HelloWorld.addin');

    if FileExists(AddInFilePath) then
    begin
      DeleteFile(AddInFilePath);
    end;
  end;
end;


[Setup]
AppName=HelloWorld
AppVerName=HelloWorld
RestartIfNeededByRun=false
DefaultDirName={pf32}\HelloWorld
OutputBaseFilename=Setup_HelloWorld
ShowLanguageDialog=auto
FlatComponentsList=false
UninstallFilesDir={app}\Uninstall
UninstallDisplayName=HelloWorld
AppVersion=2012.0
VersionInfoVersion=2012.0
VersionInfoDescription=HelloWorld
VersionInfoTextVersion=HelloWorld

I included some Pascal code for installing an addin manifest to the %APPDATA% folder. This is generally something like C:\Users\username\AppData\Roaming\ADDIN_NAME. Running this setup will produce a file called Setup_Helloworld.exe that can then be given to your friends to try out your new cool HelloWorld Revit Addin, coded in the sweet python language we all love so much!

Wednesday, January 16, 2013

Everyone vs. Just Me for Custom Actions Installer Classes

This post explains how to change the behavior of your Custom Actions Installer class depending on the user choice in the dialog box:

https://dl.dropbox.com/u/8112069/darenatwork/2013.01.16_JustMeEveryone.png

The Problem

When you create a Custom Action project to be called from a Visual Studio 2010 Setup project, you subclass System.Configuration.Install.Installer. This will then be called from the Visual Studio 2010 Setup project, passing in some context information about the installation via the Context.Parameters StringDictionary.

Except it doesn't contain the information about wether the user chose "Just Me" or "Everyone"!

The Solution

I came across this page that explained how to pass in information to Context.Parameters in the custom action: You can use the CustomActionData property to add values to Context.Parameters.

The trick is, to use the format "/varname=value", and variable substitution happens for Windows Installer Properties, if they are enclosed in square brackets...

After googling a bit more, I found a msdn page containing the Windows Installer Property Reference and searching that, I found the property ALLUSERS. This is set to the string "1" if the user chose to install for "Everyone" and set to the empty string "" if the user chose to install for "Just Me".

https://dl.dropbox.com/u/8112069/darenatwork/2013.01.16_CustomActionData.png

Putting two and two together, I added the string "/allusers=[ALLUSERS]" to the CustomActionData property for the custom actions. And then can query this inside the Installer class:

if (Context.Parameters["allusers"] == "1")
{
    // user selected "Everyone"
}
else
{
    // user selected "Just Me"
}

Testing this proved that the ALLUSERS property is retained by the Windows Installer and can also be used for uninstalling.

Context: Installing Revit Addins

When installing addins for the Autodesk Revit product, a manifest file has to be created and placed in a specific directory. There are two addin directories to choose from: One for the current user and one for all users of the system.

The RevitPythonShell installer for Revit 2013 has been updated with the above technique to install to different locations based on the users choice. You can see a working example of what the custom action code looks like in the RevitPythonShell source for the RegisterAddinCustomAction class.

Friday, October 5, 2012

RPS script to print a list of floor types and their functions

Yesterday, I needed to print out all the floor types in a Revit document along with their function (Interior/Exterior). Obviously, I decided to write a quick RevitPythonShell script, but ran into some snags along the way that I would like to share.

This should really be as easy as:

collector = FilteredElementCollector(doc)
for floor_type in collector.OfClass(FloorType):
   print floor_type.Name, floor_type.Function

But, well, nothing is ever easy, is it? What I ended up doing was this:

from System import Enum
collector = FilteredElementCollector(doc)
collector.OfClass(FloorType)
for floor_type in collector:
   name = Element.Name.GetValue(floor_type)
   function_param = Element.get_Parameter(floor_type, BuiltInParameter.FUNCTION_PARAM)
   if function_param:
      function = Enum.ToObject(WallFunction, function_param.AsInteger()).ToString()
   else:
      function = 'None'
   print '%(name)-25s\t%(function)s' % locals()

Let's go through this line by line...

from System import Enum

Uh... I'll explain that later on...

collector = FilteredElementCollector(doc)
collector.OfClass(FloorType)
for floor_type in collector:

Nothing unusual here - this is how to get a list of elements from a Revit document using a FilteredElementCollector and iterating over that list.

name = Element.Name.GetValue(floor_type)

What?! Shouldn't that just have been floor_type.Name? I thought so too. But somehow, whenever I do that, I get an error like this:

>>> # note how ugly one-liners get ;)
>>> ft = list(FilteredElementCollector(doc).OfClass(FloorType))[0].Name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Name

This AttributeError is somehow related to IronPython not being able to figure out that it needs to use the base method inherited from Autodesk.Revit.DB.Element. So, I tell it to use that explicitly by referencing the property explicitly with Element.Name - and then retrieving its value: .GetValue. It needs to be told for which instance to retrieve the value, which is why I then plug in the floor_type object.

So... now we have the name of the FloorType. What about its function? That isn't a property, but rather a parameter, that can be found using the Revit Lookup tool: BuiltInParameter.FUNCTION_PARAM. Retrieving parameters is easy:

function_param = Element.get_Parameter(floor_type, BuiltInParameter.FUNCTION_PARAM)

Note again, how I use the base method Element.get_Parameter, as it doesn't seem to work on FloorType directly - my guess is that the definition of FloorType somehow isn't what IronPython is used to...

 if function_param:
       function = Enum.ToObject(WallFunction, function_param.AsInteger()).ToString()
else:
   function = 'None'

The parameter may be null or, in python None, which will evaluate to False in a condition. In that case, we just set the text of the variable function to 'None'`. But the other line is a lot more interesting! This is why we did the from System import Enum earlier on: The (integer) value of the FUNCTION_PARAM is actually a member of an enumeration. As far as I can tell, it is equivalent to the WallFunction enumeration. With Enum.ToObject(WallFunction, function_param.AsInteger()), we can an the appropriate instance of this enumeration, which we then convert to a string (ToString()).

The last line:

print '%(name)-25s\t%(function)s' % locals()

Prints out the name and function nicely, using pythons string formatting: Left-alined name with a minimum width of 25 characters, a tab and then the function.

Try the script!

Wednesday, January 18, 2012

Converting line endings on the cheap

You're at a client's PC, installing some scripts and need to edit a text file. Of course your client has no real editor, just that stupid notepad.exe! And then you notice: All the line endings are wrong, that is, they're LF (unix convention) as opposed to CR-LF (dos convention). That is because you do have a real editor, and so does the other guy working on that file and one of you was not working on windows, because it's only an OS, right? A tool, not a religion.

Not religous either: What escapes your mouth when you are confronted with a single line of script and black box characters laughing at you where line breaks should be.

Now, users of cygwin are probably familiar with a myriad of tools (e.g. dos2unix.exe) to convert from dos line endings to unix line endings. You might be able to get tr to work for you too. Or you could just open the file in your trusty real editor and type away.

But your client has none of this installed.

What your client does have installed, is MS Word. Seriously. I'm going to use MS Word now. This is how:

  1. open the script/text file in notepad
  2. select all text, copy it to the clipboard ([CTRL]+[A], [CTRL]+[C])
  3. open a blank document in MS Word
  4. paste from the clipboard ([CTRL]+[V])
  5. select all text in MS Word, copy it to the clipboard ([CTRL]+[A], [CTRL]+[C])
  6. paste over (replacing) the selection in notepad ([CTRL]+[V])
  7. close MS Word and wash your hands to get rid of the dirty hack feeling...

Friday, November 11, 2011

How to install Trac on a Synology DS2011+

In a previous post, I described installing Trac on a Synology DS209+II. I recently had to migrate this installation to a Synology DS2011+ and ran into some snags. This post will help you avoid spending all the hours I did on getting it to work.

Modifying the Synology DiskStation

To be able to do anything to your DS2011+, you first have to install the ipkg bootstrap. I basically followed along with page on the synology forum Overview on modifying the Synology Server, bootstrap, ipkg etc.:

  • You need to enable ssh access to your NAS and then log in with PuTTY
  • The Synology DS2011+ has a "Marvell Kirkwood mv6282" processor, so these steps should do the trick:
cd /volume1/@tmp
wget http://wizjos.endofinternet.net/synology/archief/syno-mvkw-bootstrap_1.2-7_arm-ds111.xsh
chmod +x syno-mvkw-bootstrap_1.2-7_arm-ds111.xsh
sh syno-mvk-bootstrap_1.2-7_arm-ds111.xsh
rm syno-mvk-bootstrap_1.2-7_arm-ds111.xsh

After rebooting the NAS, you can enter these commands:

ipkg update
ipkg upgrade

We are now ready to start installing all the stuff needed for running Trac!

Software to install

Apache

Log on to your DS2011+ and install the following package:

ipkg install apache

Now we have a second httpd listening on port 8000. The configuration file for this instance of apache is /opt/etc/apache2/httpd.conf. You can find the log files in /opt/var/apache2/log. Also note that it is started on system boot by the script /opt/etc/init.d/S80apache. I suggest you execute this script and see if apache starts up nicely.

I ran into some problems with this installation: A syntax error on line 74 of /opt/etc/apache2/httpd.conf. Somehow apache can't load the mod_ext_filter.so. I didn't really care and just commented out that line.

Subversion

Create a new share using the DSM (that web interface on port 5000) - call it "svn". It will show up on your box as /volume1/svn.

I mainly followed the Step-by-step guide to installing Subversion on the Synology wiki. But I didn't bother messing with inetd.conf, since I am running svn under apache with mod_dav_svn. So, that leaves the following steps:

ipkg install svn
su svnowner
cd /volume1/svn
svnadmin create testsvn

You now have a new repository on your NAS called "testsvn". You just can't access it yet - but we'll fix that next! Also note that the Step-by-step guide to installing Subversion includes a few hints on how to get su svnowner to work if you get an error message.

To get Apache to serve your subversion repository, append this to /opt/etc/apache2/httpd.conf:

# Subversion
Include etc/apache2/conf.d/mod_dav_svn.conf

You will have to create /opt/etc/apache2/conf.d/mod_dav_svn.conf of course:

LoadModule dav_svn_module     libexec/mod_dav_svn.so
LoadModule authz_svn_module   libexec/mod_authz_svn.so

<Location /svn>
   DAV svn
   SVNParentPath /volume1/svn
   AuthType Basic
   AuthName "Subversion repository"
   AuthUserFile /volume1/svn/svn-auth-file
   Require valid-user
</Location>

This expects a file /volume1/svn/svn-auth-file to be present. That file contains the passwords used for Subversion (and, via Apache, Trac authentication). This file is generated with apaches htpasswd -c command. I used the cm too, to force md5 hashing of the passwords.

Trac

So far, this guide is much the same as the one it replaces. The new stuff is in the version of python used:

ipkg install python26 py26-trac py26-genshi py26-setuptools sqlite svn-py

The need for upgrading comes from the package svn-py, which is now python 2.6.

Create a share (using the DSM) called "trac-env". This is where we will keep our trac installations.

I am now running Trac version 0.12. The Trac environments can be found at /volume1/trac-env. They are called "MYTRAC1" and "MYTRAC2". Access to them is via the following URLs:

Trac is run using tracd. This is configured to start automatically on system start by creation of the following file: /opt/etc/init.d/S81trac:

#!/bin/sh
# run tracd
/opt/bin/tracd -d -p 8888 -e /volume1/trac-env

The file must be set to executable for root (see documentation for chmod), so that it will be run at system start.

Trac configuration files can be found at

  • /volume1/trac-env/MYTRAC1/conf/trac.ini
  • /volume1/trac-env/MYTRAC2/conf/trac.ini

Authentication via Apache

appended following lines to /opt/etc/apache2/httpd.conf:

# Subversion
Include etc/apache2/conf.d/trac.conf

And then, create the file /opt/etc/apache2/conf.d/trac.conf:

# allow Apache to be used for authenticating Trac
<Directory /opt/share/www/trac>
   AuthType Basic
   AuthName "Subversion repository"
   AuthUserFile /volume1/svn/svn-auth-file
   Require valid-user
</Directory>

Authentication is done with the AccountManager plugin for trac (http://trac-hacks.org/wiki/AccountManagerPlugin). The website has a good explanation on how that works. See the copies of the trac.ini files for reference. I'd like to point out the last section:

[account-manager]
password_store = HttpAuthStore
authentication_url = http://myserver.example.com:8000/trac

This is where we tell Trac to use our Apache Server for authentication. Note how the authentication_url is the same as the Directory in trac.conf.

Backup

Backup is still the same as in the previous post, so I'll just point you there: http://darenatwork.blogspot.com/2011/03/how-to-install-trac-on-synology-ds209ii.html

Friday, August 12, 2011

Using the Snoop Objects dialog from RevitLookup in RevitPythonShell

One of my main goals with RevitPythonShell is to provide a handy environment for exploring the Autodesk Revit API. But anyone seriously interested in exploring the Revit API should also be familiar with RevitLookup, formally known as RvtMgdDbg. This tool can be found in the SDK and includes a handy command for "snooping" a selected element called "Snoop Current Selection...". I use this so often, that I created a keyboard shortcut to invoke it. It provides vital information for working with Revit objects - information you probably want access to while inside an interactive RPS shell!

Unfortunately, you can't execute plugins while inside the RPS shell. Well, not through the Revit UI. You can access them via code. This blog is about a simple little module I wrote that let's you do this anywhere inside the shell:

>>>import revitsnoop

>>>snooper = revitsnoop.RevitSnoop(__revit__)

>>>snooper.snoop(doc.ProjectInformation)

And have a dialog like this popped up:

http://dl.dropbox.com/u/8112069/20110812-snoop-objects-dialog.png

Notice how you specify the object you want to snoop in code, as opposed to having to select it in the UI. Pretty nifty, eh?

How it works

The Autodesk.Revit.UI.UIApplication object has a property LoadedApplications that returns a list of all loaded external applications. As long as you have RevitLookup installed (quick test: can you find it in the Add-Ins tab?), it will show up in this list. It is pretty easy to find it:

rlapp = [app for app in uiApplication.LoadedApplications
         if app.GetType().Namespace == 'RevitLookup'
         and app.GetType().Name == 'App'][0]

I can't test with isinstance, since RPS doesn't know about RevitLookup yet and therefore I can't name the type. That is why I used GetType() and compared the namespace and name of the type.

Once we have the type, though, we can load the assembly and import RevitLookup as a module into IronPython:

import clr
clr.AddReference(rlapp.GetType().Assembly)
import RevitLookup

The statement import RevitLookup will only work after adding a reference to the assembly, but afterwards, we can start using the classes inside. To figure out what had to be done, I checked the source for RevitLookup, specifically the ExternalCommand that implements the "Snoop Current Selection..." functionality: RevitLookup.CmdSnoopModScope. It would have been possible to create an instance of this class and call its Execute method, but it turned out to be quicker and easier to just implement the body myself:

# See note in CollectorExt.cs in the RevitLookup source:
RevitLookup.Snoop.CollectorExts.CollectorExt.m_app = __revit__

# create a list of elements to snoop
elementSet = ElementSet()
elementSet.Insert(doc.ProjectInformation)

# create and show the dialog
form = RevitLookup.Snoop.Forms.Objects(elementSet)
form.ShowDialog()

This code is wrapped up in a module called revitsnoop for your convenience in the RevitPythonShell download section. To use it, place it inside one of the search paths configured in RPS.

The Code

For completeness sake, here is the whole module:

'''
revitsnoop.py

a simple library to access the RevitLookup snoop functionality
while inside the interactive shell...
'''
import clr
from Autodesk.Revit.DB import ElementSet

class RevitSnoop(object):
    def __init__(self, uiApplication):
        '''
        for RevitSnoop to function properly, it needs to be instantiated
        with a reverence to the Revit Application object.
        '''
        # find the RevitLookup plugin
        rlapp = [app for app in uiApplication.LoadedApplications
                 if app.GetType().Namespace == 'RevitLookup'
                 and app.GetType().Name == 'App'][0]
        # tell IronPython about the assembly of the RevitLookup plugin
        clr.AddReference(rlapp.GetType().Assembly)
        import RevitLookup
        self.RevitLookup = RevitLookup
        # See note in CollectorExt.cs in the RevitLookup source:
        self.RevitLookup.Snoop.CollectorExts.CollectorExt.m_app = uiApplication

    def snoop(self, element):
        elementSet = ElementSet()
        elementSet.Insert(element)
        form = self.RevitLookup.Snoop.Forms.Objects(elementSet)
        form.ShowDialog()

I think a logical further improvement would be to add some methods (e.g. snoopDocument and snoopApplication) for accessing the other useful functions of RevitLookup. But by now you should already know how to do it yourself!

Thursday, June 30, 2011

Wall.Orientation Property for walls based on arcs in Autodesk Revit 2011

This is what the documentation for the Wall.Orientation property in Autodesk Revit 2011 says:

The normal vector projected from the exterior side of the wall.

While this is tried and tested, it is only half of the story. I don't know the whole story, but this post is about one exception i found: Walls based on an arc.

Repro steps:

  1. create a new Revit document
  2. in the ribbon, on the "Home" tab click "Wall"
  3. in the "Draw" panel, choose the icon "Start-End-Radius Arc"
  4. create a wall by specifying the start, the end... well, you get it.

So, we now have a wall that isn't planar:

http://dl.dropbox.com/u/8112069/20110630-start-end-radius-arc.png

What is its orientation? What do you think it should be? Let's ask Revit! I'm going to use the RevitPythonShell (RPS) to query this wall, so we can see interactivly what is going on:

>>>collector = FilteredElementCollector(doc)

>>>wall = collector.OfClass(Wall).ToElements()[0]

>>>wall.Orientation

<Autodesk.Revit.DB.XYZ object at 0x000000000000002F [(-0.858104216, 0.513475563, 0.000000000)]>

I was expecting the orientation to be due North: (0, 1, 0). That is where the orientation handle bars are shown. But that is not what we get. What we get is a vector from the curve center point to the start point of the arc:

>>>curve = wall.Location.Curve

>>>P0 = curve.get_EndPoint(0)

>>>M = curve.Center

>>>Vector(P0, M)

IronTextBoxControl error: name 'Vector' is not defined
>>>XYZ(P0.X - M.X, P0.Y - M.Y, P0.Z - M.Z)

<Autodesk.Revit.DB.XYZ object at 0x0000000000000030 [(-34.284776903, 20.515451146, 0.000000000)]>
>>>r = XYZ(P0.X - M.X, P0.Y - M.Y, P0.Z - M.Z)

>>>r.Normalize()

<Autodesk.Revit.DB.XYZ object at 0x0000000000000031 [(-0.858104216, 0.513475563, 0.000000000)]>
>>>

As you can see, the orientation has the same direction as a vector from the arc center to the start point of the arc. This is for walls that face away from the arc center. Let's flip the wall and see what happens:

>>>t.Start()

<Autodesk.Revit.DB.TransactionStatus object at 0x0000000000000032 [Started]>
>>>wall.flip()

>>>t.Commit()

<Autodesk.Revit.DB.TransactionStatus object at 0x0000000000000033 [Committed]>
>>>wall.Orientation

<Autodesk.Revit.DB.XYZ object at 0x0000000000000034 [(0.858104216, -0.513475563, 0.000000000)]>
>>>

As you see, the vector changed it's orientation too. So, for arcs with wall orientation facing the arc center, the Wall.Orientation property is the normalized vector from the arc start point to the arc center.