Multiple loops

Multiple loops

zph
Collaborator Collaborator
1,336 Views
9 Replies
Message 1 of 10

Multiple loops

zph
Collaborator
Collaborator

Hi all!

 

To start, I've researched this a bit and found a lot that dances around what I need, but nothing spot on.

 

Please see the code below:

 

(defun c:FIXALIGN ( / SScounter MOVEcounter objectList moveDistanceList
   toPoint objectSS objectName moveDistance)

(setvar "cmdecho" 0)
(setq SScounter 0)
(setq MOVEcounter 0)
(setq objectsList ())
(setq moveDistanceList ())
(setq toPoint (car (getpoint "Select a point to align with")))

(while (< SScounter 2)  ;;;will need to exited via pressing ENTER
 (setq objectSS (ssget "_:S"))
 (setq objectName (ssname objectSS 0))
 (setq objectList (append objectList (list objectName)))
 (setq objectPoint (car (getpoint "Select point on object")))
 (setq moveDistance (- objectPoint toPoint))
 (setq moveDistanceList (append moveDistanceList (list moveDistance)))
 (setq SScounter (+ SScounter 1))
 (princ)
) ;while

(while (< MOVEcounter (length moveDistanceList))
 (command "move" (nth MOVEcounter objectList) "" (list 0 0 0)
  (list (* (nth MOVEcounter moveDistanceList) -1) 0 0)) ;move
 (setq MOVEcounter (+ MOVEcounter 1))
 (princ)
) ;while

(setvar "cmdecho" 1)
(princ)
) ;FIXALIGN

 

 

 

----

 

 

I want the first while loop (building the list values) to be exited by keystroke ENTER (and obviously not exit the lisp routine).  Currently, I've put artificial values for the number of iterations for testing only.  Will using ENTER in this way be possible?

 

Also, if there would be a way to execute the 'move' portion of the routine so if it needs to be undone (using CTRL Z) all the actions of the routine can be undone hitting undo once rather than multiple times in order to account for each iteration of the move command.  With reason, is this possible?

0 Likes
Accepted solutions (1)
1,337 Views
9 Replies
Replies (9)
Message 2 of 10

ВeekeeCZ
Consultant
Consultant

Try this code (sorry for not using yours...)

 

Spoiler
(vl-load-com)

