How to make components origin attached to the component instead of the model origin

I’m working on a tool to draw cubes and turn them into components. Once I add the component instance, if I go into the model and right click my component and “Change Axes”, I see that the components axes are attached to the models origin instead of the component itself.

I’ve tested the sample hellocube example and turned it’s result into a component just to ensure it wasn’t some other bug in my custom tool.

group = model.active_entities.add_group
entities = group.entities
points = [
    Geom::Point3d.new(1, 1, 1),
    Geom::Point3d.new(2, 1, 1),
    Geom::Point3d.new(2, 2, 1),
    Geom::Point3d.new(1, 2, 1)
]
face = entities.add_face(points)
face.pushpull(-1)
group.to_component

My goal is to have the origin set at the first point, and Z axis in the chosen direction. My cube tool can be drawn at any angle, so I want to set the Z axis to be set to the vector I pushpulled my cube. I already have my vector, I just don’t now how to force my components origin to be where the component was initially created and set the Z axis to my vector.

Screenshot showing the components axis are attached to the models origin.

This statement adds an empty instance of a new group definition at the the ORIGIN.

This statement is a shorthand wrapper for:

entities = group.definition.entities

These statements …

… add a face using the group definition’s local coordinate system.

So it is you that are drawing the cube offset from the group’s local origin.

Also leverage mapping methods …

points = [
  [0,0,0], [1,0,0], [1,1,0], [0,1,0] 
].map(&Geom::Point3d.method(:new))

You draw it normally as above within it’s own local coordinate system, and afterward you apply a transformation to instances.

See:

Geom::Transformation class and Entities#add_instance() method.

This is frivolous to go through a group phase.

Instead, just create a new component definition and draw geometry into it’s entities collection. Then afterward, place an instance using a transformation.

See DefinitionList#add()

model = Sketchup.active_model

points = [
  [0,0,0], [1,0,0], [1,1,0], [0,1,0] 
].map(&Geom::Point3d.method(:new))

comp = model.definitions.add("MyCube")
ents = comp.entities
face = ents.add_face(points)
face.pushpull(-1) if face

inst = model.active_entities.add_instance(comp, [1,1,1])

The frivolity comes from the fact that if you continue the group pattern, you’ll have to transform the instance away from the model ORIGIN anyway.

ADD: I also showed an API “Easter Egg” where the #add_instance method accepts a coordinate array as a point for a translational transformation. (This is not well documented.)

2 Likes

@DanRathbun On a side note, is this the reason why in the normal UI, resetting the axis in the context of an object will place it on the world axis?
axis

Resetting the axes has no effect whatsoever on group’s local axes and origin nor on component’s local axes and origin.

Only the ‘Axes’ tool can be used to relocate their local axes system.
(With components you get an extra question to make sure you do agree with the changes)

(Or with components you also have the more direct option in the ‘Right click’ context menu: ‘Change Axes’ to do so.)

@MikeWayzovski, I bet you already know all this.

1 Like

Thanks for the mapping tip.

I only used to_component to quickly showcase my problem. I’m not sure your solution exactly works though. I’m not statically drawing a cube so I can’t just fallback to starting the cube at 0,0,0 then translate it.

Here is my tools create_cube method after the user selects their 4 points

comp = Sketchup.active_model.definitions.add("TEST")
face = comp.entities.add_face @pts
face.reverse! unless face.normal.samedirection?(@lenvec)
face.pushpull(@length)

trans = Geom::Transformation.new
instance = Sketchup.active_model.active_entities.add_instance(comp, trans);

I’m aware my trans = Geom::Transformation.new line is placing my axes at the origin. But this leaves the component instance in the correct position the user drew it at.

I’ve tried the below code. This one positions and rotates my axes correctly in the direction I pushpulled, but it moves and rotates my component instance from it’s original drawn position.

trans = Geom::Transformation.new(@pts[0], face.normal.reverse)
instance = Sketchup.active_model.active_entities.add_instance(comp, trans);

screencast-nimbus-capture-2021.11.05-09_16_27

How do I get my cube to stay where it was drawn and my axes in the correct position like the video above? I’m sure I’m overlooking something simple or some other simple way to achieve this. Thanks for your help.

If I understand what you wrote, the user is inputting corner points in some manner and you want to make a cube at those locations? If that is correct, I think the issue is that the user inputs will be in the model’s global coordinates whereas you need to add geometry in the definition’s abstract local coordinates. You need to decide where the cube origin needs to be relative to the input points (e.g. at one of them, centered on a face defined by some of them, etc.) and add geometry to your component definition based on offsets from that reference point. Then place an instance at the reference point in the model using a transformation to that point.

