Extend Tool (3D)


#1

I recently added another trim tool (Trim 2) to the Medeek Truss Plugin. This new tool allows the user to select two (non-coplanar faces) and then trim a solid group of component with them. I also have the regular trim tool which does the same thing but with a single face.

For some time now I’ve been wanting to create an extend tool. The most common utilization would be for extending rafters or studs to meet a wall or plate (face).

The problem I am seeing with this feature is how to determine which edges to extend. I can’t really base it on axis orientation since studs are typically drawn along the Z-axis and joists and other elements such as rafters could be along an arbitrary axis.

I can’t really base it on edge length either since you may be starting with a very short stud or block and the edges that you want to extend may be shorter than the end edges.

I don’t know that there is an easy solution to this problem.

Just putting it out there to see if anyone has any ideas on this topological problem.


#2

After giving this some more thought here is my proposed algorithm, feel free to find the holes in it:

1.) User selects the face (plane) they want to extend to.(target face).

2.) User selects the (end) face of the stud, joist, beam or rafter they want to extend.

(now the algorithm in the API)

3.) Find all of the vertices and (edges?) belonging to selected face in step 2.

4.) Find each edge attached to the vertices in step 3 that is not belonging to the edges that make up the face.

5.) Check each edge found in step 4 by assigning a vector to one of them and then checking that each edge is parallel to this vector. If they are all parallel then proceed to step 6, if not abort.

6.) Check that the normal of the target face (step 1) is not perpendicular to the vector in step 5, if it is abort.

7.) Calculate the geometric line for each parallel edge (step 4). Then find the point (new vertex) for each line with the intersect_line_plane method and the target plane (step 1).

8.) Calculate the distance between the new vertex (point) and the existing vertex (point), then move each vertex the calculated distance in the direction of the vector (step 5).

9.) Then user can select another member face to extend (return to step 2).

10.) Spacebar terminates the tool, ESC key resets to step 1.


#3

That might not be enough. What if you have near-perpendicular? As you project/extend the edges of the other mesh - they’ll go almost to infinity on your target face. You probably want some sanity checks with greater tolerance.

Idea/suggestion, you can try to extend even if the edges aren’t perpendicular to the target plane. If you have something with a slant on one side it’d be very slick to be able to extend that tapering towards the target plane. But, in the case of framing, that scenario is probably less common?


#4

I was thinking something like that (this would resemble Smart PushPull). The problem is then you need sanity constraints on the distance to from the source face to the target plane, otherwise the slanted faces might cross in a focal point, leading to unexpected geometry.

Consider that each of the face’s vertices might be connected to multiple edges (the face might not be part of a proper manifold surface, but connected to extra edges or faces). Or neighboring faces are triangulated. You get to more edges to check, but if any edge is not parallel to the others, you have found such an edge case and can abort.

In any case, it’s better not to support features that you cannot properly implement. Either you get the algorithm right for all edge cases, or you better put strict validations on the input and abort early on all unsupported edge cases.

(Sketchup::Edge#line)


#5

I will have to give the non-parallel edge extension some more thought. I can probably make it happen but your right most of my construction objects are prismatic and non-tapering.

However, it would be nice to be able to extend a W-Flange Beam even with its profile that includes arc-curves.

The only problem is I have no way of exploding the ArcCurve in the API, the explode method doesn’t work neither does the explode_curve method:

# Check for curves and explode them

@face_edges2 = @Face2.edges

for edgei in @face_edges2
	arc_curvei = edgei.curve
	if arc_curvei
		exp_curve = arc_curvei.explode
	end
end

This code does not work:

Error: #<NoMethodError: undefined methodexplode’ for #Sketchup::ArcCurve:0x0000000dd3e908>`

If I don’t explode the arccurves then when I transform the vertices the whole thing just kind of blows up.


#6

Now I was also stumped, it doesn’t exist in the Curve class.
Sketchup::Edge#explode_curve


#7

Another weakness I am finding with my code so far is when I go to actually move or transform the vertices. In doing so a number of internal lines are created between the vertices.

The solution I have right now is to delete the existing face and then after the vertices are moved recreate the face and apply the material if one originally existed.

My code for this bit is:

# Erase existing end face

statuserase1 = @Face2.erase!

counter = 0

for vertexi in @face_vertices2
	vectori = @vec_extend_array[counter]
	trans_extend = Geom::Transformation.new(vectori)
 	@group_entities.transform_entities(trans_extend, vertexi)
	counter = counter + 1
end


# Add face to close end and apply material

firstedge = @face_vertices2[0].common_edge @face_vertices2[1]
secondedge = @face_vertices2[1].common_edge @face_vertices2[2]
numfaces = firstedge.find_faces
newface = firstedge.common_face secondedge
if @face_mat
	newface.material = @face_mat
end

This seems to work fairly well except when I have a square or rectangular tube section (HSS). Then recreating the face is messy and doesn’t really work. I would be better just to retain the original face and somehow remove all of the diagonal lines created.


#8

I’m really not understanding what this method is for:

explode_curve

Or at least how to use it properly.


#9
centerpoint = Geom::Point3d.new
# Create a circle perpendicular to the normal or Z axis
vector = Geom::Vector3d.new 0,0,1
vector2 = vector.normalize!
model = Sketchup.active_model
entities = model.active_entities
edges = entities.add_circle centerpoint, vector2, 10

edges.each(&:explode_curve)

#10

Another edge case that I didn’t mention (because your decision to move vertices handled that beautifully), is that faces can contain holes/innerloops, and they can contain multiple holes. The critical step is when you collect all vertices or points to redraw a new face:

  • The order of points matters for creating a face (not for modifying vertices). Lists are ordered, but some operations could change the order (sort, set).
  • Vertices of loops need to be collected separately. If they are in the same list, you create an unexpected face.

You could of course simply abort if face.loops.length > 1.


Naming: For me as a reader, it is not very clear what type the references hold (Ruby is not strongly typed). Is vertexi meant to be the plural of vertex (which is vertices) or “vertex with index i” (but there is no loop variable i))? Is face_vertices a list of vertices, no, it contains lists of vertices? Is vectori a list of vectors?

