March 01, 2020

ACA: Sheet Keynote Symbols

The out-of-the-box Sheet Keynote symbol in the US Imperial content features a hexagonal "tag" (not a Schedule Tag, but a Block-type Multileader that has a User Block assigned to it).
If your office standard is to use a differently shaped symbol, for whatever reason, that can be done; just follow the instructions that follow.
  1. You will need a non-annotative block to serve as the User Block of the Multi-Leader. The block will need to have a non-annotative Attribute Definition to hold the Sheet Keynote value, along with whatever other graphics you need. The Multileader that gets inserted can be annotative, if so specified in the Multileader Style; the User Block needs to be set up such that 1" in the Block Definition will end up plotting at the AutoCAD Architecture Annotation Plot Size when plotted for the active annotation scale assigned to the Multileader. Assuming that you want the Sheet Keynote text to plot at the Annotation Plot Size, it needs to be 1" high. Size the additional graphics to suit.
  2. The Attribute Definition's default value needs to be set up with a Field, that is set to show the Sheet Key value (only), as shown in the image below. Set the Field category to AEC Keynotes, the Field names to Sheet Key only, the Format to Uppercase and check the Insert as a placeholder toggle. Your field expression should look like what is shown in the image.
  3. You will need a Block-type Multileader Style that has the leader set up the way you want for your Sheet Keynotes. The Multileader Style can be set up to use your User Block, if you like; even if you do not, the settings in the Tool Palette tool that will be created to insert the Sheet Keynote will take care of setting your block as the User Block.
  4. You can make the process of creating the block definition with the required Field easier, by placing an instance of the out-of-the-box Sheet Keynote in the file where you are going to create the Block Definition. For the US Imperial content, that will bring in the SheetKey_Hexagon_na Block Definition. Insert an instance of this Block Definition, select it, right click and choose Copy Block Definition and Assign from the context menu. Enter a new name for your block. Now edit the block in place. Keep the Attribute Definition as is, and edit the hexagonal Polyline, or erase it and replace it with your own graphics. Inserting an instance of the out-of-the-box Sheet Keynote will also bring in the Block (Straight Leader) Multileader Style (US Imperial content). If you like that leader style, use it. If not, make your own or rename the Block (Straight Leader) Multileader Style and then edit it to create the leader you want. Keep the type set to Block.
  5. Save the file that contains your Block Definition and Multileader Style in a location that will be accessible to everyone who will need to make use of the Tool Palette tool that will reference this content. A shared network location is ideal for this, but you can also set it up on a local drive if everyone has the same location.
  6. Create a Sheet Keynote type Tool Palette tool for your content, on an editable Tool Palette. You can either copy the out-of-the-box tool and edit its properties, or add a copy of the one called Sheet Keynote in the Stock Tool Catalog > Drafting Tools. NOTE: Setting up an editable tool palette, and then sharing it with others is beyond the scope of this article. Perhaps I should write about that in the future.
  7. Right click on the Sheet Keynote tool, and choose Properties from the context menu. Edit the tool's properties to suit your needs. Give the tool a meaningful Name, that clearly indicates that this is your Sheet Keynote tool. You can include an extended description in the Description property; this will be shown in the tool tip when a user hovers over the tool. To have the tool use your content, set the Multileader style location and Symbol location properties to the path/file that holds your content, and then choose your content using the drop-downs in the Multileader style and Symbol properties. Verify that the Content type property is set to Block. Set any other properties as you prefer.
  8. Select the OK button to ratify the changes you made to the tool's properties.
  9. Start a new drawing, and test the new tool by selecting it, and verify that it places your Sheet Keynote Multileader correctly. Make any adjustments to the content or the tool properties as needed. If you do not have any AEC Content in the file from which a Sheet Keynote can pull the Keynote reference, you can press the ENTER key when prompted to select an object, and then manually choose a Keynote in the Select Keynote dialog.
  10. Place several Sheet Keynote symbols in your file, then use the out-of-the-box Document > Annotation > Sheet Keynote Legend tool, selecting your Sheet Keynotes, to verify that they show in the legend.

February 01, 2020

ACA: Property for Area of a Hatch Object

