tag:blogger.com,1999:blog-92233521400907054742024-02-20T09:59:29.615+01:00Daren@WorkDaren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.comBlogger56125tag:blogger.com,1999:blog-9223352140090705474.post-91126369366879696722017-01-04T15:28:00.001+01:002017-01-04T15:28:02.367+01:00pyRevit is blowing my mind<p>I am so sorry I didn’t really let this sink in when I first heard about it: <a href="http://eirannejad.github.io/pyRevit/whatspyrevit/">Ehsan Iran-Nejad’s pyRevit</a></p>
<p>I don’t really have the time to figure out <em>all</em> the stuff it does, but one thing that really blew my mind is that you can update the ribbon panel <em>without restarting Revit</em>! I just <em>have</em> to find out how he does this! Especially, since I failed to do this a couple of years back. Maybe the API has changed, maybe there is some wizardry going on, but this is what the RevitPythonShell should have been like.</p>
<p>Adding scripts is as easy as creating a folder structure following a naming convention. I like this idea. I love this idea. Hm… I wonder if we can merge some of these ideas back into RPS?</p>
<p>So… If you haven’t done this yet, go over to <a href="http://eirannejad.github.io/pyRevit/installation/">pyRevit and give it a spin</a>!</p>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com1tag:blogger.com,1999:blog-9223352140090705474.post-29783757562481227922016-12-05T10:24:00.001+01:002016-12-05T10:24:56.812+01:00Update to "Embedding a webserver in Autodesk Revit with the RevitPythonShell"<p>As <a href="https://groups.google.com/d/topic/revitpythonshell/2iJe1Lenm30/discussion">discussed on the RevitPythonShell forum</a>, previous examples of using <code>ExternalEvent</code> don’t work anymore with Revit 2017. </p>
<p>Turns out, the fix was really easy: You just need to implement the <code>IExternalEventHandler.GetName</code> method. I have placed a comment explaining this very briefly on the page that introduces <a href="https://darenatwork.blogspot.ch/2015/06/embedding-webserver-in-autodesk-revit_30.html">embedding a webserver in Autodesk Revit with the RevitPythonShell</a>. The source for the updated <code>RpsHttpServer</code> is, of course, on GitHub: <a href="https://github.com/daren-thomas/rps-sample-scripts/blob/master/StartupScripts/RpsHttpServer/rpshttpserver.py">rpshttpserver.py</a></p>
<p>Thanks to Callum for finding the bug and @PMoureu for providing the solution :)</p>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com2tag:blogger.com,1999:blog-9223352140090705474.post-29622997680415560372016-11-16T16:05:00.001+01:002016-11-22T11:18:29.297+01:00How to write in the URL field of a family with the RevitPythonShell<p>Here is some code showing how to write the URL parameter of a <code>WallType</code> with the <a href="https://github.com/architecture-building-systems/revitpythonshell">RevitPythonShell</a>. This was created as an answer to the <a href="https://groups.google.com/forum/#!forum/revitpythonshell">RevitPythonShell group forum</a>. This method should work for other families as well!</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-comment"># find a WallType to work with...</span>
collector = FilteredElementCollector(doc).OfClass(WallType)
wt = [wt <span class="hljs-keyword">for</span> wt <span class="hljs-keyword">in</span> collector
<span class="hljs-keyword">if</span> Element.Name.__get__(wt).startswith(<span class="hljs-string">'_dpv AW1'</span>)][<span class="hljs-number">0</span>]
<span class="hljs-keyword">print</span> Element.Name.__get__(wt)
parameter = wt.get_Parameter(BuiltInParameter.ALL_MODEL_URL)
transaction = Transaction(doc, <span class="hljs-string">'setting URL'</span>)
<span class="hljs-keyword">try</span>:
transaction.Start()
parameter.Set(<span class="hljs-string">'http://systems.arch.ethz.ch'</span>)
transaction.Commit()
<span class="hljs-keyword">except</span>:
transaction.Rollback()</code></pre>
<p>The tricky part is the line with <code>wt.get_Parameter(BuiltInParameter.ALL_MODEL_URL)</code>. I found the <code>ALL_MODEL_URL</code> parameter name using the <a href="https://github.com/jeremytammik/RevitLookup">RevitLookup tool</a> - which can be launched directly from RPS if you have it installed (just type <code>lookup(wt)</code>). The basic steps are:</p>
<ol>
<li>retrieve the parameter (<code>Element.get_Parameter</code> - you can use the <code>BuiltInParameter</code> enumeration for standard parameters…)</li>
<li>set the value of the parameter (<code>Parameter.Set</code>)</li>
<li>since this changes the BIM model, you need to wrap it all in a <code>Transaction</code>.</li>
</ol>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-35352971589938981272016-10-24T15:38:00.001+02:002016-10-24T15:38:44.556+02:00Merged PR #42 by @eirannejad to RevitPythonShell<p>This is exciting: I just merged a <a href="https://github.com/architecture-building-systems/revitpythonshell/pull/42">pull request on the RevitPythonShell repository</a>. Sure, no big deal, <em>except</em>, it kinda <em>is</em>. Here’s why:</p>
<p>I don’t really have a lot of time to improve the RPS right now as I am mainly working on a totally unrelated project, the <a href="https://github.com/architecture-building-systems/CEAforArcGIS">CityEnergyAnalyst</a>. And I don’t really BIM much anymore.</p>
<p>Turns out, the RPS has a fan base and they’re stepping up with feature requests, but also improvements in the form of pull requests.</p>
<p>A pull request is a collection of commits that you propose to add to the master branch of the RPS software. You can fork your own version of the RPS and with this feature, request your changes be added back to the main version. This is probably the best way to get changes / bug fixes etc. into the software.</p>
<p>In this case, <a href="https://github.com/eirannejad">Ehsan Iran-Nejad</a> stepped up and provided a small patch that adds the folder an external script is defined in to the search paths of the interpreter. This makes it easier to split your code into reusable libraries.</p>
<p>BTW: Check out Ehsan’s awesome project <a href="https://github.com/eirannejad/pyRevit">pyRevit</a> which makes working with RPS even easier!</p>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-35682809395129550392016-10-21T11:12:00.001+02:002016-10-21T11:14:49.191+02:00An example in refactoring the CEA<p>This post is the result of a refactoring effort between Shanshan Hsieh and I for the <a href="https://github.com/architecture-building-systems/CEAforArcGIS">City Energy Analyst</a>, particularly on the <a href="https://github.com/architecture-building-systems/CEAforArcGIS/pull/371">pull request #371</a>.</p>
<p>Compare these two snippets of code:</p>
<h2 id="original">Original</h2>
<pre class="prettyprint"><code class="language-python hljs ">tHC_corr = [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>]
delta_ctrl = [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>]
<span class="hljs-comment"># emission system room temperature control type</span>
<span class="hljs-keyword">if</span> control_system == <span class="hljs-string">'T1'</span>:
delta_ctrl = [<span class="hljs-number">2.5</span>, -<span class="hljs-number">2.5</span>]
<span class="hljs-keyword">elif</span> control_system == <span class="hljs-string">'T2'</span>:
delta_ctrl = [<span class="hljs-number">1.2</span>, -<span class="hljs-number">1.2</span>]
<span class="hljs-keyword">elif</span> control_system == <span class="hljs-string">'T3'</span>:
delta_ctrl = [<span class="hljs-number">0.9</span>, -<span class="hljs-number">0.9</span>]
<span class="hljs-keyword">elif</span> control_system == <span class="hljs-string">'T4'</span>:
delta_ctrl = [<span class="hljs-number">1.8</span>, -<span class="hljs-number">1.8</span>]
<span class="hljs-comment"># calculate temperature correction</span>
<span class="hljs-keyword">if</span> heating_system == <span class="hljs-string">'T1'</span>:
tHC_corr[<span class="hljs-number">0</span>] = delta_ctrl[<span class="hljs-number">0</span>] + <span class="hljs-number">0.15</span>
<span class="hljs-keyword">elif</span> heating_system == <span class="hljs-string">'T2'</span>:
tHC_corr[<span class="hljs-number">0</span>] = delta_ctrl[<span class="hljs-number">0</span>] - <span class="hljs-number">0.1</span>
<span class="hljs-keyword">elif</span> heating_system == <span class="hljs-string">'T3'</span>:
tHC_corr[<span class="hljs-number">0</span>] = delta_ctrl[<span class="hljs-number">0</span>] - <span class="hljs-number">1.1</span>
<span class="hljs-keyword">elif</span> heating_system == <span class="hljs-string">'T4'</span>:
tHC_corr[<span class="hljs-number">0</span>] = delta_ctrl[<span class="hljs-number">0</span>] - <span class="hljs-number">0.9</span>
<span class="hljs-keyword">else</span>:
tHC_corr[<span class="hljs-number">0</span>] = <span class="hljs-number">0</span>
<span class="hljs-keyword">if</span> cooling_system == <span class="hljs-string">'T1'</span>:
tHC_corr[<span class="hljs-number">1</span>] = delta_ctrl[<span class="hljs-number">1</span>] + <span class="hljs-number">0.5</span>
<span class="hljs-keyword">elif</span> cooling_system == <span class="hljs-string">'T2'</span>: <span class="hljs-comment"># no emission losses but emissions for ventilation</span>
tHC_corr[<span class="hljs-number">1</span>] = delta_ctrl[<span class="hljs-number">1</span>] + <span class="hljs-number">0.7</span>
<span class="hljs-keyword">elif</span> cooling_system == <span class="hljs-string">'T3'</span>:
tHC_corr[<span class="hljs-number">1</span>] = delta_ctrl[<span class="hljs-number">1</span>] + <span class="hljs-number">0.5</span>
<span class="hljs-keyword">else</span>:
tHC_corr[<span class="hljs-number">1</span>] = <span class="hljs-number">0</span>
<span class="hljs-keyword">return</span> tHC_corr[<span class="hljs-number">0</span>], tHC_corr[<span class="hljs-number">1</span>]</code></pre>
<h2 id="refactored">Refactored</h2>
<pre class="prettyprint"><code class="language-python hljs ">control_delta_heating = {<span class="hljs-string">'T1'</span>: <span class="hljs-number">2.5</span>, <span class="hljs-string">'T2'</span>: <span class="hljs-number">1.2</span>, <span class="hljs-string">'T3'</span>: <span class="hljs-number">0.9</span>, <span class="hljs-string">'T4'</span>: <span class="hljs-number">1.8</span>}
control_delta_cooling = {<span class="hljs-string">'T1'</span>: -<span class="hljs-number">2.5</span>, <span class="hljs-string">'T2'</span>: -<span class="hljs-number">1.2</span>, <span class="hljs-string">'T3'</span>: -<span class="hljs-number">0.9</span>, <span class="hljs-string">'T4'</span>: -<span class="hljs-number">1.8</span>}
system_delta_heating = {<span class="hljs-string">'T0'</span>: <span class="hljs-number">0.0</span>, <span class="hljs-string">'T1'</span>: <span class="hljs-number">0.15</span>, <span class="hljs-string">'T2'</span>: -<span class="hljs-number">0.1</span>, <span class="hljs-string">'T3'</span>: -<span class="hljs-number">1.1</span>, <span class="hljs-string">'T4'</span>: -<span class="hljs-number">0.9</span>}
system_delta_cooling = {<span class="hljs-string">'T0'</span>: <span class="hljs-number">0.0</span>, <span class="hljs-string">'T1'</span>: <span class="hljs-number">0.5</span>, <span class="hljs-string">'T2'</span>: <span class="hljs-number">0.7</span>, <span class="hljs-string">'T3'</span>: <span class="hljs-number">0.5</span>}
<span class="hljs-keyword">try</span>:
result_heating = <span class="hljs-number">0.0</span> <span class="hljs-keyword">if</span> heating_system == <span class="hljs-string">'T0'</span> <span class="hljs-keyword">else</span> (control_delta_heating[control_system] +
system_delta_heating[heating_system])
result_cooling = <span class="hljs-number">0.0</span> <span class="hljs-keyword">if</span> cooling_system == <span class="hljs-string">'T0'</span> <span class="hljs-keyword">else</span> (control_delta_cooling[control_system] +
system_delta_cooling[cooling_system])
<span class="hljs-keyword">except</span> KeyError:
<span class="hljs-keyword">raise</span> ValueError(
<span class="hljs-string">'Invalid system / control combination: %s, %s, %s'</span> % (heating_system, cooling_system, control_system))
<span class="hljs-keyword">return</span> result_heating, result_cooling</code></pre>
<p>Ideally, these two codes produce the same result. I would argue though, that by using a table-based method, that is, keeping the data in lookup tables like <code>dict</code>s, makes the code a bit clearer to understand than using a lot of <code>if...elif...else</code> statements. This simplifies the code down to it’s core message: The result is the sum of the deltas for the control system and the system type.</p>
<p>Another improvement, in my mind, is getting rid of the <code>X[0]</code> and <code>X[1]</code> constructs. In the original code, these were used to group the heating and the cooling values respectively. While in a small snippet like this, that can still be understood, this paradigm breaks down quickly. One way to improve that would be to assign <code>HEATING = 0</code> and <code>COOLING = 1</code> and then index like this: <code>tHC_corr[HEATING]</code>. That communicates the intention much better. Using separate dictionaries for heating and cooling values for both emission systems and control systems sidesteps the issue alltogether.</p>
<h2 id="documentation">Documentation</h2>
<p>@shanshan and I went a step further than just changing the code. Here is a model of what the documentation of the function could look like:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-string">"""
Model of losses in the emission and control system for space heating and cooling.
Correction factor for the heating and cooling setpoints. Extracted from EN 15316-2
(see cea\databases\CH\Systems\emission_systems.xls for valid values for the heating and cooling system values)
T0 means there's no heating/cooling systems installed, therefore, also no control systems for heating/cooling.
In short, when the input system is T0, the output set point correction should be 0.0.
So if there is no cooling systems, the setpoint_correction_for_space_emission_systems function input: (T1, T0, T1) (type_hs, type_cs, type_ctrl),
return should be (2.65, 0.0), the control system is only specified for the heating system.
In another case with no heating systems: input: (T0, T3, T1) return: (0.0, -2.0), the control system is only
specified for the heating system.
PARAMETERS
----------
:param heating_system: The heating system used. Valid values: T0, T1, T2, T3, T4
:type heating_system: str
:param cooling_system: The cooling system used. Valid values: T0, T1, T2, T3
:type cooling_system: str
:param control_system: The control system used. Valid values: T1, T2, T3, T4 - as defined in the
contributors manual under Databases / Archetypes / Building Properties / Mechanical systems.
T1 for none, T2 for PI control, T3 for PI control with optimum tuning, and T4 for room temperature control
(electromagnetically/electronically).
:type control_system: str
RETURNS
-------
:returns: two delta T to correct the set point temperature, dT_heating, dT_cooling
:rtype: tuple(double, double)
"""</span></code></pre>
<p>Note: The documentation explains what the function is for and also mentions the relevant standards (EN 15316-2) - this could also be references to research papers or whatever. Further, the input values and types are explained and also a link to the documentation for reference. A list of valid inputs is given.</p>
<p>What is missing? Boundary case behaviour - this should actually also be in the documentation! We test this behaviour in the next section:</p>
<h2 id="unit-tests">Unit tests</h2>
<p>Check out the file <code>tests/test_sensible_loads.py</code>:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-keyword">import</span> unittest
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestCorrectionFactorForHeatingAndCoolingSetpoints</span><span class="hljs-params">(unittest.TestCase)</span>:</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_calc_t_em_ls_raises_ValueError</span><span class="hljs-params">(self)</span>:</span>
<span class="hljs-keyword">from</span> cea.demand.sensible_loads <span class="hljs-keyword">import</span> setpoint_correction_for_space_emission_systems
self.assertRaises(ValueError, setpoint_correction_for_space_emission_systems, heating_system=<span class="hljs-string">'T1'</span>,
cooling_system=<span class="hljs-string">'T1'</span>, control_system=<span class="hljs-keyword">None</span>)
self.assertRaises(ValueError, setpoint_correction_for_space_emission_systems, heating_system=<span class="hljs-string">'T1'</span>,
cooling_system=<span class="hljs-string">'XYZ'</span>, control_system=<span class="hljs-string">'T1'</span>)
self.assertRaises(ValueError, setpoint_correction_for_space_emission_systems, heating_system=<span class="hljs-string">'T1'</span>,
cooling_system=<span class="hljs-keyword">None</span>, control_system=<span class="hljs-string">'T1'</span>)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_calc_t_em_ls_T0</span><span class="hljs-params">(self)</span>:</span>
<span class="hljs-keyword">from</span> cea.demand.sensible_loads <span class="hljs-keyword">import</span> setpoint_correction_for_space_emission_systems
self.assertEqual(setpoint_correction_for_space_emission_systems(<span class="hljs-string">'T1'</span>, <span class="hljs-string">'T0'</span>, <span class="hljs-string">'T1'</span>), (<span class="hljs-number">2.65</span>, <span class="hljs-number">0.0</span>))
self.assertEqual(setpoint_correction_for_space_emission_systems(<span class="hljs-string">'T0'</span>, <span class="hljs-string">'T3'</span>, <span class="hljs-string">'T1'</span>), (<span class="hljs-number">0.0</span>, -<span class="hljs-number">2.0</span>))
</code></pre>
<p>Right now, it contains a single class, <code>TestCorrectionFactorForHeatingAndCoolingSetpoints</code> that inherits from <code>unittest.TestCase</code>. When the Jenkins runs, it will pick up this class, a) because the filename starts with <code>test_</code> and b) because the classes inherit from <code>TestCase</code>. The Jenkins will then run all the test cases. Each method starting with <code>test_</code> is run and checked. The <code>self.assert*</code> calls test expected values and actual computations of the <code>setpoint_correction_for_space_emission_systems</code> function. We can see that:</p>
<ul>
<li>calling the method with <code>None</code> or <code>'XYZ'</code> as one of the parameters should raise an instance of <code>ValueError</code> - this is an edge case that is being tested</li>
<li>the setpoint correction for heating or cooling emission systems of type <code>'T0'</code> (no emission system) should be <code>0.0</code></li>
</ul>
<p>Unit tests like this are a great way to describe how a function <em>should</em> behave, independant of the implementation. It also represents another “mode” of thinking, where you consider edge cases, expected values, failure modes etc. at a very low level. At the level of a “unit” of code.</p>
<p>I believe that especially for code like the CEA, we should use this technique to specify the expected behavior of the system, as we will be on the hook for bugs for the next decade or so. We want to be able to prove the correctnes of our implementation!</p>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-37558769533453297352016-06-22T15:51:00.001+02:002016-06-22T15:51:11.195+02:00RevitPythonShell for Revit 2017<p>I have just released a version of <a href="https://github.com/architecture-building-systems/revitpythonshell/releases/tag/2017.06.22">RPS for Revit 2017</a>. It is labeled as a “Pre-Release”, since I have not really had time to test it, but you are all welcome to give it a spin and tell me about any problems you find.</p>
<p>Better yet: Send me pull requests with fixes to your problems!</p>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com3tag:blogger.com,1999:blog-9223352140090705474.post-56419428914027213272015-10-20T14:55:00.001+02:002015-10-20T14:56:44.053+02:00Accessing specific overloads in IronPython (Document.LoadFamily)The RevitPythonShell makes working with Revit a bit easier, because, you know, Python. The specific flavour of Python used is IronPython - a port of the Python language to the .NET platform. IronPython lets you call into .NET objects seamlessly. In theory. Except when it doesen’t. <a href="http://www.joelonsoftware.com/articles/LeakyAbstractions.html">All abstractions are leaky</a>.<br />
This article is all about a specific leak, based on an impedance mismatch between the object model used in .NET and that used in Python: In C#, you can <a href="https://msdn.microsoft.com/en-us/library/ms229029%28v=vs.110%29.aspx">overload a method</a>. A simple way to think about this is to realize that the name of the method includes its <em>signature</em>, the list of parameter (types) it takes. Go read a book if you want the gory details. Any book. I’m just going to get down to earth here and talk about a specific example:<br />
The <a href="http://revitapisearch.com/html/2966229b-60b0-404d-5ffe-e4c4d85d2d7a.htm"><code>Document.LoadFamily</code></a> method.<br />
The standard method for selecting a specific overload is just calling the function and having IronPython figure it out.<br />
I guess the place to read up on how to call overloaded methods is here: <a href="http://ironpython.net/documentation/dotnet/dotnet.html#method-overloads">http://ironpython.net/documentation/dotnet/dotnet.html#method-overloads</a>. To quote: <br />
<blockquote>
When IronPython code calls an overloaded method, IronPython tries to select one of the overloads at runtime based on the number and type of arguments passed to the method, and also names of any keyword arguments.</blockquote>
This works really well if the types passed in match the signatures of a specific method overload well. IronPython will try to automatically convert types, but will fail with a <code>TypeError</code> if more than one method overload matches.<br />
The <code>Document.LoadFamily</code> method is special in that one of its parameters is marked as <code>out</code> in .NET - according to the standard IronPython documentation (REF) that should translate into a tuple of return values - and it does, if you know how. It is just non-intuitive - see <a href="http://stackoverflow.com/questions/31471089/how-to-pick-the-right-loadfamily-function-in-revitpythonshell">this question on Stack Overflow</a>:<br />
<blockquote>
revitpythonshell provides two very similar methods to load a family.<br />
<pre class="prettyprint"><code class="language-python hljs ">LoadFamily(self: Document, filename:str) -> (bool, Family)
LoadFamily(self: Document, filename:str) -> bool</code></pre>
So it seems like only the return values are different. I have tried to calling it in several different ways:<br />
(success, newFamily) = doc.LoadFamily(path) <br />
success, newFamily = doc.LoadFamily(path) <br />
o = doc.LoadFamily(path) <br />
But I always just get a bool back. I want the Family too.</blockquote>
What is happening here is that the c# definitions of the method are:<br />
<pre class="prettyprint"><code class="language-c# hljs cs"><span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">LoadFamily</span>(
<span class="hljs-keyword">string</span> filename
)</code></pre>
and<br />
<pre class="prettyprint"><code class="language-c# hljs cs"><span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">LoadFamily</span>(
<span class="hljs-keyword">string</span> filename,
<span class="hljs-keyword">out</span> Family family
)</code></pre>
The IronPython syntax candy for <code>out</code> parameters, returning a tuple of results, can’t automatically be selected here, because calling <code>LoadFamily</code> with just a string argument matches the first method overload. <br />
You can get at the overload you are looking for like this:<br />
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-keyword">import</span> clr
family = clr.Reference[Family]()
<span class="hljs-comment"># family is now an Object reference (not set to an instance of an object!)</span>
success = doc.LoadFamily(path, family) <span class="hljs-comment"># explicitly choose the overload</span>
<span class="hljs-comment"># family is now a Revit Family object and can be used as you wish</span></code></pre>
This works by creating an object reference to pass into the function and the method overload resultion thingy now knows which one to look for.<br />
Working under the assumption that the list of overloads shown in the RPS help is the same order as they appear, you can also do this:<br />
<pre class="prettyprint"><code class="language-python hljs ">success, family = doc.LoadFamily.Overloads.Functions[<span class="hljs-number">0</span>](path)</code></pre>
and that will, indeed, return a tuple <code>(bool, Autodesk.Revit.DB.Family)</code>. I just don’t think you should be doing it that way, as it introduces a dependency on the order of the method overloads - I wouldn’t want that smell in my code…<br />
Note, that this has to happen inside a transaction, so a complete example might be:<br />
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-keyword">import</span> clr
t = Transaction(doc, <span class="hljs-string">'loadfamily'</span>)
t.Start()
<span class="hljs-keyword">try</span>:
family = clr.Reference[Family]()
success = doc.LoadFamily(path, family)
<span class="hljs-comment"># do stuff with the family</span>
t.Commit()
<span class="hljs-keyword">except</span>:
t.Rollback()</code></pre>
Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com1tag:blogger.com,1999:blog-9223352140090705474.post-62562825116515944882015-07-07T15:54:00.001+02:002015-07-07T15:54:22.004+02:00The __file__ variable in RevitPythonShell<p>Today I’m going to talk about a special builtin variable <code>__file__</code> as it is implemented in the <a href="https://github.com/architecture-building-systems/revitpythonshell">RevitPythonShell</a>. This feature is only available to <a href="https://github.com/architecture-building-systems/revitpythonshell/wiki/External-Scripts">external scripts</a> <br>
and <a href="https://github.com/architecture-building-systems/revitpythonshell/wiki/What-is-a-RpsAddin">RpsAddins</a>. This is similar to how <code>__file__</code> is normally defined for Python: It is not defined in the REPL.</p>
<p>Simply put: <code>__file__</code> contains the path to the current file being run.</p>
<p>With external scripts, this is a path to a python script. With RpsAddins, it is the path to the addin’s DLL, a path separator, and the name of the script (e.g. <code>C:\Program Files (x86)\MyAddin\MyAddin.dll\helloworld.py</code>).</p>
<p>Suppose you have deployed some scripts to another computer. Suppose those scripts rely on other files - a database, maybe, or icons, pictures, anything really. If you keep referencing these files as <code>C:\Users\Gareth\AwesomeScripts\all_my_data.sqlite</code> you are going to run into difficulties on a computer that doesn’t belong to Gareth. Meg’s computer won’t know how to find the database! And she is going to complain to Meg and meg is going to complain to her boss and her boss is going to complain to your boss and your boss is going to go get coffee, lock himself up in his office and brood for a very long time. Then, he’s going to send someone to tell you to come to his office ASAP. Now!</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/GjJCdCXFslY" allowfullscreen=""></iframe>
<p>You don’t want that to happen! That is why you’re going to make sure that all file references are <em>relative</em> to the installation of your external scripts / RpsAddins. And unless you have <em>a priori</em> knowledge of the folder name you’re installing to, well, I’ve got your back:</p>
<pre class="prettyprint"><code class=" hljs python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_folder</span><span class="hljs-params">()</span>:</span>
<span class="hljs-keyword">import</span> os
<span class="hljs-comment"># assume external script</span>
folder = os.path.dirname(__file__)
<span class="hljs-keyword">if</span> folder.lower().endswith(<span class="hljs-string">'.dll'</span>):
<span class="hljs-comment"># nope - RpsAddin</span>
folder = os.path.dirname(folder)
<span class="hljs-keyword">return</span> folder</code></pre>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com5tag:blogger.com,1999:blog-9223352140090705474.post-62669078803010258072015-06-30T13:40:00.001+02:002016-12-05T10:30:27.804+01:00Embedding a webserver in Autodesk Revit with the RevitPythonShell<p>This is a more elaborate example that shows how to embedd a webserver in Autodesk Revit and use it to automate tasks.</p>
<p>How do you access the BIM from outside Revit? With the Revit API it is easy to access the outside world from <em>within</em> Revit. Sometimes you want to write software that needs to read a schedule from a <code>.rvt</code> document - from <em>outside</em> of Revit.</p>
<p>As an example, say you have a shell script that reads in schedule data from a Revit document and saves it to a CSV file.</p>
<p>One way to solve this is to have Revit act as a web server, say, <a href="http://localhost:8080">http://localhost:8080</a>. You could then use curl:</p>
<pre class="prettyprint"><code class=" hljs cs">curl http:<span class="hljs-comment">//localhost:8080/schedules/my_schedule_name > my_local_file_name.csv</span></code></pre>
<p>Let us build a RevitPythonShell script that allows you to do just that: Export any schedule in the BIM as a CSV file through a web service. Depending on the URL requested, you could return a screenshot of the current view or ways to open / close documents:</p>
<pre class="prettyprint"><code class=" hljs ruby">curl <span class="hljs-symbol">http:</span>/<span class="hljs-regexp">/localhost:8080/screenshot</span>
curl <span class="hljs-symbol">http:</span>/<span class="hljs-regexp">/localhost:8080/open</span><span class="hljs-regexp">/Desktop/</span><span class="hljs-constant">Project1</span>.rvt</code></pre>
<p>This is a variation on the <a href="http://thebuildingcoder.typepad.com/blog/2010/04/asynchronous-api-calls-and-idling.html">non-modal dialog issue</a> (<a href="http://thebuildingcoder.typepad.com/blog/2013/12/replacing-an-idling-event-handler-by-an-external-event.html">see here too!</a>). We want to run a web server in a separate thread, but have handling requests run in the main Revit thread so that we have access to the API. We will be using an <a href="http://help.autodesk.com/view/RVT/2016/ENU/?guid=GUID-0A0D656E-5C44-49E8-A891-6C29F88E35C0">external event</a> to solve this.</p>
<p>The web server itself uses the <a href="https://msdn.microsoft.com/en-us/library/system.net.httplistener.aspx"><code>HttpListener</code></a>class, which runs in a separate thread and just waits for new connections. These are then handled by pushing them into a queue and notifying the <code>ExternalEvent</code> that a new event has happened. </p>
<p>This is where the script starts:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span><span class="hljs-params">()</span>:</span>
contexts = ContextQueue()
eventHandler = RpsEventHandler(contexts)
externalEvent = ExternalEvent.Create(eventHandler)
server = RpsServer(externalEvent, contexts)
serverThread = Thread(ThreadStart(server.serve_forever))
serverThread.Start()</code></pre>
<p>Whoa! What is going on here?</p>
<ul>
<li>a communication channel <code>contexts</code> is created for sending web requests (stashed as <code>HttpListenerContext</code> instances) to the <code>ExternalEvent</code> thread.</li>
<li>an <code>IExternalEventHandler</code> implementation called <code>RpsEventHandler</code> that handles producing the output.</li>
<li>a web server wrapped in a method <code>serve_forever</code> that listens for web requests with the <code>HttpListener</code>, stores them into the context queue and notifies the external event that there is work to be done.</li>
</ul>
<p>We’ll look into each component one by one below. Note: The full code can be found here in the <a href="https://github.com/daren-thomas/rps-sample-scripts/blob/master/StartupScripts/RpsHttpServer/rpshttpserver.py">rps-sample-scripts GitHub repository</a>.</p>
<p>Let’s start with the <code>ContextQueue</code>:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ContextQueue</span><span class="hljs-params">(object)</span>:</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self)</span>:</span>
<span class="hljs-keyword">from</span> System.Collections.Concurrent <span class="hljs-keyword">import</span> ConcurrentQueue
self.contexts = ConcurrentQueue[HttpListenerContext]()
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__len__</span><span class="hljs-params">(self)</span>:</span>
<span class="hljs-keyword">return</span> len(self.contexts)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">append</span><span class="hljs-params">(self, c)</span>:</span>
self.contexts.Enqueue(c)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pop</span><span class="hljs-params">(self)</span>:</span>
success, context = self.contexts.TryDequeue()
<span class="hljs-keyword">if</span> success:
<span class="hljs-keyword">return</span> context
<span class="hljs-keyword">else</span>:
<span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">"can't pop an empty ContextQueue!"</span>)</code></pre>
<p>This is nothing speciall - just a thin wrapper arround <code>ConcurrentQueue</code> from the .NET library. The <code>RpsServer</code> will <code>append</code> to the <code>context</code> while the <code>RpsEventHandler</code> <code>pop</code>s the <code>context</code>. </p>
<p>A more interesting class to look at is probably <code>RpsEventHandler</code>:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RpsEventHandler</span><span class="hljs-params">(IExternalEventHandler)</span>:</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, contexts)</span>:</span>
self.contexts = contexts
self.handlers = {
<span class="hljs-string">'schedules'</span>: get_schedules
<span class="hljs-comment"># add other handlers here</span>
}
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">Execute</span><span class="hljs-params">(self, uiApplication)</span>:</span>
<span class="hljs-keyword">while</span> self.contexts:
context = self.contexts.pop()
request = context.Request
parts = request.RawUrl.split(<span class="hljs-string">'/'</span>)[<span class="hljs-number">1</span>:]
handler = parts[<span class="hljs-number">0</span>] <span class="hljs-comment"># FIXME: add error checking here!</span>
args = parts[<span class="hljs-number">1</span>:]
<span class="hljs-keyword">try</span>:
rc, ct, data = self.handlers[handler](args, uiApplication)
<span class="hljs-keyword">except</span>:
traceback.print_exc()
rc = <span class="hljs-number">404</span>
ct = <span class="hljs-string">'text/plain'</span>
data = <span class="hljs-string">'unknown error'</span>
response = context.Response
response.ContentType = ct
response.StatusCode = rc
buffer = Encoding.UTF8.GetBytes(data)
response.ContentLength64 = buffer.Length
output = response.OutputStream
output.Write(buffer, <span class="hljs-number">0</span>, buffer.Length)
output.Close()
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">GetName</span><span class="hljs-params">(self)</span>:</span>
<span class="hljs-keyword">return</span> <span class="hljs-string">'RpsHttpServer'</span></code></pre>
<p>The <code>Execute</code> method here does the grunt work of working with the .NET libraries and delegating requests to the specific handlers. You can extend this class can by adding new handlers to it. In fact, you don’t even need to extend the class to add handlers - just register them in the <code>handlers</code> dictionary.</p>
<p>Each handler takes a list of path elements and a <code>UIApplication</code> object. The handler runs in the Revit API context. It should return an HTTP error code, a content type and a string containing the response.</p>
<p>An example of such a handler is <code>get_schedules</code>:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_schedules</span><span class="hljs-params">(args, uiApplication)</span>:</span>
<span class="hljs-string">'''add code to get a specific schedule by name here'''</span>
<span class="hljs-keyword">print</span> <span class="hljs-string">'inside get_schedules...'</span>
<span class="hljs-keyword">from</span> Autodesk.Revit.DB <span class="hljs-keyword">import</span> ViewSchedule
<span class="hljs-keyword">from</span> Autodesk.Revit.DB <span class="hljs-keyword">import</span> FilteredElementCollector
<span class="hljs-keyword">from</span> Autodesk.Revit.DB <span class="hljs-keyword">import</span> ViewScheduleExportOptions
<span class="hljs-keyword">import</span> tempfile, os, urllib
doc = uiApplication.ActiveUIDocument.Document
collector = FilteredElementCollector(doc).OfClass(ViewSchedule)
schedules = {vs.Name: vs <span class="hljs-keyword">for</span> vs <span class="hljs-keyword">in</span> list(collector)}
<span class="hljs-keyword">if</span> len(args):
<span class="hljs-comment"># export a single schedule</span>
schedule_name = urllib.unquote(args[<span class="hljs-number">0</span>])
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> schedule_name.lower().endswith(<span class="hljs-string">'.csv'</span>):
<span class="hljs-comment"># attach a `.csv` to URL for browsers</span>
<span class="hljs-keyword">return</span> <span class="hljs-number">302</span>, <span class="hljs-keyword">None</span>, schedule_name + <span class="hljs-string">'.csv'</span>
schedule_name = schedule_name[:-<span class="hljs-number">4</span>]
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> schedule_name <span class="hljs-keyword">in</span> schedules.keys():
<span class="hljs-keyword">return</span> <span class="hljs-number">404</span>, <span class="hljs-string">'text/plain'</span>, <span class="hljs-string">'Schedule not found: %s'</span> % schedule_name
schedule = schedules[schedule_name]
fd, fpath = tempfile.mkstemp(suffix=<span class="hljs-string">'.csv'</span>)
os.close(fd)
dname, fname = os.path.split(fpath)
opt = ViewScheduleExportOptions()
opt.FieldDelimiter = <span class="hljs-string">', '</span>
schedule.Export(dname, fname, opt)
<span class="hljs-keyword">with</span> open(fpath, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> csv:
result = csv.read()
os.unlink(fpath)
<span class="hljs-keyword">return</span> <span class="hljs-number">200</span>, <span class="hljs-string">'text/csv'</span>, result
<span class="hljs-keyword">else</span>:
<span class="hljs-comment"># return a list of valid schedule names</span>
<span class="hljs-keyword">return</span> <span class="hljs-number">200</span>, <span class="hljs-string">'text/plain'</span>, <span class="hljs-string">'\n'</span>.join(schedules.keys())</code></pre>
<p>When you write your own handler functions, make sure to implement the function signature: <code>rc, ct, data my_handler_function(args, uiApplication)</code>.</p>
<p>In <code>get_schedules</code>, a <code>FilteredElementCollector</code> is used to find all <code>ViewSchedule</code> instances in the currently active document. Using a <a href="https://www.python.org/dev/peps/pep-0274/">dict comprehension</a> is a nifty way to quickly make a lookup table for checking the arguments.</p>
<p>The <code>args</code> parameter contains the components of the url after the first part, which is used to select the handler function. So if the requested URL were, say, <code>http://localhost:8080/schedules</code>, then <code>args</code> would be an empty list. In this case, we just return a list of valid schedule names, one per line - see the <code>else</code> at the bottom of the function.</p>
<p>If the URL were, say <code>http://localhost:8080/schedules/My%20Schedule%20Name</code>, then the <code>args</code> list would contain a single element, <code>"My%20Schedule%20Name"</code>. The <code>%20</code> encoding is a standard for URLs and is used to encode a space character. We use <code>urllib</code> to unquote the name.</p>
<p>In order to make the function work nicely with a browser, it is nice to have a <code>.csv</code> ending to it - we redirect to the same URL with a <code>.csv</code> tacked on if it is missing! The code for handling the redirect can be found in the <a href="https://github.com/daren-thomas/rps-sample-scripts/blob/master/StartupScripts/RpsHttpServer/rpshttpserver.py">full sample script on GitHub</a>. Notice how the HTTP return code 302 is used as the return value for <code>rc</code> - you can <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">look up all the HTTP return codes online</a>, we will only be using <a href="https://en.wikipedia.org/wiki/HTTP_200">200 (OK)</a>, <a href="https://en.wikipedia.org/wiki/HTTP_302">302 (Found - used for redirects)</a> and <a href="https://en.wikipedia.org/wiki/HTTP_404">404 (Not Found)</a>.</p>
<p>Next, the script checks to make sure the schedule name is a valid schedule in the document. A 404 return code is used to indicate an error here. </p>
<p>The actual code for returning a schedule makes use of a technique described in Jeremy Tammik’s blog post <a href="http://thebuildingcoder.typepad.com/blog/2012/05/the-schedule-api-and-access-to-schedule-data.html">The Schedule API and Access to Schedule Data</a>. The <code>ViewSchedule.Export</code> method is used to write the schedule to a temporary file in CSV format and then read back into memory before deleting the file on disk. This is a bit of a hack and coming up with a better solution is left as an exercise for the reader…</p>
<p>The final piece in our puzzle is the <code>RpsServer</code>:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RpsServer</span><span class="hljs-params">(object)</span>:</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, externalEvent, contexts, port=<span class="hljs-number">8080</span>)</span>:</span>
self.port = port
self.externalEvent = externalEvent
self.contexts = contexts
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">serve_forever</span><span class="hljs-params">(self)</span>:</span>
<span class="hljs-keyword">try</span>:
self.running = <span class="hljs-keyword">True</span>
self.listener = HttpListener()
prefix = <span class="hljs-string">'http://localhost:%i/'</span> % self.port
self.listener.Prefixes.Add(prefix)
<span class="hljs-keyword">try</span>:
<span class="hljs-keyword">print</span> <span class="hljs-string">'starting listener'</span>, prefix
self.listener.Start()
<span class="hljs-keyword">print</span> <span class="hljs-string">'started listener'</span>
<span class="hljs-keyword">except</span> HttpListenerException <span class="hljs-keyword">as</span> ex:
<span class="hljs-keyword">print</span> <span class="hljs-string">'HttpListenerException:'</span>, ex
<span class="hljs-keyword">return</span>
waiting = <span class="hljs-keyword">False</span>
<span class="hljs-keyword">while</span> self.running:
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> waiting:
context = self.listener.BeginGetContext(
AsyncCallback(self.handleRequest),
self.listener)
waiting = <span class="hljs-keyword">not</span> context.AsyncWaitHandle.WaitOne(<span class="hljs-number">100</span>)
<span class="hljs-keyword">except</span>:
traceback.print_exc()
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">stop</span><span class="hljs-params">(self)</span>:</span>
<span class="hljs-keyword">print</span> <span class="hljs-string">'stop()'</span>
self.running = <span class="hljs-keyword">False</span>
self.listener.Stop()
self.listener.Close()
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handleRequest</span><span class="hljs-params">(self, result)</span>:</span>
<span class="hljs-string">'''
pass the request to the RevitEventHandler
'''</span>
<span class="hljs-keyword">try</span>:
listener = result.AsyncState
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> listener.IsListening:
<span class="hljs-keyword">return</span>
<span class="hljs-keyword">try</span>:
context = listener.EndGetContext(result)
<span class="hljs-keyword">except</span>:
<span class="hljs-comment"># Catch the exception when the thread has been aborted</span>
self.stop()
<span class="hljs-keyword">return</span>
self.contexts.append(context)
self.externalEvent.Raise()
<span class="hljs-keyword">print</span> <span class="hljs-string">'raised external event'</span>
<span class="hljs-keyword">except</span>:
traceback.print_exc()</code></pre>
<p>This class implements the <code>serve_forever</code> function that starts an <code>HttpListener</code> on a specified port and uses <code>handleRequest</code> to pass any requests on to the external event for processing inside the Revit API context.</p>
<p><a href="https://github.com/daren-thomas/rps-sample-scripts/blob/master/StartupScripts/RpsHttpServer/rpshttpserver.py">Check the rpshttpserver.py example on GitHub.</a></p>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com1tag:blogger.com,1999:blog-9223352140090705474.post-61346996543270496322015-06-01T16:28:00.001+02:002015-06-01T16:28:00.929+02:00Using esoreader to parse EnergyPlus eso files<p>A short while ago I posted <a href="http://darenatwork.blogspot.ch/2014/12/a-short-tutorial-on-esoreader-module.html">a short tutorial on the esoreader module</a>. This post is an update, showing off the new <a href="http://pandas.pydata.org/">pandas</a> interface that makes life so much easier when exploring EnergyPlus output files.</p>
<p>The building simulation engine <a href="http://apps1.eere.energy.gov/buildings/energyplus/">EnergyPlus</a> stores its main output in a file with the ending ‘.eso’. This format makes it easy to log variable values during simulation, but is hard to use for post-processing. EnergyPlus offers a sqlite version of this data, but using it requires understanding the eso file format itself. EnergyPlus also can output a csv file, but that is limited in the number of columns.</p>
<p>The <a href="https://github.com/architecture-building-systems/esoreader">esoreader</a> module makes it very easy to explore the output of EnergyPlus, say, in an <a href="http://ipython.org/notebook.html">IPython notebook</a> interactive environment.</p>
<p>I wrote this module as part of my work at the chair for <a href="http://systems.arch.ethz.ch">Architecture and Building Systems (A/S)</a> at the <a href="http://ita.arch.ethz.ch">Institute of Technology in Architecture</a>, ETH Zürich, Switzerland.</p>
<pre class="prettyprint"><code class="language-python hljs ">In [<span class="hljs-number">1</span>]: <span class="hljs-keyword">import</span> esoreader
In [<span class="hljs-number">2</span>]: eso = esoreader.read_from_path(<span class="hljs-string">r"C:\...\experiment01.eso"</span>)
In [<span class="hljs-number">3</span>]: eso.find_variable(<span class="hljs-string">'heating'</span>)
Out[<span class="hljs-number">3</span>]:
[(<span class="hljs-string">'TimeStep'</span>, <span class="hljs-keyword">None</span>, <span class="hljs-string">'Heating:EnergyTransfer'</span>),
(<span class="hljs-string">'TimeStep'</span>,
<span class="hljs-string">'DEFAULT_ZONEZONEHVAC:IDEALLOADSAIRSYSTEM'</span>,
<span class="hljs-string">'Zone Ideal Loads Zone Total Heating Energy'</span>)]
In [<span class="hljs-number">4</span>]: df = eso.to_frame(<span class="hljs-string">'heating energy'</span>)
In [<span class="hljs-number">5</span>]: df[:<span class="hljs-number">10</span>]
Out[<span class="hljs-number">5</span>]:
DEFAULT_ZONEZONEHVAC:IDEALLOADSAIRSYSTEM
<span class="hljs-number">0</span> <span class="hljs-number">8596050.719384</span>
<span class="hljs-number">1</span> <span class="hljs-number">8672511.667988</span>
<span class="hljs-number">2</span> <span class="hljs-number">8737544.119096</span>
<span class="hljs-number">3</span> <span class="hljs-number">8799182.506582</span>
<span class="hljs-number">4</span> <span class="hljs-number">8862116.803218</span>
<span class="hljs-number">5</span> <span class="hljs-number">8928593.537248</span>
<span class="hljs-number">6</span> <span class="hljs-number">5296266.226576</span>
<span class="hljs-number">7</span> <span class="hljs-number">0.000000</span>
<span class="hljs-number">8</span> <span class="hljs-number">0.000002</span>
<span class="hljs-number">9</span> <span class="hljs-number">0.000000</span>
In [<span class="hljs-number">6</span>]: df.plot()
Out[<span class="hljs-number">6</span>]: <matplotlib.axes._subplots.AxesSubplot at <span class="hljs-number">0x7854090</span>>
In [<span class="hljs-number">7</span>]: %matplotlib tk
In [<span class="hljs-number">8</span>]: df.plot()
Out[<span class="hljs-number">8</span>]: <matplotlib.axes._subplots.AxesSubplot at <span class="hljs-number">0x7b66670</span>></code></pre>
<p><img src="https://raw.githubusercontent.com/architecture-building-systems/esoreader/master/doc/ZoneIdealLoadsZoneTotalHeatingEnergy.png" alt="Zone Ideal Loads Zone Total Heating Energy" title=""></p>
<p>Notice in the above example how the variable is matched by substring - you don’t have to specify the whole variable name. Each matching variable will show up in the resulting <code>DataFrame</code> with the key used as the column name - in this case ‘DEFAULT_ZONEZONEHVAC:IDEALLOADSAIRSYSTEM’.</p>
<p>Also, as this is an IPython session, I used the magic variable incantation <code>%matplotlib tk</code> to switch on the GUI loop that allowes plotting. You can choose another backend if you like, but I am pretty sure that <code>tk</code> should be available with your Python distribution.</p>
<p>An example with multiple columns:</p>
<pre class="prettyprint"><code class="language-python hljs ">In [<span class="hljs-number">1</span>]: eso.find_variable(<span class="hljs-string">'net thermal radiation heat gain energy'</span>)
Out[<span class="hljs-number">1</span>]:
[(<span class="hljs-string">'TimeStep'</span>,
<span class="hljs-string">'DPVROOF:1157058.3'</span>,
<span class="hljs-string">'Surface Outside Face Net Thermal Radiation Heat Gain Energy'</span>),
(<span class="hljs-string">'TimeStep'</span>,
<span class="hljs-string">'DPVWALL:1157028'</span>,
<span class="hljs-string">'Surface Outside Face Net Thermal Radiation Heat Gain Energy'</span>),
(<span class="hljs-string">'TimeStep'</span>,
<span class="hljs-string">'DPVWALL:1157027'</span>,
<span class="hljs-string">'Surface Outside Face Net Thermal Radiation Heat Gain Energy'</span>),
(<span class="hljs-string">'TimeStep'</span>,
<span class="hljs-string">'DPVROOF:1157058.2'</span>,
<span class="hljs-string">'Surface Outside Face Net Thermal Radiation Heat Gain Energy'</span>),
(<span class="hljs-string">'TimeStep'</span>,
<span class="hljs-string">'DPVFLOOR:1157042'</span>,
<span class="hljs-string">'Surface Outside Face Net Thermal Radiation Heat Gain Energy'</span>),
(<span class="hljs-string">'TimeStep'</span>,
<span class="hljs-string">'DPVWALL:1157029'</span>,
<span class="hljs-string">'Surface Outside Face Net Thermal Radiation Heat Gain Energy'</span>),
(<span class="hljs-string">'TimeStep'</span>,
<span class="hljs-string">'DPVROOF:1157058.1'</span>,
<span class="hljs-string">'Surface Outside Face Net Thermal Radiation Heat Gain Energy'</span>),
(<span class="hljs-string">'TimeStep'</span>,
<span class="hljs-string">'DPVROOF:1157058.0'</span>,
<span class="hljs-string">'Surface Outside Face Net Thermal Radiation Heat Gain Energy'</span>),
(<span class="hljs-string">'TimeStep'</span>,
<span class="hljs-string">'DPVWALL:1157026'</span>,
<span class="hljs-string">'Surface Outside Face Net Thermal Radiation Heat Gain Energy'</span>)]
In [<span class="hljs-number">2</span>]: df = eso.to_frame(<span class="hljs-string">'net thermal radiation heat gain energy'</span>)
In [<span class="hljs-number">3</span>]: df.plot()
Out[<span class="hljs-number">3</span>]: <matplotlib.axes._subplots.AxesSubplot at <span class="hljs-number">0xbd11150</span>></code></pre>
<p><img src="https://raw.githubusercontent.com/architecture-building-systems/esoreader/master/doc/NetThermalRadiationHeatGainEnergy.png" alt="Net Thermal Radiation Heat Gain Energy" title=""></p>
<h2 id="the-key-parameter-to-toframe">The <code>key</code> parameter to <code>to_frame</code></h2>
<p>You can use the <code>key</code> parameter to select a single column:</p>
<pre class="prettyprint"><code class="language-python hljs ">In [<span class="hljs-number">1</span>]: df = eso.to_frame(<span class="hljs-string">'net thermal radiation'</span>, key=<span class="hljs-string">'DPVROOF:1157058.3'</span>)
In [<span class="hljs-number">2</span>]: df[:<span class="hljs-number">10</span>]
Out[<span class="hljs-number">2</span>]:
DPVROOF:<span class="hljs-number">1157058.3</span>
<span class="hljs-number">0</span> -<span class="hljs-number">8985934.016604</span>
<span class="hljs-number">1</span> -<span class="hljs-number">8453530.628023</span>
<span class="hljs-number">2</span> -<span class="hljs-number">7611418.498363</span>
<span class="hljs-number">3</span> -<span class="hljs-number">6936246.291753</span>
<span class="hljs-number">4</span> -<span class="hljs-number">6206109.857522</span>
<span class="hljs-number">5</span> -<span class="hljs-number">5879653.262523</span>
<span class="hljs-number">6</span> -<span class="hljs-number">5676601.453020</span>
<span class="hljs-number">7</span> -<span class="hljs-number">5606988.050900</span>
<span class="hljs-number">8</span> -<span class="hljs-number">5844912.195173</span>
<span class="hljs-number">9</span> -<span class="hljs-number">4712551.701917</span></code></pre>
<h2 id="the-index-parameter-to-toframe">The <code>index</code> parameter to <code>to_frame</code></h2>
<p>You can use the <code>index</code> parameter to specify an index for the DataFrame. Since this is time-series data, a common pattern could be:</p>
<pre class="prettyprint"><code class="language-python hljs ">In [<span class="hljs-number">1</span>]: hours_in_year = pd.date_range(<span class="hljs-string">'2013-01-01'</span>, <span class="hljs-string">'2013-12-31 T23:00'</span>, freq=<span class="hljs-string">'H'</span>)
In [<span class="hljs-number">2</span>]: df = eso.to_frame(<span class="hljs-string">'heating energy'</span>, index=hours_in_year)
In [<span class="hljs-number">3</span>]: df[:<span class="hljs-number">10</span>]
Out[<span class="hljs-number">4</span>]:
DEFAULT_ZONEZONEHVAC:IDEALLOADSAIRSYSTEM
<span class="hljs-number">2013</span>-<span class="hljs-number">01</span>-<span class="hljs-number">01</span> <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">8596050.719384</span>
<span class="hljs-number">2013</span>-<span class="hljs-number">01</span>-<span class="hljs-number">01</span> <span class="hljs-number">01</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">8672511.667988</span>
<span class="hljs-number">2013</span>-<span class="hljs-number">01</span>-<span class="hljs-number">01</span> <span class="hljs-number">02</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">8737544.119096</span>
<span class="hljs-number">2013</span>-<span class="hljs-number">01</span>-<span class="hljs-number">01</span> <span class="hljs-number">03</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">8799182.506582</span>
<span class="hljs-number">2013</span>-<span class="hljs-number">01</span>-<span class="hljs-number">01</span> <span class="hljs-number">04</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">8862116.803218</span>
<span class="hljs-number">2013</span>-<span class="hljs-number">01</span>-<span class="hljs-number">01</span> <span class="hljs-number">05</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">8928593.537248</span>
<span class="hljs-number">2013</span>-<span class="hljs-number">01</span>-<span class="hljs-number">01</span> <span class="hljs-number">06</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">5296266.226576</span>
<span class="hljs-number">2013</span>-<span class="hljs-number">01</span>-<span class="hljs-number">01</span> <span class="hljs-number">07</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">0.000000</span>
<span class="hljs-number">2013</span>-<span class="hljs-number">01</span>-<span class="hljs-number">01</span> <span class="hljs-number">08</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">0.000002</span>
<span class="hljs-number">2013</span>-<span class="hljs-number">01</span>-<span class="hljs-number">01</span> <span class="hljs-number">09</span>:<span class="hljs-number">00</span>:<span class="hljs-number">00</span> <span class="hljs-number">0.000000</span></code></pre>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-32654862924726274362015-05-19T16:05:00.001+02:002015-05-19T16:05:56.300+02:00RevitLookup and RevitPythonShell<h1 id="revitlookup-and-revitpythonshell">RevitLookup and RevitPythonShell</h1>
<p><a href="https://github.com/jeremytammik/RevitLookup">RevitLookup</a> is a handy tool for interactively exploring Revit BIM databases. If you want to get anywhere with Revit API programming, you should look into RevitLookup. You should also be reading the RevitLookup’s author Jeremy Tammik’s blog <a href="http://thebuildingcoder.typepad.com/">The Building Coder</a>!</p>
<p>The standard RevitPythonShell <em>InitScript</em> contains a section dedicated to RevitLookup:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-comment">#------------------------------------------------------------------------------</span>
<span class="hljs-keyword">import</span> clr
<span class="hljs-keyword">from</span> Autodesk.Revit.DB <span class="hljs-keyword">import</span> ElementSet, ElementId
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RevitLookup</span><span class="hljs-params">(object)</span>:</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, uiApplication)</span>:</span>
<span class="hljs-string">'''
for RevitSnoop to function properly, it needs to be instantiated
with a reference to the Revit Application object.
'''</span>
<span class="hljs-comment"># find the RevitLookup plugin</span>
<span class="hljs-keyword">try</span>:
rlapp = [app <span class="hljs-keyword">for</span> app <span class="hljs-keyword">in</span> uiApplication.LoadedApplications
<span class="hljs-keyword">if</span> app.GetType().Namespace == <span class="hljs-string">'RevitLookup'</span>
<span class="hljs-keyword">and</span> app.GetType().Name == <span class="hljs-string">'App'</span>][<span class="hljs-number">0</span>]
<span class="hljs-keyword">except</span> IndexError:
self.RevitLookup = <span class="hljs-keyword">None</span>
<span class="hljs-keyword">return</span>
<span class="hljs-comment"># tell IronPython about the assembly of the RevitLookup plugin</span>
clr.AddReference(rlapp.GetType().Assembly)
<span class="hljs-keyword">import</span> RevitLookup
self.RevitLookup = RevitLookup
<span class="hljs-comment"># See note in CollectorExt.cs in the RevitLookup source:</span>
self.RevitLookup.Snoop.CollectorExts.CollectorExt.m_app = uiApplication
self.revit = uiApplication
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lookup</span><span class="hljs-params">(self, element)</span>:</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.RevitLookup:
<span class="hljs-keyword">print</span> <span class="hljs-string">'RevitLookup not installed. Visit https://github.com/jeremytammik/RevitLookup to install.'</span>
<span class="hljs-keyword">return</span>
<span class="hljs-keyword">if</span> isinstance(element, int):
element = self.revit.ActiveUIDocument.Document.GetElement(ElementId(element))
<span class="hljs-keyword">if</span> isinstance(element, ElementId):
element = self.revit.ActiveUIDocument.Document.GetElement(element)
<span class="hljs-keyword">if</span> isinstance(element, list):
elementSet = ElementSet()
<span class="hljs-keyword">for</span> e <span class="hljs-keyword">in</span> element:
elementSet.Insert(e)
element = elementSet
form = self.RevitLookup.Snoop.Forms.Objects(element)
form.ShowDialog()
_revitlookup = RevitLookup(__revit__)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lookup</span><span class="hljs-params">(element)</span>:</span>
_revitlookup.lookup(element)
<span class="hljs-comment">#------------------------------------------------------------------------------</span></code></pre>
<p>What this does is define a global function <code>lookup</code> that will open up the “Snoop Objects” dialog from RevitLookup with the element passed. In fact, you can pass <code>ElementId</code>s, lists of <code>Element</code> objects or just plain integers, that are converted to <code>ElementId</code>s - obtain these from the <em>Manage->IDs of Selection</em> command in the Ribbon. Since the <code>Document</code> also derives from <code>Element</code>, you can inspect it too!</p>
<p>Here are some fun things to try, assuming you haven’t removed anything from the standard <em>InitScript</em>:</p>
<p>Select an element in the BIM, start an interactive Python shell and type:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-prompt">>>> </span>lookup(selection[<span class="hljs-number">0</span>])</code></pre>
<p>You should see something like this:</p>
<p><img src="https://dl.dropboxusercontent.com/u/8112069/scripting-autodesk-revit-with-revitpythonshell/revitlookup_selection.png" alt="lookup(selection[0])" title=""></p>
<p>If you don’t have RevitLookup installed, you will instead get a message like this:</p>
<p><img src="https://dl.dropboxusercontent.com/u/8112069/scripting-autodesk-revit-with-revitpythonshell/revitlookup_selection_notinstalled.png" alt="lookup(selection[0]) - not installed" title=""></p>
<p>Typing</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-prompt">>>> </span>lookup(<span class="hljs-number">1157058</span>) <span class="hljs-comment"># make sure this is a valid ElementId!</span></code></pre>
<p>will result in something like this:</p>
<p><img src="https://dl.dropboxusercontent.com/u/8112069/scripting-autodesk-revit-with-revitpythonshell/revitlookup_int.png" alt="lookup(selection[0]) - not installed" title=""></p>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com2tag:blogger.com,1999:blog-9223352140090705474.post-51279631804933674662015-04-29T15:50:00.001+02:002015-04-29T15:50:51.073+02:00RevitPythonShell ported to Autodesk Revit 2016<p>Get the latest <a href="https://github.com/architecture-building-systems/revitpythonshell/releases/tag/2015.04.29">RPS installer for Revit 2016</a> here!</p>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-66505817828975415292015-03-20T13:59:00.001+01:002015-03-20T13:59:20.910+01:00The RevitPythonShell moved to GitHub<p>The RevitPythonShell has moved to GitHub! You can find the project here: <a href="https://github.com/architecture-building-systems/revitpythonshell">https://github.com/architecture-building-systems/revitpythonshell</a></p>
<p>Google Code is shutting down so I moved the code to GitHub. I’m currently working on fixing / creating the wiki pages, but you should be able to download the latest release already.</p>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-50967739966521615732015-03-19T16:43:00.001+01:002015-03-20T09:37:51.500+01:00More control over the RibbonPanel in RevitPythonShell<p>This post explains how to exert more control over the items shown in the RibbonPanel by your RevitPythonShell scripts.</p>
<p>The default behaviour of RevitPythonShell for external scripts is to group them into split buttons and place the remaining scripts in stacks of up to three. External scripts that are assigned the same “Group” value are placed together in a split button - in the example below, the external scripts “Button five”, “Button six” and “Button seven” are all assigned the group value “Group 5-7”.</p>
<p><img src="https://dl.dropboxusercontent.com/u/8112069/darenatwork/2015.03.18_Configure_RevitPythonShell.png" alt="Configuring external scripts" title="Configuring external scripts"></p>
<p>After saving your changes, you need to restart Revit to see any changes. The Ribbon will then include this panel:</p>
<p><img src="https://dl.dropboxusercontent.com/u/8112069/darenatwork/2015.02.27_Ribbon_Panel.png" alt="External scripts in Ribbon panel" title="External scripts in Ribbon panel"></p>
<p>This is fine for collecting your personal scripts and while developing new scripts. When it comes to publishing a plugin (even inside your organization) you might want more control of how the buttons appear.</p>
<p><strong>NOTE</strong>: When you create a <a href="http://darenatwork.blogspot.ch/2013/05/deploying-rps-scripts-with.html">RpsAddin for deployment</a>, you use a slightly different approach by specifying the buttons in the RpsAddin xml file. But you are still limited to PushButtons.</p>
<p>The Revit API contains a selection of controls you can use to execute scripts that you can use instead. You can find out all about these in the <a href="http://help.autodesk.com/view/RVT/2015/ENU/?guid=GUID-1547E521-59BD-4819-A989-F5A238B9F2B3">Revit API Developers Guide (Ribbon Panels and Controls)</a>.</p>
<p>The Revit API expects you to build your Ribbon panels and controls while the application is starting up - during the <code>IExternalApplication.OnStartup</code> method of a plugin. After that, the access to the <code>UIControlledApplication</code> which is needed to alter the Ribbon is not available anymore. Therefore, you can only access the <code>UIControlledApplication</code> object in the RPS <a href="http://darenatwork.blogspot.ch/2013/05/new-feature-startupscript-in.html">startup script</a> - through a special variable called <code>__uiControlledApplication__</code>. The startup script in RPS gives you access to the very variable it uses to configure its own user interface. All we need to do now is hook up an RPS script with a button on the ribbon. Let us assume a very simple script:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-string">"""
helloworld.py - prints a greeting
"""</span>
<span class="hljs-keyword">print</span> <span class="hljs-string">'hello, world!'</span></code></pre>
<p>(you can find this script in the <a href="https://github.com/daren-thomas/rps-sample-scripts/blob/master/helloworld.py">rps-sample-scripts project</a> on GitHub)</p>
<p>This script shall be called whenever the user clicks a button or other control on our custom Ribbon panel.</p>
<p>There is just one problem here. When you create a control on a Revit Ribbon panel, you need to pass in the path to a DLL and the fully qualified name (including namespaces) of a class inside that DLL that implements <code>IExternalCommand</code>. That does not sound like a python script at all!</p>
<p>To support this, the RevitPythonShell exposes a special class called <code>ExternalCommandAssemblyBuilder</code> which can create such a DLL. To use it, you just pass in a dictionary of class names and the corresponding python script files. The DLL created is just a very thin wrapper that calls into the <code>RpsRuntime.dll</code> (yes, that means you can use this for your RpsAddins!) with the script path and gets them executed.</p>
<p>So, a simple startup script that creates a Ribbon panel with a single <code>PushButton</code> would look like this:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-string">'''
simple_ribbon.py - creates a ribbon panel with a single push button.
NOTE:
- this MUST be set as a startup script for it to work
- the RPS variable "EXAMPLES_PATH" must be set and contain "helloworld.py"
'''</span>
<span class="hljs-comment"># script that is run when Revit starts in the IExternalApplication.Startup event.</span>
<span class="hljs-keyword">try</span>:
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">from</span> RevitPythonShell.RpsRuntime <span class="hljs-keyword">import</span> ExternalCommandAssemblyBuilder
<span class="hljs-keyword">from</span> Autodesk.Revit.UI <span class="hljs-keyword">import</span> *
SCRIPT_PATH = os.path.join(__vars__[<span class="hljs-string">'EXAMPLES_PATH'</span>], <span class="hljs-string">"helloworld.py"</span>)
DLL_PATH = os.path.expandvars(<span class="hljs-string">r"%APPDATA%\RevitPythonShell2015\simple_ribbon.dll"</span>)
<span class="hljs-keyword">print</span> <span class="hljs-string">'storing external command assembly here:'</span>, DLL_PATH
builder = ExternalCommandAssemblyBuilder()
builder.BuildExternalCommandAssembly(
DLL_PATH,
{<span class="hljs-string">'HelloWorld'</span>: SCRIPT_PATH})
panel = __uiControlledApplication__.CreateRibbonPanel(<span class="hljs-string">'simple_ribbon'</span>)
pbd = PushButtonData(<span class="hljs-string">'pb_HelloWorld'</span>, <span class="hljs-string">'hello, world!'</span>, DLL_PATH, <span class="hljs-string">'HelloWorld'</span>)
panel.AddItem(pbd)
<span class="hljs-comment">#__window__.Close() # closes the window</span>
<span class="hljs-keyword">except</span>:
<span class="hljs-keyword">import</span> traceback <span class="hljs-comment"># note: add a python27 library to your search path first!</span>
traceback.print_exc() <span class="hljs-comment"># helps you debug when things go wrong</span></code></pre>
<p>(NOTE: you can find this script in the <a href="https://github.com/daren-thomas/rps-sample-scripts/blob/master/StartupScripts/simple_ribbon.py">rps-sample-scripts repository</a> on GitHub)</p>
<p>The script (and all following scripts in this post) assumes you have the directory structure of the rps-sample-scripts repository downloaded to your machine - a simple git clone should do the trick - and have set the RPS variable <code>EXAMPLES_PATH</code> to point to that folder. You could also just edit the script text to hard code the paths and live with the nasty code smell…</p>
<p>Adding <code>simple_ribbon.py</code> as your startup script will result in a new ribbon panel added to RPS: </p>
<p><img src="https://dl.dropboxusercontent.com/u/8112069/darenatwork/2015.03.05_simple_ribbon.png" alt="simple_ribbon.py output" title="simple_ribbon.py output"></p>
<p>When clicked, you will be greeted as expected.</p>
<p>There are a few things going on here, so let’s go through them one by one. First, the whole body of the script is wrapped in a try/except. This is important, since a crash in the script might bring down the whole of Revit and you will not know where it happened - so we use <code>traceback.print_exc()</code> to print an exception trace. This is a handy debugging tip for your RPS scripting skill set!</p>
<p>Next, we create an assembly for Revit to load when you click the push button. The arguments to <code>builder.BuildExternalCommandAssembly</code> include the path of the assembly to be created. I chose to place it in the same folder as the <code>CommandLoaderAssembly.dll</code> - which is a similar dll created by RPS for the external scripts defined in the Configure dialog. You can place it anywhere the script has write access to.</p>
<p>Adding a panel to the ribbon and adding a push button to the panel is basically just an exercise of translating the sample c# code from the SDK into python. Adding images is also possible. Consider the following revised version:</p>
<p>% simple_ribbon_with_icons.py (includes images)</p>
<p>This produces a Ribbon panel that looks like this:</p>
<p><img src="https://dl.dropboxusercontent.com/u/8112069/darenatwork/2015.03.19_simple_ribbon_with_icon.png" alt="simple ribbon with icon" title="simple ribbon with icon"></p>
<p>As far as I can tell, you only really need to set the <code>LargeImage</code> property of the <code>PushButtonData</code> object - the <code>Image</code> property seems to be an atavism from an ancient version of Revit…</p>
<p>The <a href="https://github.com/daren-thomas/rps-sample-scripts/blob/master/StartupScripts/new_ribbon_panel.py">rps-sample-scripts repository on GitHub</a> contains a translation of the <a href="http://help.autodesk.com/view/RVT/2015/ENU/?guid=GUID-1547E521-59BD-4819-A989-F5A238B9F2B3">New Ribbon Panel example from the Revit API Developer’s Guide</a>:</p>
<pre class="prettyprint"><code class="language-python hljs "><span class="hljs-string">"""
new_ribbon_panel.py - a startup script to create a selection of
controls on the ribbon.
This script is based on the New Ribbon Panel and Controls example in the
Revit API Devolopers Guide.
NOTE:
- this MUST be set as a startup script for it to work
- the RPS variable "EXAMPLES_PATH" must be set and contain "helloworld.py"
"""</span>
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> clr
clr.AddReference(<span class="hljs-string">'PresentationCore'</span>)
<span class="hljs-keyword">from</span> System.Windows.Media.Imaging <span class="hljs-keyword">import</span> BitmapImage
<span class="hljs-keyword">from</span> System <span class="hljs-keyword">import</span> Uri
<span class="hljs-keyword">from</span> RevitPythonShell.RpsRuntime <span class="hljs-keyword">import</span> ExternalCommandAssemblyBuilder
<span class="hljs-keyword">from</span> Autodesk.Revit.UI <span class="hljs-keyword">import</span> *
SCRIPT_PATH = os.path.join(__vars__[<span class="hljs-string">'EXAMPLES_PATH'</span>], <span class="hljs-string">"helloworld.py"</span>)
LARGE_IMG_PATH = os.path.join(__vars__[<span class="hljs-string">'EXAMPLES_PATH'</span>], <span class="hljs-string">"PythonScript32x32.png"</span>)
SMALL_IMG_PATH = os.path.join(__vars__[<span class="hljs-string">'EXAMPLES_PATH'</span>], <span class="hljs-string">"PythonScript16x16.png"</span>)
EXAMPLES_PATH = __vars__[<span class="hljs-string">'EXAMPLES_PATH'</span>]
DLL_PATH = os.path.expandvars(<span class="hljs-string">r"%APPDATA%\RevitPythonShell2015\simple_ribbon.dll"</span>)
<span class="hljs-keyword">print</span> <span class="hljs-string">'storing external command assembly here:'</span>, DLL_PATH
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_ribbon_panel</span><span class="hljs-params">()</span>:</span>
panel = __uiControlledApplication__.CreateRibbonPanel(<span class="hljs-string">"New Ribbon Panel"</span>)
add_radio_group(panel)
panel.AddSeparator()
add_push_button(panel)
panel.AddSeparator()
add_split_button(panel)
panel.AddSeparator()
add_stacked_buttons(panel)
panel.AddSeparator()
add_slide_out(panel)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_radio_group</span><span class="hljs-params">(panel)</span>:</span>
<span class="hljs-string">"""add radio button group"""</span>
radio_data = RadioButtonGroupData(<span class="hljs-string">"radioGroup"</span>)
radio_button_group = panel.AddItem(radio_data)
tb1 = ToggleButtonData(<span class="hljs-string">"toggleButton1"</span>, <span class="hljs-string">"Red"</span>)
tb1.ToolTip = <span class="hljs-string">"Red Option"</span>
tb1.LargeImage = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'red.png'</span>)))
tb2 = ToggleButtonData(<span class="hljs-string">"toggleButton2"</span>, <span class="hljs-string">"Green"</span>)
tb2.ToolTip = <span class="hljs-string">"Green Option"</span>
tb2.LargeImage = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'green.png'</span>)))
tb3 = ToggleButtonData(<span class="hljs-string">"toggleButton3"</span>, <span class="hljs-string">"Blue"</span>)
tb3.ToolTip = <span class="hljs-string">"Blue Option"</span>
tb3.LargeImage = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'blue.png'</span>)))
radio_button_group.AddItem(tb1)
radio_button_group.AddItem(tb2)
radio_button_group.AddItem(tb3)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_push_button</span><span class="hljs-params">(panel)</span>:</span>
<span class="hljs-string">"""add push button"""</span>
push_button = panel.AddItem(
PushButtonData(<span class="hljs-string">"pb_HelloWorld"</span>, <span class="hljs-string">"Hello, world!"</span>,
DLL_PATH, <span class="hljs-string">"HelloWorld"</span>))
push_button.ToolTip = <span class="hljs-string">"Say hello world"</span>
context_help = ContextualHelp(ContextualHelpType.Url, <span class="hljs-string">"http://www.autodesk.com"</span>)
push_button.SetContextualHelp(context_help)
push_button.LargeImage = BitmapImage(Uri(LARGE_IMG_PATH))
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_split_button</span><span class="hljs-params">(panel)</span>:</span>
<span class="hljs-string">"""add a split button"""</span>
button_one = PushButtonData(<span class="hljs-string">"pbButtonOne"</span>, <span class="hljs-string">"Option one"</span>,
DLL_PATH, <span class="hljs-string">"HelloWorld"</span>)
button_one.LargeImage = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'one.png'</span>)))
button_two = PushButtonData(<span class="hljs-string">"pbButtonTwo"</span>, <span class="hljs-string">"Option two"</span>,
DLL_PATH, <span class="hljs-string">"HelloWorld"</span>)
button_two.LargeImage = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'two.png'</span>)))
button_three = PushButtonData(<span class="hljs-string">"pbButtonThree"</span>, <span class="hljs-string">"Option three"</span>,
DLL_PATH, <span class="hljs-string">"HelloWorld"</span>)
button_three.LargeImage = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'three.png'</span>)))
split_button = panel.AddItem(SplitButtonData(<span class="hljs-string">"splitButton"</span>, <span class="hljs-string">"Split"</span>))
split_button.AddPushButton(button_one)
split_button.AddPushButton(button_two)
split_button.AddPushButton(button_three)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_stacked_buttons</span><span class="hljs-params">(panel)</span>:</span>
<span class="hljs-string">"""Add a text box and combo box as stacked items"""</span>
combo_box_data = ComboBoxData(<span class="hljs-string">"comboBox"</span>)
text_data = TextBoxData(<span class="hljs-string">"Text Box"</span>)
text_data.Image = BitmapImage(Uri(SMALL_IMG_PATH))
text_data.Name = <span class="hljs-string">"Text Box"</span>
text_data.ToolTip = <span class="hljs-string">"Enter some text here"</span>
text_data.LongDescription = <span class="hljs-string">"""This is text that will appear next to the image
when the user hovers the mouse over the control"""</span>
text_data.ToolTipImage = BitmapImage(Uri(LARGE_IMG_PATH))
stacked_items = panel.AddStackedItems(text_data, combo_box_data)
text_box = stacked_items[<span class="hljs-number">0</span>]
text_box.PromptText = <span class="hljs-string">"Enter a comment"</span>
text_box.ShowImageAsButton = <span class="hljs-keyword">True</span>
text_box.ToolTip = <span class="hljs-string">"Enter some text"</span>
text_box.EnterPressed += <span class="hljs-keyword">lambda</span> sender, args: TaskDialog.Show(<span class="hljs-string">'new_ribbon_panel'</span>, sender.Value)
combo_box = stacked_items[<span class="hljs-number">1</span>]
combo_box.ItemText = <span class="hljs-string">"ComboBox"</span>
combo_box.ToolTip = <span class="hljs-string">"Select an Option"</span>
combo_box.LongDescription = <span class="hljs-string">"Select a number or letter"</span>
member_data_a = ComboBoxMemberData(<span class="hljs-string">'A'</span>, <span class="hljs-string">'Option A'</span>)
member_data_a.Image = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'a.png'</span>)))
member_data_a.GroupName = <span class="hljs-string">'Letters'</span>
combo_box.AddItem(member_data_a)
member_data_b = ComboBoxMemberData(<span class="hljs-string">'B'</span>, <span class="hljs-string">'Option B'</span>)
member_data_b.Image = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'b.png'</span>)))
member_data_b.GroupName = <span class="hljs-string">'Letters'</span>
combo_box.AddItem(member_data_b)
member_data_c = ComboBoxMemberData(<span class="hljs-string">'C'</span>, <span class="hljs-string">'Option C'</span>)
member_data_c.Image = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'c.png'</span>)))
member_data_c.GroupName = <span class="hljs-string">'Letters'</span>
combo_box.AddItem(member_data_c)
member_data_1 = ComboBoxMemberData(<span class="hljs-string">'1'</span>, <span class="hljs-string">'Option 1'</span>)
member_data_1.Image = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'one_small.png'</span>)))
member_data_1.GroupName = <span class="hljs-string">'Numbers'</span>
combo_box.AddItem(member_data_1)
member_data_2 = ComboBoxMemberData(<span class="hljs-string">'2'</span>, <span class="hljs-string">'Option 2'</span>)
member_data_2.Image = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'two_small.png'</span>)))
member_data_2.GroupName = <span class="hljs-string">'Numbers'</span>
combo_box.AddItem(member_data_2)
member_data_3 = ComboBoxMemberData(<span class="hljs-string">'3'</span>, <span class="hljs-string">'Option 3'</span>)
member_data_3.Image = BitmapImage(Uri(os.path.join(
EXAMPLES_PATH, <span class="hljs-string">'StartupScripts'</span>, <span class="hljs-string">'three_small.png'</span>)))
member_data_3.GroupName = <span class="hljs-string">'Numbers'</span>
combo_box.AddItem(member_data_3)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_slide_out</span><span class="hljs-params">(panel)</span>:</span>
<span class="hljs-keyword">pass</span> <span class="hljs-comment"># left as exercise for the reader :)</span>
<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
<span class="hljs-keyword">try</span>:
builder = ExternalCommandAssemblyBuilder()
builder.BuildExternalCommandAssembly(
DLL_PATH,
{<span class="hljs-string">'HelloWorld'</span>: SCRIPT_PATH})
create_ribbon_panel()
<span class="hljs-keyword">except</span>:
<span class="hljs-keyword">import</span> traceback
traceback.print_exc()</code></pre>
<p>If you are really interested on what is produced by the <code>ExternalCommandAssemblyBuilder</code>, just use <a href="https://www.jetbrains.com/decompiler/">JetBrains dotPeek</a> to decompile the <code>simple_ribbon.dll</code>. When I tried, I ended up with this:</p>
<p><img src="https://dl.dropboxusercontent.com/u/8112069/darenatwork/2015.03.18_simple_ribbon_dotPeek.png" alt="Disassembled source code of simple_ribbon.py" title="Disassembled source code of simple_ribbon.py"></p>
<p>I’ll leave it as an exercise to the reader to get on GitHub and <a href="https://github.com/architecture-building-systems/revitpythonshell/blob/master/RpsRuntime/RpsExternalCommandBase.cs">check out the source for <code>RpsExternalCommandScriptBase</code></a> - it has a constructor that saves the script path as a member to be called when Revit decides to <code>Execute</code> it.</p>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-74774885783830399862015-02-06T15:12:00.001+01:002015-02-06T15:15:16.837+01:00Batteries included!<p>The newest versions of the <a href="https://code.google.com/p/revitpythonshell/">RevitPythonShell</a> include a copy of the python standard library. This copy is taken from IronPython. This means you don’t need to add a search path to a local python distribution when importing standard python modules.</p>
<p>Since the library is included in a zip file embedded in the <code>RpsRuntime.dll</code>, it is also automatically deployed with when creating <a href="http://darenatwork.blogspot.ch/2013/05/deploying-rps-scripts-with.html">RpsAddIns</a>.</p>
<p>The versions supporting this feature are:</p>
<ul>
<li>(r223) Installer for Autodesk Revit 2015 (<em>experimental</em>)</li>
<li>Installer for Autodesk Vasari (Beta 3)</li>
<li>List item</li>
</ul>Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-35606046441888125032015-01-15T10:41:00.001+01:002015-01-15T10:43:29.764+01:00Introducing the non-modal shell in RevitPythonShell (r223)You have no idea how excited I am about this release! Revision r223 of the RevitPythonShell has been tested by a select few very brave people (Ehsan Iran Nejad and Callum Freeman) and seems to work. Normally I’m not so worried about pushing out new versions, but this one is… different.<br />
The idea actually came from Ehsan. The Next Big Thing. Also known as the non-modal shell.<br />
I know, I know - I need to get a lot better at coming up with good names for the various parts in RevitPythonShell, but for now, that is what it is called: Non-modal shell.<br />
The non-modal shell is different from the standard RevitPythonShell shell in that it is… well… non-modal. What does that mean? Currently, when you start a shell in RPS, the shell gets the focus and you can’t interact with the rest of the Revit GUI until you close the shell again. This is cool for working with short scripts, trying stuff out etc, but we can do better:<br />
The non-modal shell does not block the rest of Revit. You can select stuff in the view, make changes to the BIM etc. And then query the selection in the shell or do whatever you like.<br />
With some exceptions.<br />
You see, because the shell is non-modal, it is not running in the special thread Revit needs you to be in for accessing the API. You would get a lot of ugly errors, if there wasn’t some clever magic going on in the background that sends all input to the interactive shell to an implementation of <a href="http://help.autodesk.com/cloudhelp/2015/ENU/Revit-API/files/GUID-0A0D656E-5C44-49E8-A891-6C29F88E35C0.htm"><code>IExternalEventHandler</code></a> which Revit then runs in the correct thread and everything is fine and dandy.<br />
What doesn’t work though, is using transactions in the interactive shell:<br />
<pre><code>>>> t = Transaction(doc, 'test')
>>> t.Start()
Autodesk.Revit.DB.TransactionStatus.Started
>>> t.GetStatus()
Autodesk.Revit.DB.TransactionStatus.RolledBack
>>> t.Commit()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Exception: The transaction has not been started yet (the current status is not 'Started'). A transaction must be started before it can be either committed or rolled back.
>>>
</code></pre>
You can get around this by using the IronPython Pad - the little editor below the interactive shell. This allows you to run multiple lines at a time and those will be run in the same <code>IExternalEventHandler</code> so the problem does not show up.<br />
You can find the installer for this (experimental) version of RevitPythonShell in the <a href="http://code.google.com/p/revitpythonshell/#Downloads">Downloads section of the project homepage</a>.<br />
I intend to post some more articles in the future that showcase some other new features in the r223 release, so you can expect some more goodies soon :)Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com1tag:blogger.com,1999:blog-9223352140090705474.post-5519232502867764752014-12-22T11:53:00.001+01:002014-12-22T11:54:53.442+01:00A short tutorial on the esoreader moduleThis post explains how to use the <a href="https://github.com/daren-thomas/esoreader"><code>esoreader</code></a>, a python module for parsing the <code>.eso</code> file produced by <a href="http://apps1.eere.energy.gov/buildings/energyplus/">EnergyPlus</a>. It also includes a small (incomplete) reverse engineering of the <code>.eso</code> file format.<br />
EnergyPlus is a whole building simulation tool EnergyPlus is a whole building energy simulation program that engineers, architects, and researchers use to model energy and water use in buildings. The <code>.eso</code> file is the main output file produced by EnergyPlus and is normally parsed by tools that come with the EnergyPlus suit of tools. The <code>esoreader</code> module lets you read in the time series data in python scripts, which for my research is quite useful. I published the module thinking other people might want to do so too.<br />
Last week I got an email about how the documentation on the <a href="https://pypi.python.org/pypi/esoreader">pypi page for the <code>esoreader</code> module</a> is rather… terse. So I went to check the <code>esoreader</code> page and yes, there is not a lot of documentation. The example code I published was this:<br />
<pre><code>import esoreader
PATH_TO_ESO = r'/Path/To/EnergyPlus/Output/eplusout.eso'
dd, data = esoreader.read(PATH_TO_ESO)
frequency, key, variable = dd.find_variable(
'Zone Ventilation Total Heat Loss Energy')[0]
idx = dd.index[frequency, key, variable]
time_series = data[idx]
</code></pre>
What can I say? There is not much more you can do with <code>esoreader</code>. I think the best way to understand the module is to look at the .eso file format:<br />
The eso file format starts of with a header section called the “data dictionary” (I used the variable <code>dd</code> in the example code for that). The first few lines of a sample eso file look something like this:<br />
<pre><code>Program Version,EnergyPlus-Windows-32 8.1.0.009, YMD=2014.03.20 14:18
1,5,Environment Title[],Latitude[deg],Longitude[deg],Time Zone[],Elevation[m]
2,6,Day of Simulation[],Month[],Day of Month[],DST Indicator[1=yes 0=no],Hour[],StartMinute[],EndMinute[],DayType
3,3,Cumulative Day of Simulation[],Month[],Day of Month[],DST Indicator[1=yes 0=no],DayType ! When Daily Report Variables Requested
4,2,Cumulative Days of Simulation[],Month[] ! When Monthly Report Variables Requested
5,1,Cumulative Days of Simulation[] ! When Run Period Report Variables Requested
6,1,DEFAULT_ZONE,Zone Outdoor Air Drybulb Temperature [C] !TimeStep
99,1,DPVWALL:1157026,Surface Outside Face Temperature [C] !TimeStep
100,1,DPVWINDOW:COMBINED:DPVWALL:1157026:DEFAULTWINDOWCONSTRUCTION,Surface Outside Face Temperature [C] !TimeStep
101,1,DPVWALL:1157027,Surface Outside Face Temperature [C] !TimeStep
102,1,DPVWINDOW:COMBINED:DPVWALL:1157027:DEFAULTWINDOWCONSTRUCTION,Surface Outside Face Temperature [C] !TimeStep
103,1,DPVWALL:1157028,Surface Outside Face Temperature [C] !TimeStep
104,1,DPVWINDOW:COMBINED:DPVWALL:1157028:DEFAULTWINDOWCONSTRUCTION,Surface Outside Face Temperature [C] !TimeStep
105,1,DPVWALL:1157029,Surface Outside Face Temperature [C] !TimeStep
106,1,DPVWINDOW:COMBINED:DPVWALL:1157029:DEFAULTWINDOWCONSTRUCTION,Surface Outside Face Temperature [C] !TimeStep
107,1,DPVFLOOR:1157042,Surface Outside Face Temperature [C] !TimeStep
108,1,DPVROOF:1157058.0,Surface Outside Face Temperature [C] !TimeStep
109,1,DPVROOF:1157058.1,Surface Outside Face Temperature [C] !TimeStep
110,1,DPVROOF:1157058.2,Surface Outside Face Temperature [C] !TimeStep
111,1,DPVROOF:1157058.3,Surface Outside Face Temperature [C] !TimeStep
112,1,DEFAULT_ZONE,Zone Mean Air Temperature [C] !TimeStep
278,1,DEFAULT_ZONEZONEHVAC:IDEALLOADSAIRSYSTEM,Zone Ideal Loads Zone Total Heating Energy [J] !TimeStep
279,1,DEFAULT_ZONEZONEHVAC:IDEALLOADSAIRSYSTEM,Zone Ideal Loads Zone Total Cooling Energy [J] !TimeStep
End of Data Dictionary
</code></pre>
The first line is stored in the DataDictionary object (<code>dd</code>) as <code>version</code> and <code>timestamp</code>. After that, each line represents a variable being reported. Each such variable has an index, a number of values being reported and then a reporting frequency. Well… the first few lines (indexes 1 through 5) are a bit special and I just discard them. The rest of the data dictionary lines are built like this:<br />
<ul>
<li>index (e.g. 100)</li>
<li>column count (is always one as far as I can tell)</li>
<li>key (the same variable can be measured for different keys, as per the Output:Variable object in the IDF file) <br />
(e.g. “DPVWINDOW:COMBINED:DPVWALL:1157026:DEFAULTWINDOWCONSTRUCTION”, a surface name in one of my models)</li>
<li>variable name (e.g. “Surface Outisde Face Temperature”)</li>
<li>unit (e.g. “C”)</li>
<li>reporting frequency (e.g. “TimeStep”)</li>
</ul>
These get parsed into a DataDictionary object and stored in the attributes <code>variables</code> and <code>index</code>.<br />
<pre><code>variables = dict of ids, int => [reporting_frequency,
key, variable, unit]
index = dict {(key, variable, reporting_frequency) => id)}
</code></pre>
here is an example (I’m using the <a href="http://ipython.org/">IPython</a> shell, in case you’re wondering about the In [71] line - check it out! it is awesome!!)<br />
<pre><code>In [71]: dd.variables.items()[1]
Out[71]:
(100,
['TimeStep',
'DPVWINDOW:COMBINED:DPVWALL:1157026:DEFAULTWINDOWCONSTRUCTION',
'Surface Outside Face Temperature',
'C'])
</code></pre>
The DataDictionary object has a method <code>find_variable</code>. Say, you want to find the variable for ‘Zone Mean Air Temperature’:<br />
<pre><code>In [75]: dd.find_variable('Zone Mean Air Temperature')
Out[75]: [('TimeStep', 'DEFAULT_ZONE', 'Zone Mean Air Temperature')]
</code></pre>
Notice how the result is a list? If you had looked for surface temperatures instead:<br />
<pre><code>In [76]: dd.find_variable('surface')
Out[76]:
[('TimeStep', 'DPVROOF:1157058.1', 'Surface Outside Face Temperature'),
('TimeStep', 'DPVWALL:1157029', 'Surface Outside Face Temperature'),
('TimeStep',
'DPVWINDOW:COMBINED:DPVWALL:1157029:DEFAULTWINDOWCONSTRUCTION',
'Surface Outside Face Temperature'),
('TimeStep', 'DPVWALL:1157028', 'Surface Outside Face Temperature'),
('TimeStep', 'DPVROOF:1157058.3', 'Surface Outside Face Temperature'),
('TimeStep', 'DPVROOF:1157058.0', 'Surface Outside Face Temperature'),
('TimeStep',
'DPVWINDOW:COMBINED:DPVWALL:1157028:DEFAULTWINDOWCONSTRUCTION',
'Surface Outside Face Temperature'),
('TimeStep',
'DPVWINDOW:COMBINED:DPVWALL:1157026:DEFAULTWINDOWCONSTRUCTION',
'Surface Outside Face Temperature'),
('TimeStep', 'DPVWALL:1157027', 'Surface Outside Face Temperature'),
('TimeStep',
'DPVWINDOW:COMBINED:DPVWALL:1157027:DEFAULTWINDOWCONSTRUCTION',
'Surface Outside Face Temperature'),
('TimeStep', 'DPVFLOOR:1157042', 'Surface Outside Face Temperature'),
('TimeStep', 'DPVWALL:1157026', 'Surface Outside Face Temperature'),
('TimeStep', 'DPVROOF:1157058.2', 'Surface Outside Face Temperature')]
</code></pre>
you’d have gotten a list of all variables that match ‘surface’ (case-insensitive, substring match). The tuples define the variable you’re looking for: Frequency, key and variable name, since you can have the same variable output for different frequencies and keys!<br />
So the index of the variable we’re looking for (Zone Mean Air Temperature) can be found like this:<br />
<pre><code>In [77]: dd.index['TimeStep', 'DEFAULT_ZONE', 'Zone Mean Air Temperature']
Out[77]: 112
</code></pre>
When you parse an <code>.eso</code> file, you get two values back: The <code>DataDictionary</code> and the data itself, which is stored in a simple dictionary mapping the variable index to the timeseries data:<br />
<pre><code>In [82]: dd, data = esoreader.read('RevitToCitySim_fmibeta.eso')
In [84]: data[112]
Out[84]:
[19.9999999999999,
20.0,
20.0,
20.0,
20.0,
20.0,
20.0,
</code></pre>
Where does that data come from? From the rest of the <code>.eso</code> file, which looks like this:<br />
<pre><code>1,Zuerich-SMA - - TMY2-66600 WMO#=, 47.38, 8.57, 1.00, 556.00
2,1, 1, 1, 0, 1, 0.00,60.00,Tuesday
6,-9.733141026918536E-003
99,4.22860281958676
100,2.29752216466107
101,4.62549195332972
102,2.48360878690238
103,4.45346228786434
104,2.40363283464546
105,4.29531948374435
106,2.37522804444541
107,18.
108,4.50377052140968
109,4.62335191215081
110,4.38341749556803
111,4.62165617120029
112,19.9999999999999
278,69070426.0448551
279,2.200249582529068E-006
2,1, 1, 1, 0, 2, 0.00,60.00,Tuesday
6,-20.0097331410269
99,-3.72067959222121
100,-11.9333570144822
101,-3.2185453285921
102,-11.9539672821419
103,-3.21604754356428
104,-11.6999602208759
105,-3.69978393125912
106,-12.1422252778574
107,18.
108,-0.183133489472591
109,0.134352957894094
110,-0.291113509003108
111,9.541793763769267E-002
112,20.
278,12241499.5530833
279,0.0
2,1, 1, 1, 0, 3, 0.00,60.00,Tuesday
6,-20.0097331410269
99,-8.25275818278861
100,-10.9050845966823
</code></pre>
For all the main variables (id > 5) the format is:<br />
<ul>
<li>index</li>
<li>value</li>
</ul>
Thus, the data dictionary is necessary to figure out what variables (with what frequency) are being output.<br />
To sum up the tutorial: The code on the pypi page shows you pretty much all you can do and also all you need to do to retrieve a specific timeseries from an <code>.eso</code> file:<br />
<ul>
<li>read in the <code>eso file</code> to obtain the data dictionary and the data</li>
<li>find the key, frequency and variable name you need in the data dictionary (with <code>find_variable</code>) or by guessing from your IDF input</li>
<li>retrieve the index of that variable</li>
<li>retrieve the time series data using that index</li>
</ul>
Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-78694631624272219572013-10-17T16:58:00.001+02:002013-10-17T17:02:16.782+02:00How to replace a color in a PNG with python preserving transparency<p>Here is a small bit of code I thought I'd share with you, since I couldn't find
a good solution on the web quickly...</p>
<p>I was coding up a web site with a horizontal navigation menu that used a
downwards pointing arrow to denote a menu entry that has a drop down menu. The
graphics designer had given me the image for this arrow. Here it is enlarged in
a GIMP window:</p>
<img alt="https://dl.dropboxusercontent.com/u/8112069/darenatwork/2010.10.17_arrow_white.png" src="https://dl.dropboxusercontent.com/u/8112069/darenatwork/2010.10.17_arrow_white.png" />
<p>Which is all fine and dandy, <em>except</em>, when the mouse hovers above the menu
item, the arrow should turn cyan. <tt class="docutils literal">#00aeef</tt>.</p>
<p>I was just about to ask the designer for the cyan version of the arrow, after
failing to find a quick and easy way to change all the white pixels to cyan
<em>preserving transparency</em>, when I decided to see how hard that would be to do with the <a class="reference external" href="http://www.pythonware.com/products/pil/">Python Imaging Library (PIL)</a>.</p>
<p>This is the throwaway script I came up with:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">OLD_PATH <span style="color: #666666">=</span> <span style="color: #BA2121">r'c:\path\to\images\arrow_white.png'</span>
NEW_PATH <span style="color: #666666">=</span> <span style="color: #BA2121">r'c:\path\to\images\arrow_cyan.png'</span>
R_OLD, G_OLD, B_OLD <span style="color: #666666">=</span> (<span style="color: #666666">255</span>, <span style="color: #666666">255</span>, <span style="color: #666666">255</span>)
R_NEW, G_NEW, B_NEW <span style="color: #666666">=</span> (<span style="color: #666666">0</span>, <span style="color: #666666">174</span>, <span style="color: #666666">239</span>)
<span style="color: #008000; font-weight: bold">import</span> <span style="color: #0000FF; font-weight: bold">Image</span>
im <span style="color: #666666">=</span> Image<span style="color: #666666">.</span>open(OLD_PATH)
pixels <span style="color: #666666">=</span> im<span style="color: #666666">.</span>load()
width, height <span style="color: #666666">=</span> im<span style="color: #666666">.</span>size
<span style="color: #008000; font-weight: bold">for</span> x <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(width):
<span style="color: #008000; font-weight: bold">for</span> y <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #008000">range</span>(height):
r, g, b, a <span style="color: #666666">=</span> pixels[x, y]
<span style="color: #008000; font-weight: bold">if</span> (r, g, b) <span style="color: #666666">==</span> (R_OLD, G_OLD, B_OLD):
pixels[x, y] <span style="color: #666666">=</span> (R_NEW, G_NEW, B_NEW, a)
im<span style="color: #666666">.</span>save(NEW_PATH)
</pre></div>
<p>As you can see, that was not so difficult at all... And produced this result:</p>
<img alt="https://dl.dropboxusercontent.com/u/8112069/darenatwork/2010.10.17_arrow_cyan.png" src="https://dl.dropboxusercontent.com/u/8112069/darenatwork/2010.10.17_arrow_cyan.png" />
<p>Let's just go through this quickly... <tt class="docutils literal">OLD_PATH</tt> and <tt class="docutils literal">NEW_PATH</tt> should be self explaining.</p>
<p>Next, I configure the R(ed), G(reen) and B(lue) values of the color I'm looking
for and the color I want to change that to. White is easy, you should be able
to do that in your head, but for the cyan bit, the python interpreter can help us:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">>>></span> <span style="color: #008000">int</span>(<span style="color: #BA2121">'00'</span>, <span style="color: #666666">16</span>)
<span style="color: #666666">0</span>
<span style="color: #666666">>>></span> <span style="color: #008000">int</span>(<span style="color: #BA2121">'ae'</span>, <span style="color: #666666">16</span>)
<span style="color: #666666">174</span>
<span style="color: #666666">>>></span> <span style="color: #008000">int</span>(<span style="color: #BA2121">'ef'</span>, <span style="color: #666666">16</span>)
<span style="color: #666666">239</span>
<span style="color: #666666">>>></span>
</pre></div>
<p>Converting bytes from hex to decimal is equivalent to parsing the hex string as an int with base 16. The other way round is even easier:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">>>></span> <span style="color: #008000">hex</span>(<span style="color: #666666">0</span>)
<span style="color: #BA2121">'0x0'</span>
<span style="color: #666666">>>></span> <span style="color: #008000">hex</span>(<span style="color: #666666">174</span>)
<span style="color: #BA2121">'0xae'</span>
<span style="color: #666666">>>></span> <span style="color: #008000">hex</span>(<span style="color: #666666">239</span>)
<span style="color: #BA2121">'0xef'</span>
<span style="color: #666666">>>></span>
</pre></div>
<p>Next, I import the imaging library and open the image. The <tt class="docutils literal">load()</tt> method returns a map of the image, which each <tt class="docutils literal">x,y</tt>-coordinate denoting a tuple <tt class="docutils literal">(r, g, b, a)</tt> with <tt class="docutils literal">a</tt> being the alpha channel or transparency.</p>
<p>The rest of the script is therefor just a simple iteration over the width and height of the image, pixel by pixel, testing for the old color and replacing that with the new color if found, preserving the original transparency.</p>
Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-76117104573839088082013-05-30T10:59:00.001+02:002013-05-30T10:59:18.162+02:00New Feature: StartupScript in RevitPythonShell<p>Here is a new hidden feature in recent versions of the <a class="reference external" href="https://code.google.com/p/revitpythonshell/">RevitPythonShell</a>: You
can specify a script to start when Revit starts. The script is called during
the <tt class="docutils literal">IExternalApplication.OnStartup</tt> event when all the Revit plugins get to
do their initialization.</p>
<p>This feature is still not quite official yet, but I have been using it for
various purposes internally and think it works quite nicely. There are some
changes between the code I have locally and what you probably have installed -
I will point these out below.</p>
<p>To specify a StartupScript for RevitPythonShell, add a tag called
<tt class="docutils literal">StartupScript</tt> to the <tt class="docutils literal">RevitPythonShell.xml</tt> file at the same level as the
<tt class="docutils literal">Commands</tt>, <tt class="docutils literal">Variables</tt>, <tt class="docutils literal">SearchPaths</tt> and <tt class="docutils literal">InitScript</tt> tags. This tag
has a single attribute <tt class="docutils literal">src</tt> that specifies the path to the script to run.</p>
<p>You can find the <tt class="docutils literal">RevitPythonShell.xml</tt> file in the folder
<tt class="docutils literal"><span class="pre">%APPDATA%\RevitPythonShell2013</span></tt>.</p>
<p>This feature is also present in <a class="reference external" href="http://darenatwork.blogspot.ch/2013/05/deploying-rps-scripts-with.html">deployed RpsAddins</a>!</p>
<p>Example:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #BC7A00"><?xml version="1.0" encoding="UTF-8"?></span>
<span style="color: #008000; font-weight: bold"><RevitPythonShell></span>
<span style="color: #008000; font-weight: bold"><StartupScript</span> <span style="color: #7D9029">src=</span><span style="color: #BA2121">"c:\path-to-your-script\your-awesome-startup-script.py"</span><span style="color: #008000; font-weight: bold">/></span>
<span style="color: #008000; font-weight: bold"><Commands></span>
<span style="color: #008000; font-weight: bold"><Command</span> <span style="color: #7D9029">name=</span><span style="color: #BA2121">"Hello World"</span> <span style="color: #7D9029">src=</span><span style="color: #BA2121">"C:\...\helloworld.py"</span> <span style="color: #7D9029">group=</span><span style="color: #BA2121">"Examples"</span><span style="color: #008000; font-weight: bold">/></span>
<span style="color: #408080; font-style: italic"><!-- ... --></span>
</pre></div>
<p><strong>NOTE:</strong></p>
<blockquote>
If you are not building from source, then the attribute to use in the
<tt class="docutils literal">StartupScript</tt> tag is still <tt class="docutils literal">source</tt>. I changed it to <tt class="docutils literal">src</tt> for
consistency with the <tt class="docutils literal">Command</tt> tag. Future versions of RevitPythonShell
(especially the 2014 line) will use <tt class="docutils literal">src</tt> as described in this post!</blockquote>
<p>There is currently no GUI for changing the StartupScript. Also, this is
somewhat of a specialist feature: You probably don't want to use it except for
some very special cases.</p>
<p>So... what can you do with a startup script? So far, I have used it for two projects...</p>
<p>In the first project, I load a web server and hook it up to the <tt class="docutils literal">Idling</tt>
event so that I can provide an HTTP interface to Revit. My day job involves
extracting an abstract model of simulation relevant aspects of a building from
Revit, much like gbXML and being able to do that from <em>outside</em> Revit gives me
some flexibility.</p>
<p>In the second project, I was automating a solar study: For each window in an
elaborate model, I traced rays from a grid on the window to the sun from the
<tt class="docutils literal">SunAndShadowSettings</tt> to determine the percentage of direct sunlight a
window gets - for every 15 minutes in a whole year! Since the model was big,
with a lot of neighboring buildings modeled as mass objects, ray tracing (using
the <tt class="docutils literal">FindReferencesByDirectionWithContext</tt> method) gets very slow. Simulating
a single day takes about 20 minutes. And simulating the whole year just broke
down with Revit crashing. So... I decided to do each day separatly, and just
start Revit after each day - the startup script would check the
<tt class="docutils literal">__vars__[START_DAY]</tt> variable to get the current day to simulate and on
successful simulation, increment that day (this is where the <a class="reference external" href="http://darenatwork.blogspot.ch/2013/05/class-literal-is-now-writeable.html">writeable
__vars__ feature</a> was born) and quit Revit. Then I wrapped that up in a simple
script that just started looped 365 times, starting Revit and waiting for it to
finish.</p>
<p>Side note: quitting Revit is not that easy. Or <em>really</em> easy, whichever way you
want to view it. I ended up using this code to force the process to die:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">System.Diagnostics</span> <span style="color: #008000; font-weight: bold">import</span> Process
p <span style="color: #666666">=</span> Process<span style="color: #666666">.</span>GetCurrentProcess()
p<span style="color: #666666">.</span>Kill()
</pre></div>
Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-6340532380622620402013-05-29T16:02:00.001+02:002013-05-29T16:06:13.121+02:00__vars__ is now writeable<p>Here is another feature I sneaked in without documentation: The <tt class="docutils literal">__vars__</tt>
dictionary is now writeable, that is, assigning to a key in the dictionary
saves the variable to the <tt class="docutils literal">RevitPythonShell.xml</tt> file.</p>
<p>This feature can be used for storing data in between invocations of a script or even Revit sessions!</p>
<p><strong>NOTE:</strong></p>
<blockquote>
Changing the <tt class="docutils literal">RevitPythonShell.xml</tt> file manually will not be reflected
in the <tt class="docutils literal">__vars__</tt> dictionary until the next invocation of the shell.</blockquote>
<p>The implementation for the writeable dictionary is in <tt class="docutils literal">RevitPythonShell.RpsRuntime.SettingsDictionary</tt>, which implements <tt class="docutils literal">IDictionary<string, string></tt>.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">using</span> <span style="color: #0000FF; font-weight: bold">System</span>;
<span style="color: #008000; font-weight: bold">using</span> <span style="color: #0000FF; font-weight: bold">System.Collections.Generic</span>;
<span style="color: #008000; font-weight: bold">using</span> <span style="color: #0000FF; font-weight: bold">System.Linq</span>;
<span style="color: #008000; font-weight: bold">using</span> <span style="color: #0000FF; font-weight: bold">System.Text</span>;
<span style="color: #008000; font-weight: bold">using</span> <span style="color: #0000FF; font-weight: bold">System.Xml.Linq</span>;
<span style="color: #008000; font-weight: bold">namespace</span> <span style="color: #0000FF; font-weight: bold">RevitPythonShell.RpsRuntime</span>
{
<span style="color: #408080; font-style: italic">/// <summary></span>
<span style="color: #408080; font-style: italic">/// A subclass of Dictionary<string, string>, that writes changes back to a settings xml file.</span>
<span style="color: #408080; font-style: italic">/// </summary></span>
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #008000; font-weight: bold">class</span> <span style="color: #0000FF; font-weight: bold">SettingsDictionary</span> : IDictionary<<span style="color: #B00040">string</span>, <span style="color: #B00040">string</span>>
{
<span style="color: #008000; font-weight: bold">private</span> <span style="color: #008000; font-weight: bold">readonly</span> IDictionary<<span style="color: #B00040">string</span>, <span style="color: #B00040">string</span>> _dict;
<span style="color: #008000; font-weight: bold">private</span> <span style="color: #008000; font-weight: bold">readonly</span> <span style="color: #B00040">string</span> _settingsPath;
<span style="color: #008000; font-weight: bold">private</span> XDocument _settings;
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #0000FF">SettingsDictionary</span>(<span style="color: #B00040">string</span> settingsPath)
{
_settingsPath = settingsPath;
_settings = XDocument.Load(_settingsPath);
_dict = _settings.Root.Descendants(<span style="color: #BA2121">"StringVariable"</span>).ToDictionary(
v => v.Attribute(<span style="color: #BA2121">"name"</span>).Value,
v => v.Attribute(<span style="color: #BA2121">"value"</span>).Value);
}
<span style="color: #008000; font-weight: bold">private</span> <span style="color: #008000; font-weight: bold">void</span> <span style="color: #0000FF">SetVariable</span>(<span style="color: #B00040">string</span> name, <span style="color: #B00040">string</span> <span style="color: #008000; font-weight: bold">value</span>)
{
var variable = _settings.Root.Descendants(<span style="color: #BA2121">"StringVariable"</span>).Where(x => x.Attribute(<span style="color: #BA2121">"name"</span>).Value == name).FirstOrDefault();
<span style="color: #008000; font-weight: bold">if</span> (variable != <span style="color: #008000; font-weight: bold">null</span>)
{
variable.Attribute(<span style="color: #BA2121">"value"</span>).Value = <span style="color: #008000; font-weight: bold">value</span>.ToString();
}
<span style="color: #008000; font-weight: bold">else</span>
{
_settings.Root.Descendants(<span style="color: #BA2121">"Variables"</span>).First().Add(
<span style="color: #008000; font-weight: bold">new</span> <span style="color: #0000FF">XElement</span>(<span style="color: #BA2121">"StringVariable"</span>, <span style="color: #008000; font-weight: bold">new</span> XAttribute(<span style="color: #BA2121">"name"</span>, name), <span style="color: #008000; font-weight: bold">new</span> XAttribute(<span style="color: #BA2121">"value"</span>, <span style="color: #008000; font-weight: bold">value</span>)));
}
_settings.Save(_settingsPath);
}
<span style="color: #008000; font-weight: bold">private</span> <span style="color: #008000; font-weight: bold">void</span> <span style="color: #0000FF">RemoveVariable</span>(<span style="color: #B00040">string</span> name)
{
var variable = _settings.Root.Descendants(<span style="color: #BA2121">"StringVariable"</span>).Where(x => x.Attribute(<span style="color: #BA2121">"name"</span>).Value == name).FirstOrDefault();
<span style="color: #008000; font-weight: bold">if</span> (variable != <span style="color: #008000; font-weight: bold">null</span>)
{
variable.Remove();
_settings.Save(_settingsPath);
}
}
<span style="color: #008000; font-weight: bold">private</span> <span style="color: #008000; font-weight: bold">void</span> <span style="color: #0000FF">ClearVariables</span>()
{
var variables = _settings.Root.Descendants(<span style="color: #BA2121">"StringVariable"</span>);
<span style="color: #008000; font-weight: bold">foreach</span> (var variable <span style="color: #008000; font-weight: bold">in</span> variables)
{
variable.Remove();
}
_settings.Save(_settingsPath);
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #008000; font-weight: bold">void</span> <span style="color: #0000FF">Add</span>(<span style="color: #B00040">string</span> key, <span style="color: #B00040">string</span> <span style="color: #008000; font-weight: bold">value</span>)
{
_dict.Add(key, <span style="color: #008000; font-weight: bold">value</span>);
SetVariable(key, <span style="color: #008000; font-weight: bold">value</span>);
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #B00040">bool</span> <span style="color: #0000FF">ContainsKey</span>(<span style="color: #B00040">string</span> key)
{
<span style="color: #008000; font-weight: bold">return</span> _dict.ContainsKey(key);
}
<span style="color: #008000; font-weight: bold">public</span> ICollection<<span style="color: #B00040">string</span>> Keys
{
<span style="color: #008000; font-weight: bold">get</span> { <span style="color: #008000; font-weight: bold">return</span> _dict.Keys; }
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #B00040">bool</span> <span style="color: #0000FF">Remove</span>(<span style="color: #B00040">string</span> key)
{
RemoveVariable(key);
<span style="color: #008000; font-weight: bold">return</span> _dict.Remove(key);
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #B00040">bool</span> <span style="color: #0000FF">TryGetValue</span>(<span style="color: #B00040">string</span> key, <span style="color: #008000; font-weight: bold">out</span> <span style="color: #B00040">string</span> <span style="color: #008000; font-weight: bold">value</span>)
{
<span style="color: #008000; font-weight: bold">return</span> _dict.TryGetValue(key, <span style="color: #008000; font-weight: bold">out</span> <span style="color: #008000; font-weight: bold">value</span>);
}
<span style="color: #008000; font-weight: bold">public</span> ICollection<<span style="color: #B00040">string</span>> Values
{
<span style="color: #008000; font-weight: bold">get</span> { <span style="color: #008000; font-weight: bold">return</span> _dict.Values; }
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #B00040">string</span> <span style="color: #008000; font-weight: bold">this</span>[<span style="color: #B00040">string</span> key]
{
<span style="color: #008000; font-weight: bold">get</span>
{
<span style="color: #008000; font-weight: bold">return</span> _dict[key];
}
<span style="color: #008000; font-weight: bold">set</span>
{
_dict[key] = <span style="color: #008000; font-weight: bold">value</span>;
SetVariable(key, <span style="color: #008000; font-weight: bold">value</span>);
}
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #008000; font-weight: bold">void</span> <span style="color: #0000FF">Add</span>(KeyValuePair<<span style="color: #B00040">string</span>, <span style="color: #B00040">string</span>> item)
{
_dict.Add(item);
SetVariable(item.Key, item.Value);
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #008000; font-weight: bold">void</span> <span style="color: #0000FF">Clear</span>()
{
ClearVariables();
_dict.Clear();
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #B00040">bool</span> <span style="color: #0000FF">Contains</span>(KeyValuePair<<span style="color: #B00040">string</span>, <span style="color: #B00040">string</span>> item)
{
<span style="color: #008000; font-weight: bold">return</span> _dict.Contains(item);
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #008000; font-weight: bold">void</span> <span style="color: #0000FF">CopyTo</span>(KeyValuePair<<span style="color: #B00040">string</span>, <span style="color: #B00040">string</span>>[] array, <span style="color: #B00040">int</span> arrayIndex)
{
_dict.CopyTo(array, arrayIndex);
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #B00040">int</span> Count
{
<span style="color: #008000; font-weight: bold">get</span> { <span style="color: #008000; font-weight: bold">return</span> _dict.Count; }
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #B00040">bool</span> IsReadOnly
{
<span style="color: #008000; font-weight: bold">get</span> { <span style="color: #008000; font-weight: bold">return</span> <span style="color: #008000; font-weight: bold">false</span>; }
}
<span style="color: #008000; font-weight: bold">public</span> <span style="color: #B00040">bool</span> <span style="color: #0000FF">Remove</span>(KeyValuePair<<span style="color: #B00040">string</span>, <span style="color: #B00040">string</span>> item)
{
RemoveVariable(item.Key);
<span style="color: #008000; font-weight: bold">return</span> _dict.Remove(item);
}
<span style="color: #008000; font-weight: bold">public</span> IEnumerator<KeyValuePair<<span style="color: #B00040">string</span>, <span style="color: #B00040">string</span>>> GetEnumerator()
{
<span style="color: #008000; font-weight: bold">return</span> _dict.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
<span style="color: #008000; font-weight: bold">return</span> _dict.GetEnumerator();
}
}
}
</pre></div>
Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0tag:blogger.com,1999:blog-9223352140090705474.post-88103985751557489872013-05-21T17:26:00.001+02:002015-04-29T11:49:50.654+02:00Deploying RPS scripts with DeployRpsAddinI 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.<br />
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.<br />
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.<br />
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.<br />
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.<br />
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.<br />
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 <tt class="docutils literal">RevitPythonShell2013.xml</tt> file that gets deployed with
your addin to the <tt class="docutils literal">%AppData%</tt> folder that you can use for setting up stuff.<br />
The structure of the deployment xml file looks like this: (I will call it RpsAddin xml file from now on)<br />
<div class="highlight" style="background: #f8f8f8;">
<pre style="line-height: 125%;"><span style="color: #bc7a00;"><?xml version=" 1.0" encoding=" utf-8" ?></span>
<span style="color: green; font-weight: bold;"><RpsAddin></span>
<span style="color: green; font-weight: bold;"><RibbonPanel</span> <span style="color: #7d9029;">text=</span><span style="color: #ba2121;">" Hello World"</span><span style="color: green; font-weight: bold;">></span>
<span style="color: #408080; font-style: italic;"><!-- the script is always searched relative to the location of the RpsAddin xml file --></span>
<span style="color: green; font-weight: bold;">< PushButton</span> <span style="color: #7d9029;">text=</span><span style="color: #ba2121;">"Hello World! "</span> <span style="color: #7d9029;">src=</span><span style="color: #ba2121;">"helloworld.py "</span><span style="color: green; font-weight: bold;">/></span>
<span style="color: green; font-weight: bold;"></RibbonPanel></span>
<span style="color: green; font-weight: bold;"></RpsAddin></span>
</pre>
</div>
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.<br />
You can then use an <a class="reference external" href="http://www.jrsoftware.org/isinfo.php">InnoSetup</a> file to create an installer for this. The
HelloWorld example comes with this file:<br />
<div class="highlight" style="background: #f8f8f8;">
<pre style="line-height: 125%;">[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
</pre>
</div>
I included some Pascal code for installing an addin manifest to the <tt class="docutils literal">%APPDATA%</tt>
folder. This is generally something like
<tt class="docutils literal"><span class="pre">C:\Users\username\AppData\Roaming\ADDIN_NAME</span></tt>. Running this setup will
produce a file called <tt class="docutils literal">Setup_Helloworld.exe</tt> 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!Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com53tag:blogger.com,1999:blog-9223352140090705474.post-1714790302567148792013-01-16T14:10:00.001+01:002013-01-16T15:03:54.412+01:00Everyone vs. Just Me for Custom Actions Installer Classes<p>This post explains how to change the behavior of your Custom Actions Installer
class depending on the user choice in the dialog box:</p>
<img alt="https://dl.dropbox.com/u/8112069/darenatwork/2013.01.16_JustMeEveryone.png" src="https://dl.dropbox.com/u/8112069/darenatwork/2013.01.16_JustMeEveryone.png" />
<div class="section" id="the-problem">
<h1>The Problem</h1>
<p>When you create a Custom Action project to be called from a Visual Studio 2010
Setup project, you subclass <a class="reference external" href="http://msdn.microsoft.com/en-us/library/system.configuration.install.installer.aspx">System.Configuration.Install.Installer</a>. 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.</p>
<p>Except it doesn't contain the information about wether the
user chose "Just Me" or "Everyone"!</p>
</div>
<div class="section" id="the-solution">
<h1>The Solution</h1>
<p>I came across <a class="reference external" href="http://www.simple-talk.com/dotnet/visual-studio/visual-studio-setup---projects-and-custom-actions/">this page</a> that explained how to pass in information to
<tt class="docutils literal">Context.Parameters</tt> in the custom action: You can use the
<tt class="docutils literal">CustomActionData</tt> property to add values to <tt class="docutils literal">Context.Parameters</tt>.</p>
<p>The trick is, to use the format <tt class="docutils literal">"/varname=value"</tt>, and variable substitution
happens for Windows Installer Properties, if they are enclosed in square
brackets...</p>
<p>After googling a bit more, I found a msdn page containing the <a class="reference external" href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa370905(v=vs.85).aspx">Windows
Installer Property Reference</a> and searching that, I found the property
<a class="reference external" href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa367559(v=vs.85).aspx">ALLUSERS</a>. This is set to the string <tt class="docutils literal">"1"</tt> if the user chose to install for
"Everyone" and set to the empty string <tt class="docutils literal">""</tt> if the user chose to install for
"Just Me".</p>
<img alt="https://dl.dropbox.com/u/8112069/darenatwork/2013.01.16_CustomActionData.png" src="https://dl.dropbox.com/u/8112069/darenatwork/2013.01.16_CustomActionData.png" />
<p>Putting two and two together, I added the string <tt class="docutils literal"><span class="pre">"/allusers=[ALLUSERS]"</span></tt> to
the <tt class="docutils literal">CustomActionData</tt> property for the custom actions. And then can query
this inside the Installer class:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">if</span> (Context.Parameters[<span style="color: #BA2121">"allusers"</span>] == <span style="color: #BA2121">"1"</span>)
{
<span style="color: #408080; font-style: italic">// user selected "Everyone"</span>
}
<span style="color: #008000; font-weight: bold">else</span>
{
<span style="color: #408080; font-style: italic">// user selected "Just Me"</span>
}
</pre></div>
<p>Testing this proved that the ALLUSERS property is retained by the Windows
Installer and can also be used for uninstalling.</p>
</div>
<div class="section" id="context-installing-revit-addins">
<h1>Context: Installing Revit Addins</h1>
<p>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.</p>
<p>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 <a class="reference external" href="http://code.google.com/p/revitpythonshell/source/browse/branches/rps2013/RegisterRevit2013Addin/RegisterRevit2013Addin.cs">RegisterAddinCustomAction</a> class.</p>
</div>
Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com4tag:blogger.com,1999:blog-9223352140090705474.post-19982888785295777632012-10-05T17:22:00.001+02:002012-10-05T17:22:35.002+02:00RPS script to print a list of floor types and their functions<p>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
<a class="reference external" href="http://code.google.com/p/revitpythonshell/">RevitPythonShell</a> script, but ran into some snags along the way that I would
like to share.</p>
<p>This should really be as easy as:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">collector <span style="color: #666666">=</span> FilteredElementCollector(doc)
<span style="color: #008000; font-weight: bold">for</span> floor_type <span style="color: #AA22FF; font-weight: bold">in</span> collector<span style="color: #666666">.</span>OfClass(FloorType):
<span style="color: #008000; font-weight: bold">print</span> floor_type<span style="color: #666666">.</span>Name, floor_type<span style="color: #666666">.</span>Function
</pre></div>
<p>But, well, nothing is ever easy, is it? What I ended up doing was this:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">System</span> <span style="color: #008000; font-weight: bold">import</span> Enum
collector <span style="color: #666666">=</span> FilteredElementCollector(doc)
collector<span style="color: #666666">.</span>OfClass(FloorType)
<span style="color: #008000; font-weight: bold">for</span> floor_type <span style="color: #AA22FF; font-weight: bold">in</span> collector:
name <span style="color: #666666">=</span> Element<span style="color: #666666">.</span>Name<span style="color: #666666">.</span>GetValue(floor_type)
function_param <span style="color: #666666">=</span> Element<span style="color: #666666">.</span>get_Parameter(floor_type, BuiltInParameter<span style="color: #666666">.</span>FUNCTION_PARAM)
<span style="color: #008000; font-weight: bold">if</span> function_param:
function <span style="color: #666666">=</span> Enum<span style="color: #666666">.</span>ToObject(WallFunction, function_param<span style="color: #666666">.</span>AsInteger())<span style="color: #666666">.</span>ToString()
<span style="color: #008000; font-weight: bold">else</span>:
function <span style="color: #666666">=</span> <span style="color: #BA2121">'None'</span>
<span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">'</span><span style="color: #BB6688; font-weight: bold">%(name)-25s</span><span style="color: #BB6622; font-weight: bold">\t</span><span style="color: #BB6688; font-weight: bold">%(function)s</span><span style="color: #BA2121">'</span> <span style="color: #666666">%</span> <span style="color: #008000">locals</span>()
</pre></div>
<p>Let's go through this line by line...</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">from</span> <span style="color: #0000FF; font-weight: bold">System</span> <span style="color: #008000; font-weight: bold">import</span> Enum
</pre></div>
<p>Uh... I'll explain that later on...</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">collector <span style="color: #666666">=</span> FilteredElementCollector(doc)
collector<span style="color: #666666">.</span>OfClass(FloorType)
<span style="color: #008000; font-weight: bold">for</span> floor_type <span style="color: #AA22FF; font-weight: bold">in</span> collector:
</pre></div>
<p>Nothing unusual here - this is how to get a list of elements from a Revit
document using a <tt class="docutils literal">FilteredElementCollector</tt> and iterating over that list.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">name <span style="color: #666666">=</span> Element<span style="color: #666666">.</span>Name<span style="color: #666666">.</span>GetValue(floor_type)
</pre></div>
<p>What?! Shouldn't that just have been <tt class="docutils literal">floor_type.Name</tt>? I thought so too. But
somehow, whenever I do that, I get an error like this:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #666666">>>></span> <span style="color: #408080; font-style: italic"># note how ugly one-liners get ;)</span>
<span style="color: #666666">>>></span> ft <span style="color: #666666">=</span> <span style="color: #008000">list</span>(FilteredElementCollector(doc)<span style="color: #666666">.</span>OfClass(FloorType))[<span style="color: #666666">0</span>]<span style="color: #666666">.</span>Name
Traceback (most recent call last):
File <span style="color: #BA2121">"<stdin>"</span>, line <span style="color: #666666">1</span>, <span style="color: #AA22FF; font-weight: bold">in</span> <span style="color: #666666"><</span>module<span style="color: #666666">></span>
<span style="color: #D2413A; font-weight: bold">AttributeError</span>: Name
</pre></div>
<p>This <tt class="docutils literal">AttributeError</tt> is somehow related to IronPython not being able to
figure out that it needs to use the base method inherited from
<tt class="docutils literal">Autodesk.Revit.DB.Element</tt>. So, I tell it to use that explicitly by
referencing the property explicitly with <tt class="docutils literal">Element.Name</tt> - and then retrieving
its value: <tt class="docutils literal">.GetValue</tt>. It needs to be told for which instance to retrieve
the value, which is why I then plug in the <tt class="docutils literal">floor_type</tt> object.</p>
<p>So... now we have the name of the <tt class="docutils literal">FloorType</tt>. What about its function? That isn't a property, but rather a parameter, that can be found using the Revit Lookup tool: <tt class="docutils literal">BuiltInParameter.FUNCTION_PARAM</tt>. Retrieving parameters is easy:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">function_param <span style="color: #666666">=</span> Element<span style="color: #666666">.</span>get_Parameter(floor_type, BuiltInParameter<span style="color: #666666">.</span>FUNCTION_PARAM)
</pre></div>
<p>Note again, how I use the base method <tt class="docutils literal">Element.get_Parameter</tt>, as it doesn't seem to work on <tt class="docutils literal">FloorType</tt> directly - my guess is that the definition of <tt class="docutils literal">FloorType</tt> somehow isn't what IronPython is used to...</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"> <span style="color: #008000; font-weight: bold">if</span> function_param:
function <span style="color: #666666">=</span> Enum<span style="color: #666666">.</span>ToObject(WallFunction, function_param<span style="color: #666666">.</span>AsInteger())<span style="color: #666666">.</span>ToString()
<span style="color: #008000; font-weight: bold">else</span>:
function <span style="color: #666666">=</span> <span style="color: #BA2121">'None'</span>
</pre></div>
<p>The parameter may be <tt class="docutils literal">null</tt> or, in python <tt class="docutils literal">None</tt>, which will evaluate to <tt class="docutils literal">False</tt> in a condition. In that case, we just set the text of the variable <tt class="docutils literal">function</tt> to <tt class="docutils literal">'None'`</tt>. But the other line is a lot more interesting! This is why we did the <tt class="docutils literal">from System import Enum</tt> earlier on: The (integer) value of the <tt class="docutils literal">FUNCTION_PARAM</tt> is actually a member of an enumeration. As far as I can tell, it is equivalent to the <tt class="docutils literal">WallFunction</tt> enumeration. With <tt class="docutils literal">Enum.ToObject(WallFunction, <span class="pre">function_param.AsInteger())</span></tt>, we can an the appropriate instance of this enumeration, which we then convert to a string (<tt class="docutils literal">ToString()</tt>).</p>
<p>The last line:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">print</span> <span style="color: #BA2121">'</span><span style="color: #BB6688; font-weight: bold">%(name)-25s</span><span style="color: #BB6622; font-weight: bold">\t</span><span style="color: #BB6688; font-weight: bold">%(function)s</span><span style="color: #BA2121">'</span> <span style="color: #666666">%</span> <span style="color: #008000">locals</span>()
</pre></div>
<p>Prints out the name and function nicely, using <a class="reference external" href="http://docs.python.org/library/string.html#format-string-syntax">pythons string formatting</a>: Left-alined name with a minimum width of 25 characters, a tab and then the function.</p>
<p>Try the script!</p>
Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com3tag:blogger.com,1999:blog-9223352140090705474.post-20140568708089232842012-01-18T14:29:00.001+01:002012-01-18T14:29:30.453+01:00Converting line endings on the cheap<p>You're at a client's PC, installing some scripts and need to edit a text file.
Of course your client has no <em>real</em> editor, just that stupid <tt class="docutils literal">notepad.exe</tt>!
And then you notice: All the line endings are wrong, that is, they're <tt class="docutils literal">LF</tt>
(unix convention) as opposed to <tt class="docutils literal"><span class="pre">CR-LF</span></tt> (dos convention). That is because you
<em>do</em> 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.</p>
<p>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.</p>
<p>Now, users of cygwin are probably familiar with a myriad of tools (e.g.
<tt class="docutils literal">dos2unix.exe</tt>) to convert from dos line endings to unix line endings. You
might be able to get <tt class="docutils literal">tr</tt> to work for you too. Or you could just open the
file in your trusty <em>real</em> editor and type away.</p>
<p>But your client has none of this installed.</p>
<p>What your client <em>does</em> have installed, is MS Word. Seriously. I'm going to use MS Word now. This is how:</p>
<blockquote>
<ol class="arabic simple">
<li>open the script/text file in notepad</li>
<li>select all text, copy it to the clipboard ([CTRL]+[A], [CTRL]+[C])</li>
<li>open a blank document in MS Word</li>
<li>paste from the clipboard ([CTRL]+[V])</li>
<li>select all text in MS Word, copy it to the clipboard ([CTRL]+[A], [CTRL]+[C])</li>
<li>paste over (replacing) the selection in notepad ([CTRL]+[V])</li>
<li>close MS Word and wash your hands to get rid of the dirty hack feeling...</li>
</ol>
</blockquote>
Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com3tag:blogger.com,1999:blog-9223352140090705474.post-25418949393595271852011-11-11T16:37:00.003+01:002011-11-11T16:37:53.440+01:00How to install Trac on a Synology DS2011+<p>In a <a class="reference external" href="http://darenatwork.blogspot.com/2011/03/how-to-install-trac-on-synology-ds209ii.html">previous post</a>, I described installing <a class="reference external" href="http://trac.edgewall.org/">Trac</a> 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.</p>
<div class="section" id="modifying-the-synology-diskstation">
<h1>Modifying the Synology DiskStation</h1>
<p>To be able to do anything to your DS2011+, you first have to install the <tt class="docutils literal">ipkg</tt> bootstrap. I basically followed along with page on the synology forum <a class="reference external" href="http://forum.synology.com/wiki/index.php/Overview_on_modifying_the_Synology_Server,_bootstrap,_ipkg_etc.">Overview on modifying the Synology Server, bootstrap, ipkg etc.</a>:</p>
<blockquote>
<ul class="simple">
<li>You need to <cite>enable ssh access</cite> to your NAS and then log in with <a class="reference external" href="http://www.chiark.greenend.org.uk/~sgtatham/putty/">PuTTY</a></li>
<li>The Synology DS2011+ has a "Marvell Kirkwood mv6282" processor, so these steps should do the trick:</li>
</ul>
</blockquote>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000">cd</span> /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
</pre></div>
<p>After rebooting the NAS, you can enter these commands:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">ipkg update
ipkg upgrade
</pre></div>
<p>We are now ready to start installing all the stuff needed for running Trac!</p>
</div>
<div class="section" id="software-to-install">
<h1>Software to install</h1>
<div class="section" id="apache">
<h2>Apache</h2>
<p>Log on to your DS2011+ and install the following package:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">ipkg install apache
</pre></div>
<p>Now we have a second <tt class="docutils literal">httpd</tt> listening on port 8000. The configuration file
for this instance of apache is <tt class="docutils literal">/opt/etc/apache2/httpd.conf</tt>. You can find
the log files in <tt class="docutils literal">/opt/var/apache2/log</tt>. Also note that it is started on
system boot by the script <tt class="docutils literal">/opt/etc/init.d/S80apache</tt>. I suggest you execute
this script and see if apache starts up nicely.</p>
<p>I ran into some problems with this installation: A syntax error on line 74 of
<tt class="docutils literal">/opt/etc/apache2/httpd.conf</tt>. Somehow apache can't load the
<tt class="docutils literal">mod_ext_filter.so</tt>. I didn't really care and just commented out that line.</p>
</div>
<div class="section" id="subversion">
<h2>Subversion</h2>
<p>Create a new share using the DSM (that web interface on port 5000) - call it
"svn". It will show up on your box as <tt class="docutils literal">/volume1/svn</tt>.</p>
<p>I mainly followed the <a class="reference external" href="http://forum.synology.com/wiki/index.php/Step-by-step_guide_to_installing_Subversion_on_ARM-based_Synology_Products">Step-by-step guide to installing Subversion</a> on the
Synology wiki. But I didn't bother messing with <cite>inetd.conf</cite>, since I am
running svn under apache with <tt class="docutils literal">mod_dav_svn</tt>. So, that leaves the following
steps:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">ipkg install svn
su svnowner
<span style="color: #008000">cd</span> /volume1/svn
svnadmin create testsvn
</pre></div>
<p>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 <a class="reference external" href="http://forum.synology.com/wiki/index.php/Step-by-step_guide_to_installing_Subversion_on_ARM-based_Synology_Products">Step-by-step guide
to installing Subversion</a> includes a few hints on how to get <tt class="docutils literal">su svnowner</tt>
to work if you get an error message.</p>
<p>To get Apache to serve your subversion repository, append this to <tt class="docutils literal">/opt/etc/apache2/httpd.conf</tt>:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># Subversion</span>
Include etc/apache2/conf.d/mod_dav_svn.conf
</pre></div>
<p>You will have to create <tt class="docutils literal">/opt/etc/apache2/conf.d/mod_dav_svn.conf</tt> of course:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000">LoadModule</span> dav_svn_module libexec/mod_dav_svn.so
<span style="color: #008000">LoadModule</span> authz_svn_module libexec/mod_authz_svn.so
<span style="color: #008000; font-weight: bold"><Location</span> <span style="color: #BA2121">/svn</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000">DAV</span> svn
<span style="color: #008000">SVNParentPath</span> <span style="color: #008000">/volume1/svn</span>
<span style="color: #008000">AuthType</span> Basic
<span style="color: #008000">AuthName</span> <span style="color: #BA2121">"Subversion repository"</span>
<span style="color: #008000">AuthUserFile</span> <span style="color: #008000">/volume1/svn/svn-auth-file</span>
<span style="color: #008000">Require</span> valid-user
<span style="color: #008000; font-weight: bold"></Location></span>
</pre></div>
<p>This expects a file <tt class="docutils literal"><span class="pre">/volume1/svn/svn-auth-file</span></tt> to be present. That file
contains the passwords used for Subversion (and, via Apache, Trac
authentication). This file is generated with apaches <tt class="docutils literal">htpasswd <span class="pre">-c</span></tt> command. I
used the <tt class="docutils literal">cm</tt> too, to force md5 hashing of the passwords.</p>
</div>
<div class="section" id="id1">
<h2>Trac</h2>
<p>So far, this guide is much the same as the one it replaces. The new stuff is in the version of python used:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%">ipkg install python26 py26-trac py26-genshi py26-setuptools sqlite svn-py
</pre></div>
<p>The need for upgrading comes from the package <tt class="docutils literal"><span class="pre">svn-py</span></tt>, which is now python 2.6.</p>
<p>Create a share (using the DSM) called "trac-env". This is where we will keep our trac installations.</p>
<p>I am now running Trac version 0.12. The Trac environments can be found at <tt class="docutils literal"><span class="pre">/volume1/trac-env</span></tt>. They are called "MYTRAC1" and "MYTRAC2". Access to them is via the following URLs:</p>
<blockquote>
<ul class="simple">
<li><a class="reference external" href="http://myserver.example.com:8888/MYTRAC1">http://myserver.example.com:8888/MYTRAC1</a></li>
<li><a class="reference external" href="http://myserver.example.com:8888/MYTRAC2">http://myserver.example.com:8888/MYTRAC2</a></li>
</ul>
</blockquote>
<p>Trac is run using tracd. This is configured to start automatically on system start by creation of the following file: <tt class="docutils literal">/opt/etc/init.d/S81trac</tt>:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic">#!/bin/sh</span>
<span style="color: #408080; font-style: italic"># run tracd</span>
/opt/bin/tracd -d -p 8888 -e /volume1/trac-env
</pre></div>
<p>The file must be set to executable for root (see documentation for <tt class="docutils literal">chmod</tt>), so that it will be run at system start.</p>
<p>Trac configuration files can be found at</p>
<blockquote>
<ul class="simple">
<li><tt class="docutils literal"><span class="pre">/volume1/trac-env/MYTRAC1/conf/trac.ini</span></tt></li>
<li><tt class="docutils literal"><span class="pre">/volume1/trac-env/MYTRAC2/conf/trac.ini</span></tt></li>
</ul>
</blockquote>
</div>
<div class="section" id="authentication-via-apache">
<h2>Authentication via Apache</h2>
<p>appended following lines to <tt class="docutils literal">/opt/etc/apache2/httpd.conf</tt>:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># Subversion</span>
<span style="color: #008000">Include</span> etc/apache2/conf.d/trac.conf
</pre></div>
<p>And then, create the file <tt class="docutils literal">/opt/etc/apache2/conf.d/trac.conf</tt>:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #408080; font-style: italic"># allow Apache to be used for authenticating Trac</span>
<span style="color: #008000; font-weight: bold"><Directory</span> <span style="color: #BA2121">/opt/share/www/trac</span><span style="color: #008000; font-weight: bold">></span>
<span style="color: #008000">AuthType</span> Basic
<span style="color: #008000">AuthName</span> <span style="color: #BA2121">"Subversion repository"</span>
<span style="color: #008000">AuthUserFile</span> <span style="color: #008000">/volume1/svn/svn-auth-file</span>
<span style="color: #008000">Require</span> valid-user
<span style="color: #008000; font-weight: bold"></Directory></span>
</pre></div>
<p>Authentication is done with the AccountManager plugin for trac
(<a class="reference external" href="http://trac-hacks.org/wiki/AccountManagerPlugin">http://trac-hacks.org/wiki/AccountManagerPlugin</a>). The website has a good
explanation on how that works. See the copies of the <tt class="docutils literal">trac.ini</tt> files for
reference. I'd like to point out the last section:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%"><span style="color: #008000; font-weight: bold">[account-manager]</span>
<span style="color: #7D9029">password_store</span> <span style="color: #666666">=</span> <span style="color: #BA2121">HttpAuthStore</span>
<span style="color: #7D9029">authentication_url</span> <span style="color: #666666">=</span> <span style="color: #BA2121">http://myserver.example.com:8000/trac</span>
</pre></div>
<p>This is where we tell Trac to use our Apache Server for authentication. Note how the <tt class="docutils literal">authentication_url</tt> is the same as the <tt class="docutils literal">Directory</tt> in <tt class="docutils literal">trac.conf</tt>.</p>
</div>
</div>
<div class="section" id="backup">
<h1>Backup</h1>
<p>Backup is still the same as in the previous post, so I'll just point you there: <a class="reference external" href="http://darenatwork.blogspot.com/2011/03/how-to-install-trac-on-synology-ds209ii.html">http://darenatwork.blogspot.com/2011/03/how-to-install-trac-on-synology-ds209ii.html</a></p>
</div>
Daren Thomashttp://www.blogger.com/profile/16173188589736612418noreply@blogger.com0