I’m trying to create an extension with “grooves” added, but I can’t seem to find the right solution. The plugin should work like this: I select a face, then select an edge and fill in the options in the dialog box (groove width, groove depth, groove offset). The length of the groove is equal to the length of the selected edge. I faced two problems: 1 - How to correctly define the offset vector of the groove? (The groove must always be built on the selected face and move to the opposite edge of this face regardless of the placement of the face relative to the axes). 2 - The task of constructing a face for a groove. The below code I am trying to use is outputting a message “Points are no planar”. Please help me to fix these problems.
Ruby
def create_groove
model = Sketchup.active_model
model.start_operation("Create Groove", true)
group = @selected_face.parent.entities.add_group
group_entities = group.entities
edge_vector = @selected_edge.line[1].normalize
offset_vector = @selected_face.normal * edge_vector
offset_vector.length = @groove_offset
# Застосування зміщення для створення початкових точок канавки
offset_start_point = @selected_edge.start.position.offset(offset_vector)
offset_end_point = @selected_edge.end.position.offset(offset_vector)
# Визначення додаткових точок для формування ширини канавки
point3 = offset_end_point.offset(edge_vector, @groove_width)
point4 = offset_start_point.offset(edge_vector, @groove_width)
# Створення лиця безпосередньо з точок
begin
new_face = group_entities.add_face(offset_start_point, offset_end_point, point3, point4)
new_face.pushpull(-@groove_depth, true) if new_face.valid?
rescue ArgumentError => e
UI.messagebox("Error creating groove: #{e.message}")
ensure
model.commit_operation
end
end
Ruby
def create_groove
model = Sketchup.active_model
model.start_operation("Create Groove", true)
group = @selected_face.parent.entities.add_group
group_entities = group.entities
edge_vector = @selected_edge.line[1].normalize
offset_vector = @selected_face.normal * edge_vector
offset_vector.length = @groove_offset
# Applying offset to create starting points for the groove
offset_start_point = @selected_edge.start.position.offset(offset_vector)
offset_end_point = @selected_edge.end.position.offset(offset_vector)
# Defining additional points to form the width of the groove
point3 = offset_end_point.offset(edge_vector, @groove_width)
point4 = offset_start_point.offset(edge_vector, @groove_width)
# Creating the face directly from points
begin
new_face = group_entities.add_face(offset_start_point, offset_end_point, point3, point4)
new_face.pushpull(-@groove_depth, true) if new_face.valid?
rescue ArgumentError => e
UI.messagebox("Error creating groove: #{e.message}")
ensure
model.commit_operation
end
end
You should not create your group until the last millisecond before using it.
The main problem is likely that you are trying to draw the groove in a new subgroup whose origin will be the parent entities origin. So, will the offset points be where you really want them?
What if you just add the groove to the face’s parent entities?
Thanks for the recommendations. I tried to change the code of the create_groove function. Unfortunately I get the non-coplanar points error again. Here’s what I changed:
def create_groove
model = Sketchup.active_model
model.start_operation("Create Groove", true)
edge_vector = @selected_edge.line[1].normalize
offset_vector = @selected_face.normal * edge_vector
offset_vector.length = @groove_offset
# Checking if an offset point lies on a face
test_point = @selected_edge.start.position.offset(offset_vector)
if @selected_face.classify_point(test_point) != Sketchup::Face::PointInside
offset_vector.reverse!
end
# Apply offset to create groove start points
offset_start_point = @selected_edge.start.position.offset(offset_vector)
offset_end_point = @selected_edge.end.position.offset(offset_vector)
# Determination of additional points for forming the width of the groove
edge_vector.length = @groove_width
point3 = offset_end_point.offset(edge_vector)
point4 = offset_start_point.offset(edge_vector)
# Creating a face directly from points on the parent entities of the selected face
begin
new_face = @selected_face.parent.entities.add_face(offset_start_point, offset_end_point, point3, point4)
new_face.pushpull(-@groove_depth, true) if new_face.valid?
rescue ArgumentError => e
UI.messagebox("Error creating groove: #{e.message}")
ensure
model.commit_operation
end
end
Okay, the error message is wrong, The points are coplanar, but point3 was outside the selected face.
Bascially, you were using the wrong vector, i.e., the selected edge’s vector which runs along the edge.
I refactored and renamed point3 and point4 and used the offset_vector.
WAS:
# Determination of additional points for forming the width of the groove
edge_vector.length = @groove_width # <<<<-------<< WRONG VECTOR
point3 = offset_end_point.offset(edge_vector)
point4 = offset_start_point.offset(edge_vector)
Changed TO:
# Determination of additional points for forming the width of the groove
offset_vector.length = @groove_width
width_start_point = offset_start_point.offset(offset_vector)
width_end_point = offset_end_point.offset(offset_vector)
Since the code was done with using offsset_vector’s length to create the two offset points,
we can reuse it but change it’s length again to create the points across the groove (by the width.)
Also, the “copy” argument should not be used in the pushpull method call.
Here is the test module, use: GrooveTest.go to fire the command after selecting the face and edge:
module GrooveTest
extend self
@selected_edge = nil
@selected_face = nil
@groove_offset = 1.inch
@groove_width = 0.25.inch
@groove_depth = 0.1.inch
def go
model = Sketchup.active_model
sel = model.selection
if sel.empty?
@selected_edge = nil
@selected_face = nil
else
@selected_face = sel.grep(Sketchup::Face).first
@selected_edge = sel.grep(Sketchup::Edge).first
end
if @selected_face.nil? || @selected_edge.nil?
UI.beep
return UI.messagebox("A face and an edge must both be selected!")
end
points = get_groove_points()
create_groove(points)
end
def get_groove_points
edge_vector = @selected_edge.line[1].normalize
offset_vector = @selected_face.normal * edge_vector
offset_vector.length = @groove_offset
# Checking if an offset point lies on a face
test_point = @selected_edge.start.position.offset(offset_vector)
if @selected_face.classify_point(test_point) == Sketchup::Face::PointOutside
offset_vector.reverse!
end
# Apply offset to create groove start points
offset_start_point = @selected_edge.start.position.offset(offset_vector)
offset_end_point = @selected_edge.end.position.offset(offset_vector)
# Determination of additional points for forming the width of the groove
offset_vector.length = @groove_width
width_start_point = offset_start_point.offset(offset_vector)
width_end_point = offset_end_point.offset(offset_vector)
points = [
offset_start_point, offset_end_point, width_end_point, width_start_point
]
#puts points.inspect
return points
end
def create_groove(points)
model = Sketchup.active_model
# Creating a face directly from points on the parent entities of the selected face
begin
model.start_operation("Create Groove", true)
# Working entitites:
ents = @selected_face.parent.entities
# Groove face:
groove_face = ents.add_face(points)
#puts groove_face.inspect
groove_face.pushpull(-@groove_depth) if groove_face.valid?
rescue => e
UI.messagebox("#{e.class.name} Error creating groove: #{e.message}")
else
model.selection.clear
ensure
model.commit_operation
end
end
end # GrooveTest module
Thank you very much. It works. After seeing your code, fixes and explanations, I now understand where I went wrong. Thank you for the lesson and attention to my questions.
I would like to ask you to solve another problem that I am facing and I don’t know how to solve it. The tool code works well on unscaled objects. But if I scale the group, the face creation points are calculated incorrectly. How can this be solved for scalable objects?
class GrooveTool
def initialize
@selected_face = nil
@selected_edge = nil
@transformations = []
@step = 1
update_status_text
end
def activate
reset_tool
end
def deactivate(view)
view.invalidate
end
def onMouseMove(flags, x, y, view)
ph = view.pick_helper
ph.do_pick(x, y)
path = ph.path_at(0)
if path
last_entity = path.last
if @step == 1 && last_entity.is_a?(Sketchup::Face)
@selected_face = last_entity
@transformations = compute_transformation(path)
view.invalidate
elsif @step == 2 && last_entity.is_a?(Sketchup::Edge)
@selected_edge = last_entity
@transformations = compute_transformation(path)
view.invalidate
end
end
end
def onLButtonDown(flags, x, y, view)
if @step == 1 && @selected_face
@step = 2
update_status_text
elsif @step == 2 && @selected_edge
prompt_for_groove_parameters
reset_tool
end
view.invalidate
end
def draw(view)
draw_highlight(view)
end
private
def reset_tool
@selected_face = nil
@selected_edge = nil
@transformations = []
@step = 1
update_status_text
end
def update_status_text
Sketchup.status_text = case @step
when 1
"Оберіть площину"
when 2
"Оберіть край на площині"
end
end
def prompt_for_groove_parameters
prompts = ["Groove width (mm)", "Groove depth (mm)", "Groove offset (mm)"]
defaults = ["10", "5", "50"]
input = UI.inputbox(prompts, defaults, "Setting groove")
if input
@groove_width, @groove_depth, @groove_offset = input.map { |value| value.to_f / 25.4 }
create_groove
end
end
def create_groove
return UI.messagebox("Необхідно виділити як площину, так і край!") if @selected_face.nil? || @selected_edge.nil?
model = Sketchup.active_model
points = get_groove_points()
begin
model.start_operation("Create Groove", true)
groove_face = @selected_face.parent.entities.add_face(points)
groove_face.pushpull(-@groove_depth) if groove_face && groove_face.valid?
rescue => e
UI.messagebox("Error creating groove: #{e.message}")
ensure
model.commit_operation
end
end
def get_groove_points
edge_vector = @selected_edge.line[1].normalize
offset_vector = @selected_face.normal * edge_vector
if @groove_offset > 0
offset_vector.length = @groove_offset
test_point = @selected_edge.start.position.offset(offset_vector)
if @selected_face.classify_point(test_point) == Sketchup::Face::PointOutside
offset_vector.reverse!
end
offset_start_point = @selected_edge.start.position.offset(offset_vector)
offset_end_point = @selected_edge.end.position.offset(offset_vector)
offset_vector.length = @groove_width
width_start_point = offset_start_point.offset(offset_vector)
width_end_point = offset_end_point.offset(offset_vector)
else
offset_start_point = @selected_edge.start.position
offset_end_point = @selected_edge.end.position
width_vector = @selected_face.normal * edge_vector
width_vector.length = @groove_width
test_width_point = offset_start_point.offset(width_vector)
if @selected_face.classify_point(test_width_point) == Sketchup::Face::PointOutside
width_vector.reverse!
end
width_start_point = offset_start_point.offset(width_vector)
width_end_point = offset_end_point.offset(width_vector)
end
points = [
offset_start_point, offset_end_point, width_end_point, width_start_point
]
return points
end
def compute_transformation(path)
transformation = Geom::Transformation.new
path.each do |entity|
if entity.respond_to?(:transformation)
transformation *= entity.transformation
end
end
transformation
end
def draw_highlight(view)
if @selected_face
points = @selected_face.outer_loop.vertices.map(&:position)
transformed_points = points.map { |point| point.transform(@transformations) } if @transformations.is_a?(Geom::Transformation)
view.drawing_color = 'yellow'
view.draw(GL_POLYGON, transformed_points)
end
if @selected_edge
points = @selected_edge.vertices.map(&:position)
transformed_points = points.map { |point| point.transform(@transformations) } if @transformations.is_a?(Geom::Transformation)
view.line_width = 5
view.drawing_color = 'blue'
view.draw(GL_LINES, transformed_points)
end
end
end
Sketchup.active_model.select_tool(GrooveTool.new)