A question regarding tagging Hatch objects and being able to display the area of the hatch arose the other day. My initial response was, sure, that should not be a problem in AutoCAD® Architecture. Upon actually trying to do that, I found that there is no Area Automatic Property for Hatches. While that seems like an oversight, I then checked the object properties of Hatches and found that there is an Area property that is available through ActiveX. That is, no doubt, what drives the Area property of Hatches in the Properties palette. So while it was not quite as easy as I originally thought, it is possible to do, by creating a Formula Property that extracts the Area of the Hatch object to which the Property's Property Set is attached. Here is how to do that:

  1. Open the Style Manager, and create a Property Set Definition that applies to Hatch objects. I called mine HatchObjects (demonstrating my incredible creativity), and will use that name in the balance of this article. If you already have a Property Set that applies to Hatch Objects, you can add the required properties to it. If you name yours differently, substitute your name for HatchObjects wherever you see it here.
  2. In the HatchObjects Property Set Definition, create an Automatic Property that references the Handle Automatic Property source, if one does not already exist. Select the Add Automatic Property Definition tool on the Definition tab of the HatchObjects Property Set Definition (second from the top, at the left side of the tab, with the lightning bolt icon). Select the toggle to the right of the Hatch source to put a check mark in it, then select the OK button. I chose to leave the name of the property at the default value of Handle.
  3. In the HatchObjects Property Set Definition, create a Formula Property.
  4. Enter a name for the property in the Name edit box at the top; I used HatchArea (creativity gone wild).
  5. Uncheck the Use formula for description toggle (unless you really want the formula to be the description; I almost never do).
  6. Enter the formula in the Formula edit box. If you like, you can cut and paste the formula from the code box below. Note that the [Handle] text has to be a properly created reference to the Handle Property created in Step 2. That is done by selecting it in the Insert Property Definitions pane in the lower left corner of the Formula Property Definition dialog. You can substitute that for the pasted text, or you can follow the instructions in this blog article, if you like.
    Set acadApp = GetObject(,"AutoCAD.Application")
    Set hatchObj = acadApp.ActiveDocument.HandleToObject( "[Handle]" )
    RESULT = hatchObj.Area / 144.0
    When done correctly, the [Handle] text will have a light gray background and will be treated as one item, not individual characters. See image of dialog below.
  7. The formula is setting the variable acadApp to the AutoCAD Application, and then getting the Hatch Object associated with the value of the Handle property (which is the Hatch Object to which the Property Set is attached). The RESULT of the formula is then set to the value of the Area property of the Hatch, which will be a real number representing the area of the Hatch, in square drawing units. My sample file used imperial units, with one unit equal to one inch. Since I wanted the Area value in square feet, I divided the raw value by 144.0 to get square feet. If you are not using imperial units, or want to show the Area in some other area unit, adjust the RESULT line to do what is necessary to get the area value in the desired units.
  8. Your Formula Property Definition dialog should look similar to the image below. If so, select the OK button to save the changes to the HatchArea Formula Property.
  9. Back in the Style Manager, on the Definition tab, assign the desired Property Data Format to the newly created Formula Property.
  10. If you need additional properties for your Hatch Objects, you can add them now. When done, select the OK button to accept the changes made, close the Style Manager and return to editing the drawing.
  11. Attach the Property Set to one or more Hatches in your file. Select the Hatch and note the value of the Area property (under the Geometry category) on the Design tab of the Properties palette. Then select the Extended Data tab and verify that the value shown matches (allowing for any rounding/formatting of the value by the chosen Property Data Format).

January 09, 2020

Revit: New Ceiling Grid Pattern, Part 3 - Ceiling Type

First article in the series.
Previous article in the series.

