Hello. Recently I noticed about Eto. I started to study this and connecting my python project to this UI.
But in some cases the bug not just break process, but also break Rhino. What I mean is that, in some cases Rhino shut down.
I had to open Console App to check the last error messages. Why this happened? And How can I fix this?
Hello @์ด์ ํ is Rhino crashing when youโre editing python in the Script Editor?
not during editing, but running script through Tool > Script > Run
running this code
import sys
sys.path.append("/Users/sinhoo/Documents/RhinoPlugin")
import utils
import Rhino.Input
import Rhino.Input.Custom
import scriptcontext as sc
from apartment.apartment_builder import (
ApartmentInputStructure,
UnitInput,
CoreInput,
WindowInput,
)
from Eto.Forms import (
Application,
Form,
TextBox,
Button,
Label,
TableLayout,
TableRow,
TableCell,
)
import Eto.Forms as ef
from Eto.Drawing import Size, Padding
def get_object(): # DEPRECATED ETO loop์ ์ถฉ๋๋ฐ์
go = Rhino.Input.Custom.GetObject()
go.SetCommandPrompt("์ํํธ ๊ด๋ จ ๋๋ฉด ๊ฐ์ฒด๋ฅผ ์ ํํ์ธ์")
go.EnablePreSelect(True, True)
go.GetMultiple(1, 0)
if go.CommandResult() != Rhino.Commands.Result.Success:
ef.MessageBox.Show("์ค๋ธ์ ํธ ์ ํ์ด ์ทจ์๋์์ต๋๋ค.", ef.MessageBoxButtons.OK)
return
def get_preselected_objects():
objs = sc.doc.Objects.GetSelectedObjects(True, False)
return objs
class ValidationResultForm(Form):
def __init__(self, objects, results):
super(ValidationResultForm, self).__init__()
self.Title = "๊ฒ์ฆ ๊ฒฐ๊ณผ"
self.ClientSize = Size(400, 200)
self.Padding = Padding(10)
self.objects = objects
layout = TableLayout(
rows=[
TableRow(
Label(Text=f"๊ฒ์ฆ {key}: {'โ
ํต๊ณผ' if passed else 'โ ์คํจ'}")
)
for key, passed in results.items()
]
+ [None]
)
self.Content = layout
class ValidationForm(Form):
def __init__(
self,
):
super(ValidationForm, self).__init__()
self.objects = None
self.Title = "์ํํธ ๋๋ฉด ๊ฒ์ฆ"
self.ClientSize = Size(400, 300)
self.Padding = Padding(10)
# --- ์์ ฏ๋ค ---
self.name_input = TextBox()
self.upload_button = utils.make_button("Upload")
self.result_labels = {
"A": utils.make_label("๊ฒ์ฆ A: ๋๊ธฐ ์ค"),
"B": utils.make_label("๊ฒ์ฆ B: ๋๊ธฐ ์ค"),
"C": utils.make_label("๊ฒ์ฆ C: ๋๊ธฐ ์ค"),
"D": utils.make_label("๊ฒ์ฆ D: ๋๊ธฐ ์ค"),
}
self.upload_button.Click += self.on_upload_clicked
# --- ๋ ์ด์์ ---
layout = TableLayout(
rows=[
TableRow(
TableCell(utils.make_label("์ํํธ ์ด๋ฆ")),
TableCell(self.name_input),
),
TableRow(None, TableCell(self.upload_button)),
TableRow(TableCell(self.result_labels["A"])),
TableRow(TableCell(self.result_labels["B"])),
TableRow(TableCell(self.result_labels["C"])),
TableRow(TableCell(self.result_labels["D"])),
None, # ๋์ ์ฌ์ ๊ณต๊ฐ
]
)
self.Content = layout
def on_upload_clicked(self, sender, e):
apartment_name = self.name_input.Text
# ์ํํธ๋ก ๋ง๋ค RhinoObject๋ค์ ์ ํํ๋๋ก ํจ.
objs = sc.doc.Objects.GetSelectedObjects(True, False)
# ์ ํ๋ ์ค๋ธ์ ํธ๋ค
self.objects = objs
apartment_input_structure = self._create_apartment_input_structure(
apartment_name, self.objects
)
# ๊ฒ์ฆ ์คํ
apartment_check_result = apartment_input_structure.validate()
layout = TableLayout(
rows=[
TableRow(
TableCell(utils.make_label(self.get_result_message(result_dict)))
)
for key, result_dict in apartment_check_result.items()
]
+ [None]
)
self.Content = layout
def get_result_message(self, result_dict):
result_message = ""
result = result_dict.get("result")
message = result_dict.get("message")
if result:
result_message += f"๊ฒ์ฆ ๊ฒฐ๊ณผ: โ
ํต๊ณผ\n"
else:
result_message += f"๊ฒ์ฆ ๊ฒฐ๊ณผ: โ ์คํจ :{message}\n"
return result_message
def _create_apartment_input_structure(
self, apartment_name, clicked_items
) -> ApartmentInputStructure:
object_by_layer = {}
for rhino_object in clicked_items:
lyr_name = utils.get_layer_name(rhino_object)
if lyr_name not in object_by_layer:
object_by_layer[lyr_name] = []
object_by_layer[lyr_name].append(rhino_object)
unit_inputs = [UnitInput(unit_obj) for unit_obj in object_by_layer["unit"]]
core_inputs = [CoreInput(core_obj) for core_obj in object_by_layer["core"]]
window_inputs = [
WindowInput(window_obj) for window_obj in object_by_layer["window"]
]
apartment_input_structure = ApartmentInputStructure(
apartment_name,
unit_inputs,
core_inputs,
corridor_inputs=[],
window_inputs=window_inputs,
sidewall_inputs=[],
)
return apartment_input_structure
# --- ์ ํ๋ฆฌ์ผ์ด์
์คํ ---
if __name__ == "__main__":
form = ValidationForm()
form.Show()
Because Rhino Crash. I have to check error message thorugh Console App in Mac.
This makes development experience so bad. So I want to fix this.
This is image of UI Iโm trying to build.
If i click Upload Button, if there is any error in code(simple error such as input value count not matching). Rhino Crash.
If there is better way to create GUI, I want to try, please inform me.
functions that I need with the GUI
- interact with User
user click RhinoObjects > check Validation + Create BlockDefinition > simulate and optimize with the Block Definition > Visualize 3D results and show data with table or graph. - Python (Cpython prefer)
- Various UI elements(image, table, Rhino viewport)
the reason I choose Eto was that Eto has all of this functions, but if there is other way, I want to try that.
Thank You for reading this!
Hey @์ด์ ํ ,
A few questions.
Can you run the _systemInfo
command and paste the results here.
I canโt test the script crash unfortunately without this library, if you can add it here ot DM it to me I can try to replicate and then create a ticket.
I personally write my Eto UIโs in the Script Editor.
I do know that some things will just Crash Rhino when scripting, especially events.
try/catch can help with certain errors to prevent crashes.
Also, more Eto docs which may help.
Hello, Thank you for response.
I hope you can replicate with this script and my contexts.
My test code which stopping Rhino
import sys
sys.path.append("/Users/sinhoo/Documents/RhinoPlugin")
import Rhino.Input
import Rhino.Input.Custom
import scriptcontext as sc
from Eto.Forms import (
Application,
Form,
TextBox,
Button,
Label,
TableLayout,
TableRow,
TableCell,
Dialog,
)
import Eto.Forms as ef
from Eto.Drawing import Size, Padding
def make_label(text) -> Label:
lbl = Label()
lbl.Text = text
return lbl
def make_button(text) -> Button:
btn = Button()
btn.Text = text
return btn
class ValidationResultForm(Form):
def __init__(self, objects, results):
super(ValidationResultForm, self).__init__()
self.Title = "๊ฒ์ฆ ๊ฒฐ๊ณผ"
self.ClientSize = Size(400, 200)
self.Padding = Padding(10)
self.objects = objects
layout = TableLayout(
rows=[
TableRow(
Label(Text=f"๊ฒ์ฆ {key}: {'โ
ํต๊ณผ' if passed else 'โ ์คํจ'}")
)
for key, passed in results.items()
]
+ [None]
)
self.Content = layout
class ValidationDialog(Dialog):
def __init__(
self,
):
super(ValidationDialog, self).__init__()
self.objects = None
self.Title = "์ํํธ ๋๋ฉด ๊ฒ์ฆ"
self.Width = 400
self.Height = 800
self.Padding = Padding(10)
# --- ์์ ฏ๋ค ---
self.name_input = TextBox()
self.upload_button = make_button("Upload")
self.result_labels = {
"A": make_label("๊ฒ์ฆ A: ๋๊ธฐ ์ค"),
"B": make_label("๊ฒ์ฆ B: ๋๊ธฐ ์ค"),
"C": make_label("๊ฒ์ฆ C: ๋๊ธฐ ์ค"),
"D": make_label("๊ฒ์ฆ D: ๋๊ธฐ ์ค"),
}
self.upload_button.Click += self.on_upload_clicked
# --- ๋ ์ด์์ ---
layout = TableLayout(
rows=[
TableRow(
TableCell(make_label("์ํํธ ์ด๋ฆ")),
TableCell(self.name_input),
),
TableRow(None, TableCell(self.upload_button)),
TableRow(TableCell(self.result_labels["A"])),
TableRow(TableCell(self.result_labels["B"])),
TableRow(TableCell(self.result_labels["C"])),
TableRow(TableCell(self.result_labels["D"])),
None, # ๋์ ์ฌ์ ๊ณต๊ฐ
]
)
self.Content = layout
def on_upload_clicked(self, sender, e):
apartment_name = self.name_input.Text
# ์ํํธ๋ก ๋ง๋ค RhinoObject๋ค์ ์ ํํ๋๋ก ํจ.
objs = sc.doc.Objects.GetSelectedObjects(True, False)
# ์ ํ๋ ์ค๋ธ์ ํธ๋ค
self.objects = objs
raise Exception("this is raise test")
def get_result_message(self, result_dict):
result_message = ""
result = result_dict.get("result")
message = result_dict.get("message")
if result:
result_message += f"๊ฒ์ฆ ๊ฒฐ๊ณผ: โ
ํต๊ณผ\n"
else:
result_message += f"๊ฒ์ฆ ๊ฒฐ๊ณผ: โ ์คํจ :{message}\n"
return result_message
print("Starting ValidationForm...")
dialog = ValidationDialog()
dialog.ShowModal()
my systemInfo
Rhino 8 SR19 2025-5-12 (Rhino 8, 8.19.25132.01002, Git hash:master @ 57e3eb280b0f32cbe59f53a554be5e8ffa66fc36)
License type: Commercial, build 2025-05-12
License details: Cloud Zoo
Apple macOS Version 14.7 (Build 23H124) (Physical RAM: 16GB)
Mac Model Identifier: MacBookAir10,1
Language: en-KR (MacOS default)
.NET 7.0.0
Metal GPU Family Apple 7
Metal GPU Family Common 3
Metal GPU Family Mac 2
Graphics processors
Apple M1
Color LCD (1440 x 900 @ 60.00Hz)
GPU Vendor: Apple
USB devices
None
Bluetooth devices
None
Third party kernel extensions
None
Third party plugins
/usr/lib/swift/libswiftCore.dylib
/usr/lib/swift/libswiftCoreFoundation.dylib
/usr/lib/swift/libswiftCoreGraphics.dylib
/usr/lib/swift/libswiftCoreImage.dylib
/usr/lib/swift/libswiftDarwin.dylib
/usr/lib/swift/libswiftDispatch.dylib
/usr/lib/swift/libswiftIOKit.dylib
/usr/lib/swift/libswiftMetal.dylib
/usr/lib/swift/libswiftOSLog.dylib
/usr/lib/swift/libswiftObjectiveC.dylib
/usr/lib/swift/libswiftQuartzCore.dylib
/usr/lib/swift/libswiftUniformTypeIdentifiers.dylib
/usr/lib/swift/libswiftXPC.dylib
/usr/lib/swift/libswift_Concurrency.dylib
/usr/lib/swift/libswiftos.dylib
/usr/lib/swift/libswiftsimd.dylib
/usr/lib/swift/libswift_StringProcessing.dylib
/usr/lib/swift/libswift_RegexParser.dylib
/usr/lib/swift/libswiftCryptoTokenKit.dylib
/usr/lib/usd/libusd_ms.dylib
/usr/lib/swift/libswiftCoreAudio.dylib
/usr/lib/swift/libswiftCoreLocation.dylib
/usr/lib/swift/libswiftCoreMedia.dylib
/usr/lib/swift/libswiftCompression.dylib
/usr/lib/swift/libswiftCoreMIDI.dylib
/usr/lib/swift/libswiftAVFoundation.dylib
/usr/lib/swift/libswiftCoreML.dylib
/usr/lib/swift/libswiftFileProvider.dylib
/usr/lib/swift/libswiftIntents.dylib
/usr/lib/swift/libswiftAccelerate.dylib
/usr/lib/swift/libswiftGLKit.dylib
/usr/lib/swift/libswiftGameplayKit.dylib
/usr/lib/swift/libswiftMetalKit.dylib
/usr/lib/swift/libswiftModelIO.dylib
/usr/lib/swift/libswiftSceneKit.dylib
/usr/lib/swift/libswiftSpriteKit.dylib
/usr/lib/swift/libswiftVision.dylib
/usr/lib/swift/libswiftRegexBuilder.dylib
/usr/lib/swift/libswiftDemangle.dylib
/usr/lib/swift/libswiftShazamKit.dylib
/usr/lib/swift/libswiftObservation.dylib
/usr/lib/swift/libswiftVideoToolbox.dylib
/usr/lib/swift/libswiftWebKit.dylib
/usr/lib/swift/libswiftNaturalLanguage.dylib
/usr/lib/swift/libswiftSystem.dylib
/usr/lib/swift/libswiftMapKit.dylib
/usr/lib/log/liblog_network.dylib
/Users/sinhoo/.rhinocode/py39-rh8/libpython3.9.dylib
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/_heapq.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/_ssl.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/libssl.1.1.dylib
/Users/sinhoo/.rhinocode/py39-rh8/libcrypto.1.1.dylib
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/_socket.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/math.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/select.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/array.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/_struct.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/binascii.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/zlib.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/_bz2.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/_lzma.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/grp.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/_bisect.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/_random.cpython-39-darwin.so
/Users/sinhoo/.rhinocode/py39-rh8/lib/python3.9/lib-dynload/_sha512.cpython-39-darwin.so
Rhino plugins that do not ship with Rhino
/Users/sinhoo/Library/Application Support/McNeel/Rhinoceros/packages/8.0/Fologram/2024.2.9/Fologram.rhp โFologramโ 2024.2.9.0
/Users/sinhoo/Library/Application Support/McNeel/Rhinoceros/8.0/MacPlugIns/Enscape.Rhino8.Plugin.rhp โEnscape.Rhino8.Pluginโ 4.7.0.57
Rhino plugins that ship with Rhino
/Applications/Rhino 8.app/Contents/Frameworks/RhMaterialEditor.framework โRenderer Development Kitโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/Frameworks/RhCore.framework/Resources/ManagedPlugIns/Commands.rhp โCommandsโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/PlugIns/NamedSnapshots.rhp โSnapshotsโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/Frameworks/RhCore.framework/Resources/ManagedPlugIns/RDK_EtoUI.rhp โRDK_EtoUIโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/Frameworks/RhCore.framework/Resources/ManagedPlugIns/MeshCommands.rhp โMeshCommandsโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/Frameworks/RhCore.framework/Resources/ManagedPlugIns/RhinoRenderCycles.rhp โRhino Renderโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/Frameworks/RhCore.framework/Resources/ManagedPlugIns/RhinoCycles.rhp โRhinoCyclesโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/PlugIns/SectionTools.rhp โSectionToolsโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/PlugIns/Displacement.rhp โDisplacementโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/PlugIns/PanelingTools.rhp โPanelingToolsโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/Frameworks/RhCore.framework/Resources/ManagedPlugIns/RhinoDLR_Python.rhp โIronPythonโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/Frameworks/RhCore.framework/Resources/ManagedPlugIns/GrasshopperPlugin.rhp โGrasshopperโ 8.19.25132.1002
/Applications/Rhino 8.app/Contents/Frameworks/RhCore.framework/Resources/ManagedPlugIns/RhinoCodePlugin.rhp โRhinoCodePluginโ 8.19.25132.1002
Are you able to include me this library either here or in a direct message @์ด์ ํ? The script wonโt run without it for me
RhinoPlugin.zip (156.3 KB)
This is all files in that directory. I ran codes in python_scripts directory.
Thanks for follwing up.
The rhino ide can now write c# directly. If you want to use pyeto, it is better to write py+cs in the rhino ide. There should be fewer errors.
@์ด์ ํ for what it is worth, the screenshot of the Console app shows the error and solution. __init__()
missing an argument core_inputs
. You probably should do core_inputs=core_inputs
, but it really depends on what the __init__
of your class ApartmentInputStructure
is.
edit: I actually only now noticed the ZIP file you uploaded. I think probably should remove the named arguments in the call, just pass in the values you want to without naming them.
From
apartment_input_structure = ApartmentInputStructure(
apartment_name,
unit_inputs,
core_inputs,
corridor_inputs=[],
window_inputs=window_inputs,
sidewall_inputs=[],
)
to
apartment_input_structure = ApartmentInputStructure(
apartment_name,
unit_inputs,
core_inputs,
[],
window_inputs,
[],
)
@N Thank you for the advice. Iโm trying to find best way to build interface with Rhino - python script.
I used to build this interface with grasshopper + VSCode Studio and make every python scripts as package and import + reload in Grasshopper to make my codes reusable.
But my client had some difficulties to use GH interface so I wanted to build UI in Rhino.
What is the best way?
My concern is two things.
First, it should be flexible and interactive with rhino Document(like creating block defintions or validating)
Second, the code could be reusable(I build many tiny utilty codes and use those codes in many projects)
I found that I can write python3 in rhino script editor(before I confused script editor between RhinoPythonEditor which allows only iron python), so I started to test it. But if there is better way, please give me some advice
Thank you for your check.
I knew my code has error, my issue was that Rhino crashes so its hard to check what the error is, which makes developing experience so bad.
As you mentioned, I will try to remove named args to check if this work. Thank you!
That is indeed not ideal. @CallumSykes , @eirannejad, is there maybe something be done here that Rhino wouldnโt completely crash on a Python error?
Change to get the object name, I hope you can help
Forgive me if I donโt understand your native language, I can only guess and make some changes
#! python 3
import sys
sys.path.append("/Users/sinhoo/Documents/RhinoPlugin")
import Rhino.Input
import Rhino.Input.Custom
import scriptcontext as sc
import Eto.Forms as ef
import rhinoscriptsyntax as rs
from Eto.Forms import (
Application,
Form,
TextBox,
Button,
Label,
TableLayout,
TableRow,
TableCell,
Dialog,
)
import Eto.Forms as ef
from Eto.Drawing import Size, Padding
def make_label(text) -> Label:
lbl = Label()
lbl.Text = text
return lbl
def make_button(text) -> Button:
btn = Button()
btn.Text = text
return btn
class ValidationResultForm(Form):
def __init__(self, objects, results):
super(ValidationResultForm, self).__init__()
self.Title = "๊ฒ์ฆ ๊ฒฐ๊ณผ"
self.ClientSize = Size(400, 200)
self.Padding = Padding(10)
self.objects = objects
layout = TableLayout(
rows=[
TableRow(
Label(Text=f"๊ฒ์ฆ {key}: {'โ
ํต๊ณผ' if passed else 'โ ์คํจ'}")
)
for key, passed in results.items()
]
+ [None]
)
self.Content = layout
class ValidationDialog(ef.Form):
def __init__(
self,
):
super(ValidationDialog, self).__init__()
self.objects = None
self.Title = "์ํํธ ๋๋ฉด ๊ฒ์ฆ"
self.Width = 400
self.Height = 800
self.Padding = Padding(10)
self.Topmost = True
# --- ์์ ฏ๋ค ---
self.name_input = TextBox()
self.upload_button = make_button("Upload")
self.result_labels = {
"A": make_label("๊ฒ์ฆ A: ๋๊ธฐ ์ค"),
"B": make_label("๊ฒ์ฆ B: ๋๊ธฐ ์ค"),
"C": make_label("๊ฒ์ฆ C: ๋๊ธฐ ์ค"),
"D": make_label("๊ฒ์ฆ D: ๋๊ธฐ ์ค"),
}
self.upload_button.Click += self.on_upload_clicked
# --- ๋ ์ด์์ ---
layout = TableLayout(
rows=[
TableRow(
TableCell(make_label("์ํํธ ์ด๋ฆ")),
TableCell(self.name_input),
),
TableRow(None, TableCell(self.upload_button)),
TableRow(TableCell(self.result_labels["A"])),
TableRow(TableCell(self.result_labels["B"])),
TableRow(TableCell(self.result_labels["C"])),
TableRow(TableCell(self.result_labels["D"])),
None, # ๋์ ์ฌ์ ๊ณต๊ฐ
]
)
self.Content = layout
def on_upload_clicked(self, sender, e):
try:
# apartment_name = self.name_input.Text
# ์ํํธ๋ก ๋ง๋ค RhinoObject๋ค์ ์ ํํ๋๋ก ํจ.
# objs = sc.doc.Objects.GetSelectedObjects(True, False)
objs = "test"
# # ์ ํ๋ ์ค๋ธ์ ํธ๋ค
# self.objects = objsCV
# raise Exception("this is raise test")
self.name_input.Text = self.rs_cmd()
# self.Invalidate()
except Exception as ex:
print("Error!" + ex)
def get_result_message(self, result_dict):
result_message = ""
result = result_dict.get("result")
message = result_dict.get("message")
if result:
result_message += f"๊ฒ์ฆ ๊ฒฐ๊ณผ: โ
ํต๊ณผ\n"
else:
result_message += f"๊ฒ์ฆ ๊ฒฐ๊ณผ: โ ์คํจ :{message}\n"
return result_message
def rs_cmd(self):
obj = rs.SelectedObjects()
ro = rs.ObjectName(obj)
return ro
print("Starting ValidationForm...")
dialog = ValidationDialog()
dialog.Show()