LISP to reassociate multiple fields (in one object / block) to point to selected object

LISP to reassociate multiple fields (in one object / block) to point to selected object

LOESCH_PK
Advocate Advocate
5,305 Views
20 Replies
Message 1 of 21

LISP to reassociate multiple fields (in one object / block) to point to selected object

LOESCH_PK
Advocate
Advocate

Hello LISP Masters.

 

I've been thinking of a LISP that would prompt user to select a 'source' object -> get its ObjectID, then ask for destinantion object (Fields, MText, Block with attributes) and go through all fields in destination object and replacing original ObjectID in field expression to that of the selected source object.

 

Detaild explanation in attached image.

 

This is to allow for making detailed TAGs in 'vanilla' AutoCAD and then easily reassociating them to respected objects (eg. door TAG to door block). 

LISP - update ObjectID of all fields in selected object.jpg

0 Likes
Accepted solutions (1)
5,306 Views
20 Replies
Replies (20)
Message 2 of 21

dlanorh
Advisor
Advisor

An example of each block would help anyone undertaking this

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

Message 3 of 21

LOESCH_PK
Advocate
Advocate

Sure. You will find file attached. All explanations are in taht DWG.

 

To put it simple. Got workign door with loads of parameters. Got working TAG with fields linkt to this door, and showing 6 of those paramters.

I copy door somewhere. I copy the tag in another command (say I first get doors right, and later proceed with tagging).

 

Now I need to connect all 6 fields in TAG to a new object.  I see that script should update every field in TAG (so that I can habe as many fields as I want/need).

 

Quite similar script to this is called 'QuickField v1_3" but it works a little different because:

1. it updates only one field at a time (I need to update all fields contained in ann object/'TAG')

2. it acctually replaces a text object with a field of predefined formating (I need to leave formating and parameter - just update ObjectID's).

0 Likes
Message 4 of 21

dlanorh
Advisor
Advisor

Excellent. Will study this later today when I have a free moment.

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

0 Likes
Message 5 of 21

dlanorh
Advisor
Advisor

I have studied your blocks and attempted to to manually replace the objectID in the fied in the "DOOR_TAG" Block. This produces an error. I am not 100% sure but I think this is caused by the way the existing field points at a specific block parameter. The returned fields from the DOOR_TAG block are

 

"%<\\AcObjProp Object(%<\\_ObjId -104283040>%).Parameter(129).UserVariable>%" SYMBOL
"%<\\AcObjProp Object(%<\\_ObjId -104283040>%).Parameter(130).UserVariable>%" SUBTYPE
"%<\\AcObjProp Object(%<\\_ObjId -104283040>%).Parameter(123).UserVariable>%" FIRE_RATING
"%<\\AcObjProp Object(%<\\_ObjId -104283040>%).Parameter(91).UserVariable>%"  CLOSER
"%<\\AcObjProp Object(%<\\_ObjId -104283040>%).Parameter(50).UpdatedDistance \\f \"%lu2%pr0\">%" WIDTH_OPENING
"%<\\AcObjProp Object(%<\\_ObjId -104283040>%).Parameter(58).UpdatedDistance \\f \"%lu2%pr0\">%" HEIGHT_OPENING

 

They all point to the same parameter when the block is copied, and I cannot find the block with ObjectID -104283040

 

The two fields that contain ".UpdatedDistance" are actual block parameters in the door block, however these have no associated actions and as such are fixed values.

 

I would also have to summise that the other fields (those with .UserVariable)  relate to something in the original block, a variable in a lisp or some xdata somewhere; however I cannot find anything in the blocks which relates to these.

 

Your idea of replacing the objectID's is , I'm afraid, dead in the water.

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

0 Likes
Message 6 of 21

CodeDing
Advisor
Advisor

@LOESCH_PK ,

 

I don't have time for all the down & dirty stuff, but here is proof of concept:

 

(defun c:TEST ( / door dtag doorId)
  (if (and (setq door (car (entsel "\nSelect door: ")))
           (setq dtag (car (entsel "\nSelect door tag: "))))
    (progn
      (setq doorId (itoa (vla-get-ObjectID (vlax-ename->vla-object door))))
      (setpropertyvalue dtag "HEIGHT_OPENING"
        (strcat "%<\\AcObjProp Object(%<\\_ObjId " doorId ">%).Parameter(58).UpdatedDistance \\f \"%lu2%pr0\">%"))
      (setpropertyvalue dtag "WIDTH_OPENING"
        (strcat "%<\\AcObjProp Object(%<\\_ObjId " doorId ">%).Parameter(50).UpdatedDistance \\f \"%lu2%pr0\">%"))
      (command "_.REGEN")
    );progn
  );if
  (princ)
);defun

 

 

 

Best,

~DD

Message 7 of 21

LOESCH_PK
Advocate
Advocate

Thank You for your help. It is 'halfway' there :-).

