Offset multiple objects at once

Offset multiple objects at once

JoeKidd33
Advocate Advocate
29,728 Views
40 Replies
Message 1 of 41

Offset multiple objects at once

JoeKidd33
Advocate
Advocate

We have a process in our drawings where we need need to offset closed polygons to the interior of the polygon at a set distance. Can anyone think of a routine or way to automate this? We will sometimes have hundreds of these polygons that need to be offset. I was thinking if there was a way to have an interior coordinate calculated from the object and store that as a variable, it could be part of a lsp routine to take the selected object and use the interior coordinate as the point for side to offset.

0 Likes
29,729 Views
40 Replies
Replies (40)
Message 2 of 41

marko_ribar
Advisor
Advisor

You don't need interior point... Just offset on both sides with (vla-offset) function and then check for area of new polygons... If one area of polygon is larger, then remove that polygon - only smaller one should remain... Iterate through all selection set of polygons and apply this procedure for each one...

Marko Ribar, d.i.a. (graduated engineer of architecture)
0 Likes
Message 3 of 41

hak_vz
Advisor
Advisor

Try this.

 

(defun C:offset_in ( / *error* keyValue e el dis lyr ss i p1 npoints pts m xc yc )
(defun *error*() (setvar "cmdecho" 1) (princ))
(defun keyValue (key el)(cdr (assoc key el)))
(setq dis (getreal "\n Offset distance for all polygons >"))
(setq e (entsel "\n Select polygon to determin layer name >"))
(setvar "cmdecho" 0)
(setq lyr (cdr (assoc 8 (entget (car e)))))
(setq ss (ssget "X"  
            (list (cons -4 "<AND")
                  (cons 8 lyr)
                  (cons 0 "LWPOLYLINE")
                  (cons -4 "AND>")
            )
        ) 
      i 0)
(while (< i (sslength ss))

(setq e (ssname ss i))
(setq ent  (entget e))
(if (and ent)
        (progn
        
            (if (= (logand (cdr (assoc 70 ent)) 1) 1)(setq p1 (cdr (assoc 10 ent))))
            (setq npoints (keyValue 90 ent) npoints (fix npoints) pts nil)
              (cond (ent
                     (while (setq m (assoc 10 ent) ent (member m ent))
                       (setq pts (cons (cdr m) pts) ent (cdr ent))
                     )
                    )
              )
              
            (if (eq (keyValue 70 ent) 1) (setq pts (cons p1 pts)))
        
        
        (setq xc (/ (apply '+ (mapcar 'car pts)) npoints)
              yc (/ (apply '+ (mapcar 'cadr pts)) npoints)
        )
        (command "offset" dis e  (list xc yc) "" "")  
       ) 
 )
(setq i (+ i 1))
)
(setvar "cmdecho" 1)
(princ)
)

Miljenko Hatlak

EESignature

Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
0 Likes
Message 4 of 41

Kent1Cooper
Consultant
Consultant

@Anonymous wrote:

... we need need to offset closed polygons to the interior of the polygon at a set distance. ....


Are you talking about regular  polygons, such as those made with the POLYGON command?  Or Polylines of other shapes that will always be convex  all around?  If either, it can be very much simpler than @hak_vz's suggestion, using a point halfway between the start and the halfway-around location.  But if they might sometimes be concave  in places, you could get some Offset to the exterior from that routine.  Also, would any ever be small enough in relation to the Offset distance that Offsetting inward would fail?  If so, a routine could be made to catch that failure, and continue to do remaining ones, without killing the whole thing.

Kent Cooper, AIA
Message 5 of 41

Kent1Cooper
Consultant
Consultant

@marko_ribar wrote:

... offset on both sides with (vla-offset) function and then check for area of new polygons... If one area of polygon is larger, then remove that polygon - only smaller one should remain... ...


I would simplify that slightly -- no need to always go both  ways.  Just (vla-offset) using the desired distance, and compare the area of the result to the original.  Only if the new one is larger than the original do you need to get rid of that and (vla-offset) using the negative  of the distance to go inward instead.

 

But there is still the complication of whether any originals might be too small to be Offset inward by the desired distance, which will cause an error.

 

And there's the question of what kinds of shapes they're really talking about.  In this image, the white is the original, and when Offset outward, by a distance too large in relation to the space between the pointing-at-each-other corners, the result is both  green shapes.  If a routine compares the areas of the original and [what you would usually expect] the last object, which in this case happens to be the smaller  one, it will think it has successfully Offset it inward, but it will be wrong.

 

OffsetOutward.PNG

Kent Cooper, AIA
Message 6 of 41

