PDA

View Full Version : Reactor to toggle LTScale between MS/PS



RobertB
2004-06-04, 04:19 PM
This code should serve only as a simple boilerplate (perhaps placed in Acad.lsp) to give you a start in making a reactor that toggles LTScale when you switch from ModelSpace to a Layout.

It makes the assumption that the current DimScale is the scale factor you want for ModelSpace. This is a poor assumption IMHO, but you can decide how you want to store the scale factor yourself. (I might suggest a Dictionary/XRecord.)

Also, I perform no Regens, so the effects won't be visible until you do so.


(defun I:ChangedLayout (reactor layout)
(setvar "LTScale"
(* 0.5
(cond ((/= (strcase (car layout)) "MODEL") 1)
((zerop (getvar "DimScale")) 1)
((getvar "DimScale"))))))

(cond ((not *LayoutLTS*)
(setq *LayoutLTS* (vlr-Miscellaneous-Reactor
nil
'((:vlr-LayoutSwitched . I:ChangedLayout))))))

Steve Doman
2004-06-05, 02:36 AM
Hi Robert,

I like the idea of using a LTScale reactor to automatically change Ltscale when changing from Model space to PaperSpace and have experimeted with several versions. However I find that with all versions I've tried including the code you posted, none regen the drawing after changing the Ltscale.

From the users standpoint, having to manually enter the Regen command after switching layouts makes having a LTS reactor only slightly beneficial. I've tried modifying these reactors to include a regen by adding a (vla-regen *doc* acActiveViewport) just before the reactor callback function ends. This however crashes AutoCAD bigtime!

So to my point, do you know of a way to automatically cause a regen after changing layouts. I tried setting "Regenerate the drawing each time you switch tabs" from the options command, but that seems to cause a regen before the reactor callback runs.

Any ideas Obi-Wan?

Thanks,
Steve Doman

RobertB
2004-06-05, 03:17 AM
Steve,

What version(s) of AutoCAD do you need to support?

Steve Doman
2004-06-05, 03:32 AM
Robert,

Thanks for the reply. We are currently running Windows Xp and AutoCAD 2002 (Vanilla). Hope to upgrade to 2k5 soon.

Steve

RobertB
2004-06-05, 03:53 AM
Steve,

Actually, to be honest, I hate Visual LISP's reactors (shh, don't tell anyone). I far prefer to use VBA Events where possible.

Please see this code (http://forums.augi.com/showthread.php?p=26522#post26522) for a VBA example of the same approach. This VBA code does the regen, but I have only tested in 2004/2005. Seems to me it worked in 2002, but the memory is fading.

Steve Doman
2004-06-05, 04:30 AM
Oh my gosh, VBA? I have no experience with VBA, but can understand the code you posted. So I guess now is a good time to learn a little VB, seeing as I have to figure out how to load it!

I'll do some testing with A2k2 on Monday. My home PC isn't setup for AutoCAD at the moment. Thanks for the help and I'll get back with you on how things worked.

Thanks a billion,
Steve Doman

RobertB
2004-06-05, 04:32 AM
Happy to help. Just reply in the VBA thread if you need any VBA help.

Steve Doman
2004-06-07, 04:43 AM
I had a little time to play with AutoCAD 2005 today and experimented with adding a vla-regen to the LTScale reactor code you posted. Worked great! No crashes! So I ran with the code you posted and embellish it a bit.

It seems to me that the user would want the LTScale to automatically change when the layout changes form Model to Paper space, *and* whenever the Dimscale changes. So I threw in another reactor that watches for the dimscale to change.

In addition, I thought this LTS reactor should be user friendly and have some kind off method for the user to turn the LTS reactor off or on. So I threw in a c:function to toggle the reactor.

Here's what I got so far, based on your core reactor code. Note the warning:



;|
*** Warning ***
Note that the following code might/will crash AutoCAD unless
you are running release 16+
|;

(vl-load-com)

(defun c:AutoLTS (/ on off status prmpt answer *error*)
;;
;; User command to turn AutoLTS on or off
;;
(defun *error* (msg)
(cond
((member
msg
'("Function cancelled" "quit / exit abort" "console break")
)
)
((princ (strcat " Error: " msg)))
)
(princ)
)
;;
(setq on "1"
off "0"
)
(if *LayoutLTS*
(setq status on)
(setq status off)
)
(setq prmpt (strcat "\nEnter new value for AutoLTS <" status ">: "))
(initget (strcat on " " off))
(setq answer (getkword prmpt))
(cond ((not answer) nil) ;_<Enter> key
((and (= answer off) (= status on))
(vlr-remove *LayoutLTS*)
(vlr-remove *DimscaleLTS*)
(setq *LayoutLTS* nil
*DimscaleLTS* nil
)
)
((and (= answer on) (= status off)) (I:AutoLTS))
)
(princ)
)


(defun I:ChangedLayout (reactor layout)
;;
;; Calculate and set the LTScale
;; Regenall
;;
(setvar "LTScale"
(* 0.5
(cond ((/= (strcase (car layout)) "MODEL") 1)
((zerop (getvar "DimScale")) 1)
((getvar "DimScale"))
)
)
)
;;
;; *** WARNING ***
;; Prior to release 16, the following
;; line may crash AutoCAD hard
;;
(vla-regen
(vla-get-activedocument (vlax-get-acad-object))
acAllViewports
)
)


(defun I:ChangedDimscale (reactor info)
;;
;; If changed sysvar is Dimscale
;; Call ChangedLayout function
;; Otherwise ignore
;;
(cond ((= (strcase (car info)) "DIMSCALE")
(I:ChangedLayout
nil
(list (vla-get-name
(vla-get-activelayout
(vla-get-activedocument
(vlax-get-acad-object)
)
)
)
)
)
)
)
)

(defun I:AutoLTS ()
;;
;; Function to create two reactors
;;
;; One reactor calls I:ChangedLayout function
;; whenever the layout changes
;;
;; The other reactor calls the I:ChangedDimscale
;; function whenever the Dimscale changes.
;;
(if (not *DimscaleLTS*)
(setq *DimscaleLTS*
(vlr-sysvar-reactor
nil
'((:vlr-sysvarchanged . I:ChangedDimscale))
)
)
)
(if (not *LayoutLTS*)
(setq *LayoutLTS*
(vlr-Miscellaneous-Reactor
nil
'((:vlr-LayoutSwitched . I:ChangedLayout))
)
)
)
)
;; eof

RobertB
2004-06-07, 03:42 PM
Steve,

Once again, I'd like to point out that basing this reactor on DimScale is, IMHO, a bad idea. Far better to store the "primary scale factor" in an XRecord :-D or a user system variable :( . That way you can change DimScale without affect LTScale. (In case you have large-scale/details on the same sheet.)

Other than that, I'm glad you found a solution finally.

Steve Doman
2004-06-08, 04:50 AM
Well to each their own. I like having the Dimscale control the LTScale and don't really understand why you would want a "primary scale factor" other than Dimscale.

Having a correlation between Dimscale and LTScale seems very natural and intuitive to how drawings are created, were I work anyway. This reactor gizmo allows us to be free of maintaining the optimum LTScale while edititing and plotting.

Also too, having this gives us a different way to think about the drawing process, in that you would set Dimscale before you draw or edit the object, rather then aftwards when ready to dimension. In drawings which contain many differently scaled Viewports, the user simply changes the Dimscale when editing that particular area.

Thanks!

RobertB
2004-06-09, 05:47 PM
In many places, dimensioning is now placed in the Layout, which means DimScale=0, which, of course, is no use to this reactor when you go to MS. Granted, this obviously is not a problem (yet!) in your office, but you might consider this issue.

Steve Doman
2004-06-09, 07:29 PM
Ok now it all makes sense. If you dimension from PaperSpace, you would set Dimscale to zero or one and forget it. Gotcha.

Thanks again.

CADdancer
2004-06-17, 04:50 PM
SDOman:

Could your code be modified to work with a variable that a lisp routine sets. For example lets say that a previous lisp routine creates a variable DDD and sets it equal to a scale value such as (setq DDD 96).

In this case the scale of the drawing is 1/8"=1'-0" and the dimscale variable would be 96.

Can the DDD variable be used in your example code in place of the Dimscale variable..??

Any assistance would be appreciated.

Regards,
Vince

Steve Doman
2004-06-18, 12:54 PM
SDOman:

Could your code be modified to work with a variable that a lisp routine sets. For example lets say that a previous lisp routine creates a variable DDD and sets it equal to a scale value such as (setq DDD 96).

In this case the scale of the drawing is 1/8"=1'-0" and the dimscale variable would be 96.

Can the DDD variable be used in your example code in place of the Dimscale variable..??

Any assistance would be appreciated.

Regards,
Vince

Vince,

Sure that' s possible and I started to write a solution, but then realized there are some complicatons. For example in the I:AutoLTS and I:ChangedLayout functions, we could replace the code that creates the Dimscale sysvar reactor with code that reads your global variable. Simple enough.

However to have AutoLTS run whenever the value of the global changes, automatically without user interaction, which to me is the beauty of this whole idea, means having the "other routine" (that creates and maintains this global) also run AutoLTS. In other words your other lisp would set and maintain the value of the global, and then call (I:ChangedLayout) so that the LTScale is updated.

Or perhaps we could set up a reactor to watch this global variable and run I:ChangedLayout whenever this value changes. But I don't know if a reactor can watch a lisp variable.

Also I think it would be better to use Xdata as Robert Bell initially suggested, rather then a global lisp variable. The main benefit here I think, is that the Xdata would be persistant in the drawing. In other words if you close the drawing file, the Xdata would be saved to the drawing file and be accessible the next time the drawing is opened. Having never personally experimented with Xdata, I'd need a little time to play with that idea. But I have extensive notes and it doesn't look too difficult.

Can you expand on what your trying to do, or do you have any preferences as to how this should be implemented? Did any of this makes sense?

Regards,
Steve Doman

Glenn Pope
2004-06-18, 01:19 PM
I wrote a routine that watches when ltscale, dimscale and text styles are changed. Then that is stored into a x-record for that layout. Then when you switch to that layout, those settings are retrieved.


;Layout LTscale Switch, 2003 Glenn Pope
;
;DESCRIPTION:
;LAYOUT_SW allows you to assign different LTSCALE, DIMSCALE and TEXTSTYLE for each layout tab
;including Model space. They are saved with the drawing as well.
;
;HOW TO USE:
;Go to the layout you want to set. Then change the LTSCALE, DIMSCALE or TEXTSTYLE just like you
;normally would. The new settings will be automatically saved for that layout.


;Loads Visual LISP extensions
(vl-load-com)
;------------------------------------------------------------------------------------------------
;Reactor: Watches for a change in system variables
(setq LTDIMTEXTREACTOR (vlr-editor-reactor nil '((:vlr-sysVarChanged . LTDIMSCALECHANGE))))

;Called when there is a change in the a system variable
(defun LTDIMSCALECHANGE (REACTOR VARIABLE)
;Checks to see if LTSCALE, DIMSCALE or TEXTSTYLE were the variables changed
(if (or (= (strcase (nth 0 VARIABLE)) "LTSCALE")
(= (strcase (nth 0 VARIABLE)) "DIMSCALE")
(= (strcase (nth 0 VARIABLE)) "TEXTSTYLE")
)
(progn
;Creates or gets the entity name of a dictionary called "LAYOUT_SW"
(setq ADICT (DICTIONARY_LAYOUTSW))
;Takes the current layout tab and replaces spaces with underscore for the xrecord name
(setq LAYOUT
(strcat "SW_" (vl-string-translate " " "_" (getvar "CTAB")))
)
;Checks to see if there is an xrecord for the layout, if so deletes it
(if (/= (dictsearch ADICT LAYOUT) nil)
(entdel (cdr (assoc -1 (dictsearch ADICT LAYOUT))))
)
;Creates the xrecord using the current settings
(setq ENT (entmakex (list (vl-list* 0 "XRECORD")
(vl-list* 100 "AcDbXrecord")
(vl-list* 7 (getvar "textstyle"))
(vl-list* 40 (getvar "ltscale"))
(vl-list* 41 (getvar "dimscale"))
)
)
)
;Adds the xrecord to the LAYOUT_SW dictionary
(if ENT
(setq ENT (dictadd ADICT LAYOUT ENT))
)
(setq ENT (cdr (assoc -1 (dictsearch ADICT LAYOUT))))
)
)
(princ)
)
;------------------------------------------------------------------------------------------------
;When called, Creates a dictionary called "LAYOUT_SW"
(defun DICTIONARY_LAYOUTSW (/ ADICT)
;Checks to see if LAYOUT_SW exists
(if (not (setq ADICT (dictsearch (namedobjdict) "LAYOUT_SW")))
;if not, creates it
(progn
(setq ADICT (entmakex
'((0 . "DICTIONARY") (100 . "AcDbDictionary"))
)
)
;Adds the new dictionary to the main drawing dictionary
(if ADICT
(setq ADICT (dictadd (namedobjdict) "LAYOUT_SW" ADICT))
)
)
;Returns the entity name of the dictionary "LAYOUT_SW"
(setq ADICT (cdr (assoc -1 ADICT)))
)
)
;------------------------------------------------------------------------------------------------
;Reactor: Watches for changing from one layout to another
(setq LAYOUTREACTOR (vlr-miscellaneous-reactor nil '((:vlr-layoutSwitched . LAYOUTCHANGE))))

;Called when there is a change to a layout
(defun LAYOUTCHANGE (CALLER CMDSET / ADICT LAYOUT ACTIVEDOC ENT)
;Creates or gets the entity name of a dictionary called "LAYOUT_SW"
(setq ADICT (DICTIONARY_LAYOUTSW))
;Takes the current layout tab and replaces spaces with underscore for the xrecord name
(setq LAYOUT
(strcat "SW_" (vl-string-translate " " "_" (getvar "CTAB")))
)
;Turns off the reactor for system variable changes
(vlr-remove LTDIMTEXTREACTOR)
;Checks to see if there is an xrecord for the layout
(if (= (dictsearch ADICT LAYOUT) nil)
;if not, sets variables to default
(progn
(setvar "ltscale" 1.0)
(setvar "dimscale" 1.0)
(setvar "textstyle" "standard")
)
;if there is, retrieves the saved settings
(progn
(setq ENT (dictsearch ADICT LAYOUT))
(setvar "textstyle" (cdr (assoc 7 ENT)))
(setvar "ltscale" (cdr (assoc 40 ENT)))
(setvar "dimscale" (cdr (assoc 41 ENT)))
)
)
;Starts the reactor for system variable changes back up
(setq LTDIMTEXTREACTOR (vlr-editor-reactor nil '((:vlr-sysVarChanged . LTDIMSCALECHANGE))))
(princ)
)

CADdancer
2004-06-18, 02:18 PM
sdoman:

What you said makes a lot of sense and I understand the concept.

What I am trying to do is use your code to automatically toggle the "ltscale" and "psltscale" variable when a user switches between paper space and model space but instead of keying on the "dimscale" AutoCAD system variable key on a local variable such as "DDD" to get the value for the line type scale.

I thought you could set Xdata to the value of the "DDD" variable or as you stated above have another reactor look for a change in the "DDD" variable and then act accordingly.

One last question. Could the program be made to run automatically when a drawing file is opened so that AUTOLTS is active by default and still have the option to turn it off from the command line...??
There are too many users that would forget to turn it on initially so perhaps it would be better if it was active when a drawing is opened.

I hope my explaination was clear. Any assistance would be appreciated.

Steve Doman
2004-06-20, 04:06 AM
Vince et al,

I think this version of the LTS reactor, which uses a dictionary to store an overall drawing scale, will meet your criteria. Consider this code an example and not a finished product. So please let me/us know if you find a bug because I haven't had time to thoughly test this much.

Also please read the comments in the code for more info about syncronizing this reactor routine with your code to set the drawingscale.

I think I'd like to take this routine one step further and combine the two user functions provided below into one function for simplicity. But I've run out of time for now.

If you want to run this on startup when opening a drawing, load the code by using (load "FileName") placed in a mnl file, or s::startup, or Appload, or Acaddoc.lsp, or Acad.lsp, or whatever. If you need further help with loading at startup, then I suggest starting a new thread with that topic.

Enjoy. Hope it works for you.

Regards,
Steve Doman



;;;
;;; AutoLTS w/ User Drawing Scale 06-19-04
;;; By Steve Doman w/ essential help
;;; from Robert Bell, Frank Whaley, and
;;; billions of newsgroup postings :)
;;;
;;; Note: This routine is intended for AutoCAD A2k4 or later (R16.0+)
;;;
;;; Routine will automatically change the LTScale sysvar to some global
;;; DwgScale, supplied by the user or via your separate AutoLISP code,
;;; whenever the layout is switched from PaperSpace to ModelSpace.
;;;
;;; This routine uses a reactor to watch for the layout to be switched
;;; When this event happens, the reactor calls a function that sets the
;;; LTScale to the given DwgScale.
;;;
;;; When the layout is switched, the reactor sets the LTScale to 1.0 if
;;; PaperSpace is active, or to the DwgScale if ModelSpace is active.
;;;
;;; This routine also includes two user commands:
;;; c:AutoLTS is a command to toggle on or off the automatic LTScale feature
;;; c:DwgScale is a command to create or change the overall drawing scale
;;;
;;; The DwgScale is saved inside the drawing via a dictionary object.
;;; This value will be saved with the drawing when the file is closed, and
;;; can be recalled when the drawing is reopened. In additon, the DwgScale
;;; can be set directly via your own code that calls the appropriate function.
;;; See the SetScaleValue function below.
;;;
;;; Load this file and type DwgScale to set scale
;;; Type AutoLTS to turn automatic feature off or on
;;;
;;; Also see the end of this file for comment regarding enabling
;;; or disabling this routine from running when this file is loaded.
;;;
(vl-load-com)

(defun c:AutoLTS (/ on off status prmpt answer *error*)
;;
;; User command to turn AutoLTS on or off
;;
(defun *error* (msg)
(cond
((member
msg
'("Function cancelled" "quit / exit abort" "console break")
)
)
((princ (strcat " Error: " msg)))
)
(princ)
)
;;
(setq on "1"
off "0"
)
(if *LayoutLTS*
(setq status on)
(setq status off)
)
(setq prmpt (strcat "\nEnter new value for AutoLTS <" status ">: "))
(initget (strcat on " " off))
(setq answer (getkword prmpt))
(cond ((not answer) nil) ;_<Enter> key
((and (= answer off) (= status on))
(vlr-remove *LayoutLTS*)
(setq *LayoutLTS* nil)
)
((and (= answer on) (= status off)) (I:MakeLayoutReactor))
)
(princ)
)

(defun c:DwgScale (/ dwgscale prmpt answer *error*)
;;
;; User command to create or change the drawing scale
;;
(defun *error* (msg)
(cond
((member
msg
'("Function cancelled" "quit / exit abort" "console break")
)
)
((princ (strcat " Error: " msg)))
)
(princ)
)
;;
;; Get the drawing scale from dictionary
;; if the dictionary does not exist, create
;; a new dictionary with a default value
;;
(setq dwgscale
(cond
((cdr (assoc 40 (dictsearch (namedobjdict) "DwgScale"))))
((I:SetScaleValue 1.0) (I:ChangedLayout))
)
)
;;
;; Ask the user for a dwgscale
;;
(setq prmpt (strcat "\nEnter new value for DwgScale <"
(rtos dwgscale 2 1)
">: "
)
)
(initget (+ 2 4)) ;_ no zero or negative
(setq answer (getreal prmpt))
(cond ((not answer) nil) ;_<Enter> key
(t (I:SetScaleValue answer)
(I:ChangedLayout nil (list (getvar "ctab")))
)
)
(princ)
)

(defun I:SetScaleValue (scale)
;; Frank Whaley [slightly modified]
;;
;; Function creates a dictionary to store a real number
;; which reprensents the overall drawing scale
;;
;; To call from your code:
;; (I:SetScaleValue 96)
;; Returns: <Entity name: 7ef5b440>
;;
;; To retrieve data via your code:
;; (cdr (assoc 40 (dictsearch (namedobjdict) "DwgScale")))
;; Returns: 96.0 *OR* nil if not initialized
;;
;_
;; Remove any existing entry
(dictremove (namedobjdict) "DwgScale")
;; Make new entry
(dictadd
(namedobjdict)
"DwgScale"
(entmakex
(list '(0 . "XRECORD") '(100 . "AcDbXrecord") (cons 40 scale))
)
)
)

(defun I:ChangedLayout (reactor layout / dwgscale)
;;
;; Given a scale factor that represents the overall drawing
;; scale, calculate and set the LTScale and regen the drawing
;;
;; The scale factor is to be stored as an Xrecord inside a dictionary
;; named "DwgScale" which is created by the SetScaleValue function
;;
(setq dwgscale
(cdr (assoc 40 (dictsearch (namedobjdict) "DwgScale")))
)
(if dwgscale
(progn (setvar "LTScale"
(cond ((/= (strcase (car layout)) "MODEL") 1.0)
((zerop DwgScale) 1.0)
((abs DwgScale))
)
)
(if (<= 16.0 (atof (getvar "Acadver")))
;; Prior to release 16 the following may crash AutoCAD
(vla-regen
(vla-get-activedocument (vlax-get-acad-object))
acAllViewports
)
)
(princ (strcat "AutoLTS >> " (rtos DwgScale 2 1)))
) ;_ progn
)
)

(defun I:MakeLayoutReactor ()
;;
;; This function will create a reactor that will call the
;; function I:ChangedLayout whenever the layout changes
;;
;; This function also creates a global variable that
;; contains a pointer to the reactor object
;;
(if (not *LayoutLTS*) ;_ Create the reactor if it doesn't exist
(progn
(setq *LayoutLTS*
(vlr-Miscellaneous-Reactor
nil
'((:vlr-LayoutSwitched . I:ChangedLayout))
)
)
(princ "\nAutoLTS is ON. Type AutoLTS to toggle.")
)
)
)
;;
;; Comment out the following Progn lines if you don't want
;; to start the reactor on load
;;
(progn (I:MakeLayoutReactor)
(I:ChangedLayout nil (list (getvar "ctab")))
)
(princ)
;; eof

Steve Doman
2004-06-20, 04:14 AM
Glenn,

Interesting routine. It must save you hundreds of keystrokes everyday.

Thanks for posting.

Steve Doman

CADdancer
2004-06-28, 09:54 PM
Steve:

I was out of town for a few days and I just got back.

Thank you for your assistance, I tried your routine and it works great as is without any problems. However, I am having some difficulty trying to get it to work in conjunction with the variable that I have been using for setting drawing scales.

I have a routine that sets a local variable ( DWS ) equal to the scale factor that I want such as 1/8"=1'-0" scale would be (setq DWS 96). I have modified my code to automatically load your program and run the C:dwgScale loop from inside the routine that sets the DWS variable.

When I try to utilize this variable inside the (defun C:dwgScale (/ dwgscale prmpt answer *error*) portion of your code it does not work properly. I am probebly not substituting the variable properly.

Would it be too much trouble to ask your help in trying to use the DWS variable with your code. It would be appreciated very much.

Regards,
Vince

Steve Doman
2004-06-29, 12:39 PM
Vince,

You can set the overall drawing scale using Xdata from inside your routine by loading the AutoLTS code in my previous reply, and then calling the I:SetScaleValue function with the a real number or integer for the required argument. See header below:

(defun I:SetScaleValue (scale)
;; Frank Whaley [slightly modified]
;;
;; Function creates a dictionary to store a real number
;; which reprensents the overall drawing scale
;;
;; To call from your code:
;; (I:SetScaleValue 96) <<<<<< Like this
;; Returns: <Entity name: 7ef5b440>
;;
;; To retrieve data via your code:
;; (cdr (assoc 40 (dictsearch (namedobjdict) "DwgScale")))
;; Returns: 96.0 *OR* nil if not initialized

If that doesn't work, please post whata code you have which trys to set the drawing scale. The routine I posted hasn't been tested much, so it may need some tuning.

Steve Doman