A wondering thread on zero touch node development

For zero touch modes, node model nodes, extensions, and view extensions I have found AI assisted autocomplete in visual studio more effective than any tool building larger prompts.

There are three problems I keep seeing with ‘complete build’ tooling attempts.

The first is that there is just too much noise from other languages so you get significant use of made up methods and such. My favorite from my last sampling was something like Vertex vert = new Vertex(point.X, point.Y, point.Z);. A good MCP setup could offset that, but that gets us into problem 2.

Problem two is that configuring a system that gets it all right takes 4x the effort to use the system that does little steps, and the technology is changed so often and without notice so things can (and will) stop working midway though the day. Claude’s recent evolution in the model that tells ‘go to bed’ is a good example of this. Something changed and no one knows what or why but you have to reconfigure your setup to account for it.

Thirdly is that little changes are digestible to humans. If the inner workings of a method are provided I can read line by line. But when the output is a full solution with 4+ projects and 25 interconnected and cross referenced classes… well no one is reading all of that, so if it works correctly is anyone’s guess, and how to fix the inevitable bug even more so. Likely a new user prompt ‘hey the code you built stopped working and I can figure out where or why please help’ and a response of ‘you’re absolutely right let me delete it all and try again’. That is assuming they haven’t used up all their tokens for the month yet or the service isn’t rate limited at the moment.

And as cool as LLM tools are, those issues coupled with the price and reliability may panning out in the long term. Microsoft has announced that Claude is too costly and is ditching their licenses accordingly. Uber used all of its annual token budget in under 4 months. Costs for the popular LLMs are skyrocketing. And 70% of executives have reached the ‘confirm there is ROI or we pull the plug next year’.

And so I recommend using the tools in baby steps not in big dives. All of that could change any day now, but for now my money is on the targeted use of the tortoise not the full builds of the hare.

100% agree with everything you’ve said here, and this is if anything an experiment in identifying the struggles to hold large context and nested reference file structures/instructons. It honestly did better than I expected but it did need plenty of refactoring when it was done. Big shoutout to @john_pierson for making Rhythm’s src available, it really helped me understand most of the core concepts I needed to get started in addition to the primer.

I did find with the primer and some sample code from Rhythm (sln/csproj) as well as high level instructions from myself, it got me to the chassis of a project. I’ve had to improvise a few things for now like generating my own pkg.json via my build vs package manager, but in essence have a functional local package built in C# which is exciting. Still deciding on how far back to support, probably no earlier than 2025 to stay away from Framework land.

I do generally converse with AI via snippets and back/forths (as wastelessly as I can, usually feeding in multiple queries at once to reduce overhead), but have been constantly surprised by Claude’s capability for small to medium sized projects I provide it with. I’ll get something up on git later this week, no doubt I’m doing a lot of things wrong (even if it’s working currently…). I’ve done plenty of things where in my head I’m telling myself that I doubt this is the intended/best practice way, but keen to share where I’m at eventually and let the wolves descend, baptism of fire is often my entry point to new things by choice.

Lots of quirks I’ve come across such as the query/create/action being implied by method name in 2027, so I’ve limited my focus to just one Revit for now to come back to the rest of those later. The config system that I got taught by Nice3point (and John) seems like a good way to deal with it.

My package currently only has 3 nodes also as well @ISECnico :wink:, no doubt far simpler ones than yours as well! sCrumple is a placeholder, final package name most likely Pickles given the name change.

PS: Don’t worry about those nulls… node checks for existing sheet presence.

:joy: :rofl: :joy: so true!
I sometimes use Copilot to help me find syntax mistakes, but I have a morbid fascination of agreeing to the “performance improvements” that it suggests. I know it is going to be Python-like gibberish that doesn’t work in my Code Block, but there is just a small chance it might point to a performance tweak. There are no token limits, so why not? It’s probably hallucinating…and I’m probably delusional…:sweat_smile:

I haven’t looked at sCrumple yet, @GavinNicholls . Are you building it in C#? I have barely got my head around serpent tongue and already I am seeing other programming options.

