More Eto Examples please

Hi everyone,

When comparing Eto with WPF, it is for most developers easier and quicker to get the result they want through WPF, leading to no support of Rhino Mac.

One reason is, i think, that there are thousands of examples also for more complex scenario’s.

So I was wondering if anyone (maybe even from Rhino) could share some Code Examples of complex UIs made in Eto, that work cross platform. Just to learn from them and get a sense of the Best Practices.

Best,
Martin

6 Likes

I can only speak for my self, but I hate eto. It’s like going back to the 90’s and handcrafting html. Making a script much bigger than needed.
I would expect a designtool to handle that for me now.

10+ years ago I used Cinema4d and there the scripts could easily communicate with the UI and buttons, bools, textfields etc were created automatically.

I wish there were a simple panel solution for this, kind of like for rs.messagebox() that could handle most in/outputs automatically.

Don’t get me wrong, I love the fact that ETO is integrated and superflexible, it just doesn’t cover the need a simple script has, and that is important for all new parttime programmers IMO.

1 Like

hi @MartinIC please define complex. If you have a sample sketch of what you want to achieve, that could be helpful as well

That’s true for parttime scripting as Eto wasn’t exactly designed for that. We could probably put together some sort of rhinoscriptsyntax type set of functions for this if we better understood what you need.

2 Likes

That would be awesome.
I will find some time to scribble down my thoughts and ideas!

1 Like

I am working on a jewelry kit, and I am making it in ETO because (a) I like the ETO framework (mostly) and (b) I hope to one day get my kit working on Macs. Problem is, my scripts often don’t work on Macs.

  • My first lesson was to use os.path.join() instead of “\” for building my paths.
  • My second lesson was even though you can detect drives with python, this is a windows-only thing, so I began using rs.OpenFileName and rs.FolderBrowser (or whatever they are).
  • My third lesson was that apparently Macs require me to set a default button.

