AutoLISP to offset closed polyline, hatch, and delete the second polyline [C3D]

AutoLISP to offset closed polyline, hatch, and delete the second polyline [C3D]

Anonymous
Not applicable
13,434 Views
106 Replies
Message 1 of 107

AutoLISP to offset closed polyline, hatch, and delete the second polyline [C3D]

Anonymous
Not applicable

Hi. In our firm, we often created partial hatches within buildings (represented by closed polylines). The process is as follows:
1. We select a closed polyline that was previously drawn and that represents the outline of a building.

2. We offset the polyline 2 units inside, creating a scaled version of the original polyline within the first. 

3. We hatch the area within the two polylines with a specific hatch pattern, scale, layer and color.

4. We delete the smaller, inside polyline, leaving the original building outline and the new hatch within.

I'm looking to automate this process in some way. I had some marginal success with the Action Recorder, but it wasn't consistent if the building outline was angled, and I had to always use the rightmost edge of the building. I'm sure AutoLISP can automate this sequence, but I'm a complete beginner.

Can anybody spoonfeed me a script that does what I'm looking for? Thanks a whole ton.

13,435 Views
106 Replies
Replies (106)
Message 2 of 107

Anonymous
Not applicable

Here's the structure I'm imagined using:

User begins command,

User is prompted to select the object to inset,

User selects the object to inset,

The selected object is offset a fixed distance within its borders,

A hatch is drawn between the inner and outer objects with a specific layer, hatch scale and color,

The inner object is deleted.

I attempted to make an action recorder macro to complete this process, which I'll attach to this reply. The main issues with it are that you always have to select the rightmost edge of the polyline, as it the offset side is fixed within the macro to be to the left. Also, depending on the angle of the polyline segment you select, and the amount you're zoomed in our out, it will sometimes not delete the inner polyline, but instead delete the hatch. This is because it's able to make the inner polyline and hatch between the two, but the only way I can delete with macros is to move a fixed distance, select and delete. If the line is angled, this fixed distance will make the macro select the hatch instead of the inner line. I've wracked my brain trying to think of a way to prevent this in the macro, but I think it's a limitation with using the Action Recorder.

 

0 Likes
Message 3 of 107

dlanorh
Advisor
Advisor

@Anonymous wrote:

 

A hatch is drawn between the inner and outer objects with a specific layer, hatch scale and color,

The inner object is deleted.

 

 


We cannot make a hatch without knowing the hatch layer, pattern scale and color. Care to enlighten us?

I am not one of the robots you're looking for

0 Likes
Message 4 of 107

john.uhden
Mentor
Mentor

That's what draftspeople are for.

John F. Uhden

0 Likes
Message 5 of 107

Anonymous
Not applicable

@dlanorh wrote:

@Anonymous wrote:

 

A hatch is drawn between the inner and outer objects with a specific layer, hatch scale and color,

The inner object is deleted.

 

 


We cannot make a hatch without knowing the hatch layer, pattern scale and color. Care to enlighten us?


Are you being snarky, or are you interested in the specifics of the program? Let's say the layer would be inner_wall, the pattern would be solid fill, the scale would be 1.0 and the color would be 250,250,250.

0 Likes
Message 6 of 107

Kent1Cooper
Consultant
Consultant

@Anonymous wrote:
.... Let's say the layer would be inner_wall, the pattern would be solid fill, the scale would be 1.0 and the color would be 250,250,250.

 

If it's the SOLID pattern, the hatch scale is irrelevant.  I'm assuming the color is part of the Layer, and that the Layer already exists in the drawing [those can be accounted for if necessary].  Give this a try [minimally tested]:

(defun C:OI2H ; = Offset Inward by 2 drawing units, and Hatch
  (/ *error* doc);;;;; ss ent obj oarea pl2)
