I am trying to create an extension but cannot seem to find how to turn my draw method into a component.
Post a snippet from your code, otherwise we can only guess what might be wrong.
Please first read the wiki post on how to post code in the forum.
(Convert any TABs to 2 spaces for Ruby.)
As a general pattern, you add a group to the model entities, add your drawing elements to the group’s entities, and then make a component from the group.
Did you find a solution to your problem?
Here’s what I’m working on
#extension.rb
require 'sketchup.rb'
require 'extenions.rb'
model=Sketchup.active_model
entities=Sketchup.active_model.entities
UI.menu("Extensions").add_item("Extension") {
UI.messagebox("Welcome to Extension. Select your trim, select the path you wish it to extrude on, and click ok.")
#Method
draw_trim_profile
}
def draw_trim_profile
#Create variables
puts"Trim Height"
trim_height=gets.chomp
puts"Trim Width"
trim_width=gets.chomp
puts"Quarter Round"
quarter_rount_width=gets.chomp
quarter_round_height= quarter_rount_width
#Create points to build the trim profile (new face)
points=[
Geom::Point3d.new(0,0,0)
Geom::Point3d.new(0,0,height)
Geom::Point3d.new(0,trim_width,trim_height)
Geom::Point3d.new(0,trim_width+quarter_rount_width,0)
Geom::Point3d.new(0,trim_width+quarter_rount_width,quarter_round_height)
Geom::Point3d.new(0, trim_width,quarter_round_height)
]
face=entities.add_face(points)
#Modeling the new trim profile (calling methods to entity collection)
new_face.material=[220,220,220]
new.face.followme(edge)
end
-
Do not double post. It is a violation of the forum guidelines.
Please fix the first, and delete the second. -
As I already said above, please post code properly in these forums.
Read: [How to] Post correctly formatted and colorized code on the forum? -
All of your code needs to be within a toplevel author namespace module.
Then each one of your extensions should be in it’s own submodule inside your namespace module. -
These statements …
require 'sketchup.rb' require 'extenions.rb'
… are not needed as SketchUp loads them automatically before loading extensions.
(This has been done since SketchUp version 2014 and it’s use of Ruby 2.x.) -
Use an extension registrar filename that is unique. The “Plugins” folder file space is a shared space. Your extension registrar script (in the “Plugins” folder,) should be named like:
"CaydenWilson_TrimProfiler.rb"
, and the extension subfolder needs to have the same name (without the “.rb
” extension,) ie: folder"CaydenWilson_TrimProfiler"
See:
class
SketchupExtension
The filenames within your extension subfolder(s) can be whatever you wish. Ie:
"TrimProfiler_main.rb"
or"main.rb"
, etc. One of these files will be the loader file that loads all the rest of the extensions files (if there are any.) -
Do not open a model messagebox during SketchUp startup. It will block the startup cycle and the user will have to dismiss the messagebox manually. (A good way to anger users.)
Instead, use aUI::Notification
balloon (that can slide into view down in the lower right corner) if you really must inform the user of an important fact during startup. -
You cannot use
gets
in SketchUp’s embedded Ruby, because standard IO is not like in a normal Ruby shell (IRB.) SketchUp’s Ruby Console has hijacked it.
Instead, you must useUI.inputbox
method to display entry boxes or dropdown select controls for the user. -
As you work on an extension, you’ll be reloading it manually as your make changes to methods.
This means you must protect the UI building (commands, menus, toolbars) so they only get created once when your module loads the first time.
This will be your pattern …
# encoding: UTF-8
module CaydenWilson
module ProTrim
extend self
# CONSTANTS go here ...
# Module variables go here ...
def draw_trim_profile
# Method code goes here ...
end
# More methods go here ...
if !@loaded
# Create commands, submenus, menu items
# and toolbar objects here:
UI.menu('Extensions').add_item('CW Designs: ProTrim') {
draw_trim_profile()
}
@loaded = true
end
end
end
- A few notes on some coding …
points=[
Geom::Point3d.new(0,0,0)
Geom::Point3d.new(0,0,height)
Geom::Point3d.new(0,trim_width,trim_height)
Geom::Point3d.new(0,trim_width+quarter_rount_width,0)
Geom::Point3d.new(0,trim_width+quarter_rount_width,quarter_round_height)
Geom::Point3d.new(0, trim_width,quarter_round_height)
]
… will raise a SyntaxError
. The elements of array literals must be separated with a comma.
In SketchUp API many methods that take points will also take 3 element arrays as synonymous with a point (or a vector.) So you do these kind of things …
points=[
[0,0,0],
[0,0,height],
[0,trim_width,trim_height],
[0,trim_width+quarter_rount_width,0],
[0,trim_width+quarter_rount_width,quarter_round_height],
[0, trim_width,quarter_round_height]
]
If you really want to map the arrays to actual Geom::Point3d
objects then afterward call …
points.map! {|a| Geom::Point3d.new(a) }
… and now each member is converted to a Geom::Point3d
object.
This code …
face=entities.add_face(points)
#Modeling the new trim profile (calling methods to entity collection)
new_face.material=[220,220,220]
new.face.followme(edge)
Has several reference errors. You create face
, but then refer to it as new_face
and then new.face
. (Beware that “new” is the name of a Ruby class’ constructor method and should never used for a variable name.)
Back to the original question …
You do not draw an instance. You add a new ComponentDefinition to the DefinitionList collection.
Then you add entities to the definition’s entities collection. And lastly you add an instance of this new definition to the model’s entities collection.
If you wish to modify a component instance that already exists, then you modify it’s definition’s entities. The result will be that all of that definition’s instances will be modified.
If you only wish to modify the single instance, then you’ll need to make it unique (which clones it’s definition to make a copy.) Then you’ll modify that new definition (which means you must access the definition after the call to #make_unique
), ie …
new_cdef = inst.make_unique.definition
new_ents = new_cdef.entities
So, making unique leaves all the instances of the old definition as they were.
Here’s what I did so far…
#protrim.rb
module CaydenWilson
module ProTrim
extend self
#Creating a toolbar icon
toolbar = UI::Toolbar.new "ProTrim"
cmd = UI::Command.new("ProTrim") {
}
cmd.small_icon = "ToolPencilSmall.png"
cmd.large_icon = "ToolPencilLarge.png"
cmd.tooltip = "ProTrim Toolbar"
cmd.status_bar_text = "Loading ProTrim"
cmd.menu_text = "ProTrim"
toolbar = toolbar.add_item cmd
toolbar.show
#Creating menu buttons for extenion and its functions
menu=UI.menu('Extensions')
menu.add_item("ProTrim") {puts 'Base Trim'}
menu.add_item("ProTrim") {puts 'Door/Window Trim'}
menu.add_item("ProTrim") {puts 'Crown Molding'}
#Creating our extension
model=Sketchup.active_model
entities=Sketchup.active_model.entities
UI.menu("Extensions").add_item("ProTrim") {
notification=UI.notification.new(sketchup_extension, "Welcome to ProTrim. Select your trim, select the path you wish it to extrude on, and click ok.")
notification.show
#Method
draw_trim_profile
draw_casing_profile
draw_molding_profile
}
#Creating draw_trim_profile method
def draw_trim_profile
puts"Trim Height"
trim_height=UI.inputbox
puts"Trim Width"
trim_width=UI.inputbox
puts"Quarter Round"
quarter_rount_width=UI.inputbox
quarter_round_height= quarter_rount_width
#Create points to build the trim profile (new face)
points=[
[0,0,0],
[0,0,height],
[0,trim_width,trim_height],
[0,trim_width+quarter_rount_width,0],
[0,trim_width+quarter_rount_width,quarter_round_height],
[0, trim_width,quarter_round_height]
]
points.map! {|a|Geom::Point3d.new(a)}
face=entities.add_face(points)
#Modeling the new trim profile (calling methods to entity collection)
trim_texture=[220,220,220]
face.material=[trim_texture]
Sketchup::Material.back_material=[trim_texture]
#Creating a path for trim to follow
selection = model.selection
face.followme(selection)
end
#Creating draw_casing_profile method
def draw_casing_profile
puts"Casing Depth"
casing_depth=UI.inputbox
puts"Casing Width"
casing_width=UI.inputbox
#Building a new casing profile (face) based on the input collected
points=[
[0,0,0],
[0,0,casing_depth],
[casing_width,0,casing_depth],
[casing_width,0,0]
]
points.map! {|a|Geom::Point3d.new(a)}
face=entities.add_face(points)
casing_texture=[220,220,220]
face.material=[casing_texture]
Sketchup::Material.back_material=[casing_texture]
selection=model.selection
face.followme(selection)
end
#Creating draw_molding_profile method
def draw_molding_profile
puts"Molding Drop"
molding_drop=UI.inputbox
puts"Molding Projection"
molding_projection=UI.inputbox
puts "Cornice Size (Inches)"
molding_cornice_height=UI.inputbox
molding_corice_width=molding_cornice_height
molding_crown
#Building a new casing profile (face) based on the input collected
points=[
[0,0,0],
[0,0,casing_depth],
[casing_width,0,casing_depth],
[casing_width,0,0]
]
points.map! {|a|Geom::Point3d.new(a)}
face=entities.add_face(points)
casing_texture=[220,220,220]
face.material=[casing_texture]
Sketchup::Material.back_material=[casing_texture]
selection=model.selection
face.followme(selection)
end
if !@loaded
UI.menu('Extensions').add_item('ProTrim') {
draw_trim_profile()
}
@loaded=True
end
end
end
module CustomWindow
extend self
#Creating a menu icon
menu = UI.menu('Extensions')
menu.add_item("CustomWindow") { puts 'CustomWindow' }
extensions_menu=UI.menu("Extensions")
item=extensions_menu.set_validation_proc(item) {
if Sketchup.is_pro?
MF_ENABLED
else
MF_GRAYED
end
}
#Adding item to Sketchup menu
tool_menu=UI.menu("Tools")
tool_menu.add_item("CustomWindow")
#Creating a notification box with an icon and tooltip
UI::Notification.new(sketchup_extension, "/path/to/icon","icon Tooltip")
puts "CustomWindow: #{notification.tooltip}"
notification.icon_tooltip="icon Tooltip"
notification.message="Launched CustomWindow"
#Closing the notification with a button
notification.on_dismiss("Close") do |notification, title|
puts "The user pressed [#{Dismiss}] with message #{notification.on_dismiss_title}"
end
notification.show
#Creating method
puts"Width (Inches)"
width=UI.inputbox
puts"Height (Inches)"
height=UI.inputbox
puts"Casing Width"
casing_width=UI.inputbox
depth=4
#Fixed window creation
puts"Fixed Window"
fixed=UI.inputbox
if fixed=True
puts"Number of Panes"
pane_number=UI.inputbox
grill_width=3/4
grills=pane_number-1
sash=1.5
pane_width=(width-casing_width*2+sash*2+grills*grill_width)/pane_number
pane_height=(height-casing_width*2+sash*2+grills*grill_width)/pane_number
#Double hung window creation
puts"Double Hung"
double=UI.inputbox
if double=True
puts"Number of Panes"
pane_number=UI.inputbox
grill_width=3/4
grills=pane_number-1
sash=1.5
offset=1
pane_width=(width-casing_width*2+sash*2+grills*grill_width)/pane_number
pane_height=(height-casing_width*2+sash*3+3/4+grills*grill_width)/pane_number
model=Sketchup.active_model
entities=model.active_entities
#Creating points to build the model
pts=[]
#[x,y,z] x-width, y-depth, z-height
#front face border points
pts[1]=[0,0,0],
pts[2]=[width,0,0],
pts[3]=[0,0,height],
pts[4]=[width,0,height],
#back face border points
pts[5]=[0,depth,0],
pts[6]=[width,depth,0],
pts[7]=[0,depth,height],
pts[8]=[width,depth,height],
#Face casing points
pts[9]=[casing_width,0,casing_width],
pts[10]=[width-casing_width,0,casing_width],
pts[11]=[casing_width,0,height-casing_width],
pts[12]=[width-casing_width,0,height-casing_width],
#Creating the face
face=entities.add_face(pts)
#Creating the materials
materials=model.materials[0]
#Glass
material=materials.add('Window Glass')
material.alpha=5
puts material.name
puts material.display_name
type=material.solid
material.save_as(glass.skm)
#Window Interior
material=materials.add('Window Interior')
material.alpha=100
puts material.name
puts material.display_name
type=material.solid
material.save_as(windowinterior.skm)
#Window Exterior
material=materials.add('Window Exterior')
material.alpha=100
puts material.name
puts material.display_name
type=material.solid
material.save_as(windowexterior.skm)
#Coloring Faces
face.back_material="Window Interior"
material=face.back_material
status=face.back_material="Window Interior"```
Your last post declares a module, uses the extend self
statement (which extends the module with it’s own methods,) but you’ve not defined any methods.
Also, there is no end
at the bottom to close the module
block.
The doc clearly says the argument is a Float
between 0.0
and 1.0
Again the docs tell you the argument is a text String
for the path where the .skm
file is to be saved, not just the filename.
Never assume the current working directory is where you want it. Other plugins could have changed it.
It always best to specify full absolute pathnames.
… is incorrect. You’ve referenced the “Extensions” menu twice, and failed to properly get the item
reference when you created it.
Get the item
reference like so …
#Creating a menu icon
menu = UI.menu('Extensions')
item = menu.add_item("CustomWindow") { puts 'CustomWindow' }
menu.set_validation_proc(item) {
if Sketchup.is_pro?
MF_ENABLED
else
MF_GRAYED
end
}
… not that this has much effect as there are no more Make editions since 2017, so most everything will be running under a Pro edition.
But your menu command does nothing except send the text “CustomWindow” to STDOUT (which noone will see unless they have the Console window open.
This is not how the UI.inputbox
method works. It needs a set of arguments to know what to display for the caption, it’s control labels, and the default values to stuff into the entry boxes. It also returns an array of the edited values (or false if the user cancelled the inputbox.)
def get_window_size
caption = "Window Size"
prompts = [ "Width (Inches)", "Height (Inches)", "Casing Width" ]
defaults = [ 30.inches, 60.inches, 8.inches ]
results = UI.inputbox( prompts, defaults, caption )
return false if !results
width, height, depth = results
end
You really need to begin reading the API documentation and studying the examples.
There are full extension examples by the SketchUp Team in the Extension Warehouse.
One of them is called “Window Maker”.
First I just want to say thank you for all the help you’ve provided. I’m probably taking off a bit more than I can chew, but I am willing to do my best to make this work. To sum up my big idea, I’m trying to make extensions to automate modeling home parts such as windows, doors, trim, cabinets, and stairs. I’m fifteen and started a business building CAD and rendering those models for custom home builders, and with school it is hard to complete projects, so this new software is kind of my last ditch effort to take on more business. As far as your advice goes…
I’ve been trying to use the API guide, I just don’t know for sure what some of it means and am very new to it. If there’s some way to understand it better or any tips you may have please let me know.
Could you provide me with the SketchUp example extension names? I didn’t know they existed and would love to use them
Thanks again!
Well first of all, the API is an extension to Ruby language. It really helps to learn Ruby first.
There is a pinned topic here in this category I made just for people like yourself …
There are quite a few free downloadable books for you to read in your spare time (on the bus, on the “pot” etc.)
Secondly, the SketchUp API dictionary is a technical reference and not a learning text. It’s code examples are notorious for being frivolous or erroneous.
There is also much left unsaid in the API documentation (ie, tips and tricks and pitfalls, etc.) So when things act weird, come here and enter this category, and then use the menu (upper right) to search only this category. Chances are someone else has already run into the same “challenge”.
I strongly suggest first reading the primers on Ruby and then starting out with simple snippets in the console (or one of the console editor extensions) to get the feel of coding geometry.
Then move on to tasks a bit more complex, little by little. Otherwise you will become overwhelmed.
When you are finally ready to start a SketchUp extension, make sure you read and understand the organization of an extension’s files, and the SketchupExtension
class …
The biggest thing you need to understand about embedded programming for an application is that it is event driven programming.
The code is sitting in memory waiting for the user to cause an event that fires off parts of your code.
It might be a simple thing like a new model was loaded, or the user clicked a toolbar button or made a menu choice.
Your code’s methods react to the events.
There are quite a few others that have already done all of these things in various modeling extensions, … so it is possible.
Well more power to ya’ … being young it’s often easier to learn new things. I encourage you to learn correctly instead of skipping ahead and losing your way. What I mean is build a solid general programming and Ruby syntax foundation instead of hacking away at a complex extension.
One of the best things to learn is coding style. There are some guides out there.
But basically Ruby style is readability. You do not help the interpreter by leaving out spaces.
(Spaces actually help the interpreter divide the code into parsing tokens.)
A blank line takes up only 2 invisible characters.
- Example Ruby Scripts
- Utilities Tools
- Window Maker
- Bezier Curve Tool
- Grid Tool
- Shapes (shows use of parametric inputboxes)
- Rotated Rectangle Tool Example
Likely others at their EW store page.
Also there are example repository on GitHub …
Also I myself have posted quite a few example right here in this category.
Do a search on “[Example]” or “[code]” etc.
In your last two code posts … you defined UI objects outside the if !@loaded
block at the end of your module code.
These definitions need to be moved to the end, inside that conditional block so that they only are created once when the module loads the first time.
You will find yourself editing methods and reloading the module as you develop. (Ruby is a dynamic language which means you can modify modules, classes and their methods at any time by reloading the code files. See the global load
method.)
Also when the blocks for menu and toolbar commands are defined, you’ll want to call a method within them so that these method(s) can be redefined as you encounter bugs and fix them, reload, text, find bug, fix, reload, … repeat.
If the command block comes before the method definition that it calls, Ruby may raise an exception.
I’d recommend people refer to our GitHub examples over these older examples. They are due for a rewamp and doesn’t always show of best practices.
Well file issues.