Author Topic: vla-setXrecordData and vla-getXrecordData type issues  (Read 8581 times)

0 Members and 1 Guest are viewing this topic.

SIDESHOWBOB

  • Guest
vla-setXrecordData and vla-getXrecordData type issues
« on: November 22, 2012, 06:44:18 AM »
Implementing the code in this chapter:
http://www.scribd.com/doc/52450758/75/Working-with-XRECORD-Objects

I can load and save xrecords that are lists of strings:
Code - Auto/Visual Lisp: [Select]
  1. _$ (Xrecord-Add "PERSONAL" "userdata0" (list "a" "b" "c"))
  2. ("a" "b" "c")

But if I try putting in integers they come out as strings:
Code - Auto/Visual Lisp: [Select]
  1. _$ (Xrecord-Add "PERSONAL" "userdata0" (list 1 2 3))
  2. ("1" "2" "3")
  3.  

Stepping through the code I'm a bit baffled as to where this conversion takes place.  Is there any way to avoid it?  I plan to store a lot of data in dictionaries so don't want integers to end up as strings for efficiency reasons.

SIDESHOWBOB

  • Guest
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #1 on: November 22, 2012, 06:45:48 AM »
By the way, List->VariantArray works just fine:

Code - Auto/Visual Lisp: [Select]
  1. _$ (mapcar 'vlax-variant-value (vlax-safearray->list (vlax-variant-value (List->VariantArray (list 1 2 3) 'vlax-vbVariant))))
  2. (1 2 3)

Lee Mac

  • Seagull
  • Posts: 12906
  • London, England
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #2 on: November 22, 2012, 08:35:42 AM »
The problem is caused by the List->IntList function which is constructing a list to form the 'type' argument for the vla-setxrecorddata method.

The list of integers returned by the List->IntList function will simply be a list of consecutive integers starting at 1 with length equal to the size of the data to be stored.

However, the 'type' parameter of the vla-setxrecorddata method uses the standard AutoCAD DXF group codes to store data, and each set of group codes may only store data of a specific type, as detailed here.

In your case, by using group codes 1-4 to store data, you can only store strings (as noted by the reference linked above, for group codes 0-9). And you will immediately run into problems if you try to use the functions described in that document for any data of length greater than 9, since group codes 10-39 are reserved for 3D point values...

I strongly recommend that you first study Dictionaries and XRecord structure from a Vanilla AutoLISP perspective, since the structure of the data is far more apparent; by using Visual LISP methods, you are merely converting the Vanilla AutoLISP DXF data into Safearrays and Variants, obscuring the underlying structure.

SIDESHOWBOB

  • Guest
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #3 on: November 22, 2012, 09:27:49 AM »
Thanks, that fixed it.  I assumed the IntList thing was to specify indices for my data in the safearray.

I take it it's not possible to put raw lisp datatypes into a dictionary - without an xrecord?

