…the rs.Add… methods returned None if the method was unable to add a valid object to the document instead of simply failing and erroring out the script? That way you could at least error check the return instead of having to analyze all the input arguments first to see if they will allow a valid result.
This applies to AddLine, AddCircle, AddEllipse, AddArc, etc.
Please note also that the equivalent VB Rhinoscript methods all simply return Null if the function is unable to work with the input, and do not error out the script. @dale
For example, trying to add a zero-length line:
import rhinoscriptsyntax as rs
test=rs.AddLine([0,0,0],[0,0,0])
>>>Message: Unable to add line to document (entire script fails at that point)
vs
Option Explicit
Call Main()
Sub Main()
Dim test
test = Rhino.AddLine(Array(0, 0, 0), Array(0, 0, 0))
If IsNull(test) Then
Call Rhino.Print("It's Null")
End If
End Sub
I wouldn’t propose to use null , it requires null checking all the time and usually just shifts the problem to another location in your script if you are not doing null checking. I wouldn’t also throw on a performance critical method, since catching errors (if they occur) is costly.
Why not just returning a bool instead?
if not rs.AddLine(ptA,ptB):
log.Error()
Edit: I guess its returning a GUID if successful, so maybe returning a tuple or return-struct instead (owning the flag and the guid).
Yes +1 for catching exceptions as the recommended pythonic way to do this, and it is a very good idea to catch the specific exception you are expecting. Catching any and all errors in a try...except block is a good way to waste a day debugging.
Returning None is fine for a small simple one-job script but as soon as you assign the returned value to a variable for use later you have trouble working out why a later portion of your script is behaving oddly. It is usually much better to handle the specific exception and then move on.
rs.AddLine() seems to only raise generic Exceptions, so the most useful try ... except block you can have around it is:
import rhinoscriptsyntax as rs
try:
test=rs.AddLine([0,0,0],[0,0,0])
except Exception as e:
# Or whatever else you want to do with that exception,
# including raising a more precise one or stopping the script execution
print(e)
A great way to determine if things are going to work is to call the geometry’s IsValid or IsValidWithLog member. IsValidWithLog can be really helpful because it often returns details about why the geometry is bad.
For example:
import System
import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc
def TestAddLine(start, end, raise_on_error=False):
start = rs.coerce3dpoint(start, True)
end = rs.coerce3dpoint(end, True)
curve = Rhino.Geometry.LineCurve(start, end)
rc, log = curve.IsValidWithLog()
if not rc:
if raise_on_error:
if log:
raise Exception(log)
else:
raise Exception("Line is invalid")
else:
return System.Guid.Empty
id = sc.doc.Objects.AddCurve(curve)
if id == System.Guid.Empty:
if raise_on_error:
raise Exception("Unable to add line to document")
else:
return System.Guid.Empty
sc.doc.Views.Redraw()
return id
print TestAddLine([0,0,0],[0,0,0])
print TestAddLine([0,0,0],[0,0,0], True)
I’ve log a wish for some enhancements to rhinoscriptsyntax.
My main purpose in reporting this is to first maintain behavioral consistency between python rhinoscriptsyntax and VB Rhinoscript, and secondly to make things as simple as possible for beginning people who use rhinoscriptsyntax.
So yes, having to try and catch exceptions is not good - and not really good practice IMO except in ‘exceptional’ situations - and I maintain that despite some of the above comments, checking the return for None is one of the simplest methods of error trapping, as it only requires one line of code instead of having to verify that all the input arguments - which might separately all be totally valid - can produce a valid output of the method. More advanced people can always roll their own ‘augmented’ functions.
I believe the opposite is true. If you would not expect this particular functionality to be called thousands of times and to potentially fail hundred of times, throwing exceptions is always superior! In such case an return code/flag is always better than just null.
I would recommend any developer to use None/Null behaviour with extreme caution. If you allow None/Null states in your code in theory you would need to do null checks on almost any routine in your script/app. Never seen much nullchecks in most scripts here. And as I said, if something turns to out to be null it may occur at a complete different spot on your code causing a much higher waste of time (through debugging) than writing an additional line of code.
Guess you haven’t looked at a lot of mine… First place it gets used is to check if the user actually input/chose something to be processed. Because any of the rhinoscriptsyntax GetObject() functions return None if nothing is chosen or the user escapes.
If you look at the rhinoscriptsyntax library, there are tons of functions with internal checks for None being returned - because a lot of RhinoCommon methods return None/Null if the method failed for some reason.
Just because its like this it doesn’t mean its good design. Null reference exception is the most common and one of the most annoying exceptions I get and I prevent using it as much as possible. I mean if you have a function like GetObject or AddLine people might expect it to be null. Not every functionality is obvious to return null. See (simplified Pseudocode):
def Copy(geom):
if (geom is GeometryBase):
return curve(geom).Duplicate()
return None
def Export(geom, path):
if (geom is not None): # bad designed does not throw on None input, just does nothing
Serialize(geom, path)
def Import(path):
return Deserialize(path) # bad designed returns None on failure
def Main():
geomID = GetObject()
if (geomID is not None):
copy = Copy(geomID) # this is wrong -> Adds id instead of geometrical object
Export(copy, path)
geom = Import(path)
AddObject(geom) # It did nothing because its returning None
-> Nothing showed up. Because ‘Copy’ did not throw, nor did ‘AddObject’. Without my comments it would be difficult to find the right place where it became None at first. Do you see my point? To fix this but keeping None return values, each method would need a parameter Null/None check. Its not obvious the Copy method can return None.
Null checking: Many casual coders don’t do this. You are more experienced but most Rhino scripters are not.
Yes, but I think you more or less missed mine entirely…
Python rhinoscriptsyntax, like its counterpart VB Rhinoscript, is supposed to be a simple way to start automating Rhino via scripting and as such destined for use by both programmers and non-programmers alike. It should be as simple as possible to start with ; the same methods using the two language platforms should also be as parallel as possible as concerns input arguments and returns.
You are looking at this through the narrow, coder’s end of the binoculars, I am attempting to look at it through the wider, more casual user-oriented end, for people who do not necessarily consider themselves programmers nor want to become one, and whose goals are to make workable tools as easily as possible, even if it means that they do not have 100% of the rigor of ‘correct’ coding principles.
I am certainly not opposed to have optional ‘raise on error’ arguments for those who want their scripts to error out with some kind of exception message instead of checking for Null/None here and there, but I feel that should not be imposed on all users.
Actually I’m not so sure if you got my point. Especially for casual coders debugging should be straight forward telling them what went wrong at the right place. Furthermore I don’t see why the evaluation of an return code or catching an exception should be more difficult. But this ends up to be the same religious discussion when people say Python is so easy, fighting with Runtime errors all the time. Everything has its advantages and disadvantages. Thats also why I won’t loop this discussion endlessly .