Updating Message or Dynamo console?

Hi all,

is there a way to display data without waiting for any user input? that is, with no buttons, maybe just a close “X”?

A kind of dashboard, or console for dynamo?

I wish I could track what a script was doing, by display data as is being processed, at certain points along the script.

Thank you

regards

gio

DataShapes makes popups halfway down to refine the query using radio buttons and such

Select none will abort
Is that what you have in mind?

2 Likes

Thank you Marcel, but the window still halts the script, and waits for the user… does it not?

It will abort when you select “Select none”, and still give you the “dashboard”,
Until you select “set values” ,then the script continues
Maybe define a Watch node as Output in a Dynamo Player scenario

If you can put your script in one python block then progress bars can be made. As far as i know it cant be made to work with normal nodes.

In pyrevit there is a progress bar tool I use in most of my scripts also, which is in its forms library. Its great as the user has confidence the script is actually working.

https://pyrevit.readthedocs.io/en/latest/pyrevit/forms.html

I am not sure this is accurate anymore. It’d take some work, and it might fail for reasons I can’t foresee, but someone who’s ambitious enough could try:

  1. At the start of your graph initiate a virtual server of sorts
  2. Look into the TuneUp view extension which tracks node execution time, reporting live as long running nodes execute
  3. Pass the node names to the virtual server
  4. Have the server display a UI illustrating ‘last node executed’, and a percentage of nodes executed on canvas.
  5. This UI could be left open until a ‘finished’ result was pushed to the external server.
  6. Additional data (ie: elements created, warnings produced, etc.) could also be gathered at this point.

Some caveats:

  1. You cannot assume that the percentage of nodes executed is a percentage of runtime (see any graph which takes 15 seconds to produce results from one node, but 1 second to run the 200 other nodes on canvas). As such whatever you report will be inaccurate in terms of overall runtime.
  2. You cannot list what node is executing at any moment (as far as I know anyway)
  3. All of this will slow things down, as most integrations are in-process which means you’d be unable to pass data to the virtual server until any given node is finished, and you can’t start processing the next node until the data is transferred to the virtual server.
  4. Cancel a run once it’s underway. Once the rocket takes off it’s too late to stop without resetting the whole situation.
  5. Producing this would be a VERY big lift (I say that a few times a year, but this one may take the cake… But for someone with the right experience it could be the simplest thing ever).
  6. Build such a tool once and not have to plan on a big lift for maintenance each year.
  7. Any given firm update to Windows, or AV/ASW/Infosec may nuke the entire portal on occasion, so long term reliability might not be swell.
  8. Utilization of taht tool in the context of Dynamo player or any other RestDynamoCore instance would be limited if not impossible without significant retooling beyond what TuneUp exposes (no view extensions in those environments).

Because you’d need a front end (Dynamo Client), back end (server), UI for each, and likely other tooling/logging… this would likely best be tackled as a team. Perhaps a POC would be a super interesting hackathon project…
Edit: TuneUp code base for anyone interested: GitHub - DynamoDS/TuneUp: A profiler for Dynamo graphs

2 Likes

I guess a hackier but simpler version of that could also be putting a flow node at bottlenecks in a script and sending those moments to a virtual server as well.

1 Like

Honestly… that feels attainable in a day/afternoon. :thinking:

I really want a reason to play with tuneup though… :laughing:

1 Like

Soooooo… I am currently authoring a bit of Python to create a framework for this via HTML. This removes the need to author a back end, as your browser (chrome or edge or firefox or whatever) will just do the trick. By placing the ‘initiate’ node on canvas, and the subsequent ‘update’ nodes at desired points in your graph you can inform the user of whatever you’d like along the way.

It’s honestly so easy to build that I wonder why it hasn’t been done before, leading me to question what roadblock I haven’t found yet.

Assuming it all works out, when done I’ll share the content when complete.

4 Likes

