What is the difference between a length and a float in Sketchup Ruby, if any?
Length
is sort of a subclass of Float
. At one time, Float
actually was in Length
’s ancestor list, but (for some nitty reason) the API could no longer subclass Float
directly. The doc still show Length
being a subclass of Float
, but have a note telling you it isn’t.
I think they somehow implemented all Float
instance methods on the Length
class, and overrode #is_a?
as (3.14).to_l.is_a?(Float)
returns true
.
So likely now Length
is a wrapper class. Ie, it holds a reference to a Float
object internally.
Length.ancestors
#=> [ Length, Numeric, Comparable, Object,
# JSON::Ext::Generator::GeneratorMethods::Object, Kernel, BasicObject ]
So it still is a subclass of Numeric
(and so inherits all it’s methods as well.)
Another thing about Length
is that it could have be named Inches
and made more sense.
A length represents a distance in space while a float can represent any dimension, e.g. an angle or a volume or a light level.
How they are formatted as strings is a significant pratcical difference.
"The value is #{4}."
# => "The value is 4.
"The value is #{4.m}."
# => "The value is 4000 mm." # <- The unit used here depends units in Model Info.
I don’t agree. If this wore the case people in the other 192 or so countries would be looking for a Meter or MilliMeter class or the like, and not want to use the obviously Imperial class. The great thing about unit handling in Sketchup is that you very rarely have to think about what SketchUp uses internally as unit; you can just think of it as a distance in space.
The internal unit matters mostly when converting a Numeric to a Length implicitly or with to_l, or when passing a value to a method outside the API. The former case can be easily avoided by explicitly converting the Numeric to a Length using a given unit. In the latter case I can’t think of many scenarios where you’d want to directly pass a Length to something outside the API. A typical scenario would be to pass Lengths to the trigonometric functions, but then you typically pass a division between two lengths anyway, and the unit cancels out.
# Requires knowledge of the internal unit.
# Easily avoided.
Geom::Point3d.new(1, 1, 1)
# Does not require knowledge of the internal unit.
Geom::Point3d.new(1.m, 1.m, 1.m)
# Does not require knowledge of the internal unit.
# The unit is cancelled out by the division.
Math.tan(4.m / 2.m)
The only real exception I can think of when you really need to know what unit SketchUp uses is when working with vectors, as the unit vector sometimes has a special meaning. The unit vector is 1 inch long, regardless of Model units. However, if you think of the unit vector as a special case, separate from vectors with a length taken from model space, I don’t think you have to think about units here either.
# A unit vector.
# I don't need to care how long it is in space, only that its size is 1 "whatever".
X_AXIS
# Still a unit vector.
# Can still think of the length as 1 "whatever".
X_AXIS * Y_AXIS
# Offsetting some point in the model by the vector between two other points.
# Can think of the vector as having a length in space but don't really have to
# care what unit it is expressed in.
some_point0.offset(some_point1 - some_point2)
# Find the vector that is perpendicular to the vector between two points and the
# Z axis. Multiplying with unit vector doesn't change the vector length, so I can
# still think of the vectors as some lengths in whatever unit and 1 whatever unit.
(some_point1 - some_point2) * Z_AXIS
# Offsetting a point directly by a unit vector.
# Here the physical length of the unit vector, aka the unit, does matter!
# However I don't think I've seen this case.
# Typically a vector either represents a difference between two points in the
# model, or its a unit vector, not both.
some_point0.offset(Z_AXIS * Y_AXIS)
Then there are the cases with operations. For whatever reason Sketchup doesn’t override the operand methods and make them return Lengths as they really should.
4.cm + 5.cm
# => 3.543307086614173
# I would have expected a length of 9 cm.
4.cm * 2
# => 3.149606299212598
# I would have expected a length of 8 cm.
2 * 4.cm
# => 3.149606299212598
# I would have expected a length of 8 cm.
4.cm + 1
# => 2.574803149606299
# Technically you can't sum things of different dimensions into a single value.
# A stricter environment should throw an error here but SketchUp is very forgiving.
# If this is accepted I would still expect the result to be a Length though.
However all of these are easily overcome by running to_l on the result.
It may be look noisy to convert back to Lengths all over the place, but if the methods are short and clean already it’s not a problem, as seen here.
(4.cm * 2).to_l
I still don’t really care what the internal unit is. 4.cm + 5.cm
could evaluate to “Spongebob Squarepants” for all that I care, as long as ("Spongebob Squarepants").to_f
evaluates to the length of 9 cm.
For the record, an old overview I wrote on how to deal with units in SketchUp:
Correct. Ruby doesn’t really allow making subclasses of Float
. But way back when the API was first made the Length
class was fudged to be a subclass by tweaking some implementation detail in SketchUp. This broke when we moved to 64bit builds. So we had to re-implement it as a delegate and add a bunch of shims to preserve compatibility.
That would lock its semantic to an implementation detail.