(setq ss nil ent nil obj nil oarea nil)
  (defun *error* (errmsg)
    (if (not (wcmatch errmsg "Function cancelled,quit / exit abort,console break"))
      (princ (strcat "\nError: " errmsg))
    ); if
    (vla-endundomark doc)
    (princ)
  ); defun - *error*
  (vla-startundomark (setq doc (vla-get-activedocument (vlax-get-acad-object))))
  (prompt "\nBuilding outline to Offset Inward and Hatch: ")
  (if (setq ss (ssget "_:S+." '((0 . "LWPOLYLINE") (-4 . "&") (70 . 1)))); one, closed only
    (progn ; then
      (setq
        obj (vlax-ename->vla-object (setq ent (ssname ss 0)))
        oarea (vla-get-Area obj)
      ); setq
      (vla-offset obj 2)
      (if (> (vla-get-Area (vlax-ename->vla-object (entlast))) oarea)
        (progn ; then -- went wrong way
          (entdel (entlast))
          (vla-offset obj -2)
        ); progn
      ); if [went wrong way -- no else; do nothing if it went right way]
      (command
        "_.hatch" "SOLID" ent (setq pl2 (entlast)) "" ""
        "_.chprop" "_last" "" "_layer" "YourLayerName" ""
        "_.erase" pl2 ""
      )
    ); progn -- then
  ); if
  (vla-endundomark doc)
  (princ)
); defun -- C:OI2H
(vl-load-com)
(prompt "\nType OI2H to Offset Inward by 2 drawing units and Hatch.")

 

I also assume that no building outline will be so small that Offsetting 2 units inward will fail.  If one is of a kind of shape that results in two or more  new Polylines inside, you won't get the results you want, but if that's possible, that can also be accounted for.

Kent Cooper, AIA
0 Likes
Message 7 of 107

dlanorh
Advisor
Advisor

@Anonymous wrote:


Are you being snarky, or are you interested in the specifics of the program? Let's say the layer would be inner_wall, the pattern would be solid fill, the scale would be 1.0 and the color would be 250,250,250.





I was actually interested in helping but was unable without certain parameters. Here is my effort for what it's worth.

 

(vl-load-com)
(defun gc:clockwise-p (p1 p2 p3) (< (sin (- (angle p1 p3) (angle p1 p2))) -1e-14));end_defun

(defun c:ofin ( / *error* sv_lst sv_vals c_doc ms h_patt h_scale h_lyr ss obj cw o_dist of_obj h_obj o_poly i_poly t_col)
  
  (defun *error* ( msg )
		(mapcar 'setvar sv_lst sv_vals)
    (if (and c_doc (= 8 (logand 8 (getvar 'UNDOCTL)))) (vla-endundomark c_doc))
		(if (not (wcmatch (strcase msg) "*BREAK*,*CANCEL*,*EXIT*")) (princ (strcat "\nOops an Error : " msg " occurred.")))
		(princ)
	);end_*error*_defun
    
  (setq sv_lst (list 'cmdecho 'osmode)
				sv_vals (mapcar 'getvar sv_lst)
        c_doc (vla-get-activedocument (vlax-get-acad-object))
        ms (vla-get-modelspace c_doc)
  );end_setq

  ;; Alterable Parameters
  (setq h_patt "solid"                ;change the hatch patteren here   
        h_scale 0.1                   ;change the hatch scale here
        o_dist 2.0                    ;change the offset distance here (must be a positive real number)
        h_lyr "inner_wall"            ;change hatch layer here
  );end_setq
  
  (mapcar 'setvar sv_lst '(0 0))

  (prompt "\nSelect Closed Building Polyline : ")
  (setq ss (ssget "_+.:E:S:L" '((0 . "LWPOLYLINE") (-4 . "&") (70 . 1)))
        obj (vlax-ename->vla-object (ssname ss 0))
        cw (gc:clockwise-p (vlax-curve-getstartpoint obj) (vlax-curve-getpointatparam obj 1.0) (vlax-curve-getpointatparam obj 2.0))
  );end_setq
  
  (if (and c_doc (= 8 (logand 8 (getvar 'UNDOCTL)))) (vla-endundomark c_doc))
  (vla-startundomark c_doc)

  (if cw (setq o_dist (* o_dist -1.0)))
  (setq of_obj (car (vlax-invoke obj 'offset o_dist)))
  
  (setq h_obj (vla-addhatch ms acHatchPatternTypePredefined h_patt :vlax-false)
        o_poly (vlax-make-safearray vlax-vbObject '(0 . 0))
        i_poly (vlax-make-safearray vlax-vbObject '(0 . 0))
  );end_setq
  
  (vlax-safearray-put-element o_poly 0 obj)
  (vlax-safearray-put-element i_poly 0 of_obj)
  (vla-appendouterloop h_obj o_poly)
  (vla-appendinnerloop h_obj i_poly)
  (vla-evaluate h_obj)
  
  (vlax-put-property h_obj 'layer h_lyr)
  (setq t_col (vlax-create-object (strcat "autocad.accmcolor."(substr (getvar 'acadver) 1 2))))
  (vla-SetRGB t_col 250 250 250) ;change color RGB here (must be in R G B format)
  (vla-put-truecolor h_obj t_col)
  (vlax-release-object t_col)
  (vlax-put-property h_obj 'patternscale h_scale)
  (vla-delete of_obj)
  (vla-regen c_doc :vlax-true)
  
  (if (and c_doc (= 8 (logand 8 (getvar 'UNDOCTL)))) (vla-endundomark c_doc))
  (mapcar 'setvar sv_lst sv_vals)      
);end_defun

The lisp is pretty similar to Kent's above, with most of the same caveats; but I have explicitly changed the colour of the hatch. This is removeable if the RGB color is the same as the layers. The red commented lines are where you can change the hatch parameters if required. The hatch pattern name however must be that of a pattern contained within the acad.pat file. I have also indicated where you can change the RGB colour if required.

I am not one of the robots you're looking for

0 Likes
Message 8 of 107

john.uhden
Mentor
Mentor

If you tested whether the polyline were clockwise or counterclockwise you wouldn't have to use trial and error.

John F. Uhden

0 Likes
Message 9 of 107

Kent1Cooper
Consultant
Consultant

@Kent1Cooper wrote:

....

(defun C:OI2H ; = Offset Inward by 2 drawing units, and Hatch
  (/ *error* doc);;;;; ss ent obj oarea pl2)
(setq ss nil ent nil obj nil oarea nil)
....

 

 

Oops -- I left the part about localized variables in testing mode [don't localize, so they can be checked after running, but wipe them out at the beginning of each run].  Replace the top 3 lines with this:

(defun C:OI2H ; = Offset Inward by 2 drawing units, and Hatch
  (/ *error* doc ss ent obj oarea pl2)

Yes, @john.uhden, it could check for CW/CCW direction, but I imagine [without choosing one of the assorted ways of doing that] that it would be [in code required] as much work as, or possibly more than, the compare-the-resulting-area approach.  Admittedly, though, it could make a noticeable difference in the time it takes if the routine were altered to do a large quantity of them at once, instead of just one at a time.  If that's a possibile need, it may be worth revising.

Kent Cooper, AIA
0 Likes
Message 10 of 107

Kent1Cooper
Consultant
Consultant

@Anonymous wrote:

....
1. We select a closed polyline that was previously drawn and that represents the outline of a building.

2. We offset the polyline 2 units inside, creating a scaled version of the original polyline within the first. 

3. We hatch the area within the two polylines with a specific hatch pattern, scale, layer and color.

4. We delete the smaller, inside polyline.

....


 

Another approach occurs to me, again if you're talking about SOLID for the Hatch pattern.  How about Offsetting the outline inward by 1  unit to be the mid-line of that thick perimeter, and then giving it a global Polyline width of 2 units?   It would take considerably less memory than a Hatch pattern, and the need to delete a temporary object afterward would be eliminated.

 

EDIT:  Like this:

(defun C:OI2 ; = Offset Inward for 2-unit-wide perimeter
  (/ *error* doc ss ent obj oarea)
  (defun *error* (errmsg)
    (if (not (wcmatch errmsg "Function cancelled,quit / exit abort,console break"))
      (princ (strcat "\nError: " errmsg))
    ); if
    (vla-endundomark doc)
    (princ)
  ); defun - *error*
  (vla-startundomark (setq doc (vla-get-activedocument (vlax-get-acad-object))))
  (prompt "\nBuilding outline to Offset Inward: ")
  (if (setq ss (ssget "_:S+." '((0 . "LWPOLYLINE") (-4 . "&") (70 . 1)))); one, closed only
    (progn ; then
      (setq
        obj (vlax-ename->vla-object (setq ent (ssname ss 0)))
        oarea (vla-get-Area obj)
      ); setq
      (vla-offset obj 1)
      (if (> (vla-get-Area (vlax-ename->vla-object (entlast))) oarea)
        (progn ; then -- went wrong way
          (entdel (entlast))
          (vla-offset obj -1)
        ); progn
      ); if [went wrong way -- no else; do nothing if it went right way]
      (setq edata (entget (entlast)))
      (entmod (subst '(43 . 2.0) (assoc 43 edata) edata))
      (command "_.chprop" "_last" "" "_layer" "YourLayerName" "")
    ); progn -- then
  ); if
  (vla-endundomark doc)
  (princ)
); defun -- C:OI2
(vl-load-com)
(prompt "\nType OI2 to Offset Inward to 2-drawing-unit-wide perimeter.")
Kent Cooper, AIA
0 Likes
Message 11 of 107

john.uhden
Mentor
Mentor
No need to choose. I'll give it to you (if you want).

John F. Uhden

0 Likes
Message 12 of 107

john.uhden
Mentor
Mentor
I think the "civil" drafting standard is to use a cross hatch like ANSI131.
I remember using two triangles to draw each line by hand. A roloruler was
okay for pencil work, but not for ink.

John F. Uhden

0 Likes
Message 13 of 107

kaden.bedwell
Explorer
Explorer

I know that I'm responding to a 2 year old post but is there a way to get this to work when tracing the boundary of a building using a polyline rather than say using a rectangle? It doesn't seem to work for me on 2d polylines even though i've closed it back to the starting point. Or is there a way to code that the polyline selected will change the closed option to 'yes' rather than 'no'?

0 Likes
Message 14 of 107

john.uhden
Mentor
Mentor

I remember arguing with @Kent1Cooper  about inside vs. outside, the result of which is to pick the side.

Anyway, I got so tired of doing the same thing myself that I wrote the following.

Only thing wrong is that it doesn't set the hatch pattern scale, or angle ahead of time, but we can work on that.

(defun C:HatchBldg ( / *error* Doc vars vals ans od e ent etyp Obj1 p Obj2 start end coords)
  ;; OffsetWithEnds by John Uhden, (06-04-18)
  ;; v1.1 (11-02-2020) revised for polylines only for LDC to speed up
  ;; creating existing building hatching.

  (gc)
  (vl-load-com)
  (princ "\nOffsetWithEnds v1.1 (c)2018-20, John F. Uhden")(princ)
  (or *acad* (setq *acad* (vlax-get-acad-object)))
  (setq Doc     (vla-get-ActiveDocument *acad*))
  (setq vars '("cmdecho"))
  (setq vals (mapcar 'getvar vars))
  (defun *error* (Error)
    (mapcar 'setvar vars vals)
    (if e (redraw e 4))
    (vla-endundomark Doc)
    (cond
      ((not Error))
      ((wcmatch (strcase Error) "*QUIT*,*CANCEL*")
        ;(vl-exit-with-error "\r                                              ")
      )
      (1
        (princ (strcat "\n*ERROR*: " Error))
        ;(vl-exit-with-error (strcat "\r*ERROR*: " Error))
      )
    )
    (princ)
  )
  (vla-endundomark Doc)
  (vla-startundomark Doc)
  (mapcar 'setvar vars '(0))
  (command "_.EXPERT" (getvar "EXPERT"))
  (defun @2d (p)(list (car p)(cadr p)))
  (and
    (setq od (getvar "offsetdist"))
    (not (initget 4))
    (if (setq ans (getdist (strcat "\nOffset distance <" (rtos od) ">: ")))
      (setvar "offsetdist" (setq od ans))
      1
    )
    (or (entmake) 1)
    (setvar "errno" 0)
    (while (/= (getvar "errno") 52)
      (if e (redraw e 4))
      (and
        (setq e (car (entsel "Select object to offset: ")))
        (setq Obj1 (vlax-ename->vla-object e))
        (setq ent (entget e))
        (setq etyp (cdr (assoc 0 ent)))
        (or
          (= etyp "LWPOLYLINE")
          (prompt (strcat "\nObject selected is a(n) " etyp "."))
        )
        (setq closed (vlax-get obj1 'closed))
        (or (redraw e 3) 1)
        (setq p (getpoint "\nSide to offset: "))
        (vl-cmdf "_.offset" od e p "")
        (setq obj2 (vlax-ename->vla-object (entlast)))
        (if (= closed 0)
          (progn
            (setq start (@2d (vlax-curve-getstartpoint Obj1)))
            (setq end (@2d (vlax-curve-getendpoint Obj1)))
            (setq coords (vlax-get Obj2 'Coordinates))
            (vlax-put Obj2 'Coordinates (append start coords end))
            1
          )
          1
        )
        (vl-cmdf "_.-hatch" "_S" (ssadd (vlax-vla-object->ename Obj1) (ssadd (vlax-vla-object->ename Obj2))) "" "")
        (vla-delete Obj2)
      )
    )
  )
  (*error* nil)
)
(defun c:HB ()(c:HatchBldg))

John F. Uhden

0 Likes
Message 15 of 107

kaden.bedwell
Explorer
Explorer

@john.uhdenIt works perfect for me (as shown below) until it asks to select another object and then no matter what i do, it decides to hatch the entire inside area rather than just my entered offset.  Ideally having the option to preset the hatch and layer/color is what i'd like as it would take out a few more steps when needing to do this on numerous houses up and down a street. I currently use ANSI32 at a scale of 5. I do like how it deletes the 2nd line though!

 HATCHBLDG.png

0 Likes
Message 16 of 107

Kent1Cooper
Consultant
Consultant

@kaden.bedwell wrote:

... is there a way to get this to work when tracing the boundary of a building using a polyline rather than say using a rectangle? It doesn't seem to work for me on 2d polylines even though i've closed it back to the starting point. Or is there a way to code that the polyline selected will change the closed option to 'yes' rather than 'no'?


It works for me using any kind of shape, not just rectangles.  When you say you've "closed it back to the starting point," did you do it by just picking again at the starting point and ending the Polyline [not truly "closed" as AutoCAD means the term], or did you use the Close option for the closing segment, to make it truly closed?  If you only drew it back to the same spot, I would discourage just changing the Closed thing in Properties from 'no' to 'yes', because it will result in a zero-length closing segment between two coincident vertices, which can cause various kinds of problems with various AutoLisp routines, though it doesn't seem to be trouble for this one.

 

Other possibilities:

 

See the end of Message 6 -- could your shape be such that more than one new Polyline results from Offsetting?

 

Is it a "lightweight" Polyline?  That is, was it drawn with the PLINETYPE System Variable set to 1 or 2, not 0?  When you pick it, does the Properties palette say it's a "Polyline" [that's the "lightweight" kind that the routine was written for] or a "2D Polyline" [the "heavy" kind that it wasn't written for]?

 

And finally:  What does "it doesn't seem to work" mean?  What happens that you don't expect, or what doesn't happen that you do?  Are there any messages?  If you change the Closed property yourself to 'yes' first, does it work?

Kent Cooper, AIA
0 Likes
Message 17 of 107

kaden.bedwell
Explorer
Explorer
Your response in the first paragraph is accurate in what I was doing wrong. When i just click on the starting point the routine wouldnt even begin. When i properly "close" it, the routine works fine.

The system variable is 2 and yes if i changed the closed property myself, the routine would work fine.

I just tried changing the hatch from solid to ANSI32 and it prompted me for scale and angle and then asked to select object which in turn would hatch the entire interior of the object selected. Is there any way to include a hatch other than solid in the lisp code that would automatically have all that inputted into it and put the layer on a specified layer? I havent had any issues with yours hatching to the outside at all in my testing as others have mentioned above, but i do like how others deletes the offset line at the end.

I'm pretty new at diving into Autolisp and most of my changes dont pan out for me...
0 Likes
Message 18 of 107

Sea-Haven
Mentor
Mentor

Just maybe a different way use bpoly then its pick 1 point, the only question is that its a object with no holes. The new bpoly save ename then offset using previous pt using 1st vertice of bpoly and 1st vertice of offset go 1/2 way between then hatch. Then it does not matter if 1 or more plines make up the outer boundary eg two buildings touching. Kaden you can add correct layer etc.

 

I do understand may be objects inside the building so use Layiso if required.

 

 

 

(defun c:test ( / oldsnap ent1 ent2 co-ord pt1 pt2 pt3)

(setq oldsnap (getvar 'osmode))
(setvar 'osmode 0)

(setq pt1 (getpoint "\Pick inside point "))

(command "bpoly" pt1 "")
(setq ent1 (entlast))
(setq co-ord (mapcar 'cdr (vl-remove-if-not '(lambda (x) (= (car x) 10)) (entget ent1))))
(setq pt2 (nth 0 co-ord))

(command "offset" 5 ent1 pt1 "")
(setq ent2 (entlast))
(setq co-ord (mapcar 'cdr (vl-remove-if-not '(lambda (x) (= (car x) 10)) (entget ent2))))
(setq pt3 (nth 0 co-ord))

(setq pt3 (mapcar '* (mapcar '+ pt2 pt3 ) '(0.5 0.5)))

(command "-hatch" "P" "Ansi32" 5.0 0.0 pt3 "")

(command "erase" ent1 ent2 "")

(setvar 'osmode oldsnap)

(princ)
)

 

 

Message 19 of 107

Kent1Cooper
Consultant
Consultant

@kaden.bedwell wrote:
....
I just tried changing the hatch from solid to ANSI32 and it prompted me for scale and angle and then asked to select object which in turn would hatch the entire interior of the object selected. Is there any way to include a hatch other than solid ... and put the layer on a specified layer? I havent had any issues with yours hatching to the outside at all in my testing as others have mentioned above, but i do like how others deletes the offset line at the end.
....

When the pattern is SOLID, it doesn't ask for scale or rotation, so with any other pattern, answers to those prompts need to be added.  Doing the command manually [starting it with (command "_.hatch") so it will use the same variety as the routine will] reveals the sequence of prompts.  Try this:

(defun C:OI2H ; = Offset Inward by 2 drawing units, and Hatch
  (/ *error* doc ss ent obj oarea pl2)
  (defun *error* (errmsg)
    (if (not (wcmatch errmsg "Function cancelled,quit / exit abort,console break"))
      (princ (strcat "\nError: " errmsg))
    ); if
    (vla-endundomark doc)
    (princ)
  ); defun - *error*
  (vla-startundomark (setq doc (vla-get-activedocument (vlax-get-acad-object))))
  (prompt "\nBuilding outline to Offset Inward and Hatch: ")
  (if (setq ss (ssget "_:S+." '((0 . "LWPOLYLINE") (-4 . "&") (70 . 1)))); one, closed only
    (progn ; then
      (setq
        obj (vlax-ename->vla-object (setq ent (ssname ss 0)))
        oarea (vla-get-Area obj)
      ); setq
      (vla-offset obj 2)
      (if (> (vla-get-Area (vlax-ename->vla-object (entlast))) oarea)
        (progn ; then -- went wrong way
          (entdel (entlast))
          (vla-offset obj -2)
        ); progn
      ); if [went wrong way -- no else; do nothing if it went right way]
      (command
        "_.hatch" "ANSI32" 5 0 ent (setq pl2 (entlast)) "" ""
        "_.chprop" "_last" "" "_layer" "YourLayerName" ""
        "_.erase" pl2 ""
      )
    ); progn -- then
  ); if
  (vla-endundomark doc)
  (princ)
); defun -- C:OI2H
(vl-load-com)

Make sure you edit the "YourLayerName" to an appropriate name.  If you didn't do that before, and [as one might expect] you don't have a Layer of that name, that would explain its not removing the inboard boundary -- you would have gotten a message about that.  But with a valid Layer name, it does remove it.

Kent Cooper, AIA
Message 20 of 107

john.uhden
Mentor
Mentor

I think the value of HPSEPARATE has to be 1.  That worked for me.

As to the pattern, scale, and angle, what I do is use the properties dialog to adjust one of the hatches and then use matchprop on any others.  Another way is to make one right, then use my MakeCurrent routine to change the drawing settings to that object.  Oops, the version I have at home is older than the one I updated at work.  We'll have to wait until I go back in on Monday for me to post it.  It doesn't handle every property of every type of object, but I did update it to change most HP* variables.

John F. Uhden

0 Likes