It works manually in the GUI because as Julia said, the core supports it (gluing through the container to internal component faces) but the API does not.
Note that Neil has already formally opened an API Issue for this … (a year and 9 months ago) …
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…
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…
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.
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.
(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.
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.
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.)
# 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.
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’.
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