hak_vz
Advisor
Advisor
  • find point inside polygon
  • offset polygon to inside direction
  • check for intersections of these two polygons and if intersections exist trim all parts of the new polygon outward from original polygon
  • if intersections don’t exist and size of new polygon is smaller than original offset is correct.
  • if size of new polygon is bigger than original, delete new polygon  
  • in case of intersections rearrange remaining inner polygon so that all its points are at offset distance and close it

Miljenko Hatlak

EESignature

Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
0 Likes
Message 7 of 41

Kent1Cooper
Consultant
Consultant

@hak_vz wrote:
  • find point inside polygon ....

And therein lies the big problem, if the Polyline might be of any shape.  This is why I asked the questions in Post 4.

 

Put "point inside polyline" in the Search window for a variety of threads about the question, including this one where I just used a similar shape to the one in Post 5 here, to illustrate one kind of configuration that can lead to incorrect results.

Kent Cooper, AIA
0 Likes
Message 8 of 41

hak_vz
Advisor
Advisor

 


@Kent1Cooper wrote:

@hak_vz wrote:
  • find point inside polygon ....

And therein lies the big problem, if the Polyline might be of any shape.  This is why I asked the questions in Post 4.

 

Put "point inside polyline" in the Search window for a variety of threads about the question, including this one where I just used a similar shape to the one in Post 5 here, to illustrate one kind of configuration that can lead to incorrect results.


I agree. In my code I collect all polygon points and and search for midpoint, as a simple solution. To avoid situation that you mention one would sort points in clockwise (or counterclockwise) direction and test orientation of this apparent (or true) midpoint against polygon points connected  into a "convex hull", i.e. is it left or right to all connecting lines. If points are sorted in counterclockwise direction, then point is inside polygon if it's left to all connecting lines.

 

 

;;    -1     Point is to the right of vector.
;;     0     Point is on (colinear with) vector
;;     1     Point is to the left of vector.
 
  (defun vectorSide (v1 v2 p / r *fuzz*)
    (setq *fuzz* 1e-10)
    (setq r (- (* (-(car v2)(car v1))(-(cadr p)(cadr v1)))
               (* (-(cadr v2)(cadr v1))(-(car p)(car v1)))
            )
    )
    (cond ((equal r 0.0 *fuzz*) 0)
          (t (fix (/ (abs r) r)))
    )
  ) ; end defun

Miljenko Hatlak

EESignature

Did you find this post helpful? Feel free to Like this post.
Did your question get successfully answered? Then click on the ACCEPT SOLUTION button.
0 Likes
Message 9 of 41

Kent1Cooper
Consultant
Consultant

@JoeKidd33 wrote:

... we need need to offset closed polygons to the interior of the polygon at a set distance. ....


 

I thought it might be nice to have a universal  routine, that would not be limited to Polylines, but could do anything  that could be said to have an "inside" and "outside," and that would not go only inward, but would have commands to go either way.  I started playing with it at the time, then forgot about it for a while, but just got back into it.  The result is the attached OffsetInOrOut.lsp with its OffIn and OffOut commands.

 

It does still have possible unexpected results with shapes that result in more than one  new object when Offset [mostly self-intersections or convolutedness in Polylines and Splines].  But apart from those, it seems to work, on Polylines [of all but 3D varieties], Circles, Arcs, Ellipses and [only planar] Splines, as many as you select.  It uses the earlier suggestion here to do the Offset and compare the area of the result to the original, to decide whether to delete the first result and go the other way.

 

It even reports [in the OffIn command only] how many selected objects were too small to Offset inward by the desired distance, if any.  And it remembers that distance, separately from regular Offset's, to offer as default on subsequent use.  See the comments at the top for more details.

 

[It doesn't have regular Offset's options about the Layer of the result, nor to delete the original, but those could be added.]

Kent Cooper, AIA
Message 10 of 41

ronjonp
Mentor
Mentor

I did not read this whole thread so excuse me if I'm way off, but HERE's some code I wrote about a week ago to do something like this.

0 Likes
Message 11 of 41

john.uhden
Mentor
Mentor

It's much easier that all of that.

To offset to the inside:

If the polyline is drawn counter-clockwise then (vla-offset object <negative amount>)  [to the left]

If it is clockwise, then (vla-offset object <positive amount>).  [to the right]

 