Crumple has been a good package to reference while developing, because I can look inside. Clockwork is another great reference for its comprehensive help files. I would like to include examples of how to use my nodes in help files and I have learnt how to add help files using Markdown. I guess it is possible to include relative paths to images of workspace examples or .dyn examples, but do you think hosting these on a repository (GitHub, GitLab, Codeberg, etc.) and adding them as static links is best? I want to avoid managing a repository if possible, but I also want to avoid bloating my package.

Yep pleased to share im developing it in the open, its name changed to Pickles (nicholls > pickles, Crump > crumple).

You can find where I am up to on the wip branch below. Experimented with a few fun things like a dropdown factory, implementing wpf forms and using a view extension to handle extensible graph based data storage.

Currently just smashing out nodes from Crumple, and adjusting/adding some along the way. Hundreds to go, but ive settled into some typical patterns such as helper classes for resourced and anonymized document/link inputs for any nodes that suit - if it doesnt show in my words I am having a lot of fun!

Once I am happy with it, i will add 2025/2026 support via more configs, and then bundle it onto the package manager by revit release most likely (pickles25, pickles26 etc). Currently my csproj handles building straight to the packages folder, then i open revit and dynamo and so far it’s all loading like a package would once deployed. I am still learning so some of my techniques may be somewhat rogue, but I like to think I’m almost correct in most of my choices beyond the build process.

I was in python land for many years before some gentle pushes from people such as @john_pierson helped me take the leap. His Rhythm package has been an invaluable resource to learn from as well, and I wont lie - AI can smash out nodes with the python reference and a bit of templating context.

Where i am at:

Having toiled through two different python engines and all sorts of associated language limitations, i would strongly encourage you to consider zero touch approach and c# when you are ready. I wish i had done this sooner, but it’s all a journey. You get much more flexibility in C# and there is a lot of parity between how your project looks in code with how dynamo consumes it. It very much feels like the way it is meant to be done.

The first handful of lessons in this course are almost platform agnostic if you need a brief c# intro:

I also have a free python fundamentals course here if you prefer to start there:

On a separate note, it is very much wip, but a friend of mine is toying with the idea of using reflection and docusaurus to auto document c# based dll’s. Otherwise your approach with md sounds somewhat logical.

C# projects can be made to output an xml file that can be parsed into various forms of help documents (moreorless what my friend is working on).

I’d focus on logical tooltips over heavy help documentation to begin with, as most users download, try, break stuff, then check tooltips. Getting them to readthedocs is hard if they arent developers familiar with git.

Friends wip project for reference (yes, it is named after the fairy from zelda):

Re hosting, unless you plan to sell/monetize it, pick mit/apache or a more open license and chuck what you do up on git, even if its dyf files that cant be git viewed is my recommendation. Gives you better version control, a place to doc at high level and a wip environment if your package is between published versions. I put crumple on git also even if its python based and dynamo has to deserialize the python code itself. It is also a place where people can log issues and requests vs bother you via email directly/async.

Careful. GitHub Copilot’s billing is now token based - some users saw costs skyrocket as a result. The generic Microsoft one isn’t yet, but I can’t imagine the switch is that far off.

I’ll echo Gavin in wishing I got into C# based package development sooner. The limitations of Python go away pretty quickly when you do so, execution speed goes up by orders of magnitude, and linking to other tools is also much easier (my first foray was into additional tie-in nodes for Robot Structural Analysis).

I recommend distributing the sample graph, markdown, and images via package manager when you publish. But they absolutely live in your GitHub as well. They might want to be in a separate project so should your package grow too much you get less impacted by build times as it can become a lot of files to copy around (one of mine has upwards of 1000 files being copied to 25 directories for testing coverage). While tooltips are useful, my most recent build gave me some feedback that has me prioritizing the markdown, graph image, and samples instead. It went something like this:

User: I didn’t know about that help feature or the sample graph. Now that I do and I can see how your samples can be inserted and reused it’s a huge help. I think I can show these documents to my colleague who’s only opened Dynamo once or twice but never build anything, and then he could build something entirely on his own.