Yes you described it correctly. If you watch my gif, the user clicks 4 points to create the cube. I can get the cube to be drawn in the correct position, but then the origins are wrong OR I can get the origins correct, but the cube gets translated. That gif is the latter (correct origin placement, incorrect cube placement)

I think I understand what your are saying about the offset, but am unsure how to approach that. I’m currently attempting to choose the users first selected point as my comp origin and the pushpull direction as the Z axis in this line of code.

trans = Geom::Transformation.new(@pts[0], face.normal.reverse)

Let me know if this is what you meant

  • User clicks 4 points
  • I get the offset of all 8 cube points from the models origin
  • I then create my comp def using the offset points instead of the clicked points
  • I an add instance which will be at the model origin now instead of where the user clicked
  • Then use the translate I’m already using? Which in theory brings it back to the original drawn position?

I guess in theory I could also do my pushpull after I get the first 4 offset points.

I’ll try to test this once I’m off work.

In your second step, use offsets from a point in the model where you want the component origin to be, not from the model origin. E.g. one of the user-picked points.

To expand upon what @slbaumgartner said, you need to take your transformation’s inverse and apply it to the points in order to put them into local coordinates, then create the face.

Question: Why do you use the “face.normal.inverse” in the constructor, as opposed to just the Z_AXIS? Or, just skip the axis and just use:

trans = Geom::Transformation.new( @pts[0] )

I’m not sure what transformation you are saying I should take the inverse of.

Here is what I have so far.

comp = Sketchup.active_model.definitions.add("TEST")
                    
origvec = @pts[0].vector_to(Geom::Point3d.new(0,0,0))
# Move points to orgin
newpts = []
newpts[0] = @pts[0].offset(origvec)
newpts[1] = @pts[1].offset(origvec)
newpts[2] = @pts[2].offset(origvec)
newpts[3] = @pts[3].offset(origvec)
newpts[4] = @pts[4].offset(origvec)

face = comp.entities.add_face newpts
# @lenvec is the vector for the direction the cube was pushpulled
face.reverse! unless face.normal.samedirection?(@lenvec)
face.pushpull(@length)

trans = Geom::Transformation.new(@pts[0])
#trans = Geom::Transformation.new(@pts[0], face.normal.reverse)
instance = Sketchup.active_model.active_entities.add_instance(comp, trans)

This screenshot is “trans = Geom::Transformation.new(@pts[0])” which keeps the cube in the correct spot, but the Z_AXIS is wrong. It should be angled the direction I pushpulled.

This screenshot is trans = Geom::Transformation.new(@pts[0], face.normal.reverse) which has the Z_AXIS and origin point correct, but the transformation rotated my cube. It should be pointed the same direction as the cube in photo 1.

All I’m trying to accomplish is changing the component instance’s origin without moving the component just like “Change Axes” does.

You said you want the local Z axis in the same direction as the push pull, but you are sometimes reversing the face normal, which will affect the transformation. We would like to figure out if the face’s normal is being reversed before the face is created. Since I don’t understand the entire process, I will attempt to create the face first, and then convert the component’s entities to local coordinates:

comp = Sketchup.active_model.definitions.add("TEST")
face = comp.entities.add_face( @pts )
if face.normal.samedirection?( @lenvec ) then
  face_normal = @lenvec
else
  face_normal = @lenvec.reverse
  face.reverse!
end
face.pushpull( @length )
# The face is now defined, but not assigned to a component instance,
# lets work with it a bit more.
. . .
trans = Geom::Transformation.new( @pts[0], face_normal )
comp.entities.transform_entities( trans.inverse, comp.entities.to_a ) # Put into local coords
instance = Sketchup.active_model.active_entities.add_instance(comp, trans)

I can’t load this into Sketchup to check my logic or even the syntax, so keep us updated.

1 Like

That did the trick!!

This was the magic line.

comp.entities.transform_entities( trans.inverse, comp.entities.to_a )

I ended up tweaking it slightly to align the local X and Y too, which wasn’t required for my applicaiton but looks much neater on the final component.

trans = Geom::Transformation.new(@pts[0].vector_to(@pts[1]), @pts[0].vector_to(@pts[3]), face.normal.reverse, @pts[0])

Though I have no idea why, but the face.normal.reverse was still required. When I used face_normal from your code, the Z axis was the correct angle but backwards.