The script works for every parameter in this pair of blocks (as long as I don't use any other door block that has Parameter(xx) defined with different number).

 

as per 'dead in the water' - I see your point - parameter are defined as (Parameter(number)) and those numbers change between block definitions so its not a thing of simple ObjectID substitution.

 

As I think of that it really boils to substituting two numbers: ObjectID (done) and Paramter(xx)...

 

LISP - update ObjectID of all fields in selected object v2.jpg

 

new IDEA - more complicated that I envisioned 😞
As I am sure that source door (Block) has same parameter names as target door (though parameter numbers change) I have got this idea - this really involves different scripts for different blocks/tags pairs (doors/windows/other) 😞
1. List of all parameter names can be specified in the script itself (and actually is right now)
2. Script has to go through all parameters and numbers and get their corresponding names in new/atrget door
3. Then it has to compare fetched names say (SYMBOL parameter = Parameter(129) <- in a new / target block)
4. When having pairs (Parameter NAME=NR) it should also update those numbers (apart from Object ID which works fine)
5. Nice addition: when making all "6" pairs is not possible (someone clicked wrong block / tag pair) it shows a message "BLOCK - TAG - not proper pair" and terminates not changing anything (as a failsafe).

 

Do You see that is remotely possible?

 

My - 6 parameter 'version' of script attached.

 

0 Likes
Message 8 of 21

dlanorh
Advisor
Advisor

I think the problem here is trying to do this with fields.

 

If we drop the fields idea, accessing the dynamic properties is easy using Lee Macs  routine below

 

(defun LM:getdynprops ( blk )
    (mapcar '(lambda ( x ) (cons (vla-get-propertyname x) (vlax-get x 'value)))
        (vlax-invoke blk 'getdynamicblockproperties)
    )
)

 

This returns an assoc list of properties and values

 

so by writing a stand alone wrapper for the above we can dump the properties of a dynamic block we pick. This can be adapted if required to work as a function. Try it on a test drawing.

 

(defun LM:getdynprops ( blk )
    (mapcar '(lambda ( x ) (cons (vla-get-propertyname x) (vlax-get x 'value)))
        (vlax-invoke blk 'getdynamicblockproperties)
    )
)

