Bug in glued_to?

I know, … but can you mimic the cut and paste (in place !) process through the API?

(I know the component can stick to faces inside from being outside themself, even to three or four environments nested deep.)

I’ll definitely try that. Thanks for the suggestion.

The problem I have with that work around is the you need to open the component for editing in order to move the window. I’d like to simply select the window and move it (see gif). The component is already unique, so that isn’t an issue.

You don’t need to open the component for editing !
The API has code to add things into the definition’s entities…
Here’s a simplistic example…
Let’s assume you have already set up references to the ‘shed’ and the ‘window’ components’ instances, let’s also assume that the shed instance hasn’t been ‘transformed’ - that’s still readily doable, BUT is of course more complex…

wind = window.definition
ents = shed.definition.entities
wini = ents.add_instance(wind, ORIGIN)

You now have an instance of ‘window’ inside the ‘shed’ definition, referenced as ‘wini’.
It’s initially placed at the origin of the ‘shed’ definition.
It’s not [yet] glued.
If there’s more that one instance of the defn the window is now in all of them, and if so maybe using shed.make_unique would be appropriate…

To relocate the windows you need to use wini.transform!(trans)
Where ‘trans’ is a suitable transformation to relocate and perhaps rotate the instance.

If the user is directly clicking on a ‘shed’ instance you might want to use a ‘Tool’, to find where they’ve clicked, what was the best_picked ‘face’ and then find that face.normal to work out the ‘wini’ orientation/location - applying transformations as you go to account for the ‘shed’ transformation…

This second part is not so easy, but doable…

I don’t think you understood me quite. I realize I could put the window inside the shed component. If the window is inside the component, it is not possible to select the window, and move it with the move tool (in the UI) to a new location on the shed, unless you first open the shed component for editing. On the other hand if the window is outside the shed and glued to the shed component, then it can easily be re-positioned with the move tool and an observer re-cuts the openings inside the component once the move is completed.

1 Like

