Component.glued_to bug

It seems like there is a bug that when a component is glued to a face, it looses any components that have been glued to it.

I’ve made a refinement that enables gluing a component to another component.

Here is a sample model to show the problem.

Gluing Bug.skp (44.4 KB)

Open the model and execute this code:

def self.entity_from_name(name)
  Sketchup.active_model.entities.select { |e| e.get_attribute('test', 'entity') == name }[0]
end

def self.test
  @face = entity_from_name('face')
  @box1 = entity_from_name('box1')
  @box2 = entity_from_name('box2')

  Sketchup.active_model.start_operation('test', true)
  p "before regluing box1:"
  p "box1.glued_to = #{@box1.glued_to}"
  p "box2.glued_to = #{@box2.glued_to}"
  @box1.glued_to = @face
  p "after reglueing box1:"
  p "box1.glued_to = #{@box1.glued_to}"
  p "box2.glued_to = #{@box2.glued_to}"
  Sketchup.active_model.abort_operation
end
test

#outpputs
#< before regluing box1:
#< box1.glued_to = #<Sketchup::Face:0x0001ca9d84ed60>
#< box2.glued_to = #<Sketchup::ComponentInstance:0x0001ca9d84eb80>
#< after reglueing box1:
#< box1.glued_to = #<Sketchup::Face:0x0001ca9d84ed60>
#< box2.glued_to = 

This is starting to be a chicken and egg problem.

Gluing a component to a face causes it’s components to become unglued from it, but gluing components to a component requires and ugly hack of creating a new component from the old component, which causes it to become unglued from the entity it was glued to.

All because the api is lacking ComponentInstance.glued_to = instance

Any Ideas @eneroth3, @DanRathbun, or @thomthom?

I need to glue instance to instance to instance in an infinite level, while maintaining the gluing above and below in the hierarchy.

Whew I got that problem ‘fixed’ now. I wonder if this method could cause infinite recursion? In my testing I didn’t run into that yet, but all the ‘rabbit trails’ are a bit worrisome.

module ExtensionNameSpace
  def self.get_glued_instances(instance)
    instance.parent.entities.grep(Sketchup::ComponentInstance).find_all do |c|
      (c.glued_to == instance) || (!c.glued_to.nil? && c.glued_to.parent == instance.definition && c.parent != instance.definition)
    end
  end

  def self.glue_to(instance, target)
    #instance = self   
    if target.is_a?(Sketchup::Face)
      instance.glued_to = target
      return instance
    end
    
    instance.definition.behavior.is2d = true # "is2d" = "gluable"
    #Don't set if already has "snapping".
    instance.definition.behavior.snapto = SnapTo_Arbitrary unless instance.definition.behavior.snapto
    
    #we need to get a list of components glued to this instance because they will be unglued when the instance is glued to the target
    inst_glued_instances = get_glued_instances(instance)

    #close all open component edits so we don't crash SketchhUp
    while Sketchup.active_model.active_path do Sketchup.active_model.close_active end

    #find the model bounds and make sure to place the face beyond that so it doesn't merge with any existing geometry
    max = instance.parent.bounds.max
    corners = [
      Geom::Point3d.new(max.x + 9, max.y + 9, max.z + 10),
      Geom::Point3d.new(max.x + 10, max.y + 9, max.z + 10),
      Geom::Point3d.new(max.x + 10, max.y + 10, max.z + 10)
    ].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)
    instance.glued_to = face
    #Carry over any instances already glued to target.
    faces = [face]
    glued_instances = get_glued_instances(target)

    glued_instances.each do |inst|
      max = inst.parent.bounds.max
      corners = 
        [
          Geom::Point3d.new(max.x + 9, max.y + 9, max.z + 10),
          Geom::Point3d.new(max.x + 10, max.y + 9, max.z + 10),
          Geom::Point3d.new(max.x + 10, max.y + 10, max.z + 10)
        ].map { |pt| pt.transform(inst.transformation) }
      face = inst.parent.entities.add_face(corners)
      #we need to get a list of components glued to this instance because they will be unglued when the instance is glued to the target
      child_glued_instances = get_glued_instances(inst)
      inst.glued_to = face
      faces.push(face)
      #reglue any child components that have become unglued
      unglued = child_glued_instances.reject { |i| i.glued_to == inst }
      temp_inst = inst
      unglued.each { |i| temp_inst = i.glue_to(temp_inst) }
    end

    group = faces[0].parent.entities.add_group(faces)
    component = group.to_component
    component.definition = target.definition
    component.layer = target.layer
    component.material = target.material
    component.transformation = target.transformation
    component.glue_to(target.glued_to) unless component.glued_to == target.glued_to
    #Copy attributes.
    target.attribute_dictionaries.to_a.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

    #reglue any child components that have become unglued
    unglued = inst_glued_instances.reject { |i| i.glued_to == instance }
    unglued.each { |i| instance = i.glue_to(instance) }
    
    component
  end