Interesting challenge :grinning: :upside_down_face: I will try too (but just with a simple console dialog)

1 Like

Happy to share some code later, but I won’t be booting up the hobby machine until my weekend chores are done.

I’m at the point of writing the HTML display, as simple text didn’t see to be functional enough. Once that is done I’ll have shareable code. Winds up making HTML based dashboard displays with only a text editor (what I have available on the laptop I’ve got with me) is cumbersome, and why we have specialists for this stuff.

Already done getting the browser to open and refresh display data periodically; getting RAM info (% in use, available RAM, available VM); getting the workspace name (without requiring Dyamo for Revit); and getting object counts by type (expecting dictionaries, so I may revisit this later). I also had CPU load being pulled, but I didn’t trust the number being returned, and it required either an extra dependency or triggering a brief console window pop up which I didn’t like so I scrapped it for now.

1 Like

Current status: base level data is working.

Got a good way to go still, but I hope to finish tomorrow.

A few web things to fix still

  • Extra scroll bar pops up if the page is too short
  • Flickering which results from the forced refresh method I’m using isn’t desirable so I might revisit that.
  • Font sizes and boarders aren’t ideal
  • I’m not sold on the layout overall…
    On the Dynamo side there are a few bits to go as well:
  • It completely breaks if fed infinity (which is an edge case sure, but one worth handling)
  • My method for overwriting the HTML isn’t very clean (likely i should write out a JSON instead and read that in rather than forced text, but having the data in one spot felt useful to me for some reason)
  • CPython and IronPython compatibility needs to be fleshed out.
  • Testing in older versions will have to happen to (currently working in 2.12+; not sure how far back it’ll work).

Will share the nodes and external resources via a package (too many moving parts otherwise) sometime tomorrow.

4 Likes

OMG, what have I done!

:slight_smile:

It looks promising, keep us in the loop!

1 Like

Started me on a great little journey. :slight_smile:

Getting this to move between systems is proving a bit of a challenge… By no means will this be an ‘easy’ bit of data to work with. I’m definitely second guessing my reporting method too. Likely going to scrap directly writing out HTML entirely tomorrow (to make scalability much easier), but going to review the idea with a few colleagues before I do so.

Think about writing to json(or a database) and then having something else read the json data. This should make it scalable and readable via most applications

I am intrigued at how you are getting the data out :slight_smile:

1 Like

An example just with a console (a RichBoxText) and a progress bar to see the global progress of the script execution.
It’s not perfect (Exploring and discovering the DynamoCore API is not easy :exploding_head:) and not compatible with the Dynamo Player

Dynamo Tracker4

Uncompressed GIF link

Python Code, compatible with both engine
import clr
import time
import sys
import System
from System import EventHandler, Uri, Action

clr.AddReference('System.Drawing')
clr.AddReference('System.Windows.Forms')
import System.Drawing
import System.Windows.Forms

from System.Drawing import *
from System.Windows.Forms import *
from System.Collections.Generic import List

clr.AddReference('DynamoCoreWpf') 
clr.AddReference('DynamoCore')
clr.AddReference('DynamoRevitDS')
clr.AddReference('DynamoServices')
import Dynamo 
from Dynamo.Graph.Workspaces import *
from Dynamo.Graph.Nodes import *
from Dynamo.Models import *
	
# access to the current Dynamo instance and workspace
dynamoRevit = Dynamo.Applications.DynamoRevit()


