Jump to content

Recommended Posts

Posted (edited)

Nice test @PGia, thanks!

For some reason I didnt consider closed polylines in the _checkOffset function, so I added an extra check there.

Should work as expected now:

 

Not sure about the short corner. The lines are so narrow the centerline is pushed back out of the point.

Seems to be logical to me but it does feel intuitive.

 

Narrow indents don't get much love from the centerline. So "inlets" don't have enough influence on the shape of the line.

What would the expected result be? Below makes sense since there is not enough space to go into the indent.

inlets.gif.6b4290afb1912598f5ffdf99bd8feb20.gif

Or are you maybe something like this where the line splits and goes into the hole:

 

Edited by dexus
  • Like 4
Posted (edited)

I did some more tests today and found that when the end segments were parallel, they didn't get added to the line.

So I added support for that. This one was easy because the _cornerOffset function already checks for parallel segments and I just had to add a flag to inform the offset loop. 

 

Parallel.gif.78afc53ec0f228c7a81fadda2114d27c.gif

 

And I found another example which it has a hard time on with concentric arcs, attached below.

But that will be for another day since I have no more time for that this week.

 

AxisExple3.dxf

Edited by dexus
  • Like 3
Posted (edited)

I think the real-world situation may be more complex than what we’ve seen here so far.


I took a look at the links that @SLW210 attached and decided to test the Lisp codes proposed up to this point.

I looked through my drawings for something that could serve as an example for this problem, but it was like looking for a needle in a haystack.
So, in the end, I decided to look for something in the real world that clearly corresponds to this issue — something like this:

River.jpg.433556ff9f380c7b57665026db9915c3.jpg

 

So I drew those margins and tested all the codes that have appeared in this thread so far.
The result was… this!

Img1.thumb.png.219713fa6d001c7ebb481cf255147e33.png

 

In the drawing, you can see the ones that managed to reach the end.
However, the codes by BIGAL, GP_, GLAVCVS, and MarkoRibar couldn't even do that.

Here’s the drawing

AxisExple4.dwg

 

Edited by PGia
  • Like 3
Posted

Hi @PGia, wow, you managed to find another example that fails. Good stress testing!

 

The reason mine fails is because it ignores the offset lines that are split in two or more parts.

But some of the parts are still be usable and should be used.

I looked into fixing this and managed to add those lines, but this resulted in some other problems so I didn't update the code on the earlier post yet.

 

The zigzag problem is coming back on your example. The result below.

I really need to find a solution for that, but it looks like I might have to use a path finding algorithm which would slow down the code a lot. Sorting the points by distance on the offset line is not enough anymore.

zigzag.png.70bd9ef1f5b15f14778f6660009a79b5.png

  • Like 1
  • Thanks 1
Posted

IIRC, those are tough to manage on GIS programs with Add-ons made with Python, .NET, etc.

 

Remember, real rivers/roads/ROWs have curves and organic shapes, not just straight lines.

 

I did see some information on how to tackle those, hopefully it can be worked out, but don't expect perfection.

 

As I mentioned, from what my daughter said and what I've seen in various related forums, the GIS pros are using whatever program and add-ons they can to get the bulk of it and cleaning up and filling in manually.

 

Most that are doing those shapes want the main channel center, if needed I would ignore the very wide sections and side pieces and get those as center lines running back to the main channel separately.

 

As your example shows, it would take separate polylines, so it will need to account for those sections and then ran again on the offshoots.

 

You still haven't answered all of the questions asked.

 

What type of work are you doing? What you have posted looks to be Civil and/or GIS work.

 

I'll try some more on this when I can, I have also looked at some different shortest path codes, the last example is way over my head in LISP, I'll concentrate on the previous examples then try to run just the main channel on the last example .dwg.

 

I have a full slate at work again today, but I'll try to jump back on this when I get some things out of the way.

 

Home time is limited, but I'll try to get back on this with QGIS solution, I may see if my daughter's coworkers want to take a shot at these.

  • Like 1
Posted (edited)

I think with those big side offshoots you need to break the river into multiple plines so you would have two or more centrelines lines in that situation. As suggested by @SLW210 The problem will be how to work out the break offset shape..

 

image.png.8d5f5177556f65185be378fc75fd15dd.png

 

Ps image dummied up.

Edited by BIGAL
  • Like 1
  • Agree 2
Posted
On 12/9/2025 at 1:37 PM, SLW210 said:

 

You still haven't answered all of the questions asked.

 

What type of work are you doing? What you have posted looks to be Civil and/or GIS work.

 

 

In general, we work with plans for projects of various kinds: engineering, cadastral mapping, and so on. Sometimes we submit proposals for public tenders, but so far without success.


@dexus, the performance of your code looks different in that last drawing compared to what I’m seeing on my end.

In any case, I think this topic has now entered the realm of a real challenge.
@BIGAL: I agree. I think that’s the correct way to draw the centerline in the inlets.

Posted

Obviously, this has now become something more than just the search for a solution to a single user’s problem.

First of all, I should say that I myself was also reluctant to accept the concept of equidistance advocated by @GP_ and @dexus
For the simple reason that applying this principle forced me to accept that the centerline should be the same in these two drawings.

Captura1.thumb.png.fadfd23c8de5c245205a280bdeefd496.png

Captura3.thumb.png.eaba96fe9edbcbb64304c0a9088a0f05.png

Equidistance requires ignoring those areas of the margins that do not geometrically affect the axis.
This, which initially caused me some resistance, I eventually came to accept conceptually when I realized that it could serve as a criterion for defining what is a “recodo/inlet” and what is not.
So I have abandoned my previous approach and adapted it to this new situation.