All that and my scripts still don’t work on Macs. (well, to be fair, I haven’t set a default button on the forms yet, except for one person who got that as a clear error message…another guy got a vague "unexpected token ‘/’ " and even though I removed all possible forward slashes (except for the ones where it was performing division) it still gave that error. So I’m not sure what that means.

I can’t afford a Mac, let alone a Mac and a copy of Rhino for Mac. But trouble-shooting is a laborious process on my own computer, let alone having a client try out different things and then report the results back to me (which I’ve done a few times).

I would love some kind of compatibility-tester, or a strict mode, or something that can ensure I’m truly writing code compatible with both Windows and Macs. That’s probably a big ask, though and not something I’ve particularly considered McNeel’s responsibility (it would be cool, though).

But I intend to keep trying, and if I ever break the code, I may make some YouTube tutorials about it.

(Edit)
My fourth lesson: enclose the script path in double quotes in the alias.

In windows, apparently double quotes around the script path are optional, but are required on Macs. Once I rewrote the code that creates the aliases to enclose the script paths in double quotes, my client was able to use my kit on his mac.

I use an older Intel Dual Core Mac Mini updated to a version of MacOS that supports both R7 and R8 for cross platform testing.

It’s slow as all get out, but does work. Second hand these are CHEAP and are all you need for testing.

You do not need a separate Rhino License, cloud zoo just allows you to log onto your Rhino account from the Mac and use your current license there.

For ETO, I find it a royal pain in the a&$e for scripting, just way too much code for way to little functionality. I do use Script Syntax UI components often, as these are quick, simple and easy. What I see is missing is the ability to stack Script Syntax UI components together in one UI container.

If there was a new ‘scripting UI’ built into Rhino I would look at something like the Script Syntax UI, but stackable, even in just one vertical container.

I personally ended up building an HTML based UI framework using ETO.Webview in GH using Python just because ETO was too much work. That one sentence explains the current situation rather well, sadly.

Cheers

DK

3 Likes

If you use Python 3 you should rather use Path from pathlib for all your path wizardry.

2 Likes

@kiteboardshaper

Second hand these are CHEAP and are all you need for testing.

I’m probably broker than you think, however, if my situation improves, I will look into this. Just glancing at Ebay, I’m seeing prices range from $70 to several hundred. What kind of price should I be looking for?

I personally ended up building an HTML based UI framework using ETO.Webview

I didn’t know about Webview. I will have to look into that, too. Although, I’m personally fond of the old-school Windows 95-esque UI.

@nathanletwory

If you use Python 3 you should rather use Path from pathlib for all your path wizardry.

As it happens, I use Python 2 as I make the kit for Rhino 6 - Rhino 8. One day, though, I may make separate scripts for Rhino8+; so, hopefully I will remember this. Thanks.

Oh believe me my budget is very limited too!

My test bed Mac Mini was ‘free’, found unloved in the back of a clients storage cupboard being retired years ago.

Just check what the min running requirements are for Rhino 8 and make sure the machine can be updated to that version of MacOS.

For more info on my WebView based cross platform UI journey check this thread:

Cheers

DK

2 Likes

Side note: For using the current Rhino style automatically (e.g. light and dark mode):
in Python:
Rhino.UI.EtoExtensions.UseRhinoStyle(self)

in C#:
this.UseRhinoStyle()

5 Likes

i also hate ETO… but… we did need to convert our plugin from winforms, so i started this project with this very simple question to chatGPT and it literally built the entire UX. (thereafter i was impressed enough to start asking it to add function, my only contribution to this code was passing the statics for our alloys and their specific gravities, the rest is 100% ChatGPT code , it also completely refactored all code to remove reliance on netfx and ensure all code is netcore. it also fully documents all the code.

Id say maybe in total 5 times i had to correct, but once you do, it remembers that in that session and the error will not recur.

in order for example,

the prompts to build the entire “UX colour customiser” in the tools tab, were

and its output nailed it in a single shot, creating the ux, creating the xml in plugin root to repopulate the selection on load etc

i had to tweak it once to add a reset button.



otherwise the crazy selections are hard to return to normal :wink:

this represent three mornings of work ~ 6hours, as I only have a few hours to spare before the staff get to work.

A tick for anyone trying this is save the last good copy of your code somewhere beside VS, it has a habit of omitting entire functions, my viewpanel class for example is 2300 line of eto code, about 40-50 functions, and if i make a small tweak somewhere it might omit an entire panel tab when it returns the code to me, if that happens paste your last working copy back into it, ask it to “read it carefully and not omit any functions, only add the requested changes”, then it nails it, I needed to do this about 10 times through this build, and if you werent aware and 10 functions later you lost half your core that would be a disaster… outside of that annoyance it was an awesome experience. (i use comparator in notepad++ to double check its submissions)

Video of the ux in action, including the implimentation of some of the function.
so we are clear, these functions in this video are ALSO built by ChatGPT, my original core code is spaghetti, so im taking the opportunity to rebuild it cleaner using chatgpt, the functions it is replicating, i find then to be ~ 1/3rd of the orginal size and cleaner to read)

4 Likes

Impressive and inspiring! Great video too.
My favourite part was this youtube’s auto texter though :smiley:
image

ChatGPT finally has a name…
(According to dictionary.com: A chad is a popular, confident, sexually active young white male)

3 Likes

I’m also testing chatGPT extensively, and it has built some impressive things already. Eto UI stuff that is repetitive, it is very good at. I also had it figure out some complex drag over behavior.
For example, this is something I want to have in Rhino, so with the right questions, it made 90% what you see below. You do need to know some coding to correct it here and there, sometimes it stubbornly keeps doing stupid stuff.

8 Likes

Sweet! I would love a ‘favourites’ button with a right-click ‘set favourites’ option too.
over 90 percent of the time I have the same snaps on, and every now and then I just use a few. So a quick reset to my favourites would be sweet.

1 Like

prototype: very clunky with a very short memory :wink:
(this was an example where ChatGPT had no idea what to do with the Osnap properties)
But setting the form up in no time

using System;
using Eto.Forms;
using Eto.Drawing;
using Rhino;
using Rhino.ApplicationSettings;
using Rhino.Commands;
using Rhino.UI;

class Osnaps : FloatingForm
{
    // Array to hold the stored Osnap states
    private OsnapModes [] storedSettings = new OsnapModes[3];
    
    public Osnaps()
    {
        Title = "Osnap Settings Manager";
        ClientSize = new Size(300, 100);
        this.UseRhinoStyle();
        // Create a table layout for the 3 rows of buttons
        var layout = new TableLayout { Spacing = new Size(5, 5), Padding = 10 };

        // Generate buttons for each row
        for (int i = 0; i < 3; i++)
        {
            int index = i;

            // "Store" button
            var storeButton = new Button { Text = $"Store Settings {i + 1}" };
            storeButton.Click += (sender, e) => StoreSettings(index);

            // "Retrieve" button
            var retrieveButton = new Button { Text = $"Retrieve Settings {i + 1}" };
            retrieveButton.Click += (sender, e) => RetrieveSettings(index);

            // Add buttons to a horizontal row
            layout.Rows.Add(new TableRow(storeButton, retrieveButton));
        }
        layout.Rows.Add(""); //clearing
        // Set layout to the form content
        Content = layout;
    }

    // Method to store the current Osnap settings
    private void StoreSettings(int index)
    {
        try
        {
            storedSettings[index] = ModelAidSettings.OsnapModes;
            RhinoApp.WriteLine($"Osnap settings stored in slot {index + 1}.");
        }
        catch (Exception ex)
        {
            RhinoApp.WriteLine($"Error storing settings: {ex}");
        }
    }

    // Method to retrieve the stored Osnap settings
    private void RetrieveSettings(int index)
    {
        if (storedSettings[index] != null)
        {
            try
            {
                Rhino.ApplicationSettings.ModelAidSettings.OsnapModes = storedSettings[index];
                RhinoApp.WriteLine($"Osnap settings retrieved from slot {index + 1}.");
            }
            catch (Exception ex)
            {
                RhinoApp.WriteLine($"Error retrieving settings: {ex}");
            }
        }
        else
        {
            RhinoApp.WriteLine($"No Osnap settings stored in slot {index + 1}.");
        }
    }
}


var form = new Osnaps();
form.Show();




(If this would be made into a plugin, it could store the settings in the plugins settings)

2 Likes

Open-source-ish collaboration idea, a Visual-Studio-esque IDE for ETO forms built with ETO that runs in Rhino.

It’s mostly the manual instancing and property-setting of all the controls and setting up events that I dislike.

1 Like

It’s weirdly tricky to make a visual editor for a UI that is not defined by uncompiled text, i.e xaml, js/html/css etc.

It’s trivial to make a drag and drop eto UI Editor thing.
And then turning it into code, isn’t too tricky, you just need to turn the control tree into C#. No biggie when System.Reflection exists.

But correctly reading some C# code, which a user can do almost anything with, and loading it into an editor as eto components, is no small feat, and would take an enormous amount of work, you pretty much would have to write a small C# compiler.

2 Likes

You can make functions for that, for example:

import Eto.Forms as forms
import Eto.Drawing as drawing
import Rhino

class StepperForm(forms.FloatingForm):
    def __init__(self):
        super().__init__()
        Autosize = True
        Rhino.UI.EtoExtensions.UseRhinoStyle(self)
        # Create the label to show changes
        self.status_label = forms.Label()
        self.status_label.Text="Stepper status will appear here"
        self.status_label.TextAlignment = forms.TextAlignment.Left

        # Create multiple steppers, each with a unique identifier
        stepper1 = self.create_numeric_stepper(value=0, increment=1, identifier="Stepper 1")
        stepper2 = self.create_numeric_stepper(value=5, increment=2, identifier="Stepper 2")
        stepper3 = self.create_numeric_stepper(value=10, increment=5, identifier="Stepper 3")

        # Set up layout and add steppers and label
        layout = forms.DynamicLayout()
        layout.Padding = drawing.Padding(11)
        
        layout.BeginVertical()
        layout.AddRow(stepper1)
        layout.AddRow(stepper2)
        layout.AddRow(stepper3)
        layout.EndVertical()
        layout.BeginVertical()
        layout.AddRow(forms.Label())# empty spacer
        layout.AddRow(self.status_label)

        self.Content = layout

    def create_numeric_stepper(self, value=0, increment=1, decimal_places=0, min_value=None, identifier=None):
        stepper = forms.NumericStepper()
        stepper.Value = value
        stepper.Increment = increment
        stepper.DecimalPlaces = decimal_places
        if min_value is not None:
            stepper.MinValue = min_value

        # Define an internal wrapper to handle ValueChanged and pass identifier
        if identifier is not None:
            stepper.ValueChanged += lambda sender, e: self.value_changed_handler(stepper, identifier)

        return stepper

    def value_changed_handler(self, stepper, identifier):
        # Update label with stepper's identifier and current value
        self.status_label.Text = f"{identifier} was changed to {stepper.Value}"

# Instantiate and show form
myform = StepperForm()
myform.Show()

2 Likes

Ok, great points. So, maybe not a full-blown IDE. Maybe just a form-layout thing that could generate all the initialization code for the containing window, the layout, and all the controls and maybe even empty event handlers - in python or C#. (Although, tbh, I was thinking about python.) So, it could be a one-way thing where it just generates code and never tries to understand code. Maybe that would make it more doable.

But honestly, for me (I’m not a very good coder), even just that would be a huge task.