class FormInfo(Form):
	def __init__(self, dynamoRevit):
		self._dynamoRevit = dynamoRevit
		self._model = dynamoRevit.RevitDynamoModel
		self._engine = dynamoRevit.RevitDynamoModel.EngineController
		self._currentWorkspace = dynamoRevit.RevitDynamoModel.CurrentWorkspace
		self._currentWorkspaceNodes = self._currentWorkspace.Nodes
		self._startTime = None
		self._totalNodes = 0
		self._numCurrentNode = 0
		self.InitializeComponent()
	
	def InitializeComponent(self):
		self._label1 = System.Windows.Forms.Label()
		self._buttonCancel = System.Windows.Forms.Button()
		self._buttonStart = System.Windows.Forms.Button()
		self._richTextBox1 = System.Windows.Forms.RichTextBox()
		self._progressBar1 = System.Windows.Forms.ProgressBar()
		self.SuspendLayout()
	
		# 
		# label1
		self._label1.Location = System.Drawing.Point(30, 9)
		self._label1.ForeColor = Color.FromArgb(234,234,234)
		self._label1.Name = "label1"
		self._label1.Size = System.Drawing.Size(300, 30)
		self._label1.TabIndex = 0
		self._label1.Text = "Trace Execution"
		# 
		# buttonCancel
		self._buttonCancel.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left 
		self._buttonCancel.Location = System.Drawing.Point(30, 600)
		self._buttonCancel.Name = "buttonStop"
		self._buttonCancel.Size = System.Drawing.Size(156, 37)
		self._buttonCancel.TabIndex = 0
		self._buttonCancel.Text = "Quit"
		self._buttonCancel.UseVisualStyleBackColor = True
		self._buttonCancel.Click += self.ButtonCancelClick
		# 
		# buttonStart
		self._buttonStart.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right
		self._buttonStart.Location = System.Drawing.Point(270, 600)
		self._buttonStart.Name = "buttonStart"
		self._buttonStart.Size = System.Drawing.Size(156, 37)
		self._buttonStart.TabIndex = 0
		self._buttonStart.Text = "Run Current Script"
		self._buttonStart.UseVisualStyleBackColor = True
		self._buttonStart.Click += self.ButtonStartClick
		# 
		# richTextBox1
		self._richTextBox1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right
		self._richTextBox1.BackColor = Color.FromArgb(53,53,53)
		self._richTextBox1.ForeColor = System.Drawing.Color.Chartreuse
		self._richTextBox1.Location = System.Drawing.Point(30, 45)
		self._richTextBox1.Multiline = True
		self._richTextBox1.Name = "richTextBox1"
		self._richTextBox1.Size = System.Drawing.Size(400, 500)
		self._richTextBox1.TabIndex = 1
		self._richTextBox1.Text = ""
		# 
		# progressBar1
		# 
		self._progressBar1.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right
		self._progressBar1.Location = System.Drawing.Point(30, 560)
		self._progressBar1.Name = "progressBar1"
		self._progressBar1.Size = System.Drawing.Size(400, 23)
		self._progressBar1.Step = 1
		self._progressBar1.Value = 0
		self._progressBar1.TabIndex = 1
		# 
		# FormInfo
		# 
		self.ClientSize = System.Drawing.Size(460, 650)
		self.MinimumSize = System.Drawing.Size.Add(self.ClientSize, System.Drawing.Size(20, 20))
		self.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Show
		self.FormBorderStyle = FormBorderStyle.Sizable
		self.BackColor = Color.FromArgb(40,40,40)
		self.Controls.Add(self._label1)
		self.Controls.Add(self._richTextBox1)
		self.Controls.Add(self._buttonCancel)
		self.Controls.Add(self._buttonStart)
		self.Controls.Add(self._progressBar1)
		self.Name = "FormInfo"
		self.Text = "Dyanmo Tracker"
		self.ResumeLayout(False)
		
	def OnEventEndProcess(self, sender, e):
		"""handler when script finished"""
		self.UpdateConsoleMessage("***'{}.dyn' Evaluation Completed***".format(self._currentWorkspace.Name))
		self.DisableProfiling()
		
	def OnNodeExecutionEnd(self, e):
		"""handler for node End event"""
		self.UpdateConsoleMessage("END {0}".format(e.Name))

	def OnNodeExecutionStart(self, e):
		"""handler for node Start event"""
		self._numCurrentNode += 1
		self.UpdateConsoleMessage("START {0} (node {1}/{2})".format(e.Name, self._numCurrentNode, self._totalNodes), update_pg = True)
		if self._startTime is None:
			self._startTime = time.time()
		
	def UpdateConsoleMessage(self, newTxt = '', update_pg = False): 
		"""update progress bar and console"""
		try:
			Application.DoEvents()
		except:pass	
		nowstr = time.strftime("%H:%M:%S")
		self._richTextBox1.Text += "➤{}:: {}\n".format(nowstr,newTxt)
		self._richTextBox1.SelectionStart = len(self._richTextBox1.Text)#.Length
		self._richTextBox1.ScrollToCaret()
		#
		if update_pg:
			self._progressBar1.PerformStep()
		#
		try:
			self.Activate()
		except:pass	
			
	def ButtonStartClick(self, sender, e):
		"""Suscribe events and run the current script/workpace"""
		self._numCurrentNode = 0
		self._richTextBox1.Clear()
		self._startTime = None
		self._dynamoRevit.RevitDynamoModel.EvaluationCompleted += self.OnEventEndProcess
		self._engine = self._dynamoRevit.RevitDynamoModel.EngineController
		self._currentWorkspace = self._dynamoRevit.RevitDynamoModel.CurrentWorkspace
		self._currentWorkspaceNodes = self._currentWorkspace.Nodes
		self._totalNodes = len(self._currentWorkspaceNodes)
		# after some tests
		for node in self._currentWorkspaceNodes:
			node.NodeExecutionBegin  += self.OnNodeExecutionStart
			node.NodeExecutionEnd += self.OnNodeExecutionEnd
		#
		self._engine.EnableProfiling(True, self._currentWorkspace, self._currentWorkspaceNodes)
		self._label1.Text = "Trace Execution on '{}.dyn'".format(self._currentWorkspace.Name)
		self.UpdateConsoleMessage("***'{}.dyn' Start Evaluation***".format(self._currentWorkspace.Name))
		self._progressBar1.Value = 0
		self._progressBar1.Maximum = self._totalNodes
		self._currentWorkspace.Run() 

		
	def DisableProfiling(self):
		"""Disable Profiling and unsubscribe events"""
		try:
			self._engine.EnableProfiling(False, self._currentWorkspace, self._currentWorkspaceNodes)
		except Exception as ex :
			pass
		for node in self._currentWorkspaceNodes:
			node.NodeExecutionBegin -= self.OnNodeExecutionStart
			node.NodeExecutionEnd -= self.OnNodeExecutionEnd
		self._dynamoRevit.RevitDynamoModel.EvaluationCompleted -= self.OnEventEndProcess
		if self._startTime is not None:
			elsapedstr = "Time elapsed : {0:.2f} seconds".format(time.time() - self._startTime)
		else:
			elsapedstr = "Time elapsed : ?? seconds"
		MessageBox.Show("Evaluation Completed \n{}".format(elsapedstr),"Evaluation Completed", MessageBoxButtons.OK, MessageBoxIcon.Information)

	def ButtonCancelClick(self, sender, e):
		self._dynamoRevit.RevitDynamoModel.ResetEngine(True)
		MessageBox.Show("Close Dynamo to Exit Properly","Exit", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
		self.Close()
		self.Dispose()

objInfo = FormInfo(dynamoRevit)
objInfo.Show()
OUT = 0

any corrections are welcome

5 Likes

Nice work!

That was why I moved away from the view extension. I also wanted the capability to let users know more then just “what node ran last”, and expose stuff like object counts by type, messages from the author (expect this next section to go slow because ____), and to leave a record on the system when done. This moved me to the HTML solution posted above (in non-GIF format), but it’s too ham fisted. The external service would be way more useful and compact… ideally as a view extension for player and Dynamo core… That’s a VERY big ask though as the two systems aren’t necessarily speaking the same language.

1 Like