Hello,
Another Eto question from me…
I’m attempting to use the WorldToClient method to translate a Point3d location in World Space to 2D Screen Space and spawn an Eto.Form at the location of this point object in World Space.
The point object in this example is the midpoint of an arc curve but I’m trying to understand how to achieve this with any 3d point.
How can I ensure that the Eto UI location is updated in real-time so that it is always at the point3d location even as the viewport/camera moves/rotates/pans?
I’ve added a simple Rhino file and Grasshopper script for a prototype mockup.
Note how the text dot is always at the mid point of the door swing arc. (I realize this uses different location code under the hood, it’s just a visual example of where I want the UI to be)
I would like to replace the text dot with am Eto.Form UI that I can “do things with” but have this form stay visually locked to the original location point as the camera moves. The form would be closed after the object is no longer selected or an action that triggers a close is performed.
Thank you all for your help!
Nothing Selected:
Door “Guiding Point” Selected (Note that text dot appears at arc midpoint):
Eto.Form at object location:
Eto.Form after viewport moved (still at the original location, not the current location of the object):
import Rhino
import scriptcontext as sc
import System
import Eto.Forms as forms
import Eto.Drawing as drawing
sc.doc = Rhino.RhinoDoc.ActiveDoc
# Global variable to hold the form instance
form_instance = None
def GetScreenLocation(Location):
# Convert world coordinates to screen coordinates
view = Rhino.UI.RhinoEtoApp.MainWindow.Screen.WorkingArea
screen_location = view.ActiveViewport.WorldToClient(Location)
return screen_location
class SampleTransparentEtoForm(forms.Form):
def __init__(self):
self.WindowStyle = forms.WindowStyle.None
self.Styles.Add[forms.Panel]("transparent", self.MyFormStyler)
self.Style = "transparent"
self.AutoSize = False
self.Resizable = True
self.TopMost = True
self.ShowActivated = False
self.Size = drawing.Size(50, 70) # Adjust the size as needed
self.Padding = drawing.Padding(10)
self.MovableByWindowBackground = False
# Convert world coordinates to screen coordinates
view = scriptcontext.doc.Views.ActiveView
screen_location = view.ActiveViewport.WorldToClient(Location)
self.Location = drawing.Point(screen_location.X, screen_location.Y)
self.Button = forms.Button(Text=Angle, BackgroundColor=drawing.Colors.Transparent)
self.TextBox = forms.TextBox(Text=Angle)
self.TextBox.TextChanged += self.OnTextChanged
self.TextBox.KeyDown += self.OnTextBoxKeyDown
self.TextBox.Visible = False
self.updated_angle = None
# Event handler for button click
self.Button.Click += self.OnButtonClick
self.layout = forms.DynamicLayout()
self.layout.AddRow(self.Button)
self.layout.AddRow(self.TextBox)
# Add ImageView to the layout
self.layout.AddRow(self.DrawImage()) # Call DrawImage method and add it to the layout
self.Content = self.layout
def MyFormStyler(self, control):
self.BackgroundColor = drawing.Colors.Transparent
window = control.ControlObject
if hasattr(window, "AllowsTransparency"):
window.AllowsTransparency = True
if hasattr(window, "Background"):
brush = window.Background.Clone()
brush.Opacity = 0
window.Background = brush
else:
color = window.BackgroundColor
window.BackgroundColor = color.FromRgba(0,0,0,0)
def DrawImage(self):
my_image = forms.ImageView()
my_image.Size = drawing.Size(10, 10)
# Use a raw string for the file path or replace \ with \\
my_image.Image = drawing.Bitmap(r"user set image path")
return my_image
def OnTextChanged(self, sender, e):
# Update button text with the new value from the text box
self.updated_angle = self.TextBox.Text
self.Button.Text = self.TextBox.Text
# Optionally, perform other actions based on the new text value
print("Text changed:", self.TextBox.Text)
def OnTextBoxKeyDown(self, sender, e):
if e.Key == forms.Keys.Enter:
# Update angle and hide text box when Enter key is pressed
self.updated_angle = self.TextBox.Text
self.TextBox.Visible = False
self.Button.Text = self.TextBox.Text
print("Angle updated:", self.updated_angle)
def OnButtonClick(self, sender, e):
# Show the text box when the button is clicked
self.TextBox.Visible = True
def DoSomething():
global form_instance
# Check if form instance exists and clean it up
if form_instance:
form_instance.Cleanup()
# Create new instance of the form
form_instance = SampleTransparentEtoForm()
form_instance.Owner = Rhino.UI.RhinoEtoApp.MainWindow
form_instance.Show()
if __name__=="__main__":
DoSomething()
sc.doc = ghdoc
-Do I just need to enable a timer function to continually update the location?
-Can I subscribe to a Viewport Update/Changed event for this?
-How do I prevent the form form from continually spawning a duplicate of itself every time the location updates?
20240317_Door_Angle_UI_Test_Environment.3dm (285.0 KB)
20240317_Door_Angle_UI_Control_Test_01a.gh (43.1 KB)