"Scale Definition" via Ruby

There is no direct API access to this functionality. As often the API contains the low level geometry manipulation capabilities, but high level functionality may need to be recreated.

To start with, what does Scale Definition do? It changes the transformation matrix of an instance to not include any scaling, and instead apply that scaling to the geometry inside of definition that instance uses. It also updates the transformation for all other instances of the same component, to have the geometry remain in its same global position for those too.

We can try writing a simple snippet that resets the transformation matrix of the selected group/component to the identity matrix, i.e. line up its local axes and origin with those of the model, and instead bake the pre-existing transformation into the geometry itself.

# Assuming the selection is either a Group or ComponentInstance.
instance = Sketchup.active_model.selection.first
transformation = instance.transformation

# "Remove" this transformation from all instances.
instance.definition.instances.each do |i|
  i.transformation *= transformation.inverse
end

# Instead apply the transformation to the geometry inside the definition.
entities = instance.definition.entities
entities.transform_entities(transformation, entities.to_a)

Now we however lost the rotation of the instance. The blue bounding box lines up with the model axes, not the object. Since we had a mixture of scaling and rotation on the various instances, we even ended up with some having sheared axes! We probably just want to change the scaling, not the rotation/alignment.


instance = Sketchup.active_model.selection.first
old_transformation = instance.transformation

# We now create a new transformation that uses the origin and axes directions,
# but normalize them to unit vectors (removes the scaling).
new_transformation = Geom::Transformation.axes(
  old_transformation.origin,
  old_transformation.xaxis.normalize,
  old_transformation.yaxis.normalize,
  old_transformation.zaxis.normalize
)
# Represents the difference between the old and the new desired transformation.
transformation = new_transformation.inverse * old_transformation

instance.definition.instances.each do |i|
  i.transformation *= transformation.inverse
end

entities = instance.definition.entities
entities.transform_entities(transformation, entities.to_a)

By creating a new desired transformation, using the existing origin and axes, but removing the scaling, we can get around this. This snippet should closely mimic the native Scale Definition. Some cleaning up is needed though to wrap it into a method, pass the instance as a parameter rather than relying on the selection, and wrap everything in a Model#start_operation / Model#commit_operation so it shows up as a single entry in the undo stack.

However, all this said about how to moving the scaling from the instance onto the definition, if possible it’s good to not make model changes as a part of an export. Generally users don’t expect an export to be manipulating their model, just passively reading it. If any of this logic can be moved to the exporter code itself rather than running prior to the export, that could make the code cleaner and easier to understand, while eliminating a potential side effect for the end user.

If that cannot be done, you can call Model#start_operation before starting making the manipulations, and Model#abort_operation to revert the changes after having exported. This is not ideal as it flushes the redo stack, but may be used as a workaround when dealing with an exporter that cannot be modified.

3 Likes