SIDESHOWBOB

  • Guest
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #4 on: November 22, 2012, 09:46:18 AM »
For the benefit of posterity I got the desired behaviour by replacing the List->IntList function with this:
Code - Auto/Visual Lisp: [Select]
  1.           List->DxfCodeList
  2.                        (lst)
  3.   (mapcar (function (lambda (x) (type->dxfcode (type x)))) lst)
  4. )
  5. (defun type->dxfcode (typ)
  6.   (cond
  7.     ((= typ 'INT) 160) ; 64 bit
  8.     ((= typ 'REAL) 40) ; double
  9.     ((= typ 'STR) 300) ; arbitrary length?
  10.   )
  11. )

Lee Mac

  • Seagull
  • Posts: 12906
  • London, England
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #5 on: November 22, 2012, 09:52:57 AM »

dgorsman

  • Water Moccasin
  • Posts: 2437
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #6 on: November 22, 2012, 10:21:12 AM »
Thanks, that fixed it.  I assumed the IntList thing was to specify indices for my data in the safearray.

I take it it's not possible to put raw lisp datatypes into a dictionary - without an xrecord?

Not sure what you are asking... an example would be helpful.
If you are going to fly by the seat of your pants, expect friction burns.

try {GreatPower;}
   catch (notResponsible)
      {NextTime(PlanAhead);}
   finally
      {MasterBasics;}

SIDESHOWBOB

  • Guest
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #7 on: November 22, 2012, 10:52:07 AM »
Well for example can I just set a dictionary entry to be the real value 3.1?  No variant, no safearrays, no xrecords, just a 3.1?

Like this, but actually working:
Code - Auto/Visual Lisp: [Select]
  1. _$ (dictadd some_dict "pi" 3.1) ; set value of "pi" in some_dict to 3.1
  2. ; error: bad argument type: lentityp 3.1

The docs don't seem to say so explicitly, but it seems to me from the examples I've seen that dictionaries can only hold autocad "entities" - the simplest of which is an xrecord hence why it's used to store arbitrary data.
« Last Edit: November 22, 2012, 10:58:04 AM by SIDESHOWBOB »

dgorsman

  • Water Moccasin
  • Posts: 2437
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #8 on: November 22, 2012, 11:26:15 AM »
Ah, I see.  Yes, that is correct - A Dictionary is a collection, a "virtual bucket" if you will, that allows storage of various things.  For most of our uses those would be XRecords, which contain a variety of application defined information whether a single integer, a sequence of integers, a string, or any combination of values you can think up in virtually any organization that you need.

Which works out pretty well, in most cases.  I usually set up an Application dictionary, which contains a settings dictionary plus one or several data dictionaries, which in turn may contain other dictionaries and/or XRecords.  I can effectively navigate the structure using names, keeping data segregated from each other or adjacent as my needs require.
If you are going to fly by the seat of your pants, expect friction burns.

try {GreatPower;}
   catch (notResponsible)
      {NextTime(PlanAhead);}
   finally
      {MasterBasics;}

SIDESHOWBOB

  • Guest
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #9 on: November 22, 2012, 12:17:51 PM »
Lee - as you suggest I am looking at vanilla autolisp instead then. 

Still some issues though...
Code - Auto/Visual Lisp: [Select]
  1. _$ (setq blah (get-or-make-Xrecord sdnadict "blah"))
  2. <Entity name: 7ee79538>
  3. _$ (entget blah)
  4. ((-1 . <Entity name: 7ee79538>) (0 . "XRECORD") (5 . "21F") (102 . "{ACAD_REACTORS") (330 . <Entity name: 7ee79528>) (102 . "}") (330 . <Entity name: 7ee79528>) (100 . "AcDbXrecord") (280 . 1))
  5. _$ (add-to-Xrecord blah (list 1 "two" 3.1))
  6. ((-1 . <Entity name: 7ee79538>) (0 . "XRECORD") (5 . "21F") (102 . "{ACAD_REACTORS") (330 . <Entity name: 7ee79528>) (102 . "}") (330 . <Entity name: 7ee79528>) (100 . "AcDbXrecord") (280 . 1) (160 . 1) (300 . "two") (40 . 3.1))
    Ok, so I can create, read and modify an Xrecord (routines shown below).  However

    • Do I really have to store all that extra data ("XRECORD, 21F, ACAD_REACTORS, }, AdDbXrecord, (280 . 1))?

    • If so, can I guarantee that my own data will start at position 10 in the list?  If not then how do I find it?
  • Will this be significantly slower than the vlx- way of doing it?  Does vlx get away without storing the extra gubbins?

Cheers

Code - Auto/Visual Lisp: [Select]
  1. (defun create-or-get-dict (location name / dict)
  2.   ;; is it present?
  3.   (if (not (setq dict (dictsearch location name)))
  4.     ;;if not present then create a new one
  5.     (progn
  6.       (setq dict (entmakex '((0 . "DICTIONARY")(100 . "AcDbDictionary"))))
  7.       ;;if succesfully created, add it to the location
  8.       (if dict (setq dict (dictadd location name dict)))
  9.     )
  10.     ;;if present then just return its entity name
  11.     (setq dict (cdr (assoc -1 dict)))
  12.   )
  13. )
  14.  
  15. (defun get-or-make-Xrecord (adict name / anXrec)
  16.   (cond
  17.     ((not (setq anXrec (dictsearch adict name)))
  18.      ;;if name was not found then create it
  19.      (setq anXrec (entmakex '((0 . "XRECORD")
  20.                               (100 . "AcDbXrecord")
  21.                              )
  22.                   )
  23.      )
  24.      ;;if creation succeeded then add it to our dictionary
  25.      (if anXrec
  26.        (setq anXrec (dictadd adict name anXrec))
  27.      )
  28.     )
  29.     ;;if it's already present then just return its entity name
  30.     (setq
  31.      anXrec
  32.      (cdr (assoc -1 (dictsearch adict name)))
  33.     )
  34.   )
  35. )
  36.  
  37. (defun add-to-Xrecord (xrec data)
  38.   (entmod (append (entget xrec) (list->dxf-pairs data)))
  39. )
  40.  
  41. (defun list->dxf-pairs (data)
  42.   (mapcar (function (lambda (x) (cons (type->dxfcode (type x)) x))) data)
  43. )
  44.  
  45.  

Lee Mac

  • Seagull
  • Posts: 12906
  • London, England
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #10 on: November 22, 2012, 12:53:10 PM »
  • Do I really have to store all that extra data ("XRECORD, 21F, ACAD_REACTORS, }, AdDbXrecord, (280 . 1))?

The majority of the 'extra data' that you are referring to are the basic properties of any entity: the entity type (XRECORD), the entity handle (21F), one or more subclass markers (AcDbXrecord), etc. Note that these properties will be present when creating any entity in AutoCAD, be it graphical or non-graphical, e.g. for a circle:

Code - Auto/Visual Lisp: [Select]
  1. (
  2.     (-1 . <Entity name: 7ef02b38>) ;; Pointer to self
  3.     (0 . "CIRCLE") ;; Entity type
  4.     (330 . <Entity name: 7ef01cf8>) ;; Pointer to owner entity
  5.     (5 . "557") ;; Handle
  6.     (100 . "AcDbEntity") ;; Subclass marker
  7.     (67 . 0)
  8.     (410 . "Model")
  9.     (8 . "0")
  10.     (100 . "AcDbCircle") ;; Subclass marker
  11.     (10 0.0 0.0 0.0)
  12.     (40 . 1.0)
  13.     (210 0.0 0.0 1.0)
  14. )

  • If so, can I guarantee that my own data will start at position 10 in the list?  If not then how do I find it?

By this response I gather that you have minimal experience in manipulating AutoCAD entities in general (no offence).
Although the order of DXF data is mostly consistent, you cannot rely on a DXF group code residing at a specific position in the DXF data list. However, since the DXF data list is an association list, the list may be easily manipulated using the assoc function to retrieve specific items.

  • Will this be significantly slower than the vlx- way of doing it?  Does vlx get away without storing the extra gubbins?

In fact, manipulating the DXF data using Vanilla AutoLISP is likely to be faster than the Visual LISP alternative. Note that Visual LISP still stores the additional data, such as the entity handle etc, however, these extra entries aren't as visible / accessible and are mostly part of the properties of the XRecord VLA-Object. To demonstrate this, convert an XRecord VLA-Object to its entity name equivalent (using vlax-vla-object->ename), then query the DXF data of the entity name.
« Last Edit: November 22, 2012, 01:04:07 PM by Lee Mac »

CAB

  • Global Moderator
  • Seagull
  • Posts: 10401
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #11 on: November 22, 2012, 05:25:36 PM »
I've reached the age where the happy hour is a nap. (°¿°)
Windows 10 core i7 4790k 4Ghz 32GB GTX 970
Please support this web site.

SIDESHOWBOB

  • Guest
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #12 on: November 23, 2012, 11:49:35 AM »
By this response I gather that you have minimal experience in manipulating AutoCAD entities in general (no offence).

None taken, I've don't!

I think the trouble here is I'm trying to do something autocad entities weren't designed for.  I wonder if I explain the problem a little better perhaps you could recommend the best way forward.

I need to attach a lot of named data fields (hundreds, maybe over a thousand per object) to a lot of objects (tens of thousands) in an autocad drawing.  So efficiency of reading, writing and storing data matters somewhat.  Not all objects will contain all data fields (indeed some will not contain any).

Currently I do this using xdata.  I attach a huge list of xdata to any object, registered to my app, in alternating pairs of name and data.  So on one polyline I might have 2.0 fish, 3.5 eggs:

Code - Auto/Visual Lisp: [Select]
  1. (1000 . "fish") (1040 . 2.0) (1000 . "eggs") (1040 . 3.5) ... (and . loads) (more . pairs) (of . dotted) (pairs . with) (strings . and) (float . values)

while on another I might have 3.14 eggs and 7 hams:

Code - Auto/Visual Lisp: [Select]
  1. (1000 . "eggs") (1040 . 3.14) (1000 . "hams") (1040 . 3.5) ... (and . loads) (more . pairs) (of . dotted) (pairs . with) (strings . and) (float . values)

This xdata technique is inefficient in two ways.  First, I store those strings many times over.  This is simple to solve, I'm going to replace the strings with integers and attach a string->integer lookup dictionary to the main document.  Fine.

Second, retrieving one piece of custom data from an object takes O(n) time where n is the average number of bits of data I've attached to each entity.  It would be nicer if I could attach a dictionary of string->data (well, int->data if I do what I just said with the strings) to each object thereby reducing lookup time to O(log n).  (Not an array as not all objects carry all the data). 

If I understand what you're saying though this will carry huge overhead, as dictionaries cannot hold raw floats and have to hold entities.  Thus the storage overhead for keeping the data in a per-object dictionary will be large - all the extra entity gubbins I complained about will have to accompany every single float I put in a dictionary.

So what do you reckon?  Should I just stick to xdata and bite my tongue about the linear access time? 

Or maybe I should hold all this extra data in a document level dictionary which does lookup from string -> [a long list of entity/float pairs] (albeit at the cost of losing direct spatial indexing for the data via it's linkage to entities).  In which case you can see why I'd need to know where my own data starts in the Xrecord entity, because with a long list of

Code - Auto/Visual Lisp: [Select]
  1. (105 . "7ff26e8") (40 . 2.0) (105 . "7ff253b") (40 . 3.5) ... (and . loads) (more . pairs) (of . dotted) (pairs . with) (entitynames . and) (float . values)

I can't use assoc to retrieve my own data (or maybe I can - please tell me I'm wrong!). 

Thanks for reading this far, I'd be interested to know what you think :)

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #13 on: November 28, 2012, 07:33:39 AM »
Here's my attempt to recreate an ldata functionality by only using vanilla AutoLisp - i.e. this should even work on Mac:
Code - Auto/Visual Lisp: [Select]
  1. (defun dict-list  (dict /)
  2.   (cond ((= (type dict) 'str) (dict-list (dictsearch (namedobjdict) dict)))
  3.         ((= (type dict) 'ename) (dict-list (entget dict)))
  4.         ((not (listp dict)) nil)
  5.         ((eq (cdr (assoc 0 dict)) "DICTIONARY")
  6.          (mapcar '(lambda (name) (cons (cdr name) (dict-get-xrecord (cdar dict) (cdr name))))
  7.                  (vl-remove-if-not '(lambda (item) (= (car item) 3)) dict)))
  8.         ((setq dict (assoc 360 dict)) (dict-list (cdr dict)))))
  9.  
  10. (defun dict-prepare-data  (data / lst)
  11.   (cond
  12.     ((not data) '((93 . 0)))
  13.     ((= data t) '((93 . -1)))
  14.     ((listp data)
  15.      (if (listp (cdr data))
  16.        (cons (cons 91 (length data)) (apply 'append (mapcar 'dict-prepare-data data)))
  17.        (append '((92 . 2))
  18.                (dict-prepare-data (car data))
  19.                (dict-prepare-data (cdr data)))))
  20.     ((= (type data) 'Str)
  21.      (setq lst (cons (cons 1 (substr data 1 255)) lst))
  22.      (while (not (eq (setq data (substr data 256)) ""))
  23.        (setq lst (cons (cons 3 (substr data 1 255)) lst)))
  24.      (reverse lst))
  25.     ((= (type data) 'Int) (list (cons 95 data)))
  26.     ((= (type data) 'Real) (list (cons 140 data)))
  27.     ((= (type data) 'EName) (list (cons 340 data)))))
  28.  
  29. (defun dict-extract-data  (aList / data count extract-helper extract-list)
  30.   (if (setq data (assoc 70 aList))
  31.     (progn (setq aList (cdr (member data aList)))
  32.            (defun extract-helper  (/ tmp len)
  33.              (cond ((member (caar aList) '(95 140 340))
  34.                     (setq tmp   (cdar aList)
  35.                           aList (cdr aList)))
  36.                    ((= (caar aList) 1)
  37.                     (setq tmp   (cdar aList)
  38.                           aList (cdr aList))
  39.                     (while (= (caar aList) 3)
  40.                       (setq tmp   (strcat tmp (cdar aList))
  41.                             aList (cdr aList))))
  42.                    ((= (caar aList) 91)
  43.                     (setq len   (cdar aList)
  44.                           aList (cdr aList))
  45.                     (repeat len (setq tmp (cons (extract-helper) tmp)))
  46.                     (setq tmp (reverse tmp)))
  47.                    ((= (caar aList) 92)
  48.                     (setq aList (cdr aList)
  49.                           tmp   (cons (extract-helper) (extract-helper))))
  50.                    ((= (caar aList) 93)
  51.                     (setq tmp   (< (cdar aList) 0)
  52.                           aList (cdr alist))))
  53.              tmp)
  54.            (extract-helper))))
  55.  
  56. (defun dict-get-xrecord  (owner name / d)
  57.   (cond
  58.     ((not owner) (dict-get-xrecord (namedobjdict) name))
  59.     ((= (type owner) 'str)
  60.      (dict-get-xrecord (dict-get-xrecord (namedobjdict) owner) name))
  61.     ((= (type owner) 'ename) (dict-get-xrecord (entget owner) name))
  62.     ((not (listp owner)) nil)
  63.     ((eq (cdr (assoc 0 owner)) "DICTIONARY")
  64.      (cond ((dictsearch (cdar owner) name))
  65.            ((and (setq d (entmakex '((0 . "XRECORD") (100 . "AcDbXrecord"))))
  66.                  (dictadd (cdar owner) name d))
  67.             (entget d))))
  68.     ((setq d (assoc 360 owner)) (dict-get-xrecord (cdr d) name))
  69.     ((and (setq d (entmakex '((0 . "DICTIONARY") (100 . "AcDbDictionary"))))
  70.           (entmod
  71.             (append owner (list '(102 . "{ACAD_XDICTIONARY") (cons 360 d) '(102 . "}")))))
  72.      (dict-get-xrecord d name))))
  73.  
  74. (defun dict-get-data  (owner name /)
  75.   (if (setq owner (dict-get-xrecord owner name))
  76.     (dict-extract-data owner)))
  77.  
  78. (defun dict-put-data  (owner name data /)
  79.   (if (setq owner (dict-get-xrecord owner name))
  80.     (progn (if (member '(70 . 0) owner)
  81.              (setq owner (reverse (cdr (member '(70 . 0) (reverse owner))))))
  82.            (entmod (append owner (cons '(70 . 0) (dict-prepare-data data)))))))
The 2 main defuns are: dict-get-data and dict-put-data.
 
The owner argument can be any ename, if nil then the namedobjdict is used. The name argument must be a string. Data can be any vanilla lisp data, such as integer / real / string / ename ... or a list of such values (including nested lists).
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.

irneb

  • Water Moccasin
  • Posts: 1794
  • ACad R9-2016, Revit Arch 6-2016
Re: vla-setXrecordData and vla-getXrecordData type issues
« Reply #14 on: November 28, 2012, 07:49:28 AM »
Slight modification so that a named dictionary can be used as the owner object:
Code - Auto/Visual Lisp: [Select]
  1. (defun dict-get  (owner name / d)
  2.   (cond
  3.     ((not owner) (dict-get (namedobjdict) name))
  4.     ((= (type owner) 'str)
  5.      (dict-get (dict-get (namedobjdict) owner) name))
  6.     ((= (type owner) 'ename) (dict-get (entget owner) name))
  7.     ((not (listp owner)) nil)
  8.     ((eq (cdr (assoc 0 owner)) "DICTIONARY")
  9.      (cond ((dictsearch (cdar owner) name))
  10.            ((and (setq d (entmakex '((0 . "DICTIONARY") (100 . "AcDbDictionary"))))
  11.                  (dictadd (cdar owner) name d))
  12.             (entget d))))
  13.     ((setq d (assoc 360 owner)) (dict-get (cdr d) name))
  14.     ((and (setq d (entmakex '((0 . "DICTIONARY") (100 . "AcDbDictionary"))))
  15.           (entmod
  16.             (append owner (list '(102 . "{ACAD_XDICTIONARY") (cons 360 d) '(102 . "}")))))
  17.      (dict-get d name))))
  18. (defun dict-get-xrecord  (owner name / d)
  19.   (cond
  20.     ((not owner) (dict-get-xrecord (namedobjdict) name))
  21.     ((= (type owner) 'str)
  22.      (dict-get-xrecord (dict-get (namedobjdict) owner) name))
  23.     ((= (type owner) 'ename) (dict-get-xrecord (entget owner) name))
  24.     ((not (listp owner)) nil)
  25.     ((eq (cdr (assoc 0 owner)) "DICTIONARY")
  26.      (cond ((dictsearch (cdar owner) name))
  27.            ((and (setq d (entmakex '((0 . "XRECORD") (100 . "AcDbXrecord"))))
  28.                  (dictadd (cdar owner) name d))
  29.             (entget d))))
  30.     ((setq d (assoc 360 owner)) (dict-get-xrecord (cdr d) name))
  31.     ((and (setq d (entmakex '((0 . "DICTIONARY") (100 . "AcDbDictionary"))))
  32.           (entmod
  33.             (append owner (list '(102 . "{ACAD_XDICTIONARY") (cons 360 d) '(102 . "}")))))
  34.      (dict-get-xrecord d name))))
Common sense - the curse in disguise. Because if you have it, you have to live with those that don't.