Sort of … but to work the shed instance must be temporarily open for editing.
(This can only be done via code with SU2020.0 and higher, and Neil must know the correct instance path for the model#active_path= method.)

    # Yank an instance out of the CURRENT EDITING context
    # into the parent context and paste it in place.
    # Preserves the gluing.
    def yank()
      model = Sketchup.active_model
      selection = model.selection
      object = selection.first
      Sketchup.send_action("cut:")
      UI.start_timer(1.0,false) {
        model.close_active
        if Sketchup.platform == :platform_osx
          Sketchup.send_action("pasteInPlace:")
        else
          Sketchup.send_action(21939)
        end
        puts "#{object.inspect}\n  Is glued to: #{object.glued_to}"
      }
    end

EDIT: Actually the process of CUT and PASTE creates a new object, so the prior reference becomes unusable.

Unfortunately I’m on the road for the day and can’t test anything till tonight.

Can this be done from the API? I’d hate to use the hacks mentioned here.

I’d also hate to force my users to upgrade to 2020.

This is an import feature, and I really need to be able to create the whole model without any additional input.

The only other work around I can think of is to use an attribute to track which shed a window is ‘glued_to’. Then use observers to detect when the shed is moved, so the window can be moved with it. That seems like a terrible hack though.

Yes (see code snippet) but as said it relies upon the shed instance being open for edit (which can only be done via code with v2020.0 and higher.)

I’ve since been testing with the edit context being the shed’s parent context, and yanking a known instance object (window instance) out of the shed’s entities. It will work from outside, however … paste in place no longer works to place the window correctly.

Attempting to fix the window’s transform after the fact also does not serve to preserve the gluing. (It will put it back in it’s original location outside the shed, but it is no longer glued nor is it cutting a face.)

You don’t glue through a face within the container but to the instance directly. If faces are moved insdie of the instance, other instances glued to it aren’t moved with them. I think the CompoenntInstance#glued_to method even returns a component instance in these cases.

I didn’t say that.

(EDIT: Let me explain what I meant by “through the container”. By this, I mean the context boundaries, ie the envelope of the component.)

It does, and I didn’t mean, say or imply otherwise. :wink:

OK Thanks for steering me in the right direction. I’ve finally got a solution that works for SketchUp 2017-2020.

  1. Move the shed component to the origin before cutting the window. This is need so that pasting the window actually glues to the shed.
  2. Add the window to the current selection. This works no matter what the model context.
  3. Cut the window and then paste it in place. Timers are needed so that the actions can complete.

Unfortunately the transaction is messed up by the send_actions. I think there is a way to fix that but I’ll have to do a bit of digging.


Sketchup.active_model.start_operation('Add Window', true)
tr = shed.transformation
shed.transformation = Geom::Transformation.new
Sketchup.active_model.selection.clear
Sketchup.active_model.selection.add(window)
UI.start_timer(0.01,false) {
  puts 'cut window'
Sketchup.send_action('cut:')
}
UI.start_timer(0.1,false) {
  if Sketchup.platform == :platform_osx
    Sketchup.send_action("pasteInPlace:")
  else
    puts 'paste in place'
    Sketchup.send_action(21939)
  end
  UI.start_timer(0.01,false) {
    window = Sketchup.active_model.selection[0]
    shed.transformation = tr
    window.transform!(tr)
    puts window.glued_to == shed
    BC.porch_cut_openings(shed)
    Sketchup.active_model.commit_operation
  }
}

It would be so much easier to do
window.glued_to = shed
:weary:

I was thinking of trying that next, but got busy with other things.

Does the 2nd argument (true) for the operation hide the shed from jumping around ?

Except the method would need some kind of positioning argument(s).

(Layman)
True. But the component (A) is glued to what? It is glued to something where a face is or was after editing, moving the face or even deleting the face.
The face may be moved, the parent component’s axes may be changed, the drawing axes may be changed, you may even delete the original face (not the entire parent component’s content). But still, the component (A) is glued to the original plane in the parent component.
The parent can even be reduced to a single guidepoint.
Moving the component drags the gluing plane to the new location, taking the component (A) with it.
This plane seems to be related / tied to the systems axes and can thus be moved parallel to a new location or even be rotated by rotating the parent component.

1 Like

Not at all. Positioning is irrelevant to gluing (in the api, not in the UI). In my case after a paste in place the component needed to be positioned because it was from a different model context. In reality a component can be glued to another component and not even be close to it. In other words gluing is not related to positioning, except for the the fact that moving a component (in the UI not so using the Api), will also move components glued to it. Also in the UI moving a glued component will constrain it to the plane it is glued to, but the api has no such constraint.

1 Like

Okay, that is weird. I would have thought that a glued object would need to be within (or intersect) the bounds of the parent object. Learn somethin’ new everyday. (Although I don’t know yet what use could be made of this info.)

Here’s a work in progress glue_to method:

# Glue instance to other instance.
#
# Workaround for the SketchUp API CompoenntInstance#glued_to= not supporting
# other instances. Due to technical limitations, the persistent ID is lost for
# the target.
#
# @param instance [Sketchup::ComponentInstance]
# @param target [Sketchup::ComponentInstance]
def glue(instance, target)
  instance.definition.behavior.is2d = true # "is2d" = "gluable"
  # TODO: Don't set if already has "snapping".
  instance.definition.behavior.snapto = SnapTo_Arbitrary

  corners = [
    Geom::Point3d.new(-1, -1, 0),
    Geom::Point3d.new(1, -1, 0),
    Geom::Point3d.new(1, 1, 0),
    Geom::Point3d.new(-1, 1, 0)
  ].map { |pt| pt.transform(instance.transformation) }

  # If this face merges with other geometry, everything breaks :( .
  # It has to lie loosely in this drawing context though, to be able to it.
  face = instance.parent.entities.add_face(corners)

  # TODO: Carry over any instances already glued to target.
  instance.glued_to = face

  group = face.parent.entities.add_group([face])
  component = group.to_component

  component.definition = target.definition
  component.layer = target.layer
  component.material = target.material
  component.transformation = target.transformation
  target.erase!
  # TODO: Copy attributes.
  # TODO: Purge temp definition.
end

This only works on components, not groups, and for now doesn’t carry over attributes.

1 Like

Sweet!! You’re just in time. That works much better. I just spent the last 45 minutes getting my method to work as a single transaction, and it still doesn’t ‘disable UI’, so you see things ‘jumping around’.


Sketchup.active_model.start_operation('Add Window', true, true)
tr = shed.transformation
shed.transformation = Geom::Transformation.new
Sketchup.active_model.selection.clear
Sketchup.active_model.selection.add(window)
UI.start_timer(0.01,false) {
  puts 'cut window'
  Sketchup.active_model.commit_operation
  Sketchup.active_model.start_operation('Add Window', true, true, true)
  Sketchup.send_action('cut:')
  Sketchup.active_model.commit_operation
  Sketchup.active_model.start_operation('Add Window', true, true, true)
}
UI.start_timer(0.02, false) {
  if Sketchup.platform == :platform_osx
    Sketchup.send_action("pasteInPlace:")
  else
    puts 'paste in place'
    Sketchup.active_model.commit_operation
    Sketchup.active_model.start_operation('Add Window', true, true, true)
    Sketchup.send_action(21939)
    Sketchup.active_model.commit_operation
    Sketchup.active_model.start_operation('Add Window', true, true, true)
  end
  UI.start_timer(0.01,false) {
    Sketchup.active_model.commit_operation
    Sketchup.active_model.start_operation('Add Window', true, true, true)
    window = Sketchup.active_model.selection[0]
    shed.transformation = tr
    window.transform!(tr)
    puts window.glued_to == shed
    Sketchup.active_model.commit_operation
    Sketchup.active_model.start_operation('Add Window', true, false, true)
    BC.porch_cut_openings(shed)
    Sketchup.active_model.commit_operation
  }
}

I finished it up. Many thanks for this fine workaround.

  def self.glue_to(instance, target)
    instance.definition.behavior.is2d = true # "is2d" = "gluable"
    # TODO: Don't set if already has "snapping".
    instance.definition.behavior.snapto = SnapTo_Arbitrary
  
    corners = [
      Geom::Point3d.new(-1, -1, 0),
      Geom::Point3d.new(1, -1, 0),
      Geom::Point3d.new(1, 1, 0),
      Geom::Point3d.new(-1, 1, 0)
    ].map { |pt| pt.transform(instance.transformation) }
  
    # If this face merges with other geometry, everything breaks :( .
    # It has to lie loosely in this drawing context though, to be able to it.
    face = instance.parent.entities.add_face(corners)
  
    # TODO: Carry over any instances already glued to target.
    instance.glued_to = face
  
    group = face.parent.entities.add_group([face])
    component = group.to_component
    temp_definition = component.definition
    component.definition = target.definition
    component.layer = target.layer
    component.material = target.material
    component.transformation = target.transformation
    #Copy attributes.
    target.attribute_dictionaries.each { |dict| dict.keys.each { |key| component.set_attribute(dict.name, key, dict[key]) } }
    #Purge temp definition.
    target.erase!
    Sketchup.active_model.definitions.purge_unused
  end
2 Likes

Few points, in reverse order…

If you iterate the target.attribute_dictionaries and there are none it’s working on nil (rather than the more logical []) and that causes a crash out.
Easy trapped by checking there are dictionaries before copying them over…

The temporary face to glue onto could be located outside of the definition’s bounds [min or max], thereby avoid any over laying; the face could also be defined by just three points, as a triangle…

Doesn’t adding the group from the face run the risk of a splat if the face.parent.entities is not the active_entities context ? No sure how to sidestep that !?

The ‘todo’ for getting already glued instances is also very problematical ?!

1 Like

Thanks for that input. I’ll do a bit more testing.

Not at all I have a refinement for that and almost have it ready to post.