end

Why is there a class block inside the refine block?
And why is there no refinement module wrapping the refine statement?

Because I’m extending the functionality of the Sketchup::ComponentInstance class as a refinement inside my extension namespace. So I can do instance.get_glued_instances or instance.glue_to(target_instance).

My production code is inside my extension namespace. I’ll add a generic namespace to the sample code incase somebody copies it.

That is not how the docs tell us to refine classes. Review the primer …
File: refinements.rdoc [Ruby 2.5.5]

The class identifier argument to the Module#refine() call is the class that gets refined by the block argument.

A refinement module is needed to become the argument to Module#using calls within an extension’s namespace module(s).

You show no using call at all.


Interestingly, the Module#refine() method itself returns an anonymous refinement module which could be assigned to an identifier. (This speculation was incorrect.)

(Collapsed) as the returned anonymous module is the refinement transparently held by Ruby.

INCORRECT - DISREGARD Ex:

# file: "lib/Refinements.rb"

module Author::Lib::Refinements

  RefinedComponentInstance = refine(::Sketchup::ComponentInstance) do
    # New or Overridden instance methods here
  end

  # Other Class Refinements ...

end

And then in some extension …

module Author
  module SomePlugin

    require File.join(__dir__,"lib/Refinements")
    include Author::Lib::Refinements
    using RefinedComponentInstance

  end
end

BUT … you don’t assign the anonymous modules returned by Module#refine(), … because the refine call applies to the receiver module in which it is called. The receiver module is the “Refinement module”.

So say that the lib submodule Refinements had multiple refinements for several to many API classes.
And that, the returns from all the refine() calls are not assigned to an identifier.
The “using” module can then just “use” the wrapping module and all it’s refinements should be “usable”.

module Author
  module SomePlugin

    require File.join(__dir__,"lib/Refinements")
    using ::Author::Lib::Refinements

  end
end

Hm… that is strange. The internal code adding a glueto relationship. I don’t see anything that should destroy existing ones.

Have you logged this bug in the issue tracker?

No I haven’t

Again, I’d like to point out that your example above is incorrect and actually modifying the API Sketchup::ComponentInstance class.

Hmm… I see you are right.
I was under the understanding that it would only modify the class inside my namespace.

Thanks for being persistent.

So why doesn’t this code work?

module BC::Refinements
  RefinedComponentInstance = refine(::Sketchup::ComponentInstance) do
    def get_glued_instances
      co = parent.entities.grep(Sketchup::ComponentInstance).find_all do |c|
        (c.glued_to == self) || (!c.glued_to.nil? && c.glued_to.parent == definition && c.parent != definition)
      end
      co
    end
  end
end

module BC
  include BC::Refinements
  using RefinedComponentInstance
end

On the using RefinedComponentInstance I get an error

Error: #<TypeError: wrong argument type Class (expected Module)>

I haven’t taken the time to read the link you posted yet, but I will as soon as a get a minute (I’m supposed to be working :wink: ).

Tis’ weird. The docs specifically say Module#refine returns a module.

So I tested by pasting in the first snippet, and then typing at the console …