Having made this clarification, I must say that this concept of equidistance makes the calculation of a centerline more feasible.

 

I’ve been running some tests with Dexus’s latest code, which is the best so far.
However, I’ve discovered some “holes” that I hadn’t noticed before.
I’m attaching a few images showing this.

InCor.thumb.png.9eea7101f2ae68f963542f51b891bdb9.png

In my view, these are conceptual errors rather than geometric limitations.
And what can we consider “geometric limitations”?
I believe that, in any case, every vertex of the centerline must be equidistant from both margins.
If this is not the case, the result is not correct.

However, the intermediate regions along each segment may be subject to geometric limitations depending on the desired precision.
Therefore, in bends or turns, the points taken within the adjustment or “problematic” segments may deviate (within a tolerance) from strict equidistance.

The goal, therefore (in my opinion), should be to achieve equidistance at every vertex and to remain within a tolerance in the intermediate zone of each segment.

After everything written here so far, some might wonder: is it really possible to obtain a centerline that meets these requirements?

As far as I’m concerned, I’m running some tests.

 

I’ll post something over the weekend

  • Like 4
Posted

Attempt number 2:

This code is intended to always return a centerline whose points are all perfectly equidistant from the margins.
This should happen in all cases where two LWPOLYLINEs are provided, one for each margin.
The case of islands has not been considered yet.

EQD1.thumb.png.ce412b0772869b1534a60a2153d7f7f9.png

 

EQD2.thumb.png.60895084f0f8122208a42453ef2ad09a.png

The resulting centerline is geometrically dense. This can probably be simplified in a future version.

The approach taken in this code has been to obtain points from the normals and the bisectors of each margin, which are then combined at the end to build a list of points.

Therefore, it is a fragmentary and massive approach. For this reason, the code is not very fast.

However, there is another, more elegant approach, based on dynamically relating the geometry of both margins.
It is more complex, but it would also be faster, and the error margins would be “bridgable”.

If this thread has enough life in it, I may feel sufficiently motivated to finish it.
That’s all for now.
 

 

;|***********************  CENTER-LINE  *************************     
 ************************ G L A V C V S *************************     
 ************************** F E C I T ***************************   |;
