Instance modification detection

I don’t seem to be asking the right questions with #equals? or transformation to determine if a component instance has been scaled (e.g. mirrored).

What should I be asking to determine if a component has been flipped or scaled from the original definition?

I don’t think there’s a simple answer to this. There’s no direct solution to this in the Ruby API.
The .equals? method compares instances geometrically. It doesn’t say anything about scaling.
However, if you examine (compare) the transformation matrices of instances you can figure out the scaling.
The Transformation#to_a method retrieves a 16 element array which contains the values that define the transformation.

It won’t be easy… e.g. if the instance also rotated.

For start: you can check this Extension:
https://extensions.sketchup.com/extension/1356b8f5-54c3-4df9-a6ed-28466ff41ede/transformation-inspector
and:
SketchUcation Forum Thread

1 Like

Thank you that is very useful. I need to go spend some time on this.

I have given up on this for now. There may be some simple logic associated with the matrix values and comparison checks, but it’s above my pay grade. The combination of rotation and scaling leaves me with too many uncertainties. I might come back to this when I better understand Ruby and I now what I want to achieve e.g. making modified components unique or just issuing an alert.

1 Like

Anyway, I found a snippet that I saved a good few years ago… originally I think it was made by @thomthom : on https://community.sketchucation.com/
This can tell you if selected instance is flipped or not by any its axis…

module TransformationHelper

  def flipped_x?
    dot_x, dot_y, dot_z = axes_dot_products()
    dot_x < 0 && flipped?(dot_x, dot_y, dot_z)
  end

  def flipped_y?
    dot_x, dot_y, dot_z = axes_dot_products()
    dot_y < 0 && flipped?(dot_x, dot_y, dot_z)
  end

  def flipped_z?
    dot_x, dot_y, dot_z = axes_dot_products()
    dot_z < 0 && flipped?(dot_x, dot_y, dot_z)
  end

  private

  def axes_dot_products
    [
      xaxis.dot(X_AXIS),
      yaxis.dot(Y_AXIS),
      zaxis.dot(Z_AXIS)
    ]
  end

  def flipped?(dot_x, dot_y, dot_z)
    dot_x * dot_y * dot_z < 0
  end

end


module TestFlip

  def self.inspect_flipped
    tr = Sketchup.active_model.selection[0].transformation
    tr.extend(TransformationHelper)

    p tr.flipped_x?
    p tr.flipped_y?
    p tr.flipped_z?
  end

end

TestFlip::inspect_flipped
1 Like

Interesting find. It sheds a bit more light on the issue, maybe it isn’t as complex as I was thinking…

BTW, the EntityObserver#onChangeEntity callback will fire for a watched object when scaled.

In your observer instance, when you first attach it to an object (group or component instance) you can store the original transform (for later comparison) in an instance variable or in an attribute dictionary attached to the object.

I think the following code snippet to detect object scaling is mathematically sound.

And a model to test the code with
Check Scaling.skp (101.6 KB)

# When a Group or Component is created the transformation matrix is 
# initialized with unit vectors for the x, y, and z axes as 
# represented by the following pseudo code.
#
# tr = Geom::Transformation.new(
#   [1, 0, 0],
#   [0, 1, 0],
#   [0, 0, 1],
#   model.selection.bounds.corner(0),
# )
# 
# As an object is scaled the matrix is adjusted to represent a 3D
# space where these axes are lengthened or shortened appropriately.
#
# We can detect this scaling in the matrix by createing an inverse
# transformation from the unit vector axes returned by the xaxis,
# yaxis, and zaxis methods of the transformation. Then multiply the
# two transforations, and compare the result to the IDENTITY matrix.
#
# Caveat: With this method we can not detect if the object has been
# scaled by exactly -1.0 in any direction. For those cases we must
# use the TransformationHelper developed by thomthom
#

model = Sketchup.active_model
ents = model.active_entities

# Grab the first entity in entities
tr_target = ents[0].transformation

puts
puts "Entity Transformation"
tr_target.to_a.each_slice(4) {|slice| p slice}


# Create a transformation that converts entity back to global space
tr_inverse = Geom::Transformation.axes(
  tr_target.to_a[12..14],
  tr_target.xaxis,
  tr_target.yaxis,
  tr_target.zaxis
).inverse

puts
puts "Inverse Transformation"
tr_inverse.to_a.each_slice(4) {|slice| p slice}


# un-rotate the target transformation
tr_result = tr_target * tr_inverse

# Round off accumulated floating point error
tr_result_arr = tr_result.to_a.collect {|v| v.round(12)}

puts
puts "Result Transformation"
tr_result_arr.each_slice(4) {|slice| p slice}
puts

# Compare the resulting transformation to the 
# IDENTITY transformation
# See Note: https://ruby.sketchup.com/Geom/Transformation.html#identity%3F-instance_method
if tr_result_arr == IDENTITY.to_a
  puts "The entity has NOT been scaled"
else
  puts "The entity has been scaled"
end



2 Likes