The topic title basically says it all. Is there ever a case where calling instance.parent (where ‘instance’ is an instance of ComponentInstance within a Ruby plugin) will return a different value than calling instance.definition?
Additionally, now that I’ve delved a little more deeply into understanding this situation, I’m finding myself even more puzzled. It seems to me that ComponentDefinition really shouldn’t inherit from Entity at all; as it presently stands, I can’t find a good way to get at the parent ComponentInstance of a nested ComponentInstance in the case where there may be multiple copies from the parent’s definition. What’s the deal here? Why does ComponentDefinition inherit from DrawingElement at all? It seems to me that one should always construct a ComponentInstance based upon a ComponentDefinition and then draw that, and that ComponentDefinition should not under any circumstances be drawable “bare”.
Yes - that is because you cannot traverse from the end instance and back to the root. Going that way will only give you the multiple paths that could inherit it.
What you want sound like you want to check the context of which the user is editing. Either Model.active_path or the path that PickHelper gives you if you are making a tool.
If it didn’t then you wouldn’t be able to attach observers and attributes etc.
The DrawingElement inheritance is a bit more questionable as most doesn’t really apply or have much effect. But there are some dependency on this, like .bounds. Creating a custom variation of DrawingElement for this would just complicate things by reducing code reuse. Internally it is not an issue. For the API it might have been nice to remove these for ComponentDefinition, but alas that was not done.
It hasn’t really shown to be an issue to the API users either. If anything we have more reports of quirks because Sketchup::Model doesn’t behave like Sketchup::Entity - which some times break extensions when they call parent and don’t realize Model doesn’t have the same methods.
Thanks for the reply, Dan. I can see the argument for inheriting from Entity if that’s the only way to get that functionality, although since the ComponentDefinition is a prototype it would make more sense to me to use composition here instead of inheritance. In any case, thanks for addressing the question.
Thanks a lot, that makes a lot of sense as to why I’m seeing the behavior I am; I’m working with a deeply nested component, so the owner of that Entities collection is always a ComponentDefinition except at the outermost level; however, I stop traversing the tree at that point so I never noticed it.
In my case, there isn’t a context in which the user is editing; I’m instead programmatically inspecting the model to infer adjacency relationships between subcomponents when exporting geometry data to an external format, so the active_path and PickHelper approaches won’t help me here. It’s too bad though, being able to traverse directly to the root would be really helpful.
It sounds like DrawingElement (or perhaps Entity itself) should perhaps have been defined piecemeal as a set of ruby modules that could be mixed together rather than a straight class inheritance hierarchy. Anyway, it’s good to know that I’m not just missing something. Thanks.
@tt_su To be a bit more specific about what I’m trying to do, I have a tree of nested components. What I need to be able to get is the composed Transformation object that represents the transformation from the innermost (leaf) coordinate system to the outermost (root) coordinate system, so that I can project points between leaf components, all without ever exploding or otherwise opening up the containing component tree. The lack of a relationship between a child component instance and the instance of its parent (which may be a copy) makes this problematic.
You would have to travel from the root and keep track on the instance path and/or transformation - I would do this in a recursive method.
Well, that would be one way - but it’d also add more code, more classes, more types, more complexity - stuff lat increase risk for bugs. Seeing that the side effect of DrawingElement is a couple of inert methods it’s an acceptable trade of. We haven’t heard of any issues so far.
The one to many relationship between definitions and instances makes this is technically not possible. Given an arbitrary instance it might have multiple representations in the model. There isn’t any kind of flag or anything we can add to solve this. Only if all instances was unique would this be possible. The only way is to go from the root and to the leaves.
Firstly, prototype is not a Ruby keyword, and does not even appear (as a concept,) in the “bible” of Ruby, Dave Thomas’ Progamming Ruby: The Pragmatic Programmers’ Guide (aka the “Pick-Axe Book”.)
Although Ruby is listed with the “Weakly Typed” languages, it really has no types. Ruby is a 100% Object Oriented Language. Everything is an object, of some class, of which ALL are subclasses of Object, (and so inherit it’s methods and those mixed in from the Kernel module.)
The Ruby Core team has purposely removed the Object#type() alias for the Object#class() method to drive home this point.
My point is that the “type” in prototype is out of place in Ruby.
The word protoclassmight have been more appropriate, except that proto- means "first or “foremost”. That cannot apply as the true protoclass in Ruby is BasicObject (or Object if we’re talking old 1.8 branch Ruby.)
The correct Ruby term in this case is superclass. It’s job is to pass on functionality to it’s descendant classes, and provide a common (single) place to maintain that functionality (the code.)
Now noting your mention of composition. There is always some additional functionality defined by composition in all subclasses. (Otherwise there would be no need to even define a subclass, as the direct use of the superclass would suffice.)
I will assume, because your use of prototype, of some background in other languages, and that you meant superclass. But that still does not make the above quote make any sense.
Their are no descendant classes (subclasses) of ComponentDefinition, and plugin author’s cannot and do not use it as a superclass for custom classes.
(Despite the naming of ComponentInstance as a classname, it is NOT actually a Ruby subclass of ComponentDefinition. The latter is a “wrapper class” that contains attributes, a collection of references to entity references and a collection of references to instances of the former.)
It would be better to say that since ComponentDefinition need only a few methods from DrawingElement, that those few methods should have been defined within a BoundsLib mixin module.
And since ComponentDefinition is not actually an element that appears in the model’s drawing, it should be a direct subclass of Entity, and that the BoundsLib module be mixed into both DrawingElement and ComponentDefinition.
But as Thomas said, it does not affect us API users much as when we test ancestry we’d do this: compdef.is_a?(Sketchup::Entity)
which would return true, and ignore that Sketchup::DrawingElement even appears in the ancestors() list for compdef.class.
Or we’d test for implementation, with something like: compdef.respond_to?(:bounds)
It would be nice if the API dictionary had a note on the definition page like:
"Sketchup::DrawingElement ancestry for inheritance of bounds methods only", (or similar.)
Properly, the methods that do not apply, (from Sketchup::DrawingElement,) should have been undefined within the Sketchup::ComponentDefinition class using undef_method().
(As they are now they just return bogus values of true or nil, etc. The returned values don’t mean anything, but will not raise a NoMethodError.)
To go to an extreme, the API Sketchup::ComponentDefinition class could override the ancestors() method to return an array that does not contain a Sketchup::DrawingElement member. (Fooling us, even though it does.)
This is certainly not the case in idiomatic Ruby; consider the Enumerable module, which is statically mixed in to basically every class that you can imagine which provides the most minimal iterability.
I was actually not intending to refer to this in any ruby-specific sense; more that, as I understand it, in the SketchUp world a ComponentDefinition serves the same role as a class in an object-oriented language, insofar as ComponentInstances are created from it and included in the model.
I’ve been a professional programmer for about 15 years, but have been using mostly Scala and Haskell since 2008, so I have a predilection for functional programming, and have found that most of the time inheritance hierarchies cause more trouble than good. I worked professionally in Ruby for several years before that, so I have a reasonably good understanding of idiomatic design in Ruby. This is actually why I asked about why inheritance was used here in the first place; it just didn’t see inappropriate.
That sounds like a more idiomatic Ruby design! I still feel like I’m missing something, though. If the ComponentDefinition isn’t something that’s drawable in the model, why should it define .bounds? Shouldn’t it be the individual instances that you’re getting the bounds for; since the bounding box is, according to the documentation, in the global coordinate system of the model, doesn’t this mean that different instances will have different bounding boxes?
I’m curious about this because the current adjacency test that I’m using involves finding a subcomponent, transforming a point into that subcomponent’s coordinate system, and then performing a point/plane distance test against each of the Face instances that is a child of that subcomponent. If I were able to use a bounding box in the local coordinate system of the subcomponent, this would be much more performant, but I assumed that I couldn’t because the bounds would be in the wrong coordinate system.
After reading this, I realized that going from the root to the leaves and keeping track of the path that I’d traversed was what I should have been doing all along anyway, so thank you! I’m working in a plugin codebase that I inherited and the previous developer had gone to some baroque lengths so as to be able to traverse .parent references (they made the whole tree unique), so my solution just became much simpler.
Yea - I’ve been through that myself when I first started out. wanted to go from leaf to root. Took a while before I really thought of how the relationships actually work and realizing it was impossible.
Probably worth a blog post. Comes up from time to time.
The Ruby API is generally a thin wrapper over the underlying C++ structure. The C++ core structure wasn’t made for API in mind - the API came later. So some thing where done for convenience and the API design is influenced by the underlying C++ structure it wraps. And this is all many years ago - SketchUp first came out in 2000, so we have a 15 year old code base. Some things makes more sense under the hood than what one can see from the API layer. Certainly if things where made fresh now it would probably be structured differently, but as with any old code base you’ll find quirks.
Yes, a ComponentDefinition can be seen as a class definition. And ComponentInstances are similar to instantiation of your class. Group and Image is actually in the underlying C++ sub-classes of ComponentInstances - though the Ruby API tries to hide this, but not very well. That’s something which has always bothered me. Especially since you can iterate the collection of definitions and get access to the internal entities of Image entities - which if you modify might crash SketchUp because it assumes one textured face and four edges. Again - our docs doesn’t currently really warn about this very well.
One some times want to get the dimension of the definition - not the instance.
Again - the API implementation is more of a reflection on the C++ structure and the initial implements where probably C++ programmers and not Ruby programmers. Which is why you see a bunch of methods name inconsistent with conventional Ruby naming style.
To be more precise, .bounds() called on the definition, returns an untransformed boundingbox object, whereas each component instance (or group) could be moved, stretched, scaled, etc., and have unique sized bounds. So calling this inherited instance method, on the component instance or group returns a transformed boundingbox object.