I’m updating some of my older scripts and having an issue in Python 3 related to the following lines, I have a single input value in an input “T” for “Thickness”, the input Type is an Int with List Access.
This code works fine in the old script component. Is this a Python2 → Python3 syntax thing or related to the script editor? @eirannejad
The intent is to draw curves with a custom lineweight and thickness to the foreground of the viewport.
thickness = self.t[i % len(self.t)] if self.t else None
Error:
1. Error running script: 'int' object is not subscriptable
Full Code:
from ghpythonlib.componentbase import executingcomponent as component
import Rhino
import System
__author__ = "Michael Vollrath"
__version__ = "2023.08.01"
# Made With <3 In Dallas, TX
# Modification of script originally created by Seghier Khaled
ghenv.Component.Name = "Custom Preview Lineweight"
ghenv.Component.NickName = "CPL"
ghenv.Component.Description = "Custom Preview component allowing thickness and color preview of curves"
ghenv.Component.Params.Input[0].Name = "Curve"
ghenv.Component.Params.Input[0].NickName = "C"
ghenv.Component.Params.Input[0].Description = "Input Curves"
ghenv.Component.Params.Input[1].Name = "Thickness"
ghenv.Component.Params.Input[1].NickName = "T"
ghenv.Component.Params.Input[1].Description = "Curve Thickness"
ghenv.Component.Params.Input[2].Name = "Color"
ghenv.Component.Params.Input[2].NickName = "Pc"
ghenv.Component.Params.Input[2].Description = "Preview Color"
class CurveColor(component):
def RunScript(self, C, T, Pc):
self.C = C
self.RGB = Pc
self.t = T
if not self.C:
return
def DrawViewportWires(self, arg):
if not self.C:
return
# Check if self.C is a list
if isinstance(self.C, list):
for i, curve in enumerate(self.C):
# Get the corresponding thickness and RGB values using modulo to wrap the lists
thickness = self.t[i % len(self.t)] if self.t else None
rgb = self.RGB[i % len(self.RGB)] if self.RGB else None
self.DrawCurveWithColor(arg, curve, thickness, rgb)
else:
# Single curve case
self.DrawCurveWithColor(arg, self.C, self.t[0] if self.t else None, self.RGB[0] if self.RGB else None)
def DrawCurveWithColor(self, arg, curve, thickness=None, rgb=None):
if not curve:
return
if thickness is None:
thickness = self.t
if rgb is None:
rgb = self.RGB
arg.Display.DrawCurve(curve, rgb, thickness)
# Handle Draw Foreground Events
def __exit__(self):
Rhino.Display.DisplayPipeline.DrawForeground -= self.DrawViewportWires
def __enter__(self):
Rhino.Display.DisplayPipeline.DrawForeground += self.DrawViewportWires
I’ve used a c# script for curve display in Rhino 7 but now I’m Rhino 8 I switched to the native curve display component. I like how I can set linetype styles in Rhino or with the new Grasshopper Rhino components…
I noticed that as well… when I switch between List or Item access on the script component input parameters, it defines them as System.Object Generic List which makes sense to my limited understanding of how a list is actually defined under the hood but it throws errors and complains then…
I’m confused if it’s the script component or the code.
I did as well for most all use cases, I love it. However, for this script I’m specifically after the ability to have the curves drawn to the foreground to act as a “curve highlighter” that I can set the color and color alpha on of course.
Hi @James_Parrott, T is an Int, yes. Currently it is set by right clicking the Parameter input and setting it there.
Current value is 10.
I actually don’t know. This is code Ive modified from other sources but I didn’t write or know how to write this myself off the bat so I’m not the best person to be able to answer that unfortunately.
Great. Either the input access type on the component input for T should be set to “list”, or if T really should be an Int, then the code should be rewritten to avoid trying to subscript it in self.t[...] after self.t = T (but the intention from the code very much looks like indexing into a list).
The error is probably exactly the same, as if you tried to do: 4['e']
If really needed, a patch is also possible such as: self.t = [T] if isinstance(T, int) else T.
Okay after going down the Display Conduit route I got a solution working and then realized I couldn’t figure out how to remove it dynamically and then came back around to this similar solution from you @AndersDeleuran
I adapted it and got it working how I want, thank you! Any idea how to get this working in Python3 though? I know @eirannejad removed the need for the SDK/override or whatever it was called but in the Python3 version of this it doesn’t recognize component as valid when imported as follows:
Error begin run: No module named ‘GhPython.Assemblies.ExecutingComponent’ [4:1]
I’m afraid I’ve put the new script editor on hold for now, at least till it becomes more stable and has feature parity with GHPython. Staying tuned though
Just to make sure we’re seeing the same thing: In the video I posted above, the error is only raised when the curve parameter that is input to the C parameter on the script editor component is set to preview its geometry. When I turn the preview off, the error is not thrown (but also nothing else happens). This is quite odd behaviour and not something I’ve ever seen with the GHPython component (ping @eirannejad).
Hi @eirannejad , it was auto populating this as you mention. I was getting some errors with it (perhaps not from this but I thought it was) so I manually deleted the type hinting as I was testing and got it working.
I’ll see if I can reproduce the issue I was having.
@AndersDeleuran I was able to get the code working by changing this line:
import GhPython.Assemblies.ExecutingComponent as component
To this:
import ghpythonlib.component as component
Here’s the rest of the code, now working for Python 3, not sure if this would work for similar scripts you produced? May be worth checking out though so thought I’d share.
EDIT: Updated the code below with a little bit better error handling…
Code:
import System
import Rhino
import Grasshopper as gh
import ghpythonlib.component as component # Changed to this for Python 3
__author__ = "Michael Vollrath"
__version__ = "2024.03.20"
# Made With <3 In Dallas, TX
# Modification of script originally created by Seghier Khaled
ghenv.Component.Name = "Curve Highlight"
ghenv.Component.NickName = "CH"
ghenv.Component.Description = "Custom Preview component allowing thickness and color preview of curves drawn in the foreground"
ghenv.Component.Params.Input[0].Name = "Curve"
ghenv.Component.Params.Input[0].NickName = "C"
ghenv.Component.Params.Input[0].Description = "Input Curves"
ghenv.Component.Params.Input[1].Name = "T"
ghenv.Component.Params.Input[1].NickName = "T"
ghenv.Component.Params.Input[1].Description = "Curve Thickness"
ghenv.Component.Params.Input[2].Name = "Color"
ghenv.Component.Params.Input[2].NickName = "Pc"
ghenv.Component.Params.Input[2].Description = "Preview Color"
class MyComponent(component):
def RunScript(self,
C: System.Collections.Generic.IList[Rhino.Geometry.Curve],
T: System.Collections.Generic.IList[int],
Pc: System.Collections.Generic.IList[System.Drawing.Color]):
msg_level = ghenv.Component.RuntimeMessageLevel.Warning
# Add warning message
if not C or len(C) == 0:
ghenv.Component.AddRuntimeMessage(msg_level, "Input C Failed To Collect Data")
non_null_curves = []
else:
# Filter out null curves
non_null_curves = [curve for curve in C if curve is not None]
# Set default value for thickness if T is None or empty
if not T or len(T) == 0:
T = [10.0]
# Set default value for color if Pc is None or invalid
if not Pc or any(color is None for color in Pc):
Pc = [System.Drawing.Color.OrangeRed]
# Ensure all input lists have the same length
max_length = max(len(non_null_curves), len(T), len(Pc))
# Repeat the elements in the list to match the max_length
self.cols = [Pc[i % len(Pc)] for i in range(max_length)]
self.thickness = [T[i % len(T)] for i in range(max_length)]
self.black = System.Drawing.Color.Black
self.curves = non_null_curves
def DrawForeground(self, sender, arg):
if (not ghenv.Component.Hidden and not ghenv.Component.Locked
and ghenv.Component.OnPingDocument() == gh.Instances.ActiveCanvas.Document
and arg.Viewport.Id == arg.RhinoDoc.ActiveDoc.Views.ActiveView.ActiveViewportID):
# Draw Stuff To Foreground
for i in range(len(self.curves)):
# Draw input Curve(s)
arg.Display.DrawCurve(self.curves[i], self.cols[i], self.thickness[i])
def __enter__(self):
Rhino.Display.DisplayPipeline.DrawForeground += self.DrawForeground
def __exit__(self):
Rhino.Display.DisplayPipeline.DrawForeground -= self.DrawForeground
Ah that’s good to hear, thanks for the update. I’m running a workaround about all this on a month and it’d be nice to have these methods working in Rhino 8. The behaviour I reported above is quite worrying/baffling though, so think I’ll stick with Rhino 7 as the primary platform for the workshop.
Edit: Just had a closer look at the code. It looks like it’s implementing the pattern from over here, it’s great to see that working in CPython
Yes that’s correct! Just had to change how component was defined in the import but the rest ended up working so that made me happy as this opens up all the other things we can draw now
And now the working code I shared above is giving an odd error about items can’t be null.
Error:
Error running script (ArgumentNullException): Value cannot be null. (Parameter ‘item’)
Full Code:
import System
import Rhino
import Grasshopper as gh
import ghpythonlib.component as component # Changed to this for Python 3
__author__ = "Michael Vollrath"
__version__ = "2024.03.20"
# Made With <3 In Dallas, TX
# Modification of script originally created by Seghier Khaled
ghenv.Component.Name = "Curve Highlight"
ghenv.Component.NickName = "CH"
ghenv.Component.Description = "Custom Preview component allowing thickness and color preview of curves drawn in the foreground"
ghenv.Component.Params.Input[0].Name = "Curve"
ghenv.Component.Params.Input[0].NickName = "C"
ghenv.Component.Params.Input[0].Description = "Input Curves"
ghenv.Component.Params.Input[1].Name = "T"
ghenv.Component.Params.Input[1].NickName = "T"
ghenv.Component.Params.Input[1].Description = "Curve Thickness"
ghenv.Component.Params.Input[2].Name = "Color"
ghenv.Component.Params.Input[2].NickName = "Pc"
ghenv.Component.Params.Input[2].Description = "Preview Color"
class MyComponent(component):
def RunScript(self,
C: System.Collections.Generic.IList[Rhino.Geometry.Curve],
T: System.Collections.Generic.IList[int],
Pc: System.Collections.Generic.IList[System.Drawing.Color]):
msg_level = ghenv.Component.RuntimeMessageLevel.Warning
# Add warning message
if not C or len(C) == 0:
ghenv.Component.AddRuntimeMessage(msg_level, "Input C Failed To Collect Data")
non_null_curves = []
else:
# Filter out null curves
non_null_curves = [curve for curve in C if curve is not None]
# Set default value for thickness if T is None or empty
if not T or len(T) == 0:
T = [10.0]
# Set default value for color if Pc is None or invalid
if not Pc or any(color is None for color in Pc):
Pc = [System.Drawing.Color.OrangeRed]
# Ensure all input lists have the same length
max_length = max(len(non_null_curves), len(T), len(Pc))
# Repeat the elements in the list to match the max_length
self.cols = [Pc[i % len(Pc)] for i in range(max_length)]
self.thickness = [T[i % len(T)] for i in range(max_length)]
self.black = System.Drawing.Color.Black
self.curves = non_null_curves
def DrawForeground(self, sender, arg):
if (not ghenv.Component.Hidden and not ghenv.Component.Locked
and ghenv.Component.OnPingDocument() == gh.Instances.ActiveCanvas.Document
and arg.Viewport.Id == arg.RhinoDoc.ActiveDoc.Views.ActiveView.ActiveViewportID):
# Draw Stuff To Foreground
for i in range(len(self.curves)):
# Draw input Curve(s)
arg.Display.DrawCurve(self.curves[i], self.cols[i], self.thickness[i])
def __enter__(self):
Rhino.Display.DisplayPipeline.DrawForeground += self.DrawForeground
def __exit__(self):
Rhino.Display.DisplayPipeline.DrawForeground -= self.DrawForeground