Generate central 3DPoly between two parallel 3DPolys (variable offset)

Generate central 3DPoly between two parallel 3DPolys (variable offset)

rsalastopografia
Advocate Advocate
550 Views
13 Replies
Message 1 of 14

Generate central 3DPoly between two parallel 3DPolys (variable offset)

rsalastopografia
Advocate
Advocate

I have two 3DPolylines in 3D space that run roughly parallel but with variable offset between them. I need to generate a third 3DPolyline representing the central axis between both. Is this possible using LISP? Any suggestions or sample routines are welcome.

 

rsalastopografia_0-1752027704290.png

The attached image shows two parallel 3DPolylines with non-uniform spacing. The goal is to generate a central 3DPolyline equidistant (or interpolated) between them along their entire length.

0 Likes
Accepted solutions (4)
551 Views
13 Replies
Replies (13)
Message 2 of 14

daniel_cadext
Advisor
Advisor
Accepted solution

If there’s the same number of vertices on each polyline, then you can build a new polyline from the mid points. I have no idea how to do this in lisp, but maybe this will give a hint

import traceback
from pyrx import Ap, Ax, Db, Ed, Ge, Gi


@Ap.Command()
def doit():
    try:
        db = Db.curDb()
        ps, id1, _ = Ed.Editor.entSel("\nPick p1", Db.Polyline3d.desc())
        ps, id2, _ = Ed.Editor.entSel("\nPick p2", Db.Polyline3d.desc())
        
        pl1 = Db.Polyline3d(id1)
        pl2 = Db.Polyline3d(id2)
        
        vtxids1 = pl1.vertexIds()
        vtxids2 = pl2.vertexIds()
        
        if len(vtxids1) != len(vtxids2):
            return 
        
        pnts = []
        for vid1, vid2 in zip(vtxids1,vtxids2):
            v1 = Db.Polyline3dVertex(vid1)
            v2 = Db.Polyline3dVertex(vid2)
            seg = Ge.LineSeg3d(v1.position(),v2.position())
            pnts.append(seg.midPoint())
            
        pl3 = Db.Polyline3d(Db.Poly3dType.k3dSimplePoly,pnts,pl1.isClosed())
        db.addToCurrentspace(pl3)
        print("\nWooHoo!")
        
    except Exception as err:
        traceback.print_exception(err)

 

mid.png

Python for AutoCAD, Python wrappers for ARX https://github.com/CEXT-Dan/PyRx
Message 3 of 14

komondormrex
Mentor
Mentor
Accepted solution

@rsalastopografia

check this lisped one

