miércoles, 7 de diciembre de 2016

Align Pipes at bottom

This is an old Revit macro I've created to align pipes to the bottom (BOP or BOI).
It asks for a pipe to be used as reference and the pipes to be aligned.

You need to create a new macro and paste the code below replacing all the content of the file. After this you will se a module named Align_Pipe

The module should look like this

domingo, 20 de noviembre de 2016

AU 2016 Part 1 - Import Geometry from Rhino to Revit

The guys from Perkins+Will show us a few workflows to import geometry to Revit from different platforms.
The one l'd liked more was converting Rhino models into Revit families allowing the user to change the geometry. This can be done using Dynamo to import a SAT file exported from Rhino to create Forms inside a template family.


Image copyright belongs to Perkins+Will or the autors of the talk

martes, 8 de noviembre de 2016

Accessing ModelItems from SelectionSets in Navisworks

The client needs a Synchro 4D animation of his building do to detect possible issues in constructions and also a video to show to the rest of the stakeholders.

Synchro4D is a software for 4D visualization and construction coordination.It allows you to animate and program any 3D object exported from Autodad, Revit, Infraworks, sketchup, etc.

http://xkcd.com/1739/
Not related with post but not less true...

The only input I have right now is a federated navisworks model and a mpp - Ms Project - file with tasks and a Gantt diagram.

Synchro (as Navisworks) has the ability of read any parameter on the Revit model and relate it to the right task from the project.*

In order to save time while waiting for Revit models I had the idea of creating a Selection Set per task containing Model items related to each one. This Selection Sets will be named after the task's name.

Category's Scheme to work with.

