Examples of proper extension organization


#1

I am learning Ruby, and learning the SketchUp API. One thing I am struggling to grasp is the subtle differences between Classes, Methods, and Modules, and how to split up code amongst multiple files. Although I have a couple of Ruby books that I am studying, I don’t seem to be connecting with the examples they provide. I am able to fudge my way through and have successfully made my first extension, but I know the code is horribly organized. And I think there are situations where I’ve defined a class, where really I could’ve just used a method… I’m also struggling with the correct use of Constant variables, where I want to define a variable that I can use across my entire extension.

I’m curious to see if there are examples of SketchUp extensions that you’d recommend that use multiple files, so I can read the code and follow along and sort of “figure out” why it’s organized the way it is, and what is located in a separate file vs what’s included in the main file. I think the context would really help me recognize when a class is more appropriate than a method, and how granular you should get when creating classes. I’ve read a book that really drills down the idea of making each class only do ONE thing (If your class does more than one thing, they should be two classes). Just wondering what would be considered a good, clean, well organized SketchUp extension.

I am aware of the SketchUp Ruby API Tutorials and some of the simple plugin examples by the SketchUp team. But the examples are sort of wrapped up into a “parent” extension so you don’t have to install each one individually, so that confuses me a little. I am also aware of the Multi-file template by @DanRathbun, which is great, but since (of course) the template is blank, it’s hard for me to understand exactly what I should be doing with it.

So if there are any simple examples of a multi-file extension that demonstrates good Ruby practices, I would love to study them. Thank you for your help. I’m having a lot of fun with it so far. It really gets your imagination going with what you can do with SketchUp.


#2

Very generally each file contains one single class or module (not counting wrapping modules). Each method should correspond to one distinct action, and each class/module to one distinct concept. If you find yourself writing headings within your code, either to split up methods or classes/modules in logical chunks, you should most likely really split them into separate methods, classes and modules. One rule I like to use is that if a part of an action can be named, extract it to a separate method by that name.

As an example I would actually recommend the Ruby API, even though you can’t see its source code. It can still serve as an introduction for what things can be put into different classes, and how shared behavior can be defined in a common superclass. There are some oddities though, such as how lines and planes are represented by a specifically formatted array. If you find yourself defining a specific array format to use as return value or parameter, you should typically make it a class instead.

Most of my open source extensions are quite simple, one file scripts (the exception being my railroad system, but that is a very old project with shitty code). However ThomThom may have something.

This technically isn’t a plugin, but a library intended to be used withing plugins, but may still be useful to get an idea of how code can be organized. It is a bit unconventional though to use modules that map to API classes, rather than classes which is what you’d typically do. It however shows how you can break down the code into small building blocks.


#3

Maybe a bit off topic, but another rule that helped me write better code is that a comment should never tell what the code does, but why it does it. What the code does should be evident from the code itself, and not need comments. Keeping this in mind helped me to better organize my code.


#4

A constant should not be changed, but still can be in Ruby for now, (this might change in the future) and hence is not really a variable.

That said, Ruby really does not have variables per se, it has references that point at objects, which can be redirected to point at any other object (of any kind) at any time.

This example just shows how to create a mixin module for constants that you can “mix” into any other submodule or class using the global include() method. When you do this the constants can be shared. You can do the same with @@vars (ie, module variables.)

Check out Thomas Thomassen’s plugins at his BitBucket repos.



#5

Also the official organization of extension files …


#6

This I get. Everything is an object in Ruby.

So when I say I want a constant throughout my extension, I understood it as the object assigned to the constant never changes, but I could redefine any of the objects attributes no problem. For context, I want to assign something for default height which can be defined by the user in the UI.inputbox, then referred to by various classes that need to know the default height.

Oh this is helpful! I knew I could define Class scope variables with @@, but never thought of it in the context of a module variable. I will look more into this. I think this may be really how I should be thinking of creating objects that I can share throughout my extensions; by mixing in modules that have @@ module variables defined.


#7

This makes a lot of sense, and seems especially helpful when deciding how to name variables and classes. Thanks!


#8

Most of my recent extensions are multi-file. My older might be single-file (either because they were really small - but more likely because back then I didn’t worry too ahrd about code readability and re-use).

One of my more recent extensions are TrueBend: https://github.com/thomthom/true-bend/tree/master/src

I have most of my extensions available on GitHub and BitBucket:


https://bitbucket.org/thomthom/

That’s the gist of it. When you have a set of data that represent a “thing” - make a class out of it. And make that class do be responsible for that domain.

I also prefer to put each class/module in their separate file. Makes it easier to reuse. And less scrolling up and down to find a piece of code.

