Simply dividing by a number representing the previous scale wont do since the component could be scaled differently in different axes.
What is needed is to extract and remove the scaling from a transformation matrix, which is a bit more complicated than the previous code example as the scaling, rotation and shearing are all baked into one object.
However I’ve been working for some time on a library that among other things help developers with transformations. The library isn’t finished yet but this can be seen as a sneak preview of what is to come.
module TransformationHelper
# Create transformation from origin point and axes vectors.
#
# Unlike native +Geom::Transformation.axes+ this method does not make the axes
# orthogonal or normalize them but uses them as they are, allowing for scaled
# and sheared transformations.
#
# @param origin [Geom::Point3d]
# @param xaxis [Geom::Vector3d]
# @param yaxis [Geom::Vector3d]
# @param zaxis [Geom::Vector3d]
#
# @example
# # Skew Selected Group/Component
# # Select a group or component and run:
# e = Sketchup.active_model.selection.first
# e.transformation = SUCommunityLib::LGeom::LTransformation.create_from_axes(
# ORIGIN,
# Geom::Vector3d.new(2, 0.3, 0.3),
# Geom::Vector3d.new(0.3, 2, 0.3),
# Geom::Vector3d.new(0.3, 0.3, 2)
# )
#
# @raise [ArgumentError] if any of the provided axes are parallel.
# @raise [ArgumentError] if any of the vectors are zero length.
#
# @return [Geom::Transformation]
def self.create_from_axes(origin = ORIGIN, xaxis = ZAXIS, yaxis = YAXIS, zaxis = XAXIS)
unless [xaxis, yaxis, zaxis].all?(&:valid?)
raise ArgumentError, "Axes must not be zero length."
end
if xaxis.parallel?(yaxis) || yaxis.parallel?(zaxis) || zaxis.parallel?(xaxis)
raise ArgumentError, "Axes must not be parallel."
end
Geom::Transformation.new([
xaxis.x, xaxis.y, xaxis.z, 0,
yaxis.x, yaxis.y, yaxis.z, 0,
zaxis.x, zaxis.y, zaxis.z, 0,
origin.x, origin.y, origin.z, 1
])
end
# Calculate determinant of 3X3 matrix.
#
# @param transformation [Geom::Transformation]
#
# @return [Float]
def self.determinant(transformation)
xaxis(transformation) % (yaxis(transformation) * zaxis(transformation))
end
# Test if transformation is flipped (mirrored).
#
# @param transformation [Geom::Transformation]
#
# @return [Boolean]
def self.flipped?(transformation)
determinant(transformation) < 0
end
# Return new transformation with scaling removed.
#
# All axes of the new transformation have the length 1, meaning that if the
# transformation was sheared it will still scale volumes, areas and length not
# parallel to coordinate axes.
#
# If transformation is flipped, the X axis is reversed. Otherwise axes keeps
# their direction.
#
# @param transformation [Geom::Transformation]
#
# @example
# # Mimic Context Menu > Reset Scale
# # Note that native Reset Scale also resets skew, not just scale.
# # Select a skewed group or component and run:
# e = Sketchup.active_model.selection.first
# e.transformation = SUCommunityLib::LGeom::LTransformation.reset_scaling(
# SUCommunityLib::LGeom::LTransformation.reset_shearing(e.transformation, false)
# )
#
# @return [Geom::Transformation]
def self.reset_scaling(transformation)
x_axis = xaxis(transformation).normalize
x_axis.reverse! if flipped?(transformation)
create_from_axes(
transformation.origin,
x_axis,
yaxis(transformation).normalize,
zaxis(transformation).normalize
)
end
# Get the X axis vector of a transformation.
#
#
# Unlike native +Transformation#xaxis+ the length of this axis isn't normalized
# but resamples the scaling along the axis.
#
# @param transformation [Geom::Transformation]
#
# @return [Geom::Vector3d]
def self.xaxis(transformation)
Geom::Vector3d.new(transformation.to_a.values_at(0..2))
end
# Get the Y axis vector of a transformation.
#
#
# Unlike native +Transformation#yaxis+ the length of this axis isn't normalized
# but resamples the scaling along the axis.
#
# @param transformation [Geom::Transformation]
#
# @return [Geom::Vector3d]
def self.yaxis(transformation)
Geom::Vector3d.new(transformation.to_a.values_at(4..6))
end
# Get the Z axis vector of a transformation.
#
# Unlike native +Transformation#zaxis+ the length of this axis isn't normalized
# but resamples the scaling along the axis.
# @param transformation [Geom::Transformation]
#
# @return [Geom::Vector3d]
def self.zaxis(transformation)
Geom::Vector3d.new(transformation.to_a.values_at(8..10))
end
end
With this module available you could use the following code to set the scale to a specific value:
scale_transform = Geom::Transformation.scaling(value)
entity.transformation = TransformationHelper.reset_scaling(e.transformation) * scale_transform
However scaling will be around the component axes, not bounding box center. Even if combined with my previous code that scales around another point the reset_scaling method scales around the origin.
Anyhow, what is the reason you want to scale around the bounding box center anyway. I’ve found that scaling around axes, along with positioning the axes wisely in the first place, often gives better results. If you for instance scale a chair you don’t want it to intersect the floor or float in mid air. If you place the axes on the bottom center of the chair you can scale it around its axes and it will stay in a reasonable place in space.
Some more trivia:
If you want to get a number representing the overall scale factor of the transformation, you can use the determinant method provided above. This is the scale factor for the component volume. If the transformation is uniform the length scale factor for each axis is the cubic root of this number. However it is safest not to assume the scaling is uniform.