Help Understanding Classes and Class objects
-
conv_group = ents.add_group st_ep, en_ep
st_ep
anden_ep
are instance ofEndPulley
- you can't feed that toEntities.add_group
.You seem to be confusing Ruby classes with SketchUp entities.
-
@dan rathbun said:
(3) What you want to do, is create a Group or Component, that is named "Pulley" and "Conveyor". Perhaps even DynamicComponents.
OK, I see that you ARE actually doing (3), which IS the correct thing to do. (I see that your
draw
method has nothing to do with theView.draw()
method(s), it is just a naming similarity which is best avoided. Methods that create geometry, by convention, we usually prefix with "create_", as increate_end_pully()
, etc.)(A) You need to understand that the actual objects you create will be
Sketchup::Group
class instance objects. So they already HAVE a class identity.You cannot really (or should not actually,) expect that you can assign a class identity to a
Sketchup::Drawingelement
subclass instance object, (or anySketchup::Entity
subclass instance object,) ... and expect the reference to it to be persistent. Sometimes SketchUp reindexes things on the C++ after a save, or purge of layers, etc.
If you close the model, and reopen it, your custom class instances would not be valid.(B) What you attempted to do, is a certain kind of a class implementation, called a "wrapper class." They do have their place, and come in handy sometimes. In this scenario, the custom wrapper class creates an instance of another class, and holds an internal reference the the "wrapped" object, assigned to an instance variable.
Your
Conveyor
class written as a wrapper class, (module wrapped asMagans::Conveyor
.)[Semantic Example ONLY. Still needs changes to work properly!]
module Magans class Conveyor # make @group var and .group() instance method; attr_reader(;group) private def initialize(st_point, end_point) @st_point = st_point @end_point = end_point ents = Sketchup.active_model.active_entities pully1 = create_end_pully(ents,@st_point,36,18) pully2 = create_end_pully(ents,@end_point,36,18) # hold reference to group; @group = ents.add_group( pully1, pully2 ) @group.name = "CONVEYOR ASSY" end #of initialize() def create_end_pully(ents,pts,width,length) pt1 =[pts[0] - (width/2),pts[1],pts[2]] pt2 =[pts[0] - (width/2),pts[1]+length,pts[2]] pt3 =[pts[0] + (width/2),pts[1]+length,pts[2]] pt4 =[pts[0] + (width/2),pts[1],pts[2]] face = ents.add_face( pt1,pt2,pt3,pt4 ) group = ents.add_group( face ) group.name = "END PULLEY" face.reverse! face.pushpull( 5 ) # return the pully's group reference; return group end #of create_end_pully() public # Example "wrapper" methods def name() return @group.name end #of name() def valid?() return @group.valid? end #of name() end #of Conveyor class end # module Magans
(C) try to use
Sketchup.active_model.active_entities
so you can add things into the current editing context (which can be the model, or some nesting level within a group or componet instance.)(D) Choose a toplevel namespace module name. (The
Magans
name above is an example.) All your code should run within that namespace. You'll likely be wanted to wrap each of your plugin projects within a submodule of you namespace.
You decide on a unique toplevel module (your company name, or your SCF member "screen name".) -
There is still a basic grouping protocol steps that are out of sequence in your original example, and in mine above.
Can you figure out what the proper sequence is ???
(If not... say so, we'll show you what the proper sequence is.)
-
Also - when creating Group with the API you create the group first - then add the entities directly into the group. As oppose to when you model in SketchUp where you draw the geometry - then group.
-
You CAN make a group from existing entities
gp=entities.add_group(array_of_entities)
... BUT if those entities are not all in the same context OR if their context is NOT the active_entities then you will get a Bugsplat.
If you are making new entities then it is best to make them inside the desired context in the first place - using a new [initially] empty groupgp=entities.add_group();gp.entities.add_face(points)
to get the face etc.
Doing this also removes the risk of your new geometry clashing with some existing geometry in the active_context - e.g. overlapping faces, which will intersect unexpectedly if not separated from the very creation of your new face.
The only time I would suggest you want even to think about making a group from existing objects... is when you are using a user's [premade] selection - because they can only select objects in the 'active_entities' context then you can safely group those objects [or a filtered sub-set of them - e.g. just faces and edges] into a new group that is made in the same context, thus:gp=model.active_entities.add_group(model.selection.to_a)
will make a group of the current selection. -
@dan rathbun said:
There is still a basic grouping protocol steps that are out of sequence in your original example, and in mine above.
Can you figure out what the proper sequence is ???
@thomthom said:
... when creating Group with the API you create the group first - then add the entities directly into the group. As oppose to when you model in SketchUp where you draw the geometry - then group.
Thomas gave away the answer.
So ... lets see the my example, using this protocol.
We will first create the conveyor group, add a temporary construction point to it, so SU does not clean it up, then past that parent group's
entities
reference into the other method, and have the subordinate method add it's subgroup to the parent group. Finally we will add pully primitives to the subgroup and return it's reference.module Magans class Conveyor # make @group var and .group() instance method; attr_reader(;group) private def initialize(st_point, end_point) @st_point = st_point @end_point = end_point ents = Sketchup.active_model.active_entities # hold reference to group; @group = ents.add_group() cpt = @group.entities.add_cpoint(ORIGIN) @group.name = "CONVEYOR ASSY" # pully1 create_end_pully(@group.entities,@st_point,36,18) # pully2 create_end_pully(@group.entities,@end_point,36,18) cpt.erase! end #of initialize() def create_end_pully(ents,pts,width,length) pt1 =[pts[0] - (width/2),pts[1],pts[2]] pt2 =[pts[0] - (width/2),pts[1]+length,pts[2]] pt3 =[pts[0] + (width/2),pts[1]+length,pts[2]] pt4 =[pts[0] + (width/2),pts[1],pts[2]] subgroup = ents.add_group() face = subgroup.entities.add_face( pt1,pt2,pt3,pt4 ) subgroup.name = "END PULLEY" face.reverse! face.pushpull( 5 ) # return the pully's group reference; return subgroup end #of create_end_pully() public # Example "wrapper" methods def name() return @group.name end #of name() def valid?() return @group.valid? end #of name() end #of Conveyor class end # module Magans
-
First of all I would like to thank you guys for all of the help. My thought process was skewed in the wrong direction and I don't think I would've come up with the passing the group object. Also starting with the group and then adding the entities is now clear in my head. I had read that before in other posts but could never quite understand the "how". That was where I was hopelessly stuck for the past day. I have re-written the previous code and module-ized it and it works perfectly.
I kept the classes for Conveyor and EndPulley seperate so that I can use both seperately for inheritance in the future.
I still need to check into "Wrapper" classes further for how I can implement them effectively.
The only snag was I had to remove the private designation for the methods under the Conveyor class to work. I'm not really certain why.
module Pmagans # Type in Console # load 'Conveyor/CONVEYOR2.rb' # conv = Pmagans;;Conveyor.new([0,0,0], [0,120,0]) class Conveyor #make the @group var and .group()instance attr_reader(;group) #private #why does this need to be private? def initialize(st_point, end_point) ents = Sketchup.active_model.entities @st_point = st_point @end_point = end_point #hold reference to group; @group = ents.add_group() cpt = @group.entities.add_cpoint(ORIGIN) @group.name = "CONVEYOR ASSY" self.create_geometry cpt.erase! end #of initialize() def create_geometry() ents = Sketchup.active_model.entities puts ents.length #pulley 1 st_ep = EndPulley.new(@group, @st_point) #pulley 2 end_ep = EndPulley.new(@group, @end_point) puts ents.length end #of create_geometry() end #of Conveyor class class EndPulley attr_reader(;group) def initialize(conv_group, ins_pt) #TODO; add *args if a group not passed to EndPulley just "create_geometry" @conv_group = conv_group @ins_pt = ins_pt self.create_geometry end #of initialize() def create_geometry() pts = @ins_pt width = 36 length = 18 ents = Sketchup.active_model.entities pt1 =[pts[0] - (width/2),pts[1],pts[2]] pt2 =[pts[0] - (width/2),pts[1]+length,pts[2]] pt3 =[pts[0] + (width/2),pts[1]+length,pts[2]] pt4 =[pts[0] + (width/2),pts[1],pts[2]] @group = @conv_group.entities.add_group() face = @group.entities.add_face pt1,pt2,pt3,pt4 face.reverse! face.pushpull 5 @group.name = "END PULLEY" end #of create_geometry() end #of EndPulley class end #of module Pmagans
-
I'm not sure if my last post went through. I wanted to thank everyone though. Also I got everything working and kept the two classes seperate for future inheritence. I had to remove the "private" so that the methods for the Conveyor class could be accessed. I'm not really sure why?
module CONVEYOR #CONVEYOR.rb # Type in Console # load 'Conveyor/CONVEYOR2.rb' # ex. conv = CONVEYOR;;Conveyor.new([0,0,0], [0,120,0]) require 'sketchup.rb' class Conveyor #make the @group var and .group()instance attr_reader(;group) #private #why does this need to be private? def initialize(st_point, end_point) ents = Sketchup.active_model.entities t1 = Geom;;Transformation.translation [0,0,-5] @st_point = st_point @end_point = end_point #hold reference to group; @group = ents.add_group() cpt = @group.entities.add_cpoint(ORIGIN) @group.name = "CONVEYOR ASSY" @group.transform! t1 self.create_geometry cpt.erase! end #of initialize() def create_geometry() ents = Sketchup.active_model.entities puts ents.length #pulley 1 st_ep = EndPulley.new(@group, @st_point) #pulley 2 end_ep = EndPulley.new(@group, @end_point) puts ents.length end #of create_geometry() end #of Conveyor class class EndPulley attr_reader(;group) def initialize(conv_group, ins_pt) #TODO; add *args if a group not passed to EndPulley just "create_geometry" @conv_group = conv_group @ins_pt = ins_pt self.create_geometry end #of initialize() def create_geometry() pts = @ins_pt width = 36 length = 18 ents = Sketchup.active_model.entities pt1 =[pts[0] - (width/2),pts[1],pts[2]] pt2 =[pts[0] - (width/2),pts[1]+length,pts[2]] pt3 =[pts[0] + (width/2),pts[1]+length,pts[2]] pt4 =[pts[0] + (width/2),pts[1],pts[2]] @group = @conv_group.entities.add_group() face = @group.entities.add_face pt1,pt2,pt3,pt4 face.reverse! face.pushpull 5 @group.name = "END PULLEY" end #of create_geometry() end #of EndPulley class end #of module CONVEYOR
-
@pmagans said:
I'm not sure if my last post went through.
The first few posts of new members must be approved by a moderator before it appear on the forum - this is an anti-spam measure.
-
You still have this
st_ep = EndPulley.new(@group, @st_point)
which won't return the new object [group] unless you add
return @group
as the final code in the EndPulley method... -
So I added the return @group per your recommendation TIG however I am having some confusion as to how Sketchup sees the newly created groups. It doesn't seem like you can manipulate the returned groups as per the example code below returns undefined method transform!
It seems as though Sketchup is treating the groups st_ep and end_ep as objects not groups. I have been creating "dummy-container groups to bypass this like Conveyor but I'm guessing from your comment there is an easier way to access the groups that I am creating in the EndPulley class?module CONVEYOR #CONVEYOR.rb # Type in Console # load 'Conveyor/CONVEYOR2.rb' # ex. conv = CONVEYOR;;Conveyor.new([0,0,0], [0,120,0]) require 'sketchup.rb' class Conveyor #make the @group var and .group()instance attr_reader(;group) #private #why does this need to be private? def initialize(st_point, end_point) ents = Sketchup.active_model.entities #t1 = Geom;;Transformation.translation [0,0,-5] @st_point = st_point @end_point = end_point #hold reference to group; @group = ents.add_group() cpt = @group.entities.add_cpoint(ORIGIN) @group.name = "CONVEYOR ASSY" #@group.transform! t1 self.create_geometry cpt.erase! end #of initialize() def create_geometry() ents = Sketchup.active_model.entities t1 = Geom;;Transformation.translation [0,0,-5] #pulley 1 st_ep = EndPulley.new(@group, @st_point) #puts st_ep st_ep.transform! t1 #pulley 2 end_ep = EndPulley.new(@group, @end_point) end_ep.transform! t1 end #of create_geometry() end #of Conveyor class class EndPulley attr_reader(;group) def initialize(conv_group, ins_pt) #TODO; add *args if a group not passed to EndPulley just "create_geometry" @conv_group = conv_group @ins_pt = ins_pt self.create_geometry end #of initialize() def create_geometry() pts = @ins_pt width = 36 length = 18 ents = Sketchup.active_model.entities pt1 =[pts[0] - (width/2),pts[1],pts[2]] pt2 =[pts[0] - (width/2),pts[1]+length,pts[2]] pt3 =[pts[0] + (width/2),pts[1]+length,pts[2]] pt4 =[pts[0] + (width/2),pts[1],pts[2]] @group = @conv_group.entities.add_group() face = @group.entities.add_face pt1,pt2,pt3,pt4 face.reverse! face.pushpull 5 @group.name = "END PULLEY" return @group end #of create_geometry() end #of EndPulley class end #of module CONVEYOR
-
end_ep = EndPulley.new(@group, @end_point)
NOW ADD
puts 'end_tp details...' puts end_tp puts end_tp.class puts end_tp.name puts 'END'
THEN
end_ep.transform!(t1)
FAIL ? IF end_tp is NOT a group or component_instance ???
Report back what the 'puts' show in the Ruby Console...
-
This is the output - Doesn't get to the puts statements
load 'Conveyor/CONVEYOR2.rb' true conv = CONVEYOR;;Conveyor.new([0,0,0], [0,120,0]) end_tp details... Error; #<NameError; undefined local variable or method `end_tp' for #<CONVEYOR;;Conveyor;0xb8a563c>> C;/PROGRA~2/Google/GOOGLE~1/Plugins/Conveyor/CONVEYOR2.rb;43 C;/PROGRA~2/Google/GOOGLE~1/Plugins/Conveyor/CONVEYOR2.rb;27;in `initialize' (eval);435;in `new' (eval);435
-
Try this change: in the EndPulley code after
self.create_geometry
###ADD
return @group
as the last code before the '
end
' of theinitialize
...REMOVE the equivalent line from the
create_geometry
method...Run with the '
puts
' to see what's what now... -
Same message No change - its dying on the puts statements?
-
WAIT !!
Earlier on I attempted to explain that his custom classes were WRAPPER CLASSES. (And questioned why anyone would want to actually do this. Subsequent edits by a user such as boolean operations can cause the references held by the wrapper class instances to become invalid and point at
Sketchup::DeletedEntity
objects.)I showed an example which did NOT have a
EndPully
class, because it would ALSO be a wrapper class, whosenew()
constructor returns the wrapper instance NOT aSketchup::Group
instance.SO.. therefore... he needs to call the
EndPully
instance'sgroup()
wrapper method (created via theattr_reader(:group)
call,) which will return the actual group reference:
st_ep.group.transform! t1
-
Can you also add the equivalent 'puts' just after the
st_ep = EndPulley.new(@group, @st_point)
puts 'st_tp details...' puts st_tp puts st_tp.class puts st_tp.name puts 'END'
so we can see what the first part does...
-
Sorry ... I got busy and did not get back to this thread,
I was going to continue the discussion on why wrapper classes are extremely problematic for
Sketchup::Drawingelement
subclasses. (One issue is on Mac multiple models could be open, but the API does not yet have a good way to track them, and we know of some bugs on the PC, that causes issues.)
Anyway... the alternative you REALLY should be using is to create a
Sketchup::ComponentDefinition
for your pully and conveyor, and then place instances of these (rather than Groups.)The definition's instances collection will keep track of them for you.
The model's
DefintionList
collection will keep track of your definition(s) for each model that they are inserted into.When a model is opened, your plugin can search through it's
DefintionList
to find those definitions that are "known" to the plugin. And then grab an array of their instances, and iterate it to get references. -
Dan you are correct. Adding the .group gave me access to the "group" objects that were created.
As for my thought process I am not sure that it is valid so let me describe my thoughts.
I don't want to code "like" things so I assumed that using classes would allow me to inherit class attributes things without having to copy a bunch of code it multiple places. For example classes would be:
- FUNCTIONAL AREAS
[list:11tc2ay1]1. CONVEYOR
-
CONVEYOR_COMPONENTS
-
END_PULLEY
-
DRIVE
-
INTERMEDIATE_BED
-
PAN_GUARD
-
BEARINGS
-
ETC.
-
BUILDING_MODSETC.[/*11tc2ay1][/list11tc2ay1]Later I could write Customer specific code for each of the classes and inherit all of the functionality that I wrote in the "General" Classes
One customer's end_pulley may contain different bearings, pulleys, profiles etc. Also there are many types of end pulleys. That being said they do have some similarities especially attribute definitions, layer structure, basic functionality.
Am I on the right track or am I way off the rails?
- FUNCTIONAL AREAS
-
Simply put:
A class constructor returns an instance of IT's class.
CONVEYOR::EndPully.new()
returns an instance ofCONVEYOR::EndPully
not an instanceSketchup::Group
This is one of the MAJOR drawbacks of a wrapper class, you have to write wrapper methods for all the methods of the wrapped object that you wish to access "as if" the wrapper "were" the object itself.
There are some ruby tricks to get a list of only the API methods, and define wrapper methods via an eval loop. But .. it's still can get complicated in other nitty-gritty ways.
I just do not recommend it.
Advertisement