(defun c:FixAlign2 ( / *error* en pt ens)

  (defun *error* (errmsg)
    (if (not (wcmatch errmsg "Function cancelled,quit / exit abort,console break,end"))
      (princ (strcat "\nError: " errmsg)))
    (setvar 'CMDECHO 1)
    (vla-endundomark adoc)
    (princ))


  (vla-startundomark (setq adoc (vla-get-activedocument (vlax-get-acad-object))))
  (setvar 'CMDECHO 0)
  
  (while (setq en (car (entsel "\nSelect an object to align: "))) ; hit enter to quit selecting
    (if (setq pt (getpoint "\nSelect a point on the object: "))
      (setq ens (cons (cons en pt) ens))))
  
  (if (and ens
	   (setq pt (getpoint "\nSelect a point to align with: ")))
    (foreach e ens
      (command "_.MOVE"
	       (car e) ""
	       "_none" (cdr e)
	       "_none" (list (car pt) (caddr e)))))
  (*error* "end")
)
Message 3 of 10

Kent1Cooper
Consultant
Consultant

....I want the first while loop (building the list values) to be exited by keystroke ENTER (and obviously not exit the lisp routine).  ....

 

Also, if there would be a way ... the routine can be undone hitting undo once rather than multiple times ....


I agree with BeekeeCZ that an Undo begin/end wrapping is the way to handle the undoing all at once.  But my take on the whole thing would be to have the objects Moved as soon as I give the point on each to align with the reference X position, rather than build up a list of objects and points and subsequently plow through that to Move them all.  Lightly tested:

 

(vl-load-com)
(defun c:FixAlignX (/ *error* doc cmde aliX ent ref)
  (defun *error* (msg)
    (if (not (wcmatch msg "Function cancelled,quit / exit abort,console break"))
      (prompt (strcat "\nError: " msg))
    ); if
    (setvar 'cmdecho cmde)
    (vla-endundomark doc)
    (princ)
  ); defun
  (vla-startundomark (setq doc (vla-get-activedocument (vlax-get-acad-object))))
  (setq
    cmde (getvar 'cmdecho)
    aliX (getpoint "\nSelect a point to align with in X direction: ")
  ); setq
  (setvar 'cmdecho 0)
  (while
    (setq ent (car (entsel "\nSelect an object to align / <exit>: "))); nil on Enter
    (command "_.move" ent ""
      (setq ref (getpoint "\nSelect point on object: "))
      "_none" (cons (car aliX) (cdr ref))
    ); command
  ); while
  (setvar 'cmdecho cmde)
  (vla-endundomark doc)
  (princ)
); defun

I first tried using the ".X" point filter, which should have shortened things just a little, but it wasn't behaving as I expected if Object Snap was on [I couldn't find the right place(s) to put "_none" in relation to it], but I wanted Osnap left on for use in picking those reference points on the objects.  Maybe there's a way to do it that I didn't try.

 

Kent Cooper, AIA
Message 4 of 10

zph
Collaborator
Collaborator

Awesome!

 

Both of those solutions do as I wanted on both counts!  Thanks BeeKeeCZ and KentCooper.

 

The next step is to be able to align objects by the 'y' (align horizontally) as well.

 

I do prefer to select the destination point prior to selecting the objects, but before that I'd like to have the option to select to align by x or y.

 

I'd do it myself if I were modifying my own code; your guys's code structures I am unfamiliar with.

 

edit:  Also, this option to align by 'y' I'd like to be toggle-able within the code.  It won't be used very often, so I'd like to have the option to turn it 'off' by commenting the option out. 

0 Likes
Message 5 of 10

ВeekeeCZ
Consultant
Consultant

@zph wrote:

 

... 

The next step is to be able to align objects by the 'y' (align horizontally) as well.

I do prefer to select the destination point prior to selecting the objects, but before that I'd like to have the option to select to align by x or y.

... 

edit:  Also, this option to align by 'y' I'd like to be toggle-able within the code.  It won't be used very often, so I'd like to have the option to turn it 'off' by commenting the option out. 


 

Then try this modified version. I used Kent's dynamic principle, language (partly) and few lines.

 

Spoiler
(vl-load-com)

(defun c:FixAlign3 ( / *error* adoc oCMDECHO ensel pt px)

  (defun *error* (errmsg)
    (if (not (wcmatch errmsg "Function cancelled,quit / exit abort,console break,end"))
      (princ (strcat "\nError: " errmsg)))
    (setvar 'CMDECHO oCMDECHO)
    (vla-endundomark adoc)
    (princ))


  (vla-startundomark (setq adoc (vla-get-activedocument (vlax-get-acad-object))))
  (setq oCMDECHO (getvar 'CMDECHO))
  (setvar 'CMDECHO 0)

  (setq pt (getpoint "Pick a reference point: "))

  (or *ax
      (setq *ax "X"))
  
  (while (and pt
	      (setq ensel (entsel (progn
				    (initget "X Y")
				    (strcat "\nSelect an object to align with in "
					    (if (= *ax "X")
					      (strcat "X direction"
						      " or change to [Y]"   ; comment this line for turn the 'Y choice' off
						      )
					      "Y direction or change to [X]")
					    " <exit>: ")))))
    (if (= (type ensel) 'STR)
      (setq *ax ensel)
      (command "_.MOVE" (car ensel) ""
	       (setq px (getpoint "\nSelect point on object: "))
	       "_none" (if (= *ax "X")
			 (cons (car pt) (cdr px))
			 (cons (car px) (cdr pt))))))
  (*error* "end")
)
Message 6 of 10

zph
Collaborator
Collaborator

This works!  I have one question about the portion I've highlighted.  What does the 'STR mean?  I've not seen the ' used in this context before.

 

Also, if it doesn't take up too much of your time, would you please explain the (or *ax (setq *ax "X")) portion?  I've not used 'or' without related conditional statements, and I'm not familiar with the *ax either.

 

---

 

(vl-load-com)

(defun c:FixAlign3 ( / *error* adoc oCMDECHO ensel pt px)

(defun *error* (errmsg)
 (if (not (wcmatch errmsg "Function cancelled,quit / exit abort,console break,end"))
  (princ (strcat "\nError: " errmsg))
 ) ;if

 (setvar 'CMDECHO oCMDECHO)
 (vla-endundomark adoc)
 (princ)
) ;*error*

(vla-startundomark (setq adoc (vla-get-activedocument (vlax-get-acad-object))))
(setq oCMDECHO (getvar 'CMDECHO))
(setvar 'CMDECHO 0)

(setq pt (getpoint "Select a point to align with: "))
(or *ax (setq *ax "X"))
 
(while
 (and pt (setq ensel (entsel
  (progn
   (initget "X Y")
   (strcat "\nSelect an object to align "
    (if (= *ax "X")
     (strcat "vertically"
     " or align horizontally [Y]"   ; comment line to remove 'Y' choice
     )
     "horizontal or align vertical [X]") ;if
   " <exit>: ") ;strcat
  ) ;progn
 ))) ;and

 (if (= (type ensel) 'STR)
  (setq *ax ensel)
  (command "_.MOVE" (car ensel) "" (setq px (getpoint "\nSelect point on object: ")) "_none"
   (if (= *ax "X")
    (cons (car pt) (cdr px))
    (cons (car px) (cdr pt))
   ) ;if
  ) ;command move
 ) ;if
) ;while

(*error* "end")
) ;FIXALIGN3

0 Likes
Message 7 of 10

zph
Collaborator
Collaborator

Also, I've just noticed there is only one while loop utilized in your code. You have effectively eliminated the need to exit one loop before the routine moves to the next loop by restructuring the code. This is awesome, by the way, and thank you!

However, for future reference, I'd like to know if there is a way to exit a while loop by the keystroke ENTER in order to push the routine to the next step.

0 Likes
Message 8 of 10

ВeekeeCZ
Consultant
Consultant
Accepted solution

@zph wrote:

This works!  I have one question about the portion I've highlighted.  What does the 'STR mean?  I've not seen the ' used in this context before.

 

Also, if it doesn't take up too much of your time, would you please explain the (or *ax (setq *ax "X")) portion?  I've not used 'or' without related conditional statements, and I'm not familiar with the *ax either.

 

 

...

 (if (= (type ensel) 'STR)

...


Type function returns an atom of type of a variable. The STR atom stands for a STRing. Read about the type function here . Why is there apostrophe - btw which is equal to (quote STR) function - read in this thread , especially the posts by @Kent1Cooper and @martti.halminen. The same principle was used before (setvar 'CMDECHO 0)

 

(or *ax (setq *ax "X"))

First of all *ax is just a variable name, * does not mean anything special, it's just a regular character like any other letter. I used the * for marking a global variable. 

 

(or *ax (setq *ax "X")) could be rewritten as (if (not *ax ) (setq *ax "X")).

And why (or) works as well? You need to realize how (or) works - It evaluates one condition after another UNTILL it finds a TRUE-one. If so, evaluation stops and (or) returns TRUE. If TRUE-condition is never found, (or) returns FALSE.

 

The same principle is with (and) function. It's evaluated untill if finds FALSE condition. Then (and) returns FALSE. If FALSE-condition is never found, (and) returns TRUE.

 

So in this case (or *ax (setq *ax "X"))... If *ax has some value from the previous run [in the fact "X" or "Y"] then 'default' (setq *ar "X") is not applied. If *ax is nil, then into *ar is set default value "X".

 

 


@zph wrote:

Also, I've just noticed there is only one while loop utilized in your code. You have effectively eliminated the need to exit one loop before the routine moves to the next loop by restructuring the code. This is awesome, by the way, and thank you!

However, for future reference, I'd like to know if there is a way to exit a while loop by the keystroke ENTER in order to push the routine to the next step.


 

The reducing number of loops is the @Kent1Cooper 's idea - see his comment on post #3. But it's just a good side effect, main thing was make the routine more visual - you click and it aligns the object right a way. I concurred with this.

 

Exit a while loop with Enter funcion. Actually we (both me and Kent) did use this principle in this routine. And you should always use the same! So the principle is that while condition must return nil to stop evaluating. We used this (and this very common principle):

 

(while (entsel) ...stuff... )    If you hit enter while you're asking for an object selection, it returns nil. Instead of (entsel) could be used (ssget) (getpoint) (getreal) (getdist)... Ofcause you must allow that with NOT using (initget 1). Just be aware that (getstring) if you hit enter does not return nil, but "". In this case the condition must be (while (/= "" (getstring)) ...stuff...)

 

This principle could be modified...

(while (not done)

  ...stuff...

  (if (= Please-Stop-My-Head-Is-Turning T) (setq done T)) ; for exit

)

 

Hope this help.... despite my language limitation.

Message 9 of 10

martti.halminen
Collaborator
Collaborator

 

A little aside about style: you said:

 

              First of all *ax is just a variable name, * does not mean anything special, it's just a regular character like any other letter. I used the * for marking a                 global variable. 

 

In your own programs you can use whatever style you like, but in the mainstream Lisp culture the universally approved convention is to use asterikses at both ends of the name of a global variable: *ax* instead of *ax.

- An asterisk just at the start of the name does not have any widely known conventional meaning.

- Another, somewhat rarer convention is to use an asterisk at the end of a name when creating a function that is a variant of a more common similar function. A few of these got even into the Common Lisp standard: DO* and LET*.

- none of these have any defined semantics, it is just a name like any other to the compiler.

 

-- 

 

0 Likes
Message 10 of 10

zph
Collaborator
Collaborator

BeekeeCZ, thank you for giving the time to explain this to me. You never cease to amaze!

Oh, and your English is more than satisfactory.

Thanks again!

0 Likes