When I think from the perspective as your my being a public project open to contributors (even if I am the only contributor), I try to think of a consistent intuitive naming scheme and explain my code in comments as if I wanted to introduce new contributors into my code (the idea is called Code Hospitality). Most of all, this helps me months later to understand my intentions.
There are style guides that help naming consistently (e.g. variables lowercase snakecase @Face2@face2)


Counters are so C! The problem is that the code and especially the loop body have references to other places scattered outside the loop. This is not only harder to read, but introduces bugs. Several things that need to be done concertedly in several places (initialize loop variable, loop condition, update loop variable), this is a pattern that if not done correctly leads to a bug. And a pattern is boilerplate and should be simplified! For this purpose, iterators so nice because they keep one action (iterate) in one line and there is no loop variable at all (and if needed, use each_with_index).
For example:

@face_vertices2.zip(@vec_extend_array).each{ |vertices, vector|
    trans_extend = Geom::Transformation.new(vector)
    @group_entities.transform_entities(trans_extend, vertices)
}

#11

You mean SketchUp’s AutoFold kicks in? Do you have an example? It should not kick in if you move the vertices in bulk such that they face remain planar. (How do you move your vertices?)


#12

Okay, that works. From the SU documentation I would have never figured that out. I think someone needs to add some more detail into how to actually use the explode_curve method. I find it very confusing and non-intuitive.


#13

I’m not saying my code is pretty, I could use a lesson on code styling, so I do apologize for some of my syntax and the clunky way I do certain things. Realize this code is the first draft, I’m just trying to get it working.

I usually tighten things up once I get the code working properly. This usually serves to clean it up and condense it.

Sometimes I write my code so that I can see the flow but later I realize I can usually condense multiple lines down into a single statement and also possibly eliminate a number of intermediate variables.


#14

I have to move the vertices one at a time since the new target plane does not need to be coplanar with the existing end (plane) of the member.

Yes, the AutoFold does its thing and then I am left with a number of edges on the final face. I guess I just need an algorithm to remove them.


#15

Here is kind of an extreme example:

In the case above I’ve removed the erase step and the create new face and assign material code entirely.

The upside to this approach is that the original face(s) are retained (just slightly autofolded) so any loops/holes don’t become an issue and I also don’t have to worry about re-assigning the material to the recreated face.

I think this is the more robust algorithm.

Dealing with autofolding is probably easier than mucking about with loops and holes.

Even with all of the autofolding, the extended group is still a SOLID so that is also a bit of a relief and perhaps a light at the end of the tunnel.


#16

You can translate multiple entities uniquely , in this case vertices, using entities. transform_by_vectors

Have you tried that?

That should avoid Auto-Fold, I think, if target vertices are planar.

However, if you do need to detect atofolded edges, cache the edges of the faces you transform into a collection. Then do a difference check after the transformation. I have an extension that automatically apply Soft +Smooth to auto folded edges : https://github.com/thomthom/AutoSmooth
Have a look if you want a reference implementation.

In another extension the logic is warped in a method that takes a block. I’ll dig that up tomorrow when I’m back at my computer.


#17

Bingo! That works. You’re an absolute genius.

How come I never knew about this method before? I’ve got other code (hip rafters) where I’ve added logic to remove other similar (but simpler) autofold situations, this will greatly simplify some of that (future work).

Thank-you.

I just replaced all of the code above with this single line:

@group_entities.transform_by_vectors(@face_vertices2, @vec_extend_array)

Boom! Done.

Example of a W-Flange Beam extended to a face:


Truss Plugin/Extension
#18

I’ve forgotten and discovered this method a few times myself.

But I use it a lot in Vertex Tools and SUbD. In SUbD I use to when I crease the edges, that doesn’t change the geometry other than vertex positions, so I can avoid recreating the entire mesh.

However, if you try to move a vertex by a very small distance it might not do it at all if it’s less than 0.001 - under the geometry equality tolerance in SU. Which in the case of SUbD was an issue when the user adjusted the crease amount using the mouse - there might be many small adjustments that didn’t happen.
What I really want is a method where you provide point3ds for a set of vertices, and then just set the new position in a more absolute way.

Glad it worked for you.

(And it’s a nice way to work out a feature, by spec’ing it out in the forum like that, to get more eyes. Hadn’t really thought of that. But working solo that’s a very useful way to get a second set of eyes early.)