In Parts 1 and 2, we created a Fill Pattern to define a grid pattern for an acoustic tile Ceiling, and then set up a Material that uses that Fill Pattern as a Surface Pattern. We will now assign that Material to the finish layer of a Compound Ceiling type.
  1. In the Project Browser, expand the Families node.
  2. Under the Families node, expand the Ceilings node. Then expand the Compound Ceiling node under that.
  3. Right click on the 2’ x 2’ ACT Compound Ceiling family type. Choose Duplicate from the context menu. If your project file does not have that type, choose another type used to show a grid-based acoustic tile ceiling.
  4. Give the duplicated type a meaningful name. I chose to call the example type 4-24x24_1-48x6 ACT System.
  5. Open a Ceiling Plan view that has an area to receive the new Ceiling Type. If the area does not already have a Ceiling, place one.
  6. Select the Ceiling, and, in the Properties palette, use the Type Selector at the top to change the selected Ceiling’s type to the newly created type.
  7. With the Ceiling still selected, in the Properties palette, select the Edit Type button.
  8. In the Type Properties dialog, verify that the newly created Ceiling Type name shows in the Type drop-down list. Then select the Edit button to the right of the Structure parameter, under the Construction category.
  9. In the Edit Assembly dialog, find the Finish 2 [5] layer under Core Boundary (line "4"). If you started with a different Ceiling Type that does not have a Finish 2 [5] layer, use the layer on the bottom, that will be exposed to the room below the Ceiling.
  10. Left click once in the Material column on the Finish 2 [5] line. This will highlight the name and cause a small button with an ellipsis icon (...) to appear at the right end of the column on that line. Select that button.
  11. In the Material Browser, find and select the Material you created in Part 2.
  12. Select the OK button to ratify the choice and dismiss the Material Browser dialog.
  13. In the Edit Assembly dialog, verify that the new material appears and that the value in the Thickness column for Finish 2 [5] is correct.
  14. Select the OK button in the Edit Assembly dialog to ratify the change to the material. Then select the OK button again to accept all of the changes to this ceiling type, dismiss the Type Properties dialog and see the effect on the selected ceiling.
  15. Synchronize the model to save the changes to the central file.

If you are using Basic Ceilings, rather than Compound Ceilings, you can still assign the Material created in Part 2. Instead of editing the Structure in the Type Properties dialog, you will assign the Material to the Material parameter under the Materials and Finishes category.

January 05, 2020

Revit: New Ceiling Grid Pattern, Part 2 - Material

First article in the series.

In Part 1, we created a custom Fill Pattern from a PAT file to define the grid pattern for an acoustic tile Ceiling. We will add that pattern to a Material in this article, so that it can then be assigned to the finish layer of a Ceiling in Part 3.
  1. On the Manage ribbon tab, on the Settings panel, select the Materials tool.
  2. For the purposes of this tutorial, all we care about is the Surface Pattern that will be seen in the contract documents, and the rendered appearance will not be adjusted. If that is also true for you, you can proceed by either creating a new Material from scratch or by duplicating one of the ceiling tile Materials and then editing the duplicate. I will take the latter approach here, as that way the new Material will inherit the graphic settings from the source material, maintaining consistency.
  3. In the Material Browser, find and select the Acoustic Ceiling Tile 24 x 24 material, which is part of the content supplied with the United States Imperial content in Revit. If you are working in a Metric file, choose an existing Material for a gridded ceiling.
  4. Right click on the material, and choose Duplicate from the context menu.
  5. Give the new material a meaningful name. For this example, I chose Acoustic Ceiling Tile 4-24x24_1-48x6.
  6. Select the Appearance tab. While we will not be setting a grid-specific render image for this Material, we do want to set the Material up with a unique Appearance Asset so that any future change made here will not affect other Materials, and any future change made to the Appearance Asset assigned to the Acoustic Ceiling Tile 24 x 24 Material (or any other Material using the same Appearance Asset) will not affect this Material. In the upper right corner, select the Duplicates this asset. tool. If successful, the name of the Appearance Asset will be changed (by adding a number in parentheses at the end) and the number next to the hand icon will be 0, indicating that this Appearance Asset is shared with no other Material. If you have a render image for this Ceiling, you would assign it here; that is beyond the scope of this tutorial.
  7. Select the Graphics tab.
  8. In the Surface Pattern area of the Graphics tab, select the image tile to the right of where it says Pattern.
  9. In the Fill Patterns dialog, verify that the Pattern Type is set to Model. Then find and select the Fill Pattern created in Part 1.
  10. Select the OK button to ratify the selection and dismiss the Fill Patterns dialog.
  11. In the Material Browser dialog, verify that the other settings on the Graphics tab are as desired. Assuming you want this ceiling pattern to appear the same one of the source Material (Acoustic Tile 24 x 24, or whatever Material you duplicated) (except for the Fill Pattern used), the settings should be correct.
  12. Select the OK button to add the new material to your project.

Part 3 will add the new Material to a Ceiling Type.

December 31, 2019

Revit: New Ceiling Grid Pattern, Part 1 - Fill Pattern