(defun c:CLG (/ PI/2 lst e1 e2 l1 l2 lp lp1 lp2 p0 p> p< r1? x m a tol autoInt? ordenaPts interCpta ptEqd)
  (defun autoInt? (l lp / p0 p1 p2);autointersecci贸n?
    (if l
      (setq p1 (polar (car l) (setq a (angle (car l) (cadr l))) 0.001) p2 (polar (cadr l) (+ a PI) 0.001)
	    x (if (not (vl-some '(lambda (p) (if p0 (inters p0 (setq p0 p) p1 p2) (not (setq p0 p)))) lp)) l)
      )
    )
  )
  (defun ordenaPts (lst / pIni dm d ps? ps lr); puntos en orden
    (setq pIni (mapcar '(lambda (a b) (/ (+ a b) 2.0)) (car lp1) (car lp2)))
    (while lst
      (foreach p lst
        (if (and dm (/= (min (setq d (distance (if ps ps pIni) p)) dm) dm))
          (setq dm d ps? p)
          (if (not dm) (setq dm (distance (if ps ps pIni) p) ps? p))
        )
      )
      (setq ps ps? ps? nil dm nil lst (vl-remove ps lst) lr (cons (cadr ps) (cons (car ps) lr)))
    )
    lr
  )
  (defun interCpta (pM p1 p2 lp / i? i1 i2 d a b); captura de los m谩rgenes
    (defun i? (pA pB lp / p0 i dm is a)
      (foreach p lp
	(if p0
	  (if (setq i (inters p0 (setq p0 p) pA pB))
	    (if (and dm (/= (min (setq d (distance pM i)) dm) dm))
	      (setq dm d is i)
	      (if (not dm) (setq dm (distance pm i) is i))
	    )
	  )
        )
        (setq p0 p)
      )
      (if is (list (car is) (cadr is) 0.0))
    )
    (if (and (setq a (i? p1 p2 lp1)) (setq b (i? p1 p2 lp2)))
      (list a b)
    )
  )
  (defun ptEqd (A B e1 e2 / eqDist-f t0 t1 f0 f1 tm fm n i v+- v*); captura punto equidistante
    (defun v+- (o a b) (mapcar o a b))
    (defun v* (p s) (mapcar '(lambda (x) (* x s)) p))
    (defun eqDist-f (ds A B e1 e2 / pt d1 d2)
      (setq pt (v+- '+ A (v* (v+- '- B A) ds)); Punto sobre AB: P(ds) = A + ds (B - A)
            d1 (distance pt (vlax-curve-getClosestPointTo e1 pt))
            d2 (distance pt (vlax-curve-getClosestPointTo e2 pt))
      )
      (- d1 d2)
    )
    (setq t0 0.0 t1 1.0)
    (while (and (< (setq n (if n (1+ n) 0)) 100) (> (- t1 t0) 1e-6));m茅todo de bisecci贸n
      (setq tm (/ (+ t0 t1) 2.0)
	    fm (eqDist-f tm A B e1 e2)
      )
      (if (< (abs fm) 1e-9)
	(setq n 100 t1 tm t0 tm)
        (if (< (* (if f0 f0 (eqDist-f t0 A B e1 e2)) fm) 0.0)
          (setq t1 tm f1 fm)
          (setq t0 tm f0 fm)
	)
      )
    )
    (if (< t1 1.0) ; par谩metro final y punto equidistante 
      (v+- '+ A (v* (v+- '- B A) (/ (+ t0 t1) 2.0)))
    )
  )
  
  (if (and (setq e1 (car (entsel "\nSelect FIRST LWPolyline..."))) (= (cdr (assoc 0 (setq l1 (entget e1)))) "LWPOLYLINE") )
    (if (and (setq e2 (car (entsel "\nSelect SECOND LWPolyline..."))) (= (cdr (assoc 0 (setq l2 (entget e2)))) "LWPOLYLINE") )
      (progn
	(foreach l l1 (if (= (car l) 10) (setq lp1 (cons (cdr l) lp1))))
	(foreach l l2 (if (= (car l) 10) (setq lp2 (cons (cdr l) lp2))))
	(setq r1? (> (distance (car lp1) (car lp2)) (distance (car lp1) (last lp2))))
	(setq tol 0.01 PI/2 (/ PI 2.) lp1 (if r1? (reverse lp1) lp1))
	(foreach e (list e1 e2)
	  (setq p0 nil m nil r? (if (equal e e1) r1?) lp (if (equal e e1) lp2 lp1))
	  (while (setq p (vlax-curve-getPointAtParam e (setq m (if m ((if r? 1- 1+) m) (if r? (vlax-curve-getEndParam e) 0)))))
	    (if p0
	      (progn
		(setq lAB  (autoInt? (interCpta p (polar p (setq a (+ (angle p0 p) PI/2)) 10000) (polar p (+ a PI) 10000) lp) (if (equal e e1) lp1 lp2));NORMAL AL COMIENZO DEL SEGMENTO
		      lst (if lAB (cons (ptEqd (car lAB) (cadr lAB) e1 e2) lst) lst)
	        )
	        (if (setq p> (vlax-curve-getPointAtParam e ((if r? 1- 1+) m)))
	          (setq lAB  (autoInt? (interCpta p (polar p (setq a (/ (+ (angle p p0) (angle p p>)) 2.)) 10000) (polar p (+ a PI) 10000) lp) (if (equal e e1) lp1 lp2)) ; Bisectriz
			lst (if lAB (cons (ptEqd (car lAB) (cadr lAB) e1 e2) lst) lst)
			lAB  (autoInt? (interCpta p (polar p (setq a (+ (angle p p>) PI/2)) 10000) (polar p (+ a PI) 10000) lp) (if (equal e e1) lp1 lp2));NORMAL AL FINAL DEL SEGMENTO
		        lst (if lAB (cons (ptEqd (car lAB) (cadr lAB) e1 e2) lst) lst)
	          )
		)
		(setq p< p0 p0 p)
	      )
	      (if (setq p> (vlax-curve-getPointAtParam e ((if r? 1- 1+) m)))
	        (setq lAB  (autoInt? (interCpta p (polar (setq p0 p) (setq a (+ (angle p0 p>) PI/2)) 10000) (polar p0 (+ a PI) 10000) lp) (if (equal e e1) lp1 lp2))
		      lst (if lAB (cons (ptEqd (car lAB) (cadr lAB) e1 e2) lst) lst)
		)
	      )
	    )
	  )
	)
	(vla-AddLightWeightPolyline
	  (vla-get-modelspace (vla-get-activedocument (vlax-get-acad-object)))
	  (vlax-Make-Variant (vlax-SafeArray-Fill (vlax-Make-SafeArray 5 (cons 0 (- (length (setq lst (reverse (ordenaPts lst)))) 1))) lst))
	)
      )
    )
  )
  (princ)
)

 

  • Like 6
  • Agree 1
Posted
15 hours ago, GLAVCVS said:

 

;|***********************  CENTER-LINE  *************************     
 ************************ G L A V C V S *************************     
 ************************** F E C I T ***************************   |;
(defun c:CLG (/ PI/2 lst e1 e2 l1 l2 lp lp1 lp2 p0 p> p< r1? x m a tol autoInt? ordenaPts interCpta ptEqd)
  (defun autoInt? (l lp / p0 p1 p2);autointersecci贸n?
    (if l
      (setq p1 (polar (car l) (setq a (angle (car l) (cadr l))) 0.001) p2 (polar (cadr l) (+ a PI) 0.001)
	    x (if (not (vl-some '(lambda (p) (if p0 (inters p0 (setq p0 p) p1 p2) (not (setq p0 p)))) lp)) l)
      )
    )
  )
  (defun ordenaPts (lst / pIni dm d ps? ps lr); puntos en orden
    (setq pIni (mapcar '(lambda (a b) (/ (+ a b) 2.0)) (car lp1) (car lp2)))
    (while lst
      (foreach p lst
        (if (and dm (/= (min (setq d (distance (if ps ps pIni) p)) dm) dm))
          (setq dm d ps? p)
          (if (not dm) (setq dm (distance (if ps ps pIni) p) ps? p))
        )
      )
      (setq ps ps? ps? nil dm nil lst (vl-remove ps lst) lr (cons (cadr ps) (cons (car ps) lr)))
    )
    lr
  )
  (defun interCpta (pM p1 p2 lp / i? i1 i2 d a b); captura de los m谩rgenes
    (defun i? (pA pB lp / p0 i dm is a)
      (foreach p lp
	(if p0
	  (if (setq i (inters p0 (setq p0 p) pA pB))
	    (if (and dm (/= (min (setq d (distance pM i)) dm) dm))
	      (setq dm d is i)
	      (if (not dm) (setq dm (distance pm i) is i))
	    )
	  )
        )
        (setq p0 p)
      )
      (if is (list (car is) (cadr is) 0.0))
    )
    (if (and (setq a (i? p1 p2 lp1)) (setq b (i? p1 p2 lp2)))
      (list a b)
    )
  )
  (defun ptEqd (A B e1 e2 / eqDist-f t0 t1 f0 f1 tm fm n i v+- v*); captura punto equidistante
    (defun v+- (o a b) (mapcar o a b))
    (defun v* (p s) (mapcar '(lambda (x) (* x s)) p))
    (defun eqDist-f (ds A B e1 e2 / pt d1 d2)
      (setq pt (v+- '+ A (v* (v+- '- B A) ds)); Punto sobre AB: P(ds) = A + ds (B - A)
            d1 (distance pt (vlax-curve-getClosestPointTo e1 pt))
            d2 (distance pt (vlax-curve-getClosestPointTo e2 pt))
      )
      (- d1 d2)
    )
    (setq t0 0.0 t1 1.0)
    (while (and (< (setq n (if n (1+ n) 0)) 100) (> (- t1 t0) 1e-6));m茅todo de bisecci贸n
      (setq tm (/ (+ t0 t1) 2.0)
	    fm (eqDist-f tm A B e1 e2)
      )
      (if (< (abs fm) 1e-9)
	(setq n 100 t1 tm t0 tm)
        (if (< (* (if f0 f0 (eqDist-f t0 A B e1 e2)) fm) 0.0)
          (setq t1 tm f1 fm)
          (setq t0 tm f0 fm)
	)
      )
    )
    (if (< t1 1.0) ; par谩metro final y punto equidistante 
      (v+- '+ A (v* (v+- '- B A) (/ (+ t0 t1) 2.0)))
    )
  )
  
  (if (and (setq e1 (car (entsel "\nSelect FIRST LWPolyline..."))) (= (cdr (assoc 0 (setq l1 (entget e1)))) "LWPOLYLINE") )
    (if (and (setq e2 (car (entsel "\nSelect SECOND LWPolyline..."))) (= (cdr (assoc 0 (setq l2 (entget e2)))) "LWPOLYLINE") )
      (progn
	(foreach l l1 (if (= (car l) 10) (setq lp1 (cons (cdr l) lp1))))
	(foreach l l2 (if (= (car l) 10) (setq lp2 (cons (cdr l) lp2))))
	(setq r1? (> (distance (car lp1) (car lp2)) (distance (car lp1) (last lp2))))
	(setq tol 0.01 PI/2 (/ PI 2.) lp1 (if r1? (reverse lp1) lp1))
	(foreach e (list e1 e2)
	  (setq p0 nil m nil r? (if (equal e e1) r1?) lp (if (equal e e1) lp2 lp1))
	  (while (setq p (vlax-curve-getPointAtParam e (setq m (if m ((if r? 1- 1+) m) (if r? (vlax-curve-getEndParam e) 0)))))
	    (if p0
	      (progn
		(setq lAB  (autoInt? (interCpta p (polar p (setq a (+ (angle p0 p) PI/2)) 10000) (polar p (+ a PI) 10000) lp) (if (equal e e1) lp1 lp2));NORMAL AL COMIENZO DEL SEGMENTO
		      lst (if lAB (cons (ptEqd (car lAB) (cadr lAB) e1 e2) lst) lst)
	        )
	        (if (setq p> (vlax-curve-getPointAtParam e ((if r? 1- 1+) m)))
	          (setq lAB  (autoInt? (interCpta p (polar p (setq a (/ (+ (angle p p0) (angle p p>)) 2.)) 10000) (polar p (+ a PI) 10000) lp) (if (equal e e1) lp1 lp2)) ; Bisectriz
			lst (if lAB (cons (ptEqd (car lAB) (cadr lAB) e1 e2) lst) lst)
			lAB  (autoInt? (interCpta p (polar p (setq a (+ (angle p p>) PI/2)) 10000) (polar p (+ a PI) 10000) lp) (if (equal e e1) lp1 lp2));NORMAL AL FINAL DEL SEGMENTO
		        lst (if lAB (cons (ptEqd (car lAB) (cadr lAB) e1 e2) lst) lst)
	          )
		)
		(setq p< p0 p0 p)
	      )
	      (if (setq p> (vlax-curve-getPointAtParam e ((if r? 1- 1+) m)))
	        (setq lAB  (autoInt? (interCpta p (polar (setq p0 p) (setq a (+ (angle p0 p>) PI/2)) 10000) (polar p0 (+ a PI) 10000) lp) (if (equal e e1) lp1 lp2))
		      lst (if lAB (cons (ptEqd (car lAB) (cadr lAB) e1 e2) lst) lst)
		)
	      )
	    )
	  )
	)
	(vla-AddLightWeightPolyline
	  (vla-get-modelspace (vla-get-activedocument (vlax-get-acad-object)))
	  (vlax-Make-Variant (vlax-SafeArray-Fill (vlax-Make-SafeArray 5 (cons 0 (- (length (setq lst (reverse (ordenaPts lst)))) 1))) lst))
	)
      )
    )
  )
  (princ)
)

 

 

I tested the code.

All the points are equidistant, but there are two long segments where the equidistance is drastically broken.

In the last screenshot Dexus attached, you can see approximately where the center line should be in that area.

Img1-2.thumb.jpg.24ea10c3ac8a281e27f061f193d144e4.jpg

 

The center line returned by the GLAVCVS code is shown in magenta.

And the approximate location where it should be is shown in red.

I've attached an image of this

Img1-1.thumb.png.5f398449fa8a0033d59798ff40da9aca.png

  • Agree 1
Posted
10 hours ago, PGia said:

 

I tested the code.

All the points are equidistant, but there are two long segments where the equidistance is drastically broken.

In the last screenshot Dexus attached, you can see approximately where the center line should be in that area.

Img1-2.thumb.jpg.24ea10c3ac8a281e27f061f193d144e4.jpg

 

The center line returned by the GLAVCVS code is shown in magenta.

And the approximate location where it should be is shown in red.

I've attached an image of this

Img1-1.thumb.png.5f398449fa8a0033d59798ff40da9aca.png

 

Yes. I know.

I already mentioned that this problem could arise on some long segments during turns.

I didn't want to delay posting again to fix this.

But I already have an idea of how to solve it.

I'll post the solution as soon as I can.

  • Thanks 1
  • 2 weeks later...
Posted

It's a very interesting topic that has gone unresolved for far too long.

At least I never found one.

But that solution should also consider any piece of land in the middle of the water.

In my opinion, the criterion for defining a centerline depends on the physical property that needs to be preserved.

For a river, the property that seems most convenient to preserve is equidistance. However, in a wall, the properties to preserve are angular symmetry and structural direction. Therefore, I agree with GP_ and Dexus.

But perhaps for this reason, the tools mentioned in this thread won't be able to correctly calculate the centerline of a wall.

  • 3 weeks later...
Posted (edited)

I'm interested in where this topic will be going with the different "side quests" like islands and inlets.

 

In the mean time I kept going, trying to fix my version and after a lot of testing/debugging I changed to code again.

Now it is working as expected on all of the examples I have found! 😁

 

;|
; Calculate centerline between two polylines - dexus
; Function checks intersections of the offsets of two lines to create a middle/avarage line.
; https://www.cadtutor.net/forum/topic/98778-hybrid-parallel/page/7/#findComment-677877
; Version / Date    - Change
; 0.01 [19-11-2025] - Initial release
; 0.02 [27-11-2025] - Added corner support on negative side of crossing polylines
; 0.03 [28-11-2025] - Extra check using vertex to closest point as distance
; 0.04 [28-11-2025] - Added error function
; 0.05 [01-12-2025] - Improved distance check to prevent zigzag lines
; 0.06 [01-12-2025] - Check if offset can be used before adding points
; 0.07 [01-12-2025] - Improved side check on 3 points
; 0.08 [04-12-2025] - Don't compare startpoint to offset when eiter of the polylines is closed
; 0.09 [05-12-2025] - Add points for parallel end segments
; 0.10 [18-12-2025] - More checks for deleting lines and added dedicated function
; 0.11 [08-01-2026] - Support for multiple output lines of the offset function
; 0.12 [08-01-2026] - Bulges are transformed to lines to handle cocentric arcs
; 0.13 [08-01-2026] - Rewrote the _avarageAngle and _diffAngle function
|;
(defun c:cl (/ corners ent1 ent2 gap index loop maxlen offset offsetdistance org1 org2 parallel pts sides ss start te0 te1 te2 tmp1 tmp2
               LM:ProjectPointToLine LM:intersections _addPoints _avarageAngle _checkOffset _checkSortDirection _copyPolyline _cornerOffset 
               _deleteTmpLine _diffAngle _doOffset _getAnglesAtParam _polyline _side *error*)

  (defun *error* (st)
    (if (wcmatch (strcase st t) "*break,*cancel*,*exit*")
      (redraw)
      (princ (strcat "\nOops! Something went wrong: ") st)
    )
    (mapcar '_deleteTmpLine (list ent1 ent2 te0 te1 te2))
    (princ)
  )

  ;|
  ; Deletes an object or list of objects
  ; @Param obj vla-object or list
  |;
  (defun _deleteTmpLine (obj)
    (cond
      ((null obj))
      ((vl-catch-all-error-p obj))
      ((= (type obj) 'list) (mapcar '_deleteTmpLine obj))
      ((not (vlax-erased-p obj)) (vla-delete obj))
    )
  )

  ;|
  ; Draw Polyline - dexus
  ; Draw a polyline from a list of points, but filter out colinear points
  ; @Param lst list of points
  ; @Returns ename of polyline
  |;
  (defun _polyline (lst closed / prev pts)
    (while lst
      (cond
        (
          (and (cdr lst) prev
            (or
              (equal (cdr lst) prev 1e-8) ; Remove duplicate points
              (null (inters prev (car lst) prev (cadr lst))) ; Remove collineair points
            )
          )
        )
        ((setq pts (cons (cons 10 (setq prev (car lst))) pts)))
      )
      (setq lst (cdr lst))
    )
    (entmakex
      (append
        (list
          (cons 0 "LWPOLYLINE")
          (cons 100 "AcDbEntity")
          (cons 100 "AcDbPolyline")
          (cons 90 (length pts))
          (cons 8 (getvar 'clayer))
          (cons 70 (if closed 1 0))
        )
        (reverse pts)
      )
    )
  )

  (defun _copyPolyline (ent maxlen closed rev / bul pts index curve steps size next)
    (setq ent (vlax-ename->vla-object ent) index 0)
    (repeat (1+ (fix (vlax-curve-getEndParam ent)))
      (cond
        ( (and
            (not (vl-catch-all-error-p (setq bul (vl-catch-all-apply 'vla-getbulge (list ent index)))))
            (not (equal bul 0.0 1e-8))
            (setq next (vlax-curve-getDistAtParam ent (1+ index)))
            (not (zerop (setq steps (fix (* (/ (- next (vlax-curve-getDistAtParam ent index)) maxlen) 45)))))
          )
          (setq size (/ 1.0 steps) curve index)
          (repeat steps
            (setq pts (cons (vlax-curve-getPointAtParam ent curve) pts)
                  curve (+ curve size))
          )
        )
        ((setq pts (cons (vlax-curve-getPointAtParam ent index) pts)))
      )
      (setq index (1+ index))
    )
    (_polyline (if rev (reverse pts) pts) closed)
  )

  (defun _side (pline pnt / cpt end target der) ; https://www.theswamp.org/index.php?topic=55685.msg610429#msg610429
    (setq cpt (vlax-curve-getClosestPointTo pline pnt)
          end (vlax-curve-getEndParam pline)
          target (vlax-curve-getParamAtPoint pline cpt)
          der (if
                (and
                  (equal target (fix target) 1e-8)
                  (or
                    (vlax-curve-isClosed pline)
                    (and (not (equal (vlax-curve-getStartParam pline) target 1e-8)) (not (equal end target 1e-8)))
                  )
                )
                (mapcar '-
                  (polar cpt (angle '(0 0) (vlax-curve-getFirstDeriv pline (rem (+ target 1e-3) end))) 1.0)
                  (polar cpt (angle (vlax-curve-getFirstDeriv pline (rem (+ (- target 1e-3) end) end)) '(0 0)) 1.0)
                )
                (vlax-curve-getFirstDeriv pline target)
              )
            )
    (minusp (sin (- (angle cpt pnt) (angle '(0.0 0.0) der))))
  )

  ;; Intersections - Lee Mac
  ;; mod - [int] acextendoption enum of intersectwith method
  (defun LM:intersections ( ob1 ob2 mod / lst rtn )
    (if
      (and
        (vlax-method-applicable-p ob1 'intersectwith)
        (vlax-method-applicable-p ob2 'intersectwith)
        (setq lst (vlax-invoke ob1 'intersectwith ob2 mod))
      )
      (repeat (/ (length lst) 3)
        (setq rtn (cons (list (car lst) (cadr lst) (caddr lst)) rtn) lst (cdddr lst))
      )
    )
    (reverse rtn)
  )

  (defun _doOffset (offset / lst rtn) ; Global vars: pts ent1 ent2 sides te1 te2
    (setq rtn
      (cond
        ((equal offset 0.0 1e-8)
          (if (setq lst (LM:intersections ent1 ent2 acExtendNone))
            (setq pts (_addPoints lst ent1 ent2 pts))
          )
          lst
        )
        ( (or ; Make offset
            (setq te1 nil)
            (vl-catch-all-error-p (setq te1 (vl-catch-all-apply 'vlax-invoke (list ent1 'Offset (if (car sides) offset (- offset))))))
            (not (setq tmp1 (vl-some (function (lambda (te) (_checkOffset ent1 te offset))) te1)))
            (vla-put-visible tmp1 :vlax-false) ; (vla-put-color tmp1 252)

            (setq te2 nil)
            (vl-catch-all-error-p (setq te2 (vl-catch-all-apply 'vlax-invoke (list ent2 'Offset (if (cadr sides) offset (- offset))))))
            (not (setq tmp2 (vl-some (function (lambda (te) (_checkOffset ent2 te offset))) te2)))
            (vla-put-visible tmp2 :vlax-false) ; (vla-put-color tmp2 252)
          )
          (princ (strcat "\nOffset of " (rtos offset 2 4) " failed. "))
          nil
        )
        ((setq lst (LM:intersections tmp1 tmp2 acExtendNone))
          (if parallel ; Add points of parallel end segments
            (mapcar
              (function (lambda (ent1 ent2)
                (mapcar
                  (function (lambda (pt)
                    (if (equal pt (vlax-curve-getClosestPointTo ent2 pt) 1e-10)
                      (setq lst (cons pt lst))
                    )
                  ))
                  (list
                    (vlax-curve-getStartPoint ent1)
                    (vlax-curve-getEndPoint ent1)
                  )
                )
              ))
              (list tmp1 tmp2)
              (list tmp2 tmp1)
            )
          )
          (setq pts (_addPoints lst tmp1 tmp2 pts))
          lst
        )
      )
    )
    (_deleteTmpLine te1)
    (_deleteTmpLine te2)
    rtn
  )

  ;|
  ; Check if the offset starts and ends at the correct point or is closed
  |;
  (defun _checkOffset (ent1 ent2 offset)
    (if
      (or
        (vlax-curve-isclosed ent1)
        (vlax-curve-isclosed ent2)
        (and
          (equal (distance (vlax-curve-getStartPoint ent1) (vlax-curve-getStartPoint ent2)) offset 1e-4)
          (equal (distance (vlax-curve-getEndPoint ent1) (vlax-curve-getEndPoint ent2)) offset 1e-4)
        )
      )
      ent2
    )
  )

  (defun _addPoints (lst ent1 ent2 pts / len1 len2)
    (setq len1 (vlax-curve-getDistAtParam ent1 (vlax-curve-getEndParam ent1))
          len2 (vlax-curve-getDistAtParam ent2 (vlax-curve-getEndParam ent2))
          lst
            (vl-remove nil
              (mapcar
                (function (lambda (pt / d1 d2)
                  (if
                    (and
                      (setq d1 (vlax-curve-getDistAtPoint ent1 pt))
                      (setq d2 (vlax-curve-getDistAtPoint ent2 pt))
                    )
                    (list
                      (cons
                        (
                          (lambda (ang) (if (cadr ang) (_avarageAngle (car ang) (cadr ang))))
                          (mapcar
                            (function (lambda (ent)
                              (
                                (lambda (ang) (if (cadr ang) (_avarageAngle (car ang) (cadr ang))))
                                (_getAnglesAtParam ent (vlax-curve-getParamAtPoint ent (vlax-curve-getClosestPointTo ent pt)))
                              )
                            ))
                            (list ent1 ent2)
                          )
                        )
                        (cond
                          ((and (vlax-curve-isclosed ent1) (not (vlax-curve-isclosed ent2))) (list (/ d2 len2)))
                          ((vlax-curve-isclosed ent2) (list (/ d1 len1)))
                          ((list (/ d1 len1) (/ d2 len2)))
                        )
                      )
                      pt
                    )
                  )
                ))
                lst
              )
            ))
    (append lst pts)
  )

  ;|
  ; Project Point onto Line - Lee Mac
  ; @Param pt point to project
  ; @Param p1 first point of line
  ; @Param p2 second point of line
  ; @Returns projected point
  |;
  (defun LM:ProjectPointToLine ( pt p1 p2 / nm )
    (setq nm (mapcar '- p2 p1)
          p1 (trans p1 0 nm)
          pt (trans pt 0 nm))
    (trans (list (car p1) (cadr p1) (caddr pt)) nm 0)
  )

  (defun _getAnglesAtParam (ent pa / ang1 ang2)
    (if (and (vlax-curve-isClosed ent) (= pa 0)) ; Special case for closed Polyline
      (setq ang1 (vlax-curve-getFirstDeriv ent 1e-14)
            ang2 (vlax-curve-getFirstDeriv ent (- (fix (vlax-curve-getEndParam ent)) 1e-14)))
      (setq ang1 (vlax-curve-getFirstDeriv ent (+ pa 1e-14))
            ang2 (vlax-curve-getFirstDeriv ent (- pa 1e-14)))
    )
    (if (and ang1 ang2)
      (list
        (angle '(0 0 0) ang1)
        (angle '(0 0 0) ang2)
      )
    )
  )

  ;|
  ; Avarage Angle - dexus
  ; Get angle of a line between two angles
  ; @Param ang1 real - Angle in radians
  ; @Param ang2 real - Angle in radians
  ; @Returns real - Angle in radians
  |;
  (defun _avarageAngle (ang1 ang2 / dif)
    (setq dif (- ang1 ang2))
    (if (< pi (abs dif))
      (+ ang1
        (*
          (- (+ pi pi) (abs dif))
          (if (minusp dif) -0.5 0.5)
        )
      )
      (+ ang2 (* dif 0.5))
    )
  )

  ;|
  ; Difference between angles - dexus
  ; Retuns the angle between two angles
  ; @Param ang1 real
  ; @Param ang2 real
  ; @Returns real
  |;
  (defun _diffAngle (ang1 ang2)
    (
      (lambda (ang)
        (if (> ang pi)
          (- (+ pi pi) ang)
          ang
        )
      )
      (abs (- ang2 ang1))
    )
  )

  ;|
  ; Check which of two poits is closer to the expected angle of the line
  ; @Param a (list (list angle) point)
  ; @Param b (list (list angle distance1 distance2) point)
  ; @Returns true if a is after b
  |;
  (defun _checkSortDirection (a b)
    (and (caar a) (caar b)
      (<
        (abs (_diffAngle (angle (cadr a) (cadr b)) (caar a)))
        (abs (_diffAngle (angle (cadr b) (cadr a)) (caar b)))
      )
    )
  )

  ;|
  ; Calculate exact offset distance on a corner - dexus
  ; pt1 - Point on corner
  ; pt2 - Point on other side
  ; pt3 - Center for bisector
  ; pt4 - Target for corner of the offset
  ; pt5 - Find perpendicular point for offset distance
  ;              /
  ;             /
  ;  -------- pt1  pt5
  ;             \  /
  ;              pt4
  ;                \
  ;  ---- pt3 ----- pt2 -----
  ;
  ; @Param ent1 Line to check corners
  ; @Param ent2 Opposing line
  ; @Returns List of offset distances (pt1 -> pt5) to calculate
  |;
  (defun _cornerOffset (ent1 ent2 / ang1 ang1a ang2 ang3 index pt1 pt2 pt3 pt4 pt5 rtn)
    (setq index 0)
    (repeat (fix (vlax-curve-getEndParam ent1))
      (and
        (setq pt1 (vlax-curve-getPointAtParam ent1 index)) ; Point on corner
        (setq ang1 (_getAnglesAtParam ent1 index)) ; Angles of pt1
        (setq ang1a (_avarageAngle (car ang1) (cadr ang1)))
        (setq te0 (entmakex (list (cons 0 "line") (cons 10 pt1) (cons 11 (polar pt1 (- ang1a halfPi) 1))))) ; Temp line for finding the angle on the other side
        (foreach pt2 (LM:intersections (vlax-ename->vla-object te0) ent2 acExtendThisEntity) ; Point on other side
          (and
            (setq ang2 (_getAnglesAtParam ent2 (vlax-curve-getParamAtPoint ent2 pt2))) ; Angle of pt2
            (if (equal (rem (car ang1) pi) (rem (car ang2) pi) 1e-8) ; Is parallel?
              (and
                (setq parallel (or parallel (< index 1) (<= (fix (vlax-curve-getEndParam ent1)) (1+ index)) t)) ; End of line is parallel
                (setq pt3 (mapcar (function (lambda (a b) (* (+ a b) 0.5))) pt1 pt2)) ; Midpoint
                (setq ang3 (car ang1)) ; Same angle als ang1
              )
              (and
                (setq pt3 (inters pt1 (polar pt1 (car ang1) 1) pt2 (polar pt2 (car ang2) 1) nil)) ; Find center for bisector
                (setq ang3 (_avarageAngle (angle pt1 pt3) (angle pt2 pt3))) ; Angle of bisector
              )
            )
            (setq pt4 (inters pt3 (polar pt3 ang3 1) pt1 (polar pt1 (+ ang1a halfPi) 1) nil)) ; Find target for corner of the offset
            (setq pt5 (LM:ProjectPointToLine pt4 pt1 (polar pt1 (+ (car ang1) halfPi) maxlen))) ; Find perpendicular point for offset distance
            (setq rtn (cons (distance pt1 pt5) rtn)) ; Return offset distance
          )
        )
      )
      (if (and te0 (not (vlax-erased-p te0))) (entdel te0))
      (setq index (1+ index))
    )
    rtn
  )

  (if
    (and
      (not 
        (while
          (cond
            ((not (setq ss (ssget '((0 . "LWPOLYLINE")))))
              (princ "\nNothing selected. Try again...\n")
              nil
            )
            ((/= (sslength ss) 2)
              (princ "\nSelect 2 polylines! Try again...\n")
            )
            ((and (setq org1 (ssname ss 0)) (setq org2 (ssname ss 1)))
              nil ; Stop loop
            )
          )
        )
      )
      org1
      org2
    )
    (progn
      (if (not (numberp halfPi)) (setq halfPi (* pi 0.5)))
      (setq maxlen
        (* 1.1
          (max
            (vlax-curve-getDistAtParam org1 (vlax-curve-getEndParam org1))
            (vlax-curve-getDistAtParam org2 (vlax-curve-getEndParam org2))
            (
              (lambda (ent1 ent2 / step de1 div p_step dis dmax)
                (setq step (/ (setq de1 (vlax-curve-getDistAtParam ent1 (vlax-curve-getEndParam ent1))) 500)
                      div step
                      dmax 0.0)
                (while (< div de1)
                  (setq p_step (vlax-curve-getPointAtDist ent1 div)
                        dis (distance p_step (vlax-curve-getClosestPointTo ent2 p_step)))
                  (if (> dis dmax) (setq dmax dis))
                  (setq div (+ div step))
                )
                dmax
              )
              org1 org2
            )
          )
        )
      )

      ; Convert first line
      (setq ent1 (_copyPolyline org1 maxlen (vlax-curve-isClosed org1) nil))
      (setq ent1 (vlax-ename->vla-object ent1))
      (vla-put-visible ent1 :vlax-false)

      ; Convert second line
      (setq ent2 (_copyPolyline org2 maxlen (vlax-curve-isClosed org2)
        (<
          (distance (vlax-curve-getStartPoint org1) (vlax-curve-getEndPoint org2))
          (distance (vlax-curve-getEndPoint org1) (vlax-curve-getEndPoint org2))
        )
      ))
      (setq ent2 (vlax-ename->vla-object ent2))
      (vla-put-visible ent2 :vlax-false)

      ; Get offset direction
      (setq sides
        (mapcar
          (function (lambda (a b / s m e)
            (setq s (_side a (vlax-curve-getStartPoint b))
                  m (_side a (vlax-curve-getPointAtParam b (* 0.5 (vlax-curve-getEndParam b))))
                  e (_side a (vlax-curve-getEndPoint b)))
            (or (and s m) (and s e) (and m e))
          ))
          (list ent1 ent2)
          (list ent2 ent1)
        )
      )

      (mapcar ; Add half distances from closest point to every vertex
        (function (lambda (ent1 ent2 / index pt)
          (setq index 0)
          (repeat (fix (vlax-curve-getEndParam ent1))
            (setq pt (vlax-curve-getPointAtParam ent1 index)
                  corners (cons (* (distance pt (vlax-curve-getClosestPointTo ent2 pt)) 0.5) corners)
                  index (1+ index))
          )
        ))
        (list ent1 ent2)
        (list ent2 ent1)
      )

      (setq corners (vl-sort (append corners (_cornerOffset ent1 ent2) (_cornerOffset ent2 ent1)) '<)
            offsetdistance (/ maxlen 256.0))

      (if (LM:intersections ent1 ent2 acExtendNone) ; For crossing polylines, add negative values
        (setq offset (- maxlen) corners (append (mapcar '- (reverse corners)) corners))
        (setq offset 0.0)
      )

      (setq index 0)
      (setq gap (getvar 'offsetgaptype))
      (setvar 'offsetgaptype 0)
      (while
        (progn
          (while (and corners (> offset (car corners))) ; Calculated offset values to check
            (_doOffset (car corners))
            (setq index (1+ index))
            (setq corners (cdr corners))
          )
          (setq loop ; Incremental check
            (cond
              ((> offset maxlen) nil)
              ((_doOffset offset) (setq index (1+ index)) (setq start t))
              ((not start) t)
              (start nil)
            )
          )
          (setq offset (+ offset offsetdistance))
          loop
        )
      )
      (setvar 'offsetgaptype gap)

      (if pts ; Draw polyline
        (_polyline
          (mapcar 'cadr
            (vl-sort pts
              (function (lambda (a b / ang)
                (if (and (caddar a) (caddar b))
                  (if (< (cadar a) (cadar b))
                    (or (< (caddar a) (caddar b)) (_checkSortDirection a b))
                    (and (< (caddar a) (caddar b)) (_checkSortDirection a b))
                  )
                  (< (cadar a) (cadar b))
                )
              ))
            )
          )
          (and
            (vlax-curve-isClosed ent1)
            (vlax-curve-isClosed ent2)
          )
        )
      )
      (_deleteTmpLine ent1)
      (_deleteTmpLine ent2)
      (if (and ent2 (not (vlax-erased-p ent2))) (vla-delete ent2))
    )
  )
  (princ)
)

 

River result:

centerline.png.13544c1a8e14857bc8c265095a9c7650.png

Edited by dexus
Url in header updated to this post.
  • Like 5

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...