It is worth noting that in Python, camel case usually isn’t used for naming methods/functions, like PlaceCircles( ) or CircleDist( ). Instead, the convention is to use underscores and lower case notation (e.g. place_circles(), circle_dist()).
The use of camel case is mostly reserved for classes (i.e. MyClass).
Variable names follow the same conventions as function names.
Here’s a version of your script that works with multiple curve inputs and handles the remapping of your desired distances from the end points to the curve domains:
import rhinoscriptsyntax as rs
def fit(value, source_domain, target_domain):
"""Fits a number between a target domain that is relative to a number in the source domain.
value: number to be fitted
source_domain: tuple containing the domain start and end
target_domain: tuple containing the domain start and end
The refitted value, or None, if the initial value is outside the source domain.
if (value < source_domain) or (value > source_domain):
source_range = source_domain - source_domain
if source_range == 0:
fitted_value = target_domain
target_range = target_domain - target_domain
fitted_value = (((value - source_domain) * target_range) / source_range) + target_domain
def place_circles(curve, distance):
"""Places circles at a specific distance from end points of a curve.
curve: A curve to place circles on.
distance: A distance from the end points to place circles at.
# Get the length of the curve
curve_length = rs.CurveLength(curve)
if distance > curve_length:
raise ValueError("The distance (%.2f) cannot be bigger than the curve length (%.2f)." \
elif distance < 0.0:
raise ValueError("The distance (%.2f) cannot be a negative number." %(distance))
# Get the curve domain
curve_domain = rs.CurveDomain(curve)
# Remap the desired distance to the curve domain
remapped_distance = fit(distance, (0.0, curve_length), curve_domain)
# Remap the remapped distance to the normalized curve domain from 0.0 to 1.0
normalized = fit(remapped_distance, curve_domain, (0.0, 1.0))
normalized2 = fit(remapped_distance, curve_domain, (1.0, 0.0))
# Convert the normalized curve parameter to a curve parameter within the curve domain
curve_parameter = rs.CurveParameter(curve, normalized)
curve_parameter2 = rs.CurveParameter(curve, normalized2)
# Get the curve points for each curve parameter
curve_point = rs.EvaluateCurve(curve, curve_parameter)
curve_point2 = rs.EvaluateCurve(curve, curve_parameter2)
# Create the circles
circle = rs.AddCircle(curve_point, 3)
circle2 = rs.AddCircle(curve_point2, 3)
"""Places circles at a specific distances from end points of multiple curves."""
# Get a single curve or a collection of curves
object_ids = rs.GetObjects("Select some curves", rs.filter.curve)
if not object_ids: return
cleanup =  # list for guids to cleanup at the end
for i, obj_id in enumerate(object_ids): # loop each curve
# Get the current curve colour
current_colour = rs.ObjectColor(obj_id)
# Display the currently selected curve yellow
rs.ObjectColor(obj_id, (255, 255, 0))
# Ask for the distance input for each curve individually
if len(object_ids) > 1:
distance = rs.GetReal("Selected curve %d distance from edge" %(i+1))
distance = rs.GetReal("Selected curve distance from edge")
# Return the currently selected curve to its original colour
if not distance: return
# Explode the curve
curve_segment_ids = rs.ExplodeCurves(obj_id)
# Loop each curve or curve segment and place circles
for j, curve_id in enumerate(curve_segment_ids):
# Try placing the circles
except ValueError as err:
# Prints the errors raised by place_circles()
if len(object_ids) > 1:
print "Selected curve %d, segment %d: %s" %(i+1, j+1, err)
print "Selected curve: %s" %(err)
# Cleanup superfluous curve segments from the scene
for guid in cleanup:
if __name__ == "__main__":
You can imagine the domain of a curve as the distance between its endpoints. For a line or a simple curve this is mostly a domain from 0.0 to the curve length. For a polyline, a curve defined by multiple line segments, each segment has its own domain. The first one might have the domain 0.0 to its length, the second segment follows with the length of the previous segment, as its start domain, and the length of the previous segment added to its own length as its end domain, and so fourth.
In your script, you can simply remap your distance value to the curve domain. You know that your desired distance lies within the domain 0.0 to curve length, since that is how we humans understand curves. So you need to remap the distance within that domain, to the curves actual domain, which might for example be 36.456 to 52.136.
Now, that you have your distance in the curve domain 36.456 to 52.136, you can remap it again to a normalised curve domain from 0.0 to 1.0. We need to take this extra step, since the
rs.CurveParameter(curve_id, normalized_parameter) needs a normalized parameter to work. With the resulting curve parameter you can now evaluate the curve for a point.
If you’re interested in learning more about the Python naming conventions, you can check out the very good Google Python styleguide or the already above linked, standard PEP8 styleguide.