Actually, I prefer to use the form (vlax-invoke object 'offset amount) because it returns the VLA-OBJECT of the created polyline, like in case you want to change its layer or color or whatever.  Or a list of vla-objects if more than one offset is created.

 

  ;;  Function to determine if a polyline is CW or CCW
  ;;  Returns 1 for CCW, or -1 for CW, or nil if not a polyline or only two points
  (defun @CCW? (Pline / Param Sum End P1 P2 P3)
    (cond
      ((= (type Pline) 'VLA-OBJECT))
      ((= (type Pline) 'ENAME)(setq Pline (vlax-ename->vla-object Pline)))
      (1 (setq Pline nil))
    )
    (and
      Pline
      (setq Param 0.5
            Sum 0.0
            End (vlax-curve-getendparam Pline)
            P1 (vlax-curve-getstartpoint Pline)
            PType (vlax-get Pline 'ObjectName)
      )
      (or
        (while (not (setq P2 (vlax-curve-getpointatparam Pline Param)))
          (setq Param (+ Param 0.5))
        )
        1
      )
      (while (and (> End 2)(< Param End))
        (setq Param (+ Param 0.5))
        (while (not (setq P3 (vlax-curve-getpointatparam Pline Param)))
          (setq Param (+ Param 0.5))
        )
        (setq Sum (+ Sum (@delta (angle P1 P2)(angle P2 P3)))
              P1 P2 P2 P3
        )
      )
    )
    (cond
      ((not Sum) nil)
      ((zerop Sum) nil)
      ((> Sum 0) 1)  ;; meaning it's CCW
      ((< Sum 0) -1) ;; meaning it's CW
    )
  )
where:
  ;;-----------------------------------------------------------------------
  ;; This function returns the deflection angle (in radians) of two angles:
  ;;
  (defun @delta (a1 a2)
    (cond
      ((> a1 (+ a2 pi))
        (- (+ a2 pi pi) a1)
      )
      ((> a2 (+ a1 pi))
        (- a2 (+ a1 pi pi))
      )
      (1 (- a2 a1))
    )
  )

John F. Uhden

Message 12 of 41

Kent1Cooper
Consultant
Consultant

@john.uhden wrote:

It's much easier that all of that.

To offset to the inside:

If the polyline is drawn counter-clockwise then (vla-offset object <negative amount>)  [to the left]

If it is clockwise, then (vla-offset object <positive amount>).  [to the right]

.... 


 

That's fine for Polylines, and yes, that's the entity type in the original question, but I was casting a wider net.  [And there are various CW-v.-CCW Polyline test routines out there, if people want to look at different approaches to the question.]  And it can also be easier for Circles/Arcs/Ellipses -- positive (vla-offset) value is always outboard, negative always inboard.  But those entity-type-specific approaches require that each entity in a selection set be evaluated for its type first, and then processed appropriately [i.e. differently  for different entity types].  I was going for a routine that uses the same  process for all  appropriate entity types, so that the type does not  need to be determined for individual entities.  Yes, it does need to check individually for types that the selection filter needs to allow but that can't be Offset, i.e. 3DPolylines [which would also need to be checked for if using the CW-vs.-CCW approach] and non-planar Splines.  But the result of that check doesn't need to determine how  to process the entity, only whether  or not to do so.

 

[And is it really "much easier"?  Your determination, for Polylines only, of whether one was drawn CW or CCW, without  including the (vla-offset) using the sign thus determined on the distance, takes 48 working-code lines. In mine, the did-it-go-the-wrong-way-and-if-so-go-the-other-way portion takes 28 lines, covering all  entity types, and including  the Offsetting itself and the one the other way if needed, and also keeping track of whether one is too small to Offset inward by the given distance.]

Kent Cooper, AIA
Message 13 of 41

Kent1Cooper
Consultant
Consultant

@ronjonp wrote:

.... HERE's some code I wrote about a week ago to do something like this.


 

One comment:  It restricts selection to LWPolylines with more than 2 vertices.  But there are several possible configurations of only-2-vertex Polylines, involving at least one arc  segment, that can have a definite "inside" and "outside" and should be allowed.  They include those made with the DONUT command [two equal half-circle arcs], two-arc-segment closed ones with unequal  arc segments [whether or not they look like a circle as Donuts do], one-arc-segment-and-one-line-segment closed ones [such as a capital-D shape], and single-arc-segment open  ones.

Kent Cooper, AIA
0 Likes
Message 14 of 41

ronjonp
Mentor
Mentor

@Kent1Cooper wrote:

@ronjonp wrote:

.... HERE's some code I wrote about a week ago to do something like this.


 

One comment:  It restricts selection to LWPolylines with more than 2 vertices.  But there are several possible configurations of only-2-vertex Polylines, involving at least one arc  segment, that can have a definite "inside" and "outside" and should be allowed.  They include those made with the DONUT command [two equal half-circle arcs], two-arc-segment closed ones with unequal  arc segments [whether or not they look like a circle as Donuts do], one-arc-segment-and-one-line-segment closed ones [such as a capital-D shape], and single-arc-segment open  ones.


Sure I see that. The code is offered merely as a start point for someone to expand on and not a complete solution. Thanks for the input though!

0 Likes
Message 15 of 41

john.uhden
Mentor
Mentor
You should discard the concept of outboard vs. inboard altogether. It's
actually a matter of left vs. right. Arcs and circles and ellipses are
always drawn in the direction of AutoCAD angular increase... CCW, so for
them, to the left is always inside and to the right is always outside. But
polylines are bisexual... they can go either way, thus the need for one of
the myriad direction determinators that flood the ether. But, other than
reversing, the only ones I found displayed direction arrows, that a program
can't see.

John F. Uhden

0 Likes
Message 16 of 41

Anonymous
Not applicable

I use the above offout and offin lisp about 200 times per day to offset multiple objects simultaneously.  I always need to erase the originals and have been trying to add the delete option but cannot figure out where the subroutine should go or the correct syntax.  Any help would be appreciated. 

Message 17 of 41

ronjonp
Mentor
Mentor

Add the green highlighted line:

image.png

Message 18 of 41

Anonymous
Not applicable

Wow! I was WAY over complicating it.  You are amazing and I will be raking through this lsp to see how it works and learn all I can.

0 Likes
Message 19 of 41

ronjonp
Mentor
Mentor

Glad to help! You could also try this version .. will keep settings between sessions and consolidates the two commands into one.

(defun c:offf (/ _off a b d i o p s x)
  ;; RJP » 2018-11-28
  (defun _off (o d / r)
    (cond ((= 'list (type (setq r (vl-catch-all-apply 'vlax-invoke (list o 'offset d))))) (car r)))
  )
  (or (setq p (getenv "RJP_Offset_Side")) (setq p "Inside"))
  (or (setq i (getenv "RJP_Offset_Dist")) (setq i "1"))
  (or (setq b (getenv "RJP_Offset_Delete")) (setq b "No"))
  (cond
    ((and (progn (initget "Outside Inside")
		 (setq p (cond ((getkword (strcat "\nOffset [Outside/Inside] <" p ">: ")))
			       (p)
			 )
		 )
	  )
	  (setq	i (cond	((getdist (strcat "\nEnter Offset Distance: <" i ">: ")))
			((atof i))
		  )
	  )
	  (progn (initget "Yes No")
		 (setq b (cond ((getkword (strcat "\nDelete original? [Yes/No] <" b ">: ")))
			       (b)
			 )
		 )
	  )
	  (setq s (ssget ":L" '((0 . "*POLYLINE,ARC,CIRCLE,ELLIPSE,SPLINE"))))
     )
     (setenv "RJP_Offset_Dist" (vl-princ-to-string i))
     (setenv "RJP_Offset_Side" p)
     (setenv "RJP_Offset_Delete" b)
     (foreach e	(vl-remove-if 'listp (mapcar 'cadr (ssnamex s)))
       (setq o (vlax-ename->vla-object e))
       (cond
	 ((= 2
	     (length (setq
		       a (vl-remove
			   'nil
			   (mapcar '(lambda (x)
				      (cond ((setq d (_off o (x i))) (list d x (vlax-curve-getarea d))))
				    )
				   (list + -)
			   )
			 )
		     )
	     )
	  )
	  (setq a (vl-sort a '(lambda (r j) (< (caddr r) (caddr j)))))
	  (and (= p "Outside") (setq a (reverse a)))
	  (vla-delete (car (last a)))
	  (and (= "Yes" b) (entdel e))
	 )
	 (t (vla-put-color o 1) (print "Something went wrong.."))
       )
     )
    )
  )
  (princ)
)
(vl-load-com)

 

Message 20 of 41

Kent1Cooper
Consultant
Consultant

@Anonymous wrote:

....  I always need to erase the originals .... 


 

@ronjonp's suggestion in Message 17 should do it, provided  you never have the situation where something is too small to Offset inward by the specified distance!  In that case, the (entdel ent) in that position in the code would delete the original even with no Offset result replacing it.  If you would ever have that situation, and you want the original retained, it would need to be handled differently, such as like this [very minimally tested]:

....
(if ; level4 is level3 then -- check comparative areas: ((if (= subpr "In") > <) (vla-get-Area (vlax-ename->vla-object (entlast))) oarea) (progn ; level4 then -- went wrong way (entdel (entlast)) (if ; level5 -- go the other way [if possible] (vl-catch-all-error-p ; returns T for error if inward but too small (vl-catch-all-apply 'vla-offset (list obj (- *OIOdist))) ); -error-p (setq nogo (1+ nogo)); then -- add to count of too-small; [had no else]
(entdel ent); else -- succeeded going other way, delete original ); if level5 ); progn (entdel ent); else -- went right way first, delete original ); if level4 [went wrong way] (if (= subpr "Out"); entire function was one line, now expanded ; level3 else -- too small for inward; go other way only if OffOut (progn ; then -- now two operations (vla-offset obj (- *OIOdist)); will always succeed (entdel ent); delete original ); progn (setq nogo (1+ nogo)); else, and don't delete original ; if OffIn, add to count of too-small ); if
....

 

Kent Cooper, AIA
0 Likes