Help with a short script please

Hi All,
I’ve been using the following script written by @sdmitch. It will take any selected components in the model and lay them out on the ground plane with blue axis facing up in a nice neat little row. Very useful for CNC machining etc. However, it also resets the scale of any components it encounters.

From my reading of the Ruby docs, it appears the ‘transformation.inverse’ method is responsible for resetting the scale, among other things.

I’m wondering if there’s a modification to this script which will allow a component to retain it’s scale but still perform the same lay-them-out-in-a-row out function? I’ve tried tinkering with the script with no immediate success so I need the help of one of you Ruby geniuses!

Thanks in advance.

mod = Sketchup.active_model
ent = mod.active_entities
sel = mod.selection
org =
spc =
cdn = ent.grep(Sketchup::ComponentInstance).map { |ci| }.uniq.sort
cdn.each { |n|
  cis = ent.grep(Sketchup::ComponentInstance).each { |ci|
    next unless == n
    ci.transform! ci.transformation.inverse
    ci.transform! Geom::Transformation.translation(org - ci.transformation.origin)
    if ci.bounds.width > ci.bounds.height
      ci.transform! Geom::Transformation.translation(org - ci.bounds.corner(2))
      ci.transform! Geom::Transformation.rotation(org, Z_AXIS, 90.degrees)
    if ci.bounds.depth > ci.bounds.width && ci.bounds.depth > ci.bounds.height
      ci.transform! Geom::Transformation.rotation(org, X_AXIS, 90.degrees)
    if ci.bounds.min.y < org.y
      ci.transform! Geom::Transformation.rotation(org, X_AXIS, 180.degrees)
      ci.transform! Geom::Transformation.translation(org - ci.bounds.corner(0))
    org.offset!([ci.bounds.width + spc, 0, 0])

This might point you in the right direction (pun intended).

Some highly untested code follows.

# Get the first instance in the model
mod = Sketchup.active_model
ent = mod.active_entities[0]

# Print the transformation
tr = ent.transformation
tr.to_a.each_slice(4){ |slice| p slice }

# determine the Translation and Rotation matrix of the instance
tr_T_R = Geom::Transformation.axes(tr.origin, tr.xaxis, tr.yaxis, tr.zaxis)

# transform by the inverse matrix
#  -> this should result in a scaled instance of the object at the origin of the model.

# print the resulting transformation
tr = ent.transformation
tr.to_a.each_slice(4){ |slice| p slice }

I appreciate your input. However, It is the ‘.inverse’ operation which resets the scale of the selected component.

For example, I have a component instance whose container has been manually scaled after it was originally defined as a component, using the scale tool. Let us say this component now has a scale of -1 (essentially a mirrored component). Applying the inverse transformation method will result in a component of scale = 1. Thus, the transformed component will possess it’s original scale.

I’m wanting to retain the scale factor of -1 (for instance) while changing rotation and position of said component - to orientate it with its blue axis aligned with the global blue axis… And in the case of many components, lay them out in a row.

The original script I posted does a pretty good job of this - except it resets the scale of any component in the process.

Hope I have clarified.

The posted script acts upon ALL the instances in the model’s active entities.
It does not look like it greps the instances from the selection set.

Do you have a lightweight example model to test with ? Please add to the initial post.

1 Like

Attached is an example file that contains one group which has been scaled in the X, Y, and Z directions by factors of -1, -2, and -3 respectively. And below that is the code to extract those constants from the transformation matrix of the group.

TRS matrix example.skp (104.2 KB)

# Get the first instance in the model
mod = Sketchup.active_model
ent = mod.active_entities[0]

# Print the transformation
puts "Initial Transformation:"
tr = ent.transformation
tr.to_a.each_slice(4){ |slice| p{ |value| value.round(4)}.join(" ") }

puts "\nGroup's Axes:"
p tr.xaxis
p tr.yaxis
p tr.zaxis

axis_x = tr.xaxis[0] >= 0.0 ? tr.xaxis : tr.xaxis.reverse!
axis_y = tr.yaxis[1] >= 0.0 ? tr.yaxis : tr.yaxis.reverse!
axis_z = tr.zaxis[2] >= 0.0 ? tr.zaxis : tr.zaxis.reverse!

# determine the Rotation * Translation matrix of the instance
tr_T_R = Geom::Transformation.axes(tr.origin, axis_x, axis_y, axis_z)

# print the transformation
puts "\nResulting Transformation:" 
tr = ent.transformation
tr.to_a.each_slice(4){ |slice| p{ |value| value.round(4)}.join(" ") }

puts "X scale = #{tr.to_a[0].round(4)}"
puts "Y scale = #{tr.to_a[5].round(4)}"
puts "Z scale = #{tr.to_a[10].round(4)}"

I wrote an example extension to do this, so there will not be a need to cut and paste large blocks of code into the console.

The old code is still there wrapped up in method “old_way()” for nostalgia, but the extension does not use it.

I wrote a completely new method to move the selected instances that does not use transformation.inverse.

In addition:

  • It remembers the last used spacing allowing it to be set via an inputbox.
  • The changes are wrapped up in an undo operation.
  • The command appears on the Extensions menu and on the
    right-click context menu if instances are selected.

See: [Example] Lay a set of Selected Instances In a Row Along X axis

Hi DanRathbun, you’ve really gone the extra mile here in wrapping the script into an extension and so I thank you very much for your effort already. Only problem is that your extension also resets the scale of a component too! Best way to illustrate this is with a video…

1 Like

Well, it is version 1.0.0. (I’ve made a note of this known issue in the example post.)

I did ask for a test model. I created one but I did not test a negative scale only positive scales in one or more axis. So next version would be to handle negative axis scaling.

Yes, sorry I’ve been away from my desk all day. I was going to post a test model but you beat me to it. Awaiting V2 with bated breath. :slightly_smiling_face:

I’m off to bed. It will need to wait until tomorrow.

1 Like

Here’s a longer video showing the issue in more detail. In the video, the two parts I move to the foreground are opposing. That is, the right side part is simply a mirrored instance of the left. When I run the original script, or yours, I’m left with two ‘unscaled’ parts. That is, the right side part has lost its -1 scaling.

Hopefully this will illustrate why a working script is a major time saver for CNC machining. Once the parts are laid out flat, the final step is to export a 3D DXF and then clean up the layering in the CAM software.

Attached is a model containing two pairs of mirrored parts.

Mirrored_Parts.skp (1.8 MB)

Yes, I forgot to take this into account.

Also I see that the parts seem to be arranged with their short side along the X axis. I did not do this either. I did see some comparison of bounds width vs height in the original script but did not understand what it was for.

Is this “short side” reorientation needed ?

Hi again Dan, Doesn’t matter about the x/y orientation because the CAM software can rotate the parts when I nest the parts on the sheet.

In summary, All parts arranged long side to green axis would be nice but not absolutely necessary.

Thanks agains.

Okay I updated the example to v1.1.0. Please test.

Hi Dan,
Version 1.1.0 seems to perform a complete disappearing act! :thinking: Video attached…

Okay, I’m now testing with your example model.

What I find is that the parts are not really mirrored per the transformation, (ie, one negatively scaled from the other.) Instead they show as rotated opposite each other about the Z axis. (Both have a positive 90 deg X rotation. One has a positive 90 deg Z rotation, the other -90 Z rotation.)

More work will be required.