(defun c:draw_mid_3dpoly ()
  (setq 3d_pline_1 (vlax-ename->vla-object (car (entsel "\nPick 1st 3d-pline: ")))
      3d_pline_2 (vlax-ename->vla-object (car (entsel "\nPick 2nd 3d-pline: ")))
      vertex_index 0
      3d_mid_vertices_list nil
  )
  (while (and (null (vl-catch-all-error-p (setq vertex_1 (vl-catch-all-apply 'vla-get-coordinate (list 3d_pline_1 vertex_index)))))
             (setq vertex_1 (vlax-safearray->list (vlax-variant-value vertex_1)))
        (null (vl-catch-all-error-p (setq vertex_2 (vl-catch-all-apply 'vla-get-coordinate (list 3d_pline_2 vertex_index)))))
        (setq 3d_mid_vertices_list (append 3d_mid_vertices_list (mapcar '* '(0.5 0.5 0.5) (mapcar '+ vertex_1 (vlax-safearray->list (vlax-variant-value vertex_2))))))
        (setq vertex_index (1+ vertex_index)) 
       )
  )
  (vla-add3dpoly (vla-get-block (vla-get-activelayout (vla-get-activedocument (vlax-get-acad-object))))
           (vlax-safearray-fill (vlax-make-safearray vlax-vbdouble (cons 1 (length 3d_mid_vertices_list)))
                      3d_mid_vertices_list
           )
  )
  (princ)
)
Message 4 of 14

rsalastopografia
Advocate
Advocate

Thank you very much for your help!
The Lisp routine worked perfectly. I really appreciate your time and knowledge.

If I wanted to create a 2D polyline as the centerline, I would just use vla-addLightWeightPolyline instead of vla-add3dpoly and work only with XY coordinates.

0 Likes
Message 5 of 14

komondormrex
Mentor
Mentor

your welcome. surely that will do for the  straight-segment-only lw plines. 

0 Likes
Message 6 of 14

Kent1Cooper
Consultant
Consultant
Accepted solution

Here's another AutoLisp approach [lightly tested]:

(defun C:3DPMID (/ pl1 pl2 o1 o2 co1 co2)
  (setq
    pl1 (car (entsel "\nFirst 3D Polyline: "))
    pl2 (car (entsel "\nSecond 3D Polyline: "))
  ); setq
  (if
    ;; [correct object types, same number of segments, &
    ;;   either both closed or both open]
    (and
      (=
        (cdr (assoc 100 (reverse (entget pl1))))
        (cdr (assoc 100 (reverse (entget pl1))))
        "AcDb3dPolyline"
      ); =
      (= (vlax-curve-getEndParam pl1) (vlax-curve-getEndParam pl2))
      (= (vlax-curve-isClosed pl1) (vlax-curve-isClosed pl1))
    ); and
    (progn ; then
      (setq
        o1 (vlax-ename->vla-object pl1)
        o2 (vlax-ename->vla-object pl2)
        co1 (vlax-get o1 'Coordinates)
        co2 (vlax-get o2 'Coordinates)
      ); setq
      (command "_.copy" pl1 "" '(0 0 0) '(0 0 0)); a copy to get new Coordinates
      (vlax-put
        (vlax-ename->vla-object (entlast))
        'Coordinates (mapcar '(lambda (x y) (/ (+ x y) 2)) co1 co2)
      ); vlax-put
    ); progn
  ); if
  (prin1)
)

The actual work of it is just the second half -- kind of short and sweet.  The first half is all just selection and verifying the right match of characteristics.

If the two selected 3DPolylines are not the same in all properties [Layer, linetype, color, etc.], the mid-path result with have those of the first one selected.

[It could have a prompt added to scold the User if the appropriate characteristics don't match.]

Kent Cooper, AIA
Message 7 of 14

BlackBox_
Advisor
Advisor

*IF* there's ever a need to batch process more than one pair, a slight mod (using vla-copy in lieu of COPY Command) makes your code exceptionally fast: 

 

_3DPMID
Elapsed: 25781
Average: 25.7810

 

_3DPMID2
Elapsed: 4079
Average: 4.0790

 

(defun _3DPMID (pl1 pl2 / o1 o2 co1 co2)
;;;  (setq
;;;    pl1 (car (entsel "\nFirst 3D Polyline: "))
;;;    pl2 (car (entsel "\nSecond 3D Polyline: "))
;;;  )
  (if
    ;; [correct object types, same number of segments, &
    ;;   either both closed or both open]
    (and
      (=
        (cdr (assoc 100 (reverse (entget pl1))))
        (cdr (assoc 100 (reverse (entget pl1))))
        "AcDb3dPolyline"
      ); =
      (= (vlax-curve-getEndParam pl1) (vlax-curve-getEndParam pl2))
      (= (vlax-curve-isClosed pl1) (vlax-curve-isClosed pl1))
    ); and
    (progn ; then
      (setq
        o1 (vlax-ename->vla-object pl1)
        o2 (vlax-ename->vla-object pl2)
        co1 (vlax-get o1 'Coordinates)
        co2 (vlax-get o2 'Coordinates)
      ); setq
      (command "_.copy" pl1 "" '(0 0 0) '(0 0 0)); a copy to get new Coordinates
      (vlax-put
        (vlax-ename->vla-object (entlast))
        'Coordinates (mapcar '(lambda (x y) (/ (+ x y) 2)) co1 co2)
      ); vlax-put
    ); progn
  ); if
  (prin1)
)

(defun _3DPMID2 (pl1 pl2 / o1 o2 co1 co2)
;;;  (setq
;;;    pl1 (car (entsel "\nFirst 3D Polyline: "))
;;;    pl2 (car (entsel "\nSecond 3D Polyline: "))
;;;  )
  (if
    ;; [correct object types, same number of segments, &
    ;;   either both closed or both open]
    (and
      (=
        (cdr (assoc 100 (reverse (entget pl1))))
        (cdr (assoc 100 (reverse (entget pl1))))
        "AcDb3dPolyline"
      ); =
      (= (vlax-curve-getEndParam pl1) (vlax-curve-getEndParam pl2))
      (= (vlax-curve-isClosed pl1) (vlax-curve-isClosed pl1))
    ); and
    (progn ; then
      (setq
        o1 (vlax-ename->vla-object pl1)
        o2 (vlax-ename->vla-object pl2)
        co1 (vlax-get o1 'Coordinates)
        co2 (vlax-get o2 'Coordinates)
      ); setq
      ;;(command "_.copy" pl1 "" '(0 0 0) '(0 0 0)); a copy to get new Coordinates
      (vlax-put
        (vla-copy o1)
        'Coordinates (mapcar '(lambda (x y) (/ (+ x y) 2)) co1 co2)
      ); vlax-put
    ); progn
  ); if
  (prin1)
)
;; (_3DPMID2  pl1 pl2)

(bench '(_3DPMID _3DPMID2) (list pl1 pl2) 1000)

 


"How we think determines what we do, and what we do determines what we get."

Sincpac C3D ~ Autodesk Exchange Apps

0 Likes
Message 8 of 14

rsalastopografia
Advocate
Advocate

I changed the 3dpoly Lisp code to polyline in its properties so that it would generate a central axis, but it doesn't run correctly. In the case of 2d Polyline, does the entire configuration of the original Lisp routine code change?

0 Likes
Message 9 of 14

Kent1Cooper
Consultant
Consultant

@rsalastopografia wrote:

I changed the 3dpoly Lisp code to polyline ... but it doesn't run correctly. ....


"[I]t doesn't run correctly" is never enough information.  What does it do differently from what you expect?

If by that change you mean you changed "AcDb3dPolyline" to "AcDbPolyline" [which will accept only "lightweight" Polylines], that works for me.  If you changed it to "AcDb2dPolyline" ["heavy" but planar], that also works for me.  But in both cases, if the two are not at the same elevation, the result is at the elevation of the first one.  Listing its VLA Properties, it has the Elevation property of that one, but the Coordinates property in the "heavy" 2D version has the every-third-element Z coordinates correctly at halfway between those of the originals.  So I guess the Elevation property overrides the Z coordinates of the individual vertices, and stands in place of the Z coordinates in a LWPolyline, so it's necessary to change that Elevation property, too.  But I'm not sure what should happen if they are not in parallel planes -- halfway between vertex by vertex is probably not possible except with a 3DPolyline.

Kent Cooper, AIA
0 Likes
Message 10 of 14

rsalastopografia
Advocate
Advocate

Please could you upload the Lisp routine code that works with 2d polyline? I changed the code (3dpoly to 2d polyline) but it didn't work as expected, I don't know why.

0 Likes
Message 11 of 14

Kent1Cooper
Consultant
Consultant

@rsalastopografia wrote:

....I changed the code (3dpoly to 2d polyline) but it didn't work as expected.....


Again, "it didn't work" is not enough information.  What did it do differently from what you expect?

If you changed it to "2d polyline" with that space, that would not work.  Show us the change you made.

Kent Cooper, AIA
0 Likes
Message 12 of 14

daniel_cadext
Advisor
Advisor

For a Polyline, you would probably need to set the elevation to the Z value of one of the vertices. An issue is that the original polylines have a slope. Trying to compute a transformation matrix in lisp is like trying to drive a F150 with a toilet bowl plunger as the gear shift. Maybe a min, max or average Z is good enough. This sample uses max to illustrate the issue

 

.Command()
def doit():
    try:
        db = Db.curDb()
        ps, id1, _ = Ed.Editor.entSel("\nPick p1", Db.Polyline3d.desc())
        ps, id2, _ = Ed.Editor.entSel("\nPick p2", Db.Polyline3d.desc())
        
        pl1 = Db.Polyline3d(id1)
        pl2 = Db.Polyline3d(id2)
        
        vtxids1 = pl1.vertexIds()
        vtxids2 = pl2.vertexIds()
        
        if len(vtxids1) != len(vtxids2):
            return 
        
        pnts = []
        for vid1, vid2 in zip(vtxids1,vtxids2):
            v1 = Db.Polyline3dVertex(vid1)
            v2 = Db.Polyline3dVertex(vid2)
            seg = Ge.LineSeg3d(v1.position(),v2.position())
            pnts.append(seg.midPoint())
            
        z = 0
        for p in pnts:
            z = max(z,p.z)
            
        pl3 = Db.Polyline(pnts)
        pl3.setElevation(z)
        
        db.addToCurrentspace(pl3)
        print("\nWooHoo!")
        
    except Exception as err:
        traceback.print_exception(err)

slope.png

Python for AutoCAD, Python wrappers for ARX https://github.com/CEXT-Dan/PyRx
0 Likes
Message 13 of 14

komondormrex
Mentor
Mentor
Accepted solution

@rsalastopografia 

check the following for any matched pair of *plines.

(defun c:draw_mid_poly ()
  (setq pline_1 (vlax-ename->vla-object (car (entsel "\nPick 1st *pline: ")))
        pline_2 (vlax-ename->vla-object (car (entsel "\nPick 2nd *pline: ")))
        vertex_index 0
        mid_vertices_list nil
  )
  (if (and (wcmatch (vla-get-objectname pline_1) "*Polyline")
          (wcmatch (vla-get-objectname pline_2) "*Polyline")
      )
    (progn
      (while (and (null (vl-catch-all-error-p (setq vertex_1 (vl-catch-all-apply 'vla-get-coordinate (list pline_1 vertex_index)))))
             (setq vertex_1 (vlax-safearray->list (vlax-variant-value vertex_1)))
            (null (vl-catch-all-error-p (setq vertex_2 (vl-catch-all-apply 'vla-get-coordinate (list pline_2 vertex_index)))))
              (setq vertex_2 (vlax-safearray->list (vlax-variant-value vertex_2)))
            (setq mid_vertices_list (append mid_vertices_list (mapcar '* '(0.5 0.5 0.5) (mapcar '+ vertex_1 vertex_2))))
            (setq vertex_index (1+ vertex_index)) 
              )
      )
      (setq vertices_raw_array (vlax-safearray-fill (vlax-make-safearray vlax-vbdouble (cons 1 (length mid_vertices_list)))
                                    mid_vertices_list
                     )
      )
      (cond
        ((= "AcDbPolyline" (vla-get-objectname pline_1) (vla-get-objectname pline_2))
              (vla-put-elevation
              (vla-addlightweightpolyline (vla-get-block (vla-get-activelayout (vla-get-activedocument (vlax-get-acad-object))))
                            vertices_raw_array
              )
            (* 0.5 (+ (vla-get-elevation pline_1) (vla-get-elevation pline_2)))
          )
        )
          ((= "AcDb2dPolyline" (vla-get-objectname pline_1) (vla-get-objectname pline_2))
            (vla-put-elevation
            (vla-addpolyline (vla-get-block (vla-get-activelayout (vla-get-activedocument (vlax-get-acad-object))))
                      vertices_raw_array
              )
            (* 0.5 (+ (vla-get-elevation pline_1) (vla-get-elevation pline_2)))
          )
          )
          ((= "AcDb3dPolyline" (vla-get-objectname pline_1) (vla-get-objectname pline_2))
              (vla-add3dpoly (vla-get-block (vla-get-activelayout (vla-get-activedocument (vlax-get-acad-object))))
                          vertices_raw_array
              )
          )
        (t (princ "\nPlines' mismatch"))
       )
    )
  )
    (princ)
)
Message 14 of 14

rsalastopografia
Advocate
Advocate

The lisp routine code for polyline runs perfectly, thanks for your help.

0 Likes