re: Issue #148 reported by hdm-dt-fb

I’m very excited about a new feature. This is the start of work on making the output window more interactive.

 

Current output window custom print methods:

By now, you probably know that the output window is a System.Windows.Forms.WebBrowser component. The WebBrowser uses Internet Explorer as rendering engine. All the output coming from pyRevit are formatted in html and displayed. This means that pyRevit scripts can output images, links, and anything html really.

There is a catch though. python outputs information about objects in this format:

<type str>
<object Element Id at --- >

These outputs contain < and > characters that confuses the html engine due to their structural similarity with <html> tags.

The solution was to use a formatter function that converts the < and > characters to &clt; and &cgt; and then converts them back later when actually printing the output. This helps the system distinguish between the actual html code and the < and > characters printed from python itself (Since they won’t be converted to &clt; and &cgt;).

I’ll write more about this later. Here is the formatter function:

from scriptutils import this_script

html_string = '<div>contents</div>'
this_script.output.print_html(html_string)

# this is the output. < and > are replaced with &clt; and &cgt;
# this string is sent to the output window
>>> '&clt;div&cgt;contents&clt;/div&cgt;'

Anyways,

The output window provides two other custom print functions that build on top of this method. pyRevit scripts can import this_script object from the scriptutils module and access these output window functions.

print_code()

from scriptutils import this_script

# prints contents formatted as a python script
python_script = """
import sys
print(sys.paths)
"""
this_script.output.print_code(python_script)

print_md()

from scriptutils import this_script

# prints contents in mark-down format
# e.g this script outputs a clickable link
mark_down_string = '[eirannejad](https://github.com/eirannejad)'
this_script.output.print_md(mark_down_string)

 

Linkify:

Now there is a new method available for printing object element ids (The limitation for element ids is temporary. I’ll add more data types as I test and improve this feature). Using this method, the scripts can print the element ids of Revit elements that they’re working on and the user can select those elements by clicking on the element link. See the green rectangles in the image below:

And this is they’re created:

from scriptutils import this_script

revit_element = ElementId(123456)
clickable_link = this_script.output.linkify(revit_element)

print('Here is the element: {}'.format(clickable_link))

That simple! The major difference between linkify and other print functions is that linkify doesn’t actually print the element id. It returns the generated properly-formatted html code for the link however. This way the script can format their output better and place the clickable link where they want it.

Play with it and let me know of your comments. Read the section below if you’d like to know how the output window talks back to Revit.

 

The love affair between output window and Revit:

The linkify method generates an html link element that contains a special url. In the case of clickable element ids, this is how the url looks like:

revit://{command='select', data='123456'}

The first piece is the url protocol. This is really how the output window interacts with Revit. Whenever user clicks on a link with revit:// protocol, the output window calls a callback function defined in pyrevit.coreutils.console and passes on the url contents.

From there, the pyrevit.coreutils.console module cleans up the url and passes it to pyrevit.coreutils.rvtprotocol module:

rvtprotocol.process_url(cleaned_url)

rvtprotocol module processes the json part of the url {command='select', data='123456'} (let’s call it payload) and calls the select command and passes the 123456 payload to it. It is up to the command to do whatever it needs to do with the provided data.

I should mention that this json data payload was also created by the rvtprotocol module in the first place. So when linkify wants to create the clickable link, it calls rvtprotocol.make_url(args) and passes on the given arguments (element ids in this case). rvtprotocol picks the right command based on the input data types and asks the command to process the arguments and generate a json data payload. The command is responsible for generating a payload that it can process and understand later when used clicked on the link.