In order to have a new ceiling type with a "custom" ceiling grid pattern, you need a Fill Pattern that defines the grid pattern, a Material to which that Fill Pattern can be assigned as a Surface Pattern, and, finally, a Ceiling Type to which that Material can be assigned. While you can start by duplicating a Ceiling Type and drill your way through the various dialogs until you get to the point where you create a new Fill Pattern for the ceiling, and then drill your way back out, I am going to break the process down into three separate steps, so that each step serves as a tutorial for that operation and can be referenced when the need for that operation comes in a different context.

The first step is to create a Fill Pattern that defines the ceiling grid. I am assuming that you have a Fill Pattern file (PAT) that defines a Model type of pattern (as it represents a real-world object, a ceiling grid, that wants to hold the same dimensions, no matter the scale of the view in which it is seen). For the purposes of this example, I am using a custom Fill Pattern that is called Ceiling_4-24x24_1-48x6. It has four rows of 2'-0" x 2'-0" tiles, and then a row of 6" x 4'-0" tiles, as seen in the image below.
Here is how to make that PAT file pattern available in Revit, as a Fill Pattern.
  1. On the Manage ribbon tab, on the Settings panel, select the Additional Settings tool to deploy the menu, and then choose Fill Patterns from the menu.
  2. In the Fill Patterns dialog, set the Pattern Type to Model. Then select the New fill pattern tool.
  3. In the Add Surface Pattern dialog, set the Type to Custom. Then select the Browse button.
  4. In the Import Fill Pattern dialog, navigate to the location where you saved the Ceiling_4-24x24_1-48x6.pat file, and select it. Select the Open button.
  5. Back in the Add Surface Pattern dialog, verify that the Name is what you want (it will default to the pattern name) and the Import scale is set to 1.00 (since this is a model pattern, and the pattern was created at full size). Select the OK button to add the Fill Pattern and dismiss the Add Surface Pattern dialog.
    NOTE: This example uses a PAT file that only defines one pattern, so the desired pattern is already selected. If your desired pattern is in a PAT file that defines two or more patterns, then you will need to find and select your pattern in the list box to the left of the Browse button. Revit will only display patterns of the type selected (Model, here), and will sort them in the list alphabetically. The first pattern on the list will be selected initially, but that may not be the one you want. You can use the Search box above the list box to type in part of the pattern name you seek to make it easier to find, if the file contains a lot of Model patterns.
  6. You should see the newly added model pattern in the Fill Patterns dialog. Select the OK button to accept the added pattern and return to the Revit model.

Part 2 will cover creating a Material which will use the newly created Fill Pattern.

November 15, 2019

ACA-AMEP: Edit Property Data Via AutoLISP, Part 3

First article in the series.
Previous article in the series.

In the previous article, we used a while loop to examine each Property Set in the collection of Property Sets attached to the object of interest, comparing each Property Set's Name to the name of the Property Set that holds the Property whose value is to be changed. At this point, there are two possible conditions:
  1. A matching Property Set was found. The value in the Property Set counter variable, iPSet, was set to one more than the number of Property Sets in the collection (variable iPSets).
  2. A matching Property Set was not found. The value in the Property Set counter variable, iPSet, will be equal to the number of Property Sets in the collection (variable iPSets).