Making code more readable and reusable is something that takes longer to learn. (You never stop working on that.)

Currently my general approach is that if I start to have a growing number of private methods in a class then it’s often a code smell indicating it belong somewhere else. Maybe a new class.

Another thing I’ve started doing more is making classes for readability.

Consider the case of face.position_material, it takes pairs of Point3d and UVs, up to four pairs:

# Mapping a 1x1" face with a material:
face.position_material([
  Geom::Point3d.new(0, 0, 0),
  Geom::Point3d.new(0, 0, 0),
  Geom::Point3d.new(1, 0, 0),
  Geom::Point3d.new(1, 0, 0),
  Geom::Point3d.new(1, 1, 0),
  Geom::Point3d.new(1, 1, 0),
  Geom::Point3d.new(0, 1, 0),
  Geom::Point3d.new(0, 1, 0),
])

Which are the model points and which are the UVs?

If we introduce something like this:

class UV < Geom::Point3d; end

Then we can write the code like this:

# Mapping a 1x1" face with a material:
face.position_material([
  Geom::Point3d.new(0, 0, 0),
  UV.new(0, 0),
  Geom::Point3d.new(1, 0, 0),
  UV.new(1, 0, 0),
  Geom::Point3d.new(1, 1, 0),
  UV.new(1, 1),
  Geom::Point3d.new(0, 1, 0),
  UV.new(0, 1),
])

Easier to reason about. Now, you could go and be completely purist and make UV not be a subclass of Geom::Point3d - ensuring you get an error if you try to mix them. But then you’d need a #to_point method to convert it to Geom::Point3d which the SU API expects. IMO, going the subclass route in this case is a nice compromise of readability and usability.

I do similar stuff for plane and line objects:

class Line < Array
  # This custom initializer contrains the Line subclass to raise errors if you feed it more parameters than
  # a "line" should have.
  def initialize(point, vector)
    super(point, vector)
  end
end

You could even go further and make the mutators of your subclass private - preventing accidental modifications.


#9

This is a great example! Maybe more advanced than what I can comprehend right now, but I’m going to explore it anyways and try to learn from it. I’m going to explore some of your other extensions on GitHub too. Thanks!

And thank you for the further example on how to make code more readable/functional. Makes sense.


#10

Ask if you have any questions. (And remember that what you see in my extensions isn’t always best practices - I learn and evolve as well.)

While on this topic; after Basecamp 2018 - and our DevCamp session - we were wondering what kind of topics would be interesting/useful for future presentations. Would something in the lines of coding patterns be of interest?


#11

I think it would! Especially given how many extension developers aren’t trained developers. There is also a risk of bad patterns spreading in the community when there is no “authority” showing what is to be preferred.

Obscure variable names like mod (model), ss (selection) are very common. I used them myself for a long time (and still wonder why there is 2 s in the common abbreviation of selection :open_mouth: ).

I’ve also seen a Ruby C extension where the compiled file is renamed (Ruby build and OS removed from name) before being loaded, to make the file name match the init function. This would break if the user don’t have write permission to the folder the plugin is installed to.

There have also been extensions breaking due OS file order changing, as they relied on files with lower file name being loaded first.

It would be good with easily accessed examples showing what you are supposed to be doing, rather than the blind leading the blind.


#12

This post was flagged by the community and is temporarily hidden.


#13

Post deleted


#14

At run-time on the end user’s machine?


#15

At runtime on the user’s machine, in the install location, just before loading it.


#16

The reason is simply that .so files are locked by Sketchup at runtime.
So you cannot install an update and overwrite the .so file while Sketchup is running, which would be the case if you install from the Extension Warehouse for instance.
You have to wait for a restart of Sketchup and do the replacement before the .so file is loaded.
This has nothing to do with good or bad programming practices.


#17

Selection Set?


#18

my feeling on abbreviations are that they ‘clarify’ that this is ‘our’ code…

when I first started reading plugins I found it harder to follow code using API words as ‘local’ definitions…

the easiest to follow used things like ‘this_model’, ‘my_group’, ‘our_definitions’, etc…

but ones using sensible shortening actually helped me follow the code flow…

e.g.

  mod   = Sketchup.active_model
  ents  = model.entities
  defs  = model.definitions
  sel   = model.selection

when it says it on the top of the tin, I see no point in throwing the baby out with the bathwater…

john


#19

I can understand that beginners who don’t yet know the syntax do that, but I think it’s something we should try not to encourage as it’s a bad habit. To much code has variables like ents, ents1, g, infqr, hrbnf and such.


#20

Can you point me to any such extension? I’d like to have a close look.