Getting better-looking text in your Dynamo graph output

One of my pet peeves with Dynamo – as I typically use it standalone from Revit – is the quality of the text you can generate as graphics: the Dynamo Text package will generate curves (actually lots of little lines) that represent the outline of your characters, but they often look quite lame depending on your zoom level. Actually scratch that – they look lame irrespective of the zoom level. Sigh.

Dynamo Text out of the box

Anyway – today I finally dug into what I needed to do to create better-looking text as output from your Dynamo graphs.

My first attempt was to create a bunch of closed curves from the line segments, and then patch them with surfaces. Which looked liked I'd gone spray-painting with a poor stencil.

Filling the gaps - all of them

With some thought – and a little bit of sweat, I admit – I settled on a process that works well:

  1. Take the output from the Dynamo Text node.
  2. Create PolyCurves from the line segments using Ampersand's PolyCurve.ByCurves node.
  3. Process the (hopefully now closed) loops with a Python script (code is provided below), to find the enclosed loops (the "children") and the enclosing ones (the "parents").
    • To search the list of loops efficiently, we first sort it based on each item's minimum bounding box point, and then assume that we only need to compare each potential child with the ones that come before it in the list, to see whether any of them enclose it (i.e. are its parent).
    • It's really important to treat separate lines of text independently – hence us passing in a list – as otherwise performance will die in a combinatorial explosion.
  4. Create surfaces from each set – parents, children and others – and then get the difference between the parents and the children.
  5. View the differenced surfaces and the others (the ones with no loops).

The results look much better!

The best I could get

Here's the graph, for now. At some point I'll look at whether it makes sense to build it into a custom node/package. We'll see.

The graph, for now

There is some performance hit – as there's quite a lot of geometry to process – but I think it's a decent compromise for the final creation of your presentation graphics (such as when you want to do use Capturefinery on your Refinery study).

Here's the code for the "Fill Text" Python node:

# Imports
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
# Main
boundaries_list = IN[0]
children_out = []
parents_out = []
others_out = []
# Return the minimum point of a geometry's bounding box
def getMin(geom):
    bb = BoundingBox.ByGeometry(geom)
    min = bb.MinPoint
    return min.X, min.Y
# Check a list of potential parents, and return the ones
# that contain the child (comparing bounding boxes)
def getParents(child, parents):
    parent_list = []
    bb1 = BoundingBox.ByGeometry(child)
    for parent in parents:
        bb2 = BoundingBox.ByGeometry(parent)
        if bb2.Contains(bb1.MinPoint) and bb2.Contains(bb1.MaxPoint):
            parent_list.A
dd(parent)
    return parent_list
# We have a list of lists as input
for boundaries in boundaries_list:
    local_children = []
    local_parents = []
    local_others = []
    temp_others = []
    # Sort each list based on the contained geometry's bounding box
    boundaries.sort(key=getMin)
    # Go through the sorted list and find the parent(s) of each one
    # (as we've sorted the list, we should only need to check the
    # items that preceed the child, at least that's the theory)
    for i, boundary in enumerate(boundaries):
        parents = getParents(boundary, boundaries[0:i])
        # If we found a parent(s), add the child and its parent(s)
        if len(parents) > 0:
            local_children.Add(boundary)
            local_parents.Add(parents)
        else:
            # Otherwise we add it to the others list
            temp_others.Add(boundary)
   
    # Flatten the list of parents to help filter the others list
    flat_parents = [item for sublist in local_parents for item in sublist]
    # Select the items from the others list that aren't parents
    for o in temp_others:
        if not o in flat_parents:
            local_others.Add(o)
    # For each iteration, add our results
    children_out.Add(local_children)
    parents_out.Add(local_parents)
    others_out.Add(local_others)
# Output the lists of lists
OUT = children_out, parents_out, others_out

You can probably take a guess at what I'm working on from the images I've included in today's post. I'll be posting more on this topic in the coming weeks, for sure.

Leave a Reply

Your email address will not be published. Required fields are marked *