BC::Refinements::RefinedComponentInstance.class
#=> Module

But the two objects are not the same …

module BC
  def self.test
    puts Refinements == Refinements::RefinedComponentInstance
  end
  def self.compare
    puts "Refinements object_id = #{Refinements.object_id}"
    puts "Refinements::RefinedComponentInstance object_id = #{Refinements::RefinedComponentInstance.object_id}"
  end
end

Running test methods show the two are not the same object.

Anyway, … forget assigning the result of the #refine call. Back to the docs …

refine(mod) { block }
Refine mod in the receiver.

The mod argument is the class (which is subclass of class Module,) that is being refined.

The receiver is the module in which you are calling the #refine method.
So the refinement module is the receiver, which is (in your example) Refinements.

So you simply “use” Refinements, … ie …

module BC
  using Refinements
end

Okay? The refinement object that gets returned by the #refine call is “held” by Ruby itself and referenced during a method call operation. (Ie, a refinement is injected into the ancestry chain as a pseudo-mixin as if it was prepended to the refined class. At the bottom of this page there’s an explanation of how Ruby goes through process of choosing what method to call from classes that have mixins, superclasses and refinements.)

So my speculation (above) about assigning the returned object and using that was incorrect. (I’m going to wrap that in a collapsed detail block.)

:blush: Sorry for confusing the issue with technospeculating.

You simply “use” (ie the argument to #using) the wrapping module that the class refinement(s) are called within. Ex:

module BC::Refinements
  refine(::Sketchup::ComponentInstance) do
    def get_glued_instances
      co = parent.entities.grep(Sketchup::ComponentInstance).find_all do |c|
        (c.glued_to == self) || (!c.glued_to.nil? && c.glued_to.parent == definition && c.parent != definition)
      end
      co
    end
  end
end

I’m starting to understand refinements finally, and am now questioning their usefulness.

So I need to have a using Refinements at the top of every file to ‘activate’ the refinements.

I’m thinking it would be easier to just use a method if ‘extension methods’ (as they are called in C#) are that difficult to implement. I was hoping I could add an extension method that would be globally available in my module, but it looks like it needs to be specifically activated in each file, not just once in the module.


instance.get_glued_intances()

#will become

get_glued_instances(instance)

So much for refinements…

When first rolled out, refinements could only be at the top level and was only per file.

But at some point, this changed and you can now use them also inside module and classes blocks.

From the Refinements primer, section labeled Scope:

You may activate refinements at top-level, and inside classes and modules. You may not activate refinements in method scope. Refinements are activated until the end of the current class or module definition, or until the end of the current file if used at the top-level.

You may activate refinements in a string passed to Kernel#eval. Refinements are active until the end of the eval string.

Refinements are lexical in scope. Refinements are only active within a scope after the call to using. Any code before the using statement will not have the refinement activated.

When control is transferred outside the scope, the refinement is deactivated. This means that if you require or load a file or call a method that is defined outside the current scope the refinement will be deactivated.

If a method is defined in a scope where a refinement is active, the refinement will be active when the method is called.

… and this is the part your are asking about …

Note that the refinements in M are not activated automatically if the class Foo is reopened later.

This refers to an example where M is the refinement module and Foo is a class that uses the refinements defined by M.

So the answer to your question is, correct, … Module#using is lexical, and is only valid from where it is called, to the end of the class or module block, or the end of the file if called from the top level.

So you need a …

    using Refinements

… call at the top of each extension module block, in every extension file, that will need to “use” the refinements. (Or, yea, the top of the file if you have multiple module blocks in the file. Whatever you choose. I personally am of the school that all code should be within namespace modules unless it specifically must be evaluated in the global ObjectSpace.)

I think (generally) the top of the module blocks are a good place to declare any dependency.

But yes I myself think that it would be “useful” if there was a method that applied refinement “usage” to the whole module even across files. Maybe there is already an Ruby request for this? If not. perhaps there should be?

Ie, … using_throughout(MyRefinement)

1 Like
1 Like