Build wise I switched from pushing to my packages directory(s) directly to having each project in the package’s solution move each the relevant files into %appdata%\Dynamo/CustomPackages\*PackageName*. A ‘build’ project then takes the stuff in that folder and kicks it over to my chosen Dynamo build which directly attaches to my debugger that launches the right Dynamo type as desired (including starting a Revit project and launching Dynamo if I’m building an integration).

An interesting caveat ive encountered on execution is c# does introduce auto lacing, so i have experienced some cases where ive forced nodes that would theoretically like to be one input that laces as many take a list instead to reduce some expense being multiplied by object count, such as hashing all existing sheet numbers to find sheet by number.

There is probably a way i havent found yet to detect one/many inputs (in c# itd be overloads). I have found how to stop the node assuming input shape using arbitary array size attributes but havent found an intutive way to do this like i did in python just yet, due to python being a bit more flexible on input shape/assumed lacing.

Otherwise, boy is it quick.

I am not quite sure I follow. Can you provide a sketch or sample? My gut tells me you’re missing the use of Select or SelectMany or otherwise need to restructure a loop, but it’s too vague to really nail down otherwise.

Example node that takes a list of inputs vs one. Ideally id build it to do one input and let lacing handle list vs single input, but to my understanding that would mean id be collecting all sheets per object vs once for all inputs:

In python i used to use a precheck and function prior to output to send a single object vs list if i got an object as input vs a list of them. Otherwise you send one object in then get a list back out.

My understanding is c# input and return type would mean we either have to specifically accept auto lacing or force list input or output onto the user in such a case. Minor thing generally if i just accept some nodes sending out lists in all cases.

In this case, I would expect that input to be a list, not a single item. Reason being is that you might have a list of 10 sheet numbers to process on 3 documents, or you might have 3 lists of 10 sheet numbers to process on 3 documents; allowing a singleton input forces the input structure (as you likely did with the Python node - if I gave it a list of lists of lists of lists of sheet numbers how would it perform?). Dynamo by default aims to not do this (though there are exceptions such as String.Concat, List.Map, etc.), instead allowing users to chose how inputs should be mixed by way of lacing and list levels.

It’s unclear on quick review exactly how your node works due to the number of referenced helper classes, but my gut tells me it’s something like this:

  1. For the provided document, get the associated sheet collection ID by way of a few helper methods.
  2. Get a dictionary of the sheets in the collection by way of the ID and a helper method.
  3. Start a loop over the sheet numbers where if the sheet number is in the dictionary append the sheet and append true; otherwise append null and false.

This seems pretty straightforward, and if users want to do 1x1 they can do so by setting the list level to @L1 (which will do steps 1 and 2 for every item in the list instead of once per list of numbers), and if only one sheet number is given it should work correctly.

You might have a faster go of it by forgoing the helper method to return the sheetDict (line 270 currently) and instead use some linq methods to rapidly iterate over the list instead of passing back and forth between helper methods spread out over multiple files and classes. A line like ViewSheet[] sheets = fec.Where(x => numbers.Contains(x.SheetNumber); might be a bit more friendly to the developers as a whole (I had to unwrap 3 or 4 helper methods in place of one subsitution here) and reduce runtime.

Yep figured just sticking with list in list out is probably the practical approach. I’ve seen a few examples of nodes taking one object in and sending one object out so wasn’t sure what the typical approach is. Generally if the node is pretty light and doesn’t require a set of objects to figure out what it’s doing (aka not using a dict for lots of lookups in one node call), I’ll probably just use input naming to make it obvious to the user when that mode is based on one vs many items. I agree that returning a list as default state is probably more predictable, especially if the user gives a list of one object and wonders why the list levels aren’t as expected.

The dochelper is an idea I’d definitely encourage the dynamo team to look into, it effectively lets me call default state of nodes on current document, but send in another document of revitlinkinstance and use that as the document instead so that i only need one node per current/any document - can see the team generally separates their nodes by current and linked, but I’ve been using this approach since Crumple began and I think Genius Loci leans towards it as well to make nodes more flexible. Very useful if you need to do any background document logic as well. I appreciate it probably muddies up UI/UX for a basic user so is probably too verbose as a default pattern in ootb dynamo, and background/linked doc processing is an advanced thing to do in Dynamo as is anyway for a user given link transforms and the questionable safety of background document processing vs just using Dynamo Multiplayer.

Moreorless a rehash of what we discusssed here, think we reached same conclusion as well:

Amusing to read you offering an intro to zero touch nodes there as well. Only took me two whole years to get there…!

Anyway I’ve dragged us a bit off topic but appreciate the tips as always!

Moving this all to a new topic as we speak, as it’ll be easier for users to find and keep @ISECnico’s work the focus of this thread. :slight_smile:

Why not use the Element.Document property, for example:

Autodesk.Revit.DB.Document doc = elements.First().InternalElement.Document;

Once you get a good set of nodes on it, worth submitting as a "wishlist’ issue for the Dynamo for Revit repo. There is a LOT of automation there which gets things into the faces of the relevant team. In this case the relevant team would be the Revit development team and not the Dynamo development team as this is very much a Revit thing. Often the Dynamo team gets pulled in for assistance/support/execution since they know the immediate execution context better, but Revit still would have to ‘own’ the initial actions.

I generally use Element.Document when an element is provided to the node alternatively, this is more for nodes that do not have access to one such as collectors based ones, or where primitive types are inputs only.

On another note this also enables me to accept both the Dynamo and DB Document types into my nodes, thanks to that nifty reflection trick Jon/Erfajo developed a while back.

E.g.

Thank you, @jacob.small . The difference between the level of conversation and the sophistication of the package was embarrassing. :sweat_smile: I will bookmark this thread and tag along on this wondrous wander while pretending to understand what you are talking about. :wink:

This is good to know. I’ll have to change my goals. The only issue is that I can’t link it to my work goals, because I often get requests to avoid packages and use OOTB. I wish there were a ZT or C# equivalent to the Python Script node. Is there is a language / architectural reason that it isn’t possible? :thinking:

@GavinNicholls Awesome resources and references :heart_exclamation: I like the new name(s). :+1: Also, congratulations on Pickles and the personal developments that inspired it. :man_in_tuxedo:

It sounds like this was coded by my Irish mate when he lost something. :grin:

Honestly don’t make me sway you off Python entirely, but I encourage you at the least to keep it in mind as an eventual goal if your endgame is developing add-ins/robust packages. Python ultimately has a ceiling RE deployability in my experience, and often forces people into pyRevit into hindsight which to some degree I will admit I wasted a portion of my career developing within (in light of where my goals actually needed me to go ultimately).

There are some fairly big hurdles to get into C#, particularly around getting used to being outside the application when you code, and having to constantly open/close the app to test, but the compiler does keep you in check for the most part so that you wont write things that don’t work at the fundamental/type level - takes getting used to. You’ll still get caught out by unexpected null returns, fringe cases etc, and can’t ‘throw it at the app until it works’ like Python lets you.

Every time I’ve pivoted from a language/approach, it’s taken a few goes to ‘get it’, expect that it will be a bit of ‘what does any of this mean’ at first before it starts to fall into place. Moving from Python to C# was a big one for me as it has a lot of equivalence with other languages out there like JS - it gave me the ability to read most languages conceptually even if I don’t technically know all the calls/methods. By forcing you to engage with classes, it also made me more conscious of how I should have been using Python, but that it didn’t force me to at the time. I built a lot of bad habits in Python that C# simply told me would not do.

The hardvard CS50 course was also very valuable as a foundational layer of programming, gave me a lot of appreciation for the ‘work’ we put on our computers in our code (e.g. using a list is actually quite memory intensive behind the scenes vs other approaches, the sorting we force computers to do has some big implications at scale in arrays vs lists etc.).

Being able to pull Python code out of nodes onto canvas is definitely a big plus at times, I do it at work all the time, so having a Python package foundation might be quite valuable if you see yourself needing to do this often. I’m biased given I have both at my disposal, didn’t really think about that… it’s a very valid point.

This wiki is great to go through if you’re curious about ZT:

I don’t believe you can run C# off canvas as Dynamo wants to consume compiled code to load it into Revit as nodes, may be mistaken though. You can get zero touch to execute Python scripts as text though, funnily enough.

Thanks for the kind words, and lol at the Irish mate call. Everytime I use FEC’s, I think the same.