"Scale Definition" via Ruby

Hello all,

I am currently working on a script to “prepare” models for export. There are many components of different lengths in the models.
Unfortunately, there are problems when exporting the 3D models and importing them into other systems (all components then have the same length).
I can help myself manually by selecting the relevant components and executing the function “Scale definition” from the context menu, then the export and import works as desired.
Unfortunately, the models are sometimes larger and I would like to write a suitable script. The only thing I don’t have is a way to call the named function “Scale definition” in Ruby or to implement it myself.
Maybe one of you can help me.
Thank you,
Frank

You could show us your current code but heres how I call methods:

module Author
 module Test

  def self.scale_definition(text)
    puts text.to_s
  end

  self.scale_definition("Hello world")

 end
end

output >> Hello world

hope it helps, cya

2º Option:

module Author
 module Test

  def scale_definition(text)
    puts text.to_s
  end

  Test.scale_definition("Hello world")

 end
end

output >> Hello world

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.

2 Likes

thank you for the detailed and well-founded answer - I think this gives me a possible approach to a solution

An option might be to save out a copy of the model, open the copy and do these modifications on this copy, and afterward reopen the original model.

I have a “Scale Group Definition” that you can use as reference: scale-group-definition/core.rb at 30c19da82371f5dcaf95ef6e66098368da265a6a · thomthom/scale-group-definition · GitHub

4 Likes