zeffii / mesh_tiny_cad

a tiny set of unmissable CAD functions ( VTX, XALL ...) for Blender

Home Page:http://zeffii.github.io/mesh_tiny_cad/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Perpendicular line tool

Aiena-g opened this issue · comments

I was wondering if it would be possible to make a tool which can draw a line perpendicular to another line meeting a vertex.

I am having a lot of trouble conceptualizing how this tool will work however.

My idea was:

  1. select a vertex
  2. Select an edge
  3. Draw a line perpendicular from the selected vertex to the selected edge. Note: this is different from VTX as VTX will extend but the line need not be perpendicular (they may be perp if they meet at 90 degrees).

The issue with this is that if using vertex selection mode and edge selection mode together we cannot select both the vertex and the edge at the same time as adjacent edges will be selected if we activate vertex and edge selection mode together.

What I was thinking of is a sort of blender operator. With three button:

  1. "Assign start vertex" button: Select a vertex and press this button ;TC will remember the vertex
  2. "Assign edge" button: this button will be pressed after selecting one edge (If 2 or more edges are selected display an error saying "select only one edge". TC remembers this edge.
  3. "Draw perpendicular line" button : This button draws the perpendicular line starting at the vertex (assigned with the "Assign start vertex" button to the edge chosen with the "Assign edge" button. Here it will add a vertex at intersection point of perpendicular line and drawing the line.

Do you think this would be possible.

Please draw it, or take screenshots of the steps and annotate them.

I think you're looking to:

  1. select 3 vertices to define a plane
  2. select edge which you would like to generate the perpendicular for.
  3. tool generates perpendicular (with or without welded intersection of the new edge onto the existing edge)

for the moment this assumes the simplest scenario. 3 verts, 2 edges ( a polyline )

import math
import bpy
import bmesh
import mathutils

obj = bpy.context.edit_object
me = obj.data
bm = bmesh.from_edit_mesh(me)

active_vert = bm.select_history.active
selected_verts = [v.co for v in bm.verts if v.select and not v.hide]

axis = mathutils.geometry.normal(selected_verts)
linked_edges = active_vert.link_edges

if len(linked_edges) == 1:
    edge = linked_edges[0]
    mat_rot = mathutils.Matrix.Rotation(math.radians(90.0), 4, axis)
    pt1 = edge.verts[0].co.copy()
    pt2 = edge.verts[1].co.copy()
    mid_edge = (pt1 + pt2) / 2

    coord1 = ((pt1 - mid_edge) * mat_rot) + mid_edge
    coord2 = ((pt2 - mid_edge) * mat_rot) + mid_edge

    vec1 = bm.verts.new(coord1)
    vec2 = bm.verts.new(coord2)
    bm.edges.new((vec1, vec2))


bmesh.update_edit_mesh(me, True)

image

this is slightly simplified:

  • select 3 verts
  • the last vert selected will be part of the edge you want to get the perpendicular for
  • this does not auto intersect .

Going to test it out

Hmm not what I wanted but nice too. So I know perpendicular is possible. This is what I had in mind.
Sorry for not posting a pic.

feature request

Guess this should help

I have other ideas for this tool too. E.g. Perpendicular line slide. It allows you to slide the line specific distance away from the end point of the line. E.g. 0.6 blender units (not percentage) from one edge. Specifying 0.6 would pic one corner specifying -0.6 would pick the other corner. Assuming one BU = 1 Meter 0.6 would be 60 cm from an edge. But this necessitates that there be only one edge and one vertex. Otherwise the tool will get confused when the perpendicular edge slide feature is added. Actually "perpendicular edge slide" is wrong term. Lets say "perpendicular distance from end point" makes more sense.

Possible implementation: As a slider in the operator panel. By default this is disabled (0 means ignored) but if the user inputs a value in the slider then the perpendicular will move.

Here is a graphic:

Also can you increase the precision of the slider value for th the default 2 decimal places isn't enough. I noticed interestingly that if more that 2 decimal places are inserted in the slider it will work with those numbers but still round it up during display in the slider. E.g. a radius of a circle when input as 0.018 and 0.016 will be different in the view port but the slider will still display both as 0.02

feature req part 2

In the above graphic the yellow line is the default perpendicular drawn and the red line is it mode specific distance from an edge. Maybe instead of specifying - or plus to flip the end vertex point from which distance is calculated a checkbox/button could be used. I'll leave implementation and the UI to you. But what is done so far is cool.

I don't want to get your hopes up here, the images definitely helped communicate the intention much better. Let me encourage you to experiment with Python. I don't really need this and I also don't have time available to do it properly. (No ETA) . I started learning Python because no-one was willing to write the tools I needed for me.

In an older version of mesh_tinyCAD i had an operator called PERP, ( https://github.com/zeffii/mesh_tinyCAD/blob/f81b78314c4d4149094fcd48ba2e4b9380b96bab/mesh_tinyCAD/PERP.py ) but I removed it because I never used it.

PERP is slightly different as it sliced faces based on two selected vertices, some code might be reusable,

Ok. I understand anyway picked up a book on 3D math though math is hard for me. That's more a hindrance than just python. Hope I can figure out how to get this thing to work. But if you do get it working albeit slowly (though I don't think its going to happen) it would be nice.

I tried reading your code but I cant understand any of it.

bpy has a routine for finding the 3d location of the closest on an edge (infinite line) with respect to another 3d point. The point on the line may lie outside of the limits of the edge, but it will lie on a projected path (infinite line) . This new point when connected with the existing vertex as an edge, will produce 90 degree angle with the original edge.


A -----------B - - - D - -
                     .
                     .
                     .
                     C 


Above A,B describe the edge being projected towards, C is the vertex to project from and D is the hypothethical closest point on the infinite line AB.

A
 \
  \
   \
    B
     .
      .
       .
       _D
    -    .
C '       .

This also works when A, B have different locations.. (just imagine that CDB form a 90 angle.. tricky to show with a text diagram)

>>> mathutils.geometry.intersect_point_line(
"""
intersect_point_line(pt, line_p1, line_p2)
.. function:: intersect_point_line(pt, line_p1, line_p2)
Takes a point and a line and returns a tuple with the closest point on the line and its distance from the first point of the line as a percentage of the length of the line.
:arg pt: Point
:type pt: :class:`mathutils.Vector`
:arg line_p1: First point of the line
:type line_p1: :class:`mathutils.Vector`
:arg line_p1: Second point of the line
:type line_p1: :class:`mathutils.Vector`
:rtype: (:class:`mathutils.Vector`, float)
"""

the basic code for this (without the extra translation feature)

import bpy
import bmesh
import mathutils

ipl = mathutils.geometry.intersect_point_line

obj = bpy.context.edit_object
me = obj.data
bm = bmesh.from_edit_mesh(me)

selected_verts = [v.index for v in bm.verts if v.select and not v.hide]
selected_edges = [e for e in bm.edges if e.select and not e.hide]

if len(selected_edges) == 1:
    line = selected_edges[0]
    indices = line.verts[0].index, line.verts[1].index
    non_edge_vertex = set(selected_verts).difference(set(indices))
    existing_vert = non_edge_vertex.pop()

    vec1 = bm.verts[existing_vert]
    pt = vec1.co
    line_p1 = line.verts[0].co 
    line_p2 = line.verts[1].co 

    new_vert, factor = ipl(pt, line_p1, line_p2)
    vec2 = bm.verts.new(new_vert)
    bm.edges.new((vec1, vec2))


bmesh.update_edit_mesh(me, True)

image

unfortunately this code assumes that the 3 verts aren't connected by two edges. which is a lame assumption...

image

the solution could be let the user pick the last vertex as the point to project from (this point would then also be the active vertex) . In the case of the image above the vertex just below the green axis marker would need to be selected last to make it the active vertex.

Wow that works but still trying to digest what you wrote. Its still like pure magic to me hope reading that 3d book helps. I read the page about coordinate systems at the starting of the book. I think blender uses a right hand coordinate system. How there are 24 combinations of axis's for an Right hand coordinate system I do not understand but then that has nothing to do with TC. take care hope my brain can digest that book. Hard without some one to explain.

hmm got the idea of how your thing works. You use set theory to distinguish between vertices connected to an edge and one not connected then isolate the edge vertices from the non edge one and then draw the perpendicular. And yes it works wonderfully even at arbitrary angles.

your active vertex idea is cool.

then something that might work nicely is:

  • collect indices all selected vertices that aren't hidden
  • if the count of that collection is not 3 then discontinue
  • collect all the vertex indices of all selected edges
  • filter out the edges in that collection that contain the active vertex
  • leaves us with the edge to project to, and we already have the active vertex.

I think the above algorithm covers all scenarios and serves as the general algorithm. The point of a general algorithm is that it can work without special cases - keeps the logic simple, (albeit it sometimes overkill for certain situations, but just right for some trickier situations, yet always correct as long as the active vertex is where you want to project from)

this version relies only on selected vertices, selection mode must be in Vertex selection mode..

import bpy
import bmesh
import mathutils

ipl = mathutils.geometry.intersect_point_line


def perp_make():

    obj = bpy.context.edit_object
    me = obj.data
    bm = bmesh.from_edit_mesh(me)

    active_vert = bm.select_history.active
    print(active_vert)
    selected_verts = [v.index for v in bm.verts if v.select and not v.hide]

    if len(selected_verts) == 3:
        other_verts = set(selected_verts).symmetric_difference({active_vert.index})
        pt = active_vert.co
        p1 = other_verts.pop()
        p2 = other_verts.pop()
        line_p1 = bm.verts[p1].co 
        line_p2 = bm.verts[p2].co
        new_vert, factor = ipl(pt, line_p1, line_p2)
        vec1 = bm.verts.new(new_vert)
        bm.verts.ensure_lookup_table()
        bm.edges.new((vec1, bm.verts[active_vert.index]))

    bmesh.update_edit_mesh(me, True)


# tool_settings.mesh_select_mode == 1, 0, 0
perp_make()

slightly less verbose.

import bpy
import bmesh
import mathutils

ipl = mathutils.geometry.intersect_point_line


def perp_make():

    obj = bpy.context.edit_object
    me = obj.data
    bm = bmesh.from_edit_mesh(me)

    active_vert = bm.select_history.active
    selected_verts = [v for v in bm.verts if v.select and not v.hide]

    if len(selected_verts) == 3:
        other_verts = set(selected_verts).symmetric_difference({active_vert})
        pt = active_vert.co
        line_p1 = other_verts.pop().co
        line_p2 = other_verts.pop().co
        new_vert, factor = ipl(pt, line_p1, line_p2)
        vec1 = bm.verts.new(new_vert)
        bm.verts.ensure_lookup_table()
        bm.edges.new((vec1, active_vert))

    bmesh.update_edit_mesh(me, True)


# tool_settings.mesh_select_mode == 1, 0, 0
perp_make()

try this. will only work in Vertex selection mode:
https://github.com/zeffii/mesh_tinyCAD_PERP .