The only thing left  to do is export a csv file with the sets' content, displaying set's name, source of the file (revit's file name where were exported) and element's Id .
The file should look like this:

SelectionSet,SourceName,ElementId

This can be achieved using the Navisworks API to access all the sets, and then all elements on each one.

Here is the code I've used. You may want to test it using the add-in CodeRun include with Naviswork's API.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.DocumentParts;
using System.IO;

namespace CScript
{
    public class CScript
    {
        static public void Main()
        {
            var csv = new StringBuilder();
            var output = new List<String>();
            Document oDoc = Autodesk.Navisworks.Api.Application.ActiveDocument;
            //All selection sets in model
            Autodesk.Navisworks.Api.DocumentParts.DocumentSelectionSets oCurSets = oDoc.SelectionSets;
            //Convert to SavedItemCollectin
            SavedItemCollection coll = oCurSets.ToSavedItemCollection();
            //Path of the current file to save our csv later
            var path = (oDoc.CurrentFileName).Replace(oDoc.Title, "");

            //Iterate over all selections sets to create a current selection
            foreach (SavedItem i in coll)
            {
                //Create a selection source with all objects in selection
                SelectionSource oS = Autodesk.Navisworks.Api.Application.ActiveDocument.SelectionSets.CreateSelectionSource(i);
                //Get a list of ModelItems in Selection Set
                ModelItemCollection list = oS.TryGetSelectedItems(oDoc);

                Selection a = new Selection(list);
                var set = i.DisplayName;

                Autodesk.Navisworks.Api.Application.ActiveDocument.CurrentSelection.AddRange(list);
                foreach (ModelItem m in oDoc.CurrentSelection.SelectedItems)

                {
                    var Name = m.DisplayName;
                    var FC = m.PropertyCategories.FindPropertyByDisplayName("Item", "Source File").Value.ToDisplayString();
                    var SC = m.PropertyCategories.FindPropertyByDisplayName("Element ID", "Value").Value.ToDisplayString();

                    var newLine = string.Format("{0},{1},{2},{3}", set, Name, FC, SC);
                    output.Add(newLine);
                    csv.AppendLine(newLine);
                }
                Autodesk.Navisworks.Api.Application.ActiveDocument.CurrentSelection.Clear();
            }
            //Check if document is saved and use its path to save the csv
            if (oDoc.FileName != null)
            {
                File.WriteAllText(path + oDoc.Title + ".csv", csv.ToString());
            }
            else

            {
                //if file is not saved (untitled) use MyDocuments path instade 
                path = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                File.WriteAllText(path + oDoc.Title + ".csv", csv.ToString());
            }
            //test

            foreach (string s in output)
            {
                Console.WriteLine(s);
            }
        }
    }
}

Notice it will export the csv file to the same folder of the NWD or NWF, if the file was not saved it will be saved in MyDocuments.

Later we will input this information to each Revit model using Dynamo and then export it to Synchro4D.

Hope you find it useful! Let me know if you have any ideas to share or a different workflow to work with.

*We don't use Navisworks for this work because Synchro4D has a better platform to animate the actions on each thask - Demo, Temporary, New construction - and the final outcome needs to show how it actually has to be built.



jueves, 3 de noviembre de 2016

Coincident Points on X and Y

In honor of the past "Dia de los muertos" here in Mexico I'm bringing you today's problem.

The continuous struggle of bringing back to life the dead projects that fall on my desk
I've placed a series of points using Autodesk Point layout on all walls in my Revit model.
The client wants one point on each corner of the wall and  - this is the tricky part - the location of the point regarding the wall as a description -Bottom Of Wall or Top Of Wall.


Proposed workflow


Using list of tuples we start filling the description parameters 


The python code will look like this


def coincident(x,y,z):
    el = []
    li = x
    int=1
    #Here start the first part of the workflow
    while int < li.Count:
        i = li[0]
        li.Remove(i)
        for e in li:
        #Round the number to avoid comparing floats
            
            if round(i.Location.Point.X,4) == round(e.Location.Point.X,4) and round(i.Location.Point.Y,4) == round(e.Location.Point.Y,4):
                
                #Here start the second part of the workflow, filling the parameters
                TransactionManager.Instance.EnsureInTransaction(doc)
                if e.Location.Point.Z < i.Location.Point.Z:
                #a= the name of the parameter
                #b = to the code you need to add (BOW, or TOW)
                    e.LookupParameter(a).Set(b)
                    el.append(e)
                else: 
                    i.LookupParameter(a).Set(b)
                    el.append(i)
                TransactionManager.Instance.TransactionTaskDone()
        int+1
    return el

Note that you need to "feed" the function with a list of elements that are point based.


Plot twist:
We could change the code to  set the lower point as 0 and the upper point as the difference between the lower and the upper and then we have the wall actual height as data.


if e.Location.Point.Z < i.Location.Point.Z:
    #a= the name of the parameter
        e.LookupParameter(a).Set("0")
  i.LookupParameter(a).Set(str(i.Location.Point.Z - e.Location.Point.Z))

    else: 
        i.LookupParameter(a).Set("0")
  e.LookupParameter(a).Set(str(e.Location.Point.Z - i.Location.Point.Z))

Hope you find this useful and let me know any comments you have.


General Quick Tip: Copy folder path & Copy folder content's names

Two situations,

1-You've saved a file on a folder really deep into your drive and need to save something else there.
You could use the path but the "Save As" window don't allow you to access the path directions.

For this we'll edit window's register regedit.exe (carefully) looking for the folder
HKEY_CLASSES_ROOT\Directory\background\shell\.
Here we create a new key and name it as  "Copy Path", inside we create a new key named "command". Once you have the last key, create a string value and set the Value Data as below.

cmd.exe /s /k "chdir  |clip & exit" 

The command promps a cmd window at the location of the folder, copies the address to the clipboard and exits the cmd window.

Your register should look like this. Note the complete address in the lower left corner of the image.

2-The second situation, you need to send the name of a batch of +50 files inside a folder by email to show which files you have edited.

For this we use the same logic but instead of asking the path of the current folder, we'll call the command "dir * /B" - dir = show content of directory, * is to show all elements in the directory and /B uses bare format (no heading information or summary).

The result will be a text list on the clipboard like this.


M-001-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 1.pdf
M-002-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 2.pdf
M-003-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 3.pdf
M-004-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 4.pdf
M-005-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 5.pdf
M-006-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 6.pdf
M-007-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 7.pdf
M-008-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 8.pdf
M-009-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 9.pdf
M-010-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 10.pdf
M-011-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 11.pdf
M-012-A - COLES BUILDING HVAC LAYOUT GROUND FLOOR - SHEET 12.pdf

We follow the same instructions as before but creating a main key named "Content"
HKEY_CLASSES_ROOT\Directory\background\shell\Content\command\

The script will look like below.

cmd.exe /s /k "dir * /B |clip & exit"  

The result will be two commands on the context menu (right click) of any folder in windows.



If you think you may broke something or you are too lazy to do it all, you could use my copy of this register entries here. If you doubt of what is included open it with notepad or any text read before adding it.

Hope you find it useful.


General Quick Tip: Bulk Renamer Utility

The client wants to change the name of +100 sheets I've recently printed to reflect a temporary change in the naming convention.

For this task we could use Bulk Rename Utility, a simple program that allows you to change hundred of files simultaneously following some rules.

It supports changes over sub-folders, regular expressions, prefixes and suffixes and even touching metadata.


Bulk Rename Utility



It's free and it could be open at an specific location of the disk by right clicking on a folder.

Link: http://www.bulkrenameutility.co.uk/Main_Intro.php

miércoles, 2 de noviembre de 2016

Revit Quick Tip: All materials are Grey

It may happen to you that you start working with phases you notice something strange. If your phase filter is in "Show All" or "Show New" all existing materials are in grey!

To solve this just create a new filter - is not a good idea to change default filters - to be used as New and All showing everything in color.


I know, this is quite a rookie mistake but you probably - as myself - forget about this simple thing from time to time...


martes, 1 de noviembre de 2016

Numbering Ducts

Last week I'd an encounter of the third kind with a common issue on my company. We needed to number all ducts using a code name per branch. In the past we solved it with an specific add-in or uncountable hours doing one by one by hand.

 If you have done it before you know you can't use a schedule or the mark numbers because it will not follow the order of the branch.

 So I decided to spend a few hours of my weekend finding a way to do it in Dynamo at later port it to a Revit Add-In. As usual, it took me 3 days to get with the solution.

https://www.explainxkcd.com/wiki/index.php/974:_The_General_Problem
What commonly happens...
Disclaimer: Please don't use any of this scripts in production. I can't assure it will don't not take control over your computer and start skynet or something.


The idea:
Having an element at the beginning of the branch and all the elements that conform it as two inicial inputs, we start iterating finding the next object that connects or clashes with it.



In the path I found different ways of doing it wrong and I would like to share them with you.

Wrong Way 1:
Extract the MEPCurve points to find coincidences but they don't convey in the same point even if the elements are connected.
We could try to order the points by location and use is to change the order of elements. 

Problem:
The problem is with the fittings that have more than 2 connectors and the time processing inside Dynamo.

Wrong way 2 :
Next I try using the node Geometry.DoesIntersect() included in dynamo. This function iterates over a list of ducts (y) and the first duct on branch (x)

def order(x,y):
    m=0
    y.Remove(x)
    output = []
    output.append(x)
    for e in y:
        for i in y:
            opt = Options()
            g = UnwrapElement(x).Geometry(opt)
            if g.DoesIntersect(UnwrapElement(i).Geometry(opt)[0]): 
                if i not in output :
                    x=i
                    output.append(i)
                    break
        m+=1

    return output

Problems:
First some duct fittings includes more than one geometry - solid, lines -  and the method changes regarding the type of geometry used.
Besides this  problem, the connection between elements could be really close to each other but not actually touching each other.

Wrong Way 3:
The next attempt was using FilteredElementCollector with a ElementIntersectsSolidFilter. The filter is a quick way of getting a list of elements.


def order(x,y):
    n, m=0 , 0
    y.Remove(x)
    output = []
    output.append(x)
    ids = []
    for i in y:
        ids.append(ElementId(i.Id))
    ids = List[ElementId](ids)
    while len(y) > m:
        opt= Options()
        out = x.Geometry()
        for i in out:
            try:
                filter =  ElementIntersectsSolidFilter(i)
                collector = FilteredElementCollector(doc,ids).WherePasses(filter).ToElements()
                for e in collector:
                    if e not in output:
                        output.append(e)
                        ids.Remove(e.Id)
                        x = y[m]
            except:
                pass
        m+=1

    return output

Problem:
The loop While randomly clashes Dynamo when running. This could be solve using For instead but then we need two list to avoid removing items of  the one iterating.
Again, if the element does not really intersects the filter won't detect it.

Right Way (aka less wrong):
Each element has a bounding box around that wraps up it a little bigger than the object geometry. Using FilteredElementCollector with a BoundingBoxIntersectsFilter.
Note I've added a function to create a collector of ElementIds in order to use it to filter just the elements I prompt the user to select.
In order to be sure the Bounding boxes intersect the elements I've make it a little bit bigger - +/- XYZ(1,1,1) .



def listids(y):
    output = []
    for i in y:
        output.append(ElementId(i.Id))
    # convert list into List[ElementId] to create a specific Id collections needed by the filter
    output =ids = List[ElementId](output)
    return output

def order(x,y):
    m=0
    y.Remove(x)
    output = []
    output.append(x)
    ids = listids(y)
    while len(y) > m:
        el = UnwrapElement(x)
        min = el.get_BoundingBox(doc.ActiveView).Min - XYZ(1,1,1)
        max = el.get_BoundingBox(doc.ActiveView).Max +XYZ(1,1,1)
        out = Outline(min,max)
        filter =  BoundingBoxIntersectsFilter(out)
        if len(ids) > 0:
            collector = FilteredElementCollector(doc,ids).WherePasses(filter).ToElements()
            for i in collector:
                if i not in output:
                    output.append(i)
                    ids.Remove(i.Id)
                    x = i
        m+=1
    return output

Result: 
The result of this will be a list of elements in geometrical order defined using an Element as start.
Have in mind that Bounding boxes cover all objects included in the element, even lines so you need to have a really clean family for this to work.
If is not the case you always can run the script twice, before and after the fitting in question.


Hope you find any of this helpful and let me know if you have an idea to improve it.

EDIT: Tons Typos, hate blogspot

jueves, 28 de julio de 2016

Mechanical from scratch using Dynamo [Part 2]

fromcadtorevit-02
Today I'll explain the node we have created to generate ducts in Revit using lines coordinates from a cad file.
We will use the Revit API method Document.NewDuct Method (XYZ, XYZ, DuctType), which needs 3 parameters:
2 XYZ : (Autodesk.Revit.DB.XYZ) for the first and second point of the duct.
DuctType: (Autodesk.Revit.DB.Mechanical.DuctType) The type of the duct.
Disclaimer: Parts of the code below has been written using examples from the Dynamo Forum. We use those part to tie our code.
First you have to import all libraries needed to run the python script inside Dynamo.
import clr
# Import DocumentManager and TransactionManager
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
# Import RevitAPI
clr.AddReference('RevitAPI')
import Autodesk
# Import Revit Nodes
clr.AddReference("RevitNodes")
import Revit
# Import from Revit DB and all its parts
from Autodesk.Revit.DB import *
# Import from Revit Creation where the Create.NewDuct class is.
from Autodesk.Revit.Creation import *
# Import ToProtoType, ToRevitType geometry conversion extension methods
clr.ImportExtensions(Revit.GeometryConversion)

Then we define Current document in order to avoid calling the function by its full class name.

#Defining Current document
doc = DocumentManager.Instance.CurrentDBDocument

We need to read a cvs file and split it into strings. We also need to remove first and last row of the csv because they contains no numeric data - first line is the header of the csv and the last is empty.


#Konrad K Sobon's code from http://goo.gl/D4v6LU
#Split single string at line breaks into a list of strings
xyzList = IN[0].rsplit("\n")
# Cull the first line, which is header
del xyzList[0]
# Cull the last line, which is blank
del xyzList[len(xyzList)-1]


#Create empty list for points x and y
x = []
y = []

Asking for an input for Duct Type but no entry is needed from outside the script.

#Define Ducttype but not need to be provided by user,
# it will the first it finds
Ducttype = IN[1]

This is the part that makes the trick. First iterating over the xzy list to split it into strings. Then we need create points and append it to lists x and y.


#Iterate over list using Konrad K Sobon list to points script
for xyz in xyzList:
    tpt = xyz.rsplit(',')
    #convert first coord to points and to mm, as revit api
    #works with decimal feets you need to divide the float into 304.8 to mantain mm
    x.append(Autodesk.Revit.DB.XYZ((float(tpt[0])/304.8),(float(tpt[1])/304.8),(float(tpt[2])/304.8)).ToPoint())
    #convert second coord to points and in to mm
    y.append(Autodesk.Revit.DB.XYZ((float(tpt[3])/304.8),(float(tpt[4])/304.8),(float(tpt[5])/304.8)).ToPoint())

Printing x and y point list to check numbers after running the script.


#Print x & y points created
OUT = x,y

Once we have the list of points we need to iterate again and convert each point into XYZ which is needed by the NewDuct method. Note that the method should be contained in a transaction in order to create the duct in Revit.


#Iterate over list using Konrad K Sobon list to points script
for xyz in xyzList:
    tpt = xyz.rsplit(',')
    #convert first coord to points and to mm, as revit api
    #works with decimal feets you need to divide the float into 304.8 to mantain mm
    x.append(Autodesk.Revit.DB.XYZ((float(tpt[0])/304.8),(float(tpt[1])/304.8),(float(tpt[2])/304.8)).ToPoint())
    #convert second coord to points and in to mm
    y.append(Autodesk.Revit.DB.XYZ((float(tpt[3])/304.8),(float(tpt[4])/304.8),(float(tpt[5])/304.8)).ToPoint())

And it's ready.


dynamo-revit
Now we need to feed it with a csv containing the coordinates of the lines. This should create one duct for each couple of points.
dynamo
Node showing X and Y list of points from a CSV file
In the next post we'll explain how to model the diffusers in place using blocks coordinates from a cad file.
If you have any problems, comments or even corrections for us, leave your comments and we'll be glad to answer them.

Mechanical from scratch using Dynamo [Part 1]

fromcadtorevit-02
I would like to share with you a project where I'll try to create an HVAC system from a CAD sketch into Revit using Dynamo. This is not meant to be a new procedure to design mechanical systems more than a study of Dynamo capabilities on MEP modeling.
For this I've created this milestones/steps I'll try to accomplish on the next posts :
  1. Model a simple building on Revit and export to CAD.
  2. Sketch an HVAC layout with a supply system and a few diffusers.
  3. Export the location of the elements back to Revit using Dynamo.
  4. Change the sizes of the elements according to the flow needed on each room.
  5. Tag all elements having in count it's locations and sizes.
On this post I'll talk about the state of arts of the tools
  • My own Dynamo Node to create ducts from lines (still in beta but I'll publish it as soon as I could). Right now it only use 2 points to create a single duct but I'm trying to make it work using several points extracted from lines of a cad file using DataExtraction command.
CreateDuct node
CreateDuct node Beta
  • Node FamilyInstancebyPoint to locate the air terminals using the coordinates extracted from the cad files, as previously using DataExtraction command from Autocad.
    Family instance by point
    Family instance by point
  • Change diffusers flows according to room volume. The idea is to use the work on this node to detect how many diffusers are in the room and slip the flow on each one based on the room air volume
    http://autodesk.typepad.com/bimtoolbox/2015/07/automated-diffuser-placement-in-early-stage-mep-design.html
    Automated diffuser placement in early stage MEP design by Thomas Gregersen
  •  Konrad "Create annotations tag" node to place the tags on the elements according the sizes and orientation following a tagging standard.
    Dimitar Venkov example using Konrad
    Dimitar Venkov example using Konrad's node
As this will be an inductive study, I can't guaranty the success of the project but on the next posts I'll continue sharing my learned lessons using all these tools.
Pick anything you need and share, but please respect others authors rights.
To Be Continued...

Published first at https://scaleid.wordpress.com/2015/08/29/mechanical-from-scratch-using-dynamo/

Curve Section in Revit

First of all, right now there is no way to create a curved section in Revit even through the API., sorry! But there is a way to fake it.

Using Dynamo we could create a series of sections following a curve in the model or even a model line. For this we’ll use Luke Johnson’s Bakery package which contains the node “Revit Section View at Line”. The node uses a Revit Line as path to create a section in the model.

First thing we need is to get our Revit Line. We’ve two ways of doing it, using a Model Line or an edge of a Revit Element.

Model Line:
Using the node “Select Model Element” we’ll select the Model Line and translate it into a Dynamo Curve with “Element.Geometry”.

Select Model Element + Element.Geometry
Edge of a Revit Element:
Select the edge you want with the node “Select Edge”. No need to convert it because Dynamo understands it’s a Curve type.
Select Edge
Now that we have the curve we need to split it in curve chords. More segments we have, more detailed is the curve section we want to create.
We'll split the curve using "Curve.SplitByParameter". It needs a list of 1/n numbers to divide the curve. For this we create a list from 0 to 1 - start and end of the curve - and the the parts we want to split it. It should be written as follows.


0..1..1/x;

Curve.SplitByParameter
Now that we have our curves we need to create the lines for the section paths. With the end points of the curves we make a line by start and end points and then we use it to create the model line.

Create a model line with the ends of the curves

Now we are ready to create the sections with "Revit Section View at Line" node. By default it will create a section with 3000 units height and 100 Depth. You could change it as you need.


The final step is to erase the model lines we've created using "Tool.Eraser"

As final result we will get a series of sections following the curve. Just need to place them in a sheet and we are ready to go.


Here you will find the dyn file for you to use, I've added a formula with a boolean to be able to choose if use a model line or an edge to get the curve