(cond    ; Cond A.
  ((= iPSet iPSets)
   ;; If the Property Set is not found after processing all attached property sets, issue prompt.
   (prompt
     (strcat
       "\nProperty Set "
       sPSet
       " not found on object passed to argument obj. "
     ) ;_ End strcat.
   ) ;_ End prompt.
   nil    ; Return nil.
  ) ;_ End condition A1.
  (T    ; Else, continue.
   (setq oProps (vlax-get-property oPSet 'Properties)
          ; Get Properties object of the Property Set.
         iProps (vlax-get-property oProps 'Count)
    ; Get number of Properties in the Property Set.
         iProp  0  ; Initialize Property counter.
   ) ;_ End setq.

   (while (< iProp iProps) ; While unprocessed Properties remain...
     (setq oProp   (vlax-invoke-method oProps 'Item iProp)
    ; Get current Property object.
    sPropNm (vlax-get-property oProp 'Name)
    ; Get Property name.
     ) ;_ End setq.
     (if (= (strcase sPropNm) (strcase sProp))
        ; If target Property found...
       (setq iProp (1+ iProps)) ; ...exit while loop.
       (setq iProp (1+ iProp)) ; ...else, increment Property counter.
     ) ;_ End if.
   ) ;_ End while.

   (cond   ; Cond A2B.
     ((= iProp iProps)
      ;; If the Property is not found after processing all Properties in the Property Set, issue prompt.
      (prompt
        (strcat
          "\nProperty "
          sProp
          " not found in Property Set "
          sPSet
          " attached to the object passed to argument obj. "
        ) ;_ End strcat.
      ) ;_ End prompt.
      nil   ; Return nil.
     ) ;_ End condition A2B1.
     (T    ; Else, update Property value with processed value.
      (setq varPropValOld (vlax-get-property oProp 'Value))

      ***** ADD CODE HERE TO GENERATE THE DESIRED NEW VALUE   *****
      ***** SET VARIABLE sPropValNew to the NEW DESIRED VALUE *****

      (vlax-put-property oProp 'Value sPropValNew)
      sPropValNew   ; Return new string value.
     ) ;_ End condition A2B2.
   ) ;_ End cond A2B.

  ) ;_ End condition A2.
) ;_ End cond A.

Here is what is happening in that code:
  • A cond statement is used to determine which of the two conditions noted above is active. The first test checks to see if iPSet and iPSets are equal. If this is true, then the target Property Set was not found, and there is nothing to process. A prompt is written to the command line to inform the user. I have a separate subroutine to hold this code, so after issuing the prompt, this condition returns nil to the calling routine, to indicate that the requested Property Set was not found. The calling routine can then choose how to handle that situation.
  • If iPSet and iPSets are not equal, then the target Property Set was found and we can continue on. The test for this condition is T (True), because if the first test is false, no further test is required, and the code to process will be contained within this condition.
  • Variable oPSet holds the Property Set object whose name matched the target Property Set name. The Properties property of the Property Set object is used to obtain the Properties collection of that Property Set. The Properties collection contains all of the individual Property objects that are defined in that Property Set Definition.
  • The Count property of the Properties collection is used to determine the total number of Properties in the collection (variable iProps). A variable to hold the Property counter, iProp is initialized to 0, the index of the first Property in the collection.
  • Similar to the way we iterated over the Property Set collection, a while loop is used to iterate over the Properties collection, looking for a Property whose name matches the target property name, which is held in variable sProp. If a match is found, iProp is set to one more than the total number of Properties in the collection. If a match is not found, then iProp is incremented by 1, so that the next Property in the collection will be processed on the next pass of the loop, if at least one unprocessed Proeprty remains.
  • At this point, there are two possible conditions:
    1. A matching Property was found, and the value of iProp is one more than the value of iProps
    2. A matching Property was not found, and the values of iProp and iProps are equal.
  • Once again, a cond statement is used to run the appropriate code. If a matching Property was not found, the first condition writes a prompt to the command line to inform the user that the Property was not found, and returns nil to the calling routine, to indicate that the requested Property was not found in the requested Property Set. The calling routine can then choose how to handle that situation.
  • If the target Property was found, then the first condition will not be executed, and the second condition will be executed, as the test is set to T.
  • The Value property of the Property object is used to obtain the current value, storing it in variable varPropValOld.
  • At this point, you will need to add the code that generates the new value for the target Property, saving that value to variable varPropValNew
  • The vlax-put-property function is used to push the new value onto the Value property of the Property Object. This condition then returns the new value to the calling routine.
  • All of the currently open conditions and cond functions are then ended.

If you choose to have all your code in a single AutoLISP command function, then instead of returning nil or varPropValNew at the end of the A1, A2B1 and A2B2 conditions, you would add whatever additional code you may feel appropriate to run prior to the end of your command function.

November 05, 2019

ACA-AMEP: Edit Property Data Via AutoLISP, Part 2

First article in the series.

In the previous article, we obtained a collection of the Property Sets on the object for which we want to change the value of a Property. We also determined the total number of Property Sets in the collection, and set up a Property Set counter, initialized to 0, the index of the first Property Set. The following code assumes that you know not only the name of the Property that is to be changed, but also the name of the Property Set in which it resides and that the name is stored in a variable called sPSet.
(while (< iPSet iPSets)  ; While unprocessed Property Sets remain...
  (setq oPSet   (vlax-invoke-method oPSets 'Item iPSet) ; Get current Property Set object.
        sPSetNm (vlax-get-property oPSet 'Name)         ; Get Property Set name.
  ) ;_ End setq.
  (if (= (strcase sPSetNm) (strcase sPSet))             ; If target Property Set is found...
    (setq iPSet (1+ iPSets))                            ; ...exit while loop.
    (setq iPSet (1+ iPSet))                             ; ...else, increment Property Set counter.
  ) ;_ End if.
) ;_ End while.

Here is what is happening in that code:
  • A while loop is used to iterate over the Property Sets collection. The Item method is used to get the Property Set at the current index value in iPSet.
  • The Name property of the Property Set object is used to obtain the name of that Property Set.
  • An if statement is used to determine if that Property Set name matches the target Property Set name. The strcase function is used to change both strings to all capital letters, to avoid any issues with different capitalization of the names. If a match is found, the iPSet index counter is set to a number one greater than the total numnber of Property Sets in the collection, so that the loop will be exited. If a match is not found, the iPSet index counter is incremented by 1, so that the next Property Set in the collection will be processed on the next pass of the loop, if at least one unprocessed Property Set remains.

Next article in the series.

November 01, 2019

ACA-AMEP: Edit Property Data Via AutoLISP, Part 1

Sometimes you need to make changes to the values of a Property on all or many AEC Objects in a file. If that change can be done programatically (example, in a text-type manual Property, edit the text value, replacing all occurrences of "1ST" with "2ND"), and there are a large number of Objects to process, it may be easier to do so via AutoLISP, than to manually edit each value. This series of articles will show you how to do so.

NOTE: I have only used this technique on manual Properties. I am not certain what would happen if you tried to use this to change the value of an automatic property. I suspect the change may fail, at least in some cases, like formula Properies. Even if it works for some automatic Properties, such as the height or width of a Door, doing so may have unexpected results in the drawing. If you have a use case for trying to edit the values of automatic Properties, please test this thoroughly on copies of files, or files created strictly for testing purposes, not on actual project files, until you are certain there is no harm.

The VisualLISP commands will be used to access the object model. You will also need to operate on vla-objects. If your method of getting the selection set on which to operate results in AutoLISP entity names, these can be converted to vla-objects by using the
(setq obj (vlax-ename->vla-object ename))
function, where obj is the name of a variable holding the vla-object and ename is the name of a variable holding the entity name of the object on which you want to operate.

Getting to the Property value is not a matter of simply querying the object for it. You will need to drill down into the object model. Start with the following code. You can use different variable names if the ones used in the example code here do not conform to your naming standard. Just make certain you substitute your names consistently.
(vl-load-com)
(setq objAcad    (vlax-get-acad-object)  ; AutoCAD Application Object.
      objAecSchd (vla-GetInterfaceObject objAcad (strcat "AecX.AecScheduleApplication" (aecappver)))
                                         ; AecScheduleApplication object.
      oPSets     (vlax-invoke-method objAecSchd 'PropertySets obj)
                                         ; Get PropertySets collection object.
      iPSets     (vlax-get-property oPSets 'Count)
                                         ; Get number of Property Sets in the PropertySets collection object.
      iPSet  0                           ; Initialize Property Set counter.
) ;_ End setq.

Here is what is happening in that code:
  • The (vl-load-com) function loads the VisualLISP functions, if they are not already loaded. If they are loaded, it does nothing.
  • The first item in the setq function gets the AutoCAD object. This is then used to get the AEC Schedule Application object. This object is version-specific, so I use a subroutine I put together to return the version string, called AECAPPVER. You can find the code for that routine in this previous article. The article was updated to support versions through 2020.
  • The third item in the setq function uses the PropertySets method of the AEC Schedule Application object to get the PropertySets collection for the object on which we want to operate (which is held in the obj variable).
  • The final two items in the setq function determine the number of Property Sets that are in that collection, and initializes a Property Set counter to 0, which is the index of the first Property Set in the collection.

Next article in the series.

October 29, 2019

Dynamo: String Concatenation 2

I had some time and tested some scenarios using the "successful" nodes for concatenating a list of strings shown in this previous post and noticed that the concatenation will still work if the lists do not all have the same numbers of strings. It still concatenates the first item in each list, and so on; once the shortest list runs out of items, it simply does not contribute anything to subsequent concatenations. The process will fail, if the items in the source lists contain anything other than strings, however. That is not a particular surprise (to me, anyway), but a combination of being able to handle non-text items in the source lists without throwing an error/crashing the graph and wanting to see just what it would take to write a Python Script node that could process a variable number of lists of strings prompted me to try it.

A List Create node is still necessary to make a single list of lists to serve as the input, so that the Python Script node need only have one input, but the List.Transpose and List.Flatten nodes can be eliminated.


The Python code is fairly straightforward.
  • The input list of strings is assigned to lstInput, and lstOutput is set up as an empty list, to receive the list of concatenated strings, which will be the output of the node.
  • A for statement examines the length of each of the sublists in lstInput, to determine the maximum list length (maxLength). This is used to set the number of times the concatenation code needs to be run: once for every item in the longest list.
  • An index counter, iIndex, is initialized to the first index, 0.
  • A while statement is used to process the lists, once for each item on the longest list.
  • On each pass through the while code, a variable to hold the concatenated string from that pass (strConcat) is initialized as an empty string.
  • Then, for each sublist in lstInput, strX is set to the string at the current index being processed. This is done in a try statement, so that the code does not fail when a shorter list has no item at the current index. If a value is returned, an if statement verifies that it is a string. If not, strX is reset to an empty string. If a value is not returned to strX, the except statement sets strX to an empty string.
  • The string value in strX is then added to the end of strConcat, and the for statement is rerun for the next sublist, until all sublists have been processed.
  • With the fully processed concatenated string in hand, an if statement checks the value, to see if it remains an empty string. If not, the string is appended to lstOutput. If it is empty, no action is taken.
  • Finally, the index counter (iIndex) is incremented, and the while statement is run again, provided that iIndex remains less than the length of the longest sublist (maxLength).
  • Once the while statement has been run once for each item in the longest list, the list of concatenated strings in lstOutput is sent to the output of the node.
It turns out that the code required to handle a variable number of lists of strings was not as complicated as I thought before I wrote it. For situations where I am certain that the lists of strings to be concatenated will in fact be lists of strings, I probably would use the out-of-the-box nodes shown in the previous article, as it is easier to read (no "magic" black box of Python code doing who knows what), but in cases where the lists might not all be lists of strings (user inputs?), I might choose to use the Python Script node rather than checking the lists of strings to verify that all items are strings.

October 25, 2019

Dynamo: String Concatenation

This may be basic and blindingly obvious to more experienced Dynamo users, but this is something with which I have struggled for some time, and now that I have finally sorted it out, I wanted to document it here for easy future reference.

The String.Concat node in Dynamo provides an easy way to combine two or more strings into one string.
When each item fed into the stringn inputs is an individual string, all of the strings are combined into one string, and that is the output of the node, as shown in the image above.

I rarely need to combine one set of individual strings, however; I almost always have lists of strings to process. Perhaps there is some deficiency in the way I think, but when I first tried to use the String.Concat node with lists of strings, I was expecting that the the result would be a list of concatenated strings, and that the value at each index in that list would be the concatenation of the strings at that same index in each of the input strings. Unfortunately, that is not what the node does.
As you can see, the result is a list of strings, one for each input, with the strings in each input concatenated together. I was expecting ["abc", "123"], but I got ["a1", "b2", "c3"]. This seems contrary to what it does when single strings are input - at least, it does to me. I found this quite frustrating, and on at least one occasion, wrote a quick Python Script node to do the concatenation the way I wanted it. But that quick Python Script did not allow for varying numbers of input lists of strings (I suppose it could be written to do so, but that would no longer be "quick"). I could not understand why the String.Concat node would not do what I wanted, and could not believe that the way it works is what most users would want, most of the time.

Once I set aside my frustration and started thinking again, it dawned on me that all I needed to do was rearrange my input data to work with the way the String.Concat works, and while that requires a few extra nodes, it is fairly easy to do. (Click on the image below to see it full-size.)
By using the List Create node, I combined my individual lists into one list of lists. The List.Transpose node then transforms that list of lists into another list of lists, in which each sublist has a list of the items from the same index in the original sublists. So each sublist has a list of the strings I want combined into one string, and this can be fed into the String.Concat node to get the desired concatenation. The output of the String.Concat node ends up having the list of concatenated strings nested in a list, so adding a List.Flatten node using the default flatten amount (totally flatten) results in a list of the concatenated strings, which was my desired result.