(defun c:ddbp ( / flg obj)
  (while (not flg)
    (setq obj (vlax-ename->vla-object (car (entsel "\nSelect Dynamic Block : "))))
    (cond ( (= :vlax-true (vlax-get-property obj 'isdynamicblock)) (setq flg T)) (t (alert "Not a dynamic Block")))
  );end_while
  (lm:getdynprops obj)
)

 

If we store this list in a variable it is easy to access each value, and the value could be entered directly into each of the door_tag's attributes. This gets round having to somehow find the parameter(nr) for each different block to allow the use of a field. (The parameter numbers change in each block). I don't know if it is possible to find the parameter number, I will investigate.

 

If we add an invisible attribute to the door_tag block we can use this to store objects handle, which unlike the objectID is unique and persistant in a drawing, unlike the objectID which changes every time a drawing is closed and re-opened. With the handle we can re-reference tag to door.

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

0 Likes
Message 9 of 21

dlanorh
Advisor
Advisor

Proof of concept, without fields. This doesn't include storing the handle. That is simple enough to do but involves changing the door tag block which can be done via lisp or bedit.

 

(defun LM:getdynprops ( blk )
    (mapcar '(lambda ( x ) (cons (vla-get-propertyname x) (vlax-get x 'value)))
        (vlax-invoke blk 'getdynamicblockproperties)
    )
)

(defun rh:get_tags (blk) (mapcar '(lambda ( x ) (vlax-get x 'tagstring)) (vlax-invoke blk 'getattributes)))

(vl-load-com)

(defun c:test (/ *error* tlst flg dblk plst tblk )

  (defun *error* (msg)
    (if (not (wcmatch (strcase msg) "*BREAK*,*CANCEL*,*EXIT*")) (princ (strcat "\nOops an Error : " msg " occurred.")))
    (princ)
  );end_*error*_defun

  (setq tlst (list "FIRE_RATING" "SYMBOL" "SUBTYPE" "CLOSER" "WIDTH_OPENING" "HEIGHT_OPENING"))

  (while (not flg)
    (setq dblk (vlax-ename->vla-object (car (entsel "\nSelect Dynamic Door Block : "))))
    (if (= :vlax-true (vlax-get-property dblk 'isdynamicblock)) (setq plst (lm:getdynprops dblk)))
    (cond ( (= :vlax-false (vlax-get-property dblk 'isdynamicblock))(alert "Not a dynamic Block"))
          ( (not (vl-every '(lambda (x) (assoc x plst)) tlst)) (alert "Block missing Dynamic Properties"))
          (t (setq flg T))
    );end_cond
  );end_while

  (setq flg nil)

  (while (not flg)
    (setq tblk (vlax-ename->vla-object (car (entsel "\nSelect Door Tag Block : "))))
    (cond ( (= :vlax-false (vlax-get-property tblk 'hasattributes)) (alert "Block has no Attributes"))
          ( (not (vl-every '(lambda (x) (vl-position x tlst)) (rh:get_tags tblk))) (alert "Door Tag Block missing Attribute Tag(s)"))
          (t (setq flg T))
    );end_cond
  );end_while
  ;(princ)
  (vl-some '(lambda (x / y) (vlax-put x 'textstring (if (not (= (type (setq y (cdr (assoc (strcase (vlax-get x 'tagstring)) plst)))) 'str)) (rtos y 2 0) y))) (vlax-invoke tblk 'getattributes))
  (princ)
)

 

This is a bit rough and ready at the moment, and could also be adapted to insert a new instance of the door_tag block and populate the attributes by selecting the insertion point instead of selecting an existing block.

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

Message 10 of 21

LOESCH_PK
Advocate
Advocate

Thank You -  @dlanorh - your solution completes 90% of the task. Your script is quite robust as it uses a simple dictionary, in contrast to previous "brute-force" method.

 

I'm truly grateful, and can see lot of use for it. Ability to insert new tag and populate it along with command would be a great addition.

 

This metod has 2 major downsides:

- it is not entirely universal, as it uses a fixed dictionary stored in a script - so one needs number of commands for each tag type.

- it does not produce fields (which clumsy as they are in reference to user parameters) stay up to date (refresh).

 

The problem I put in front of You was a little broader and more universal than a solution.

 

After reading and some checking right now I know that:

  1. There is a script ready for doors and tag with fields - but it has to be super clean - script has all specific paramters in it, and whole range of 'doors' has to be made on the base of one single block with constant number of paramters, to keep them from randomly changing numbers between blocks. This is very hard when having employees on lower CAD levels. Sooner or later some block will get redefined and will lose 'static parameter numbering'
  2. @dlanorh produced an excellent script that has minor drawbacks (biggest one is no easy refresh)
  3. I still think that a fully automatic metod with fields is possible but it would have to:
    • read tag first and fetch names of atributes (say those are exactly the same as parameters in 'door block')
    • form a dictionary
    • read 'door' block
    • get parameter numbers along with their names (as list)
    • compare dictionary to list
    • get 'door' parameter numbers coresponding to each name from the dictionary
    • rewrite fields in tag using new 'door' ObjectIDs and parameter numbers (leaving formating as is)
  4. Now I know that this is much more complex task than simple ObjectID substitution...
  5. After giving it more thought - Your last script will get the job done (as there is limited types of blocks to tag). Right now a lowest-tech "solution" would be to just treat that 'door' block is just a plain representation (maybe having just EffectiveName to be linked with a field in tag), and all other parameters should be stored in TAG object, as its equally good to retrieve schudules form TAGS as it is with graphic blocks. Storing that info in TAGs eliminates problem with no refresh... But it defeats the purpose of tags along the way 😉

Nevertheless, I still believe making it universal (with no user specified dictionary) and using fields is possible, but needs more digging. My problem is that I get only visual scripting languages like Dynamo in Revit. Writing scripts in LISP is black magic to me... 😐

  

 

0 Likes
Message 11 of 21

dlanorh
Advisor
Advisor

I don't know about a "universal" lisp, but I have found a few things that may enable the use of fields. The problem at the moment is working out how to alter the routines to return something usable so I can reconstruct the field with the new objectID string. It would still require the first tag_block to be manually set up with the field command.

 

I still haven't found the one thing that would allow a "universal" lisp, and that is how to get the parameter number from the dynamic block.

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

0 Likes
Message 12 of 21

LOESCH_PK
Advocate
Advocate

Sorry. Maybe I wasn't clear. By 'universal' I don't mean a 'smart' solution doing all by itself. Having the first pair associated by hand is a good way to go in my opinion.

0 Likes
Message 13 of 21

dlanorh
Advisor
Advisor

OK. Just finished a single attribute test (selected using nentsel) and the door block, which worked after tweaking the substitution routine.

 

Way past my bedtime so I will attempt to loop all attributes tomorrow when I have time.  

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

0 Likes
Message 14 of 21

dlanorh
Advisor
Advisor
Accepted solution

Attached is working version. Please test extensively as I only have your small test drawing. I have altered the routine slightly and the block selection sequence is now Door_Tag then Door Block.

 

There are NO tag or dynamic property checks, the lisp merely updates the field expression in the TAG Block with the ObjectID string of the door block, so this should also work on any other tag - block combinations.

 

Once loaded type ufcs to run

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

Message 15 of 21

LOESCH_PK
Advocate
Advocate

Thank You @dlanorh !

I tested it on 13 different blocks and it works like a charm.

 

As the script doesn't use dynamic 'matching' of parameter names, blocks used in the block-tag pair have to have same parameter numbering (for custom parameters). This means they have to come from common block 'tree' or 'root' where all future 'tagged' parameters are non-changable. Also linear parameters of width and height cannot be deleted and added again in a block. But surely we can manage to use always the same block "root" when producing new ones.

 

It's very convenient to select in tag-block order, as your script is not 'static' but adapts to fields in a tag. As of 1h testing in numerous cases on 13 different blocks (all originating from one 'root') and it works perfectly.

 

One slight question. Can You make it so script doesn't require dynamic block? When testing it on static blocks, it does show an alert that its not a dynamic block". I had to add at least one linear parameter to otherwise static blocks to make them 'dynamic' for the script to work. Otherwise it performed great (updating all fields - custom ones and standard object parameters).

It woud be even better if it could be used on any type of object - not just blocks...

 

As one can expect - when used on other blocks - silmilar but defined 'from scratch' parameter numbers change, and script gives erroneous and chaotic results.

 

I'm truly satisfied with proposed script, and wish to once again thank you for your help.

 

HINT for other CAD users:

1. Build your BLOCKS (eg. door types) on common 'matrix' or 'root' blockwith a set number of parameters

2. Make first complex TAG with multiple fields.

3. Use the script for efortless reataching complex TAGS to your complex BLOCKS.

0 Likes
Message 16 of 21

dlanorh
Advisor
Advisor

Will look at the dynamic/static block problem later today.

 

I have finally solved accessing the dynamic block property parameter numbers for field strings, They were buried inside a dictionary within a dictionary and only accessable through autolisp entity lists.

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

0 Likes
Message 17 of 21

dlanorh
Advisor
Advisor

Attached is tweaked lisp. Let me know if there are any problems.

 

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

0 Likes
Message 18 of 21

LOESCH_PK
Advocate
Advocate

@dlanorh there seems to be a problem with tweaked script. I don't know if the 'dictionary part' works fine, but it seems to crash tag fields definition when parameter number is different than in original block. I prepared a simple file with two blocks. Both have "TESTOWY" user parameter but they have different Parameter numbers (1 nad 4). There is a simple tag object with one attribute named "TESTOWY" and connected to the first block. After using revised script the link breaks up - whole "Parameter(1/4).UserParameter" part seems to be missing form the field definition, and when edited field diverts back to default/first parameter from object parameters list.

Still - the scripts works when paramters in both blocks have same number (are made of a single root object).

2020-07-20_10h28_35.jpg

 

Test file attached.

0 Likes
Message 19 of 21

dlanorh
Advisor
Advisor

The dictionary search is not included in the tweaked lisp, as this is still at the development stage. It retrieves the source blocks parameter name(s) and number (s) and displays it/them. It is not at a deployment stage as yet.

 

All the tweaked lisp does is replace the existing ObjectID string in the "tag" block with the ObjectID of the selected block. It is source/target block specific at the moment, not universal.

 

Each individual source blocks parameter(s) will be in a different order and have different numbers depending on how they were initial constructed. The only thing that is common is that the parameter number(s) and data type(s) of dynamic block inserts do not change, only their values.

 

If the tag block attribute is pointing to a different parameter because the source block is different but the parameter data type is the same, then it will display the incorrect information.

 

If the tag block attribute is pointing to a different parameter because the source block is different and the parameter doesn't exist or is of a different type; then it will crash.

 

Your only option at the moment is to manually change one instance of the tag block to point to the new source blocks parameter(s) and data type(s) using the "field" command and cut and paste.

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

0 Likes
Message 20 of 21

LOESCH_PK
Advocate
Advocate

Hello @dlanorh .

I've was thinking of a part solution to my problem - and came up with and idea of trying to associate custom parameters with different numbers by using Order parameter in Block Parameter Manager.

 

Matching could be done by having original TAG connection to original BLOCK - get parameter order numbers form original BLOCK, then tak NEW BLOCK and find new parameter numbers by matching order numbers stored with the ones in target block, find out new parameter numbers and subtitute those in fields.

LOESCH_PK_0-1595435789300.png

 

0 Likes