Author Topic: DisEval  (Read 3869 times)

0 Members and 1 Guest are viewing this topic.

Grrr1337

  • Swamp Rat
  • Posts: 812
DisEval
« on: October 23, 2020, 01:51:25 PM »
Hey guys,
Although I've seen this sub of Lee Mac through the years -

Code - Auto/Visual Lisp: [Select]
  1. ;; Active Document  -  Lee Mac
  2. ;; Returns the VLA Active Document Object
  3.  
  4. (defun LM:acdoc nil
  5.   (LM:acdoc)
  6. )

I was recently inspired by MP's sub from this thread -

Code - Auto/Visual Lisp: [Select]
  1.   (list 'defun 'mp-get-owner '( object / owner )
  2.     (list 'vl-catch-all-apply
  3.       (list 'function
  4.         (list 'lambda nil
  5.           (list 'setq 'owner
  6.             (list
  7.               'vla-objectidtoobject
  8.               (list 'vla-get-ownerid 'object)
  9.             )
  10.           )
  11.         )
  12.       )      
  13.     )
  14.     'owner
  15.   )
  16. )

Got me thinking, did he first wrote the sub and then refactored it manually to be used with eval, or maybe he have already automated the refactoring part?  :thinking:
Despite my accumulation of LISP writing experience I would struggle to refactor any code in order to disevaluate it.

So I played the last few days and I'm sharing with you my DisEval sub:
Code - Auto/Visual Lisp: [Select]
  1. ;; DisEval - (Inverse evaluator)
  2. ;; Grrr [21.10.2020]
  3. (defun _DisEval ( f / _dottedpair-p diseval )
  4.  
  5.   ; _$ (_dottedpair-p '(-4 . "OR>")) >> T
  6.   ; _$ (_dottedpair-p '(1 2 3)) >> nil
  7.   ; _$ (_dottedpair-p "ABC") >> nil
  8.   ; _$ (_dottedpair-p nil) >> nil
  9.   (defun _dottedpair-p ( x )
  10.     (and (vl-consp x) (vl-catch-all-error-p (vl-catch-all-apply (function length) (list x))))
  11.   )
  12.  
  13.   (defun diseval ( f / x )
  14.     (setq x (if (listp f) (car f) f))
  15.     (cond
  16.       ( (not f) f)
  17.       ( (_dottedpair-p f)
  18.         f
  19.       )
  20.       ( (atom x)
  21.         (append
  22.           (list
  23.             (cond
  24.               ((null x) x)
  25.               ((numberp x) x)
  26.               ((eq t x) x)
  27.               ( (read (strcat "'" (vl-prin1-to-string x))) )
  28.             ); cond
  29.           ); list
  30.           (diseval (if (listp f) (cdr f)))
  31.         )
  32.       )
  33.       ( (listp x)
  34.         (append
  35.           (list
  36.             (
  37.               '((xx)
  38.                 (cond
  39.                   ( (_dottedpair-p xx)
  40.                     (list 'cons (car xx) (cdr xx))
  41.                   )
  42.                   ( (cons 'list xx) )
  43.                 )
  44.               )
  45.               (diseval x)
  46.             )
  47.           )
  48.           (diseval (if (listp f) (cdr f)))
  49.         )
  50.       ); (listp x)
  51.     ); cond
  52.   ); defun
  53.  
  54.   (if (vl-consp f) (cons 'list (diseval f)))
  55. ); defun _DisEval

Sample usage:
Code: [Select]
(_DisEval
  '(defun AcDoc nil
    (vla-get-ActiveDocument (vlax-get-acad-object))
  )
)
>> (LIST (QUOTE DEFUN) (QUOTE ACDOC) nil (LIST (QUOTE vla-get-ActiveDocument) (LIST (QUOTE vlax-get-acad-object))))
>> Pretty-printed:
(LIST 'DEFUN 'ACDOC nil
  (LIST 'vla-get-ActiveDocument (LIST 'vlax-get-acad-object))
)

Code: [Select]
(_DisEval
  '(LM:ssget "\nSelect lines, arcs and/or polylines to extend: "
    '("_:L"
      (
        (-4 . "<OR")
        (0 . "LINE,ARC")
        (-4 . "<AND")
        (0 . "LWPOLYLINE")
        (-4 . "<NOT")
        (-4 . "&=") (70 . 1)
        (-4 . "NOT>")
        (-4 . "AND>")
        (-4 . "<AND")
        (0 . "POLYLINE")
        (-4 . "<NOT")
        (-4 . "&=") (70 . 87)
        (-4 . "NOT>")
        (-4 . "AND>")
        (-4 . "OR>")
      )
    )
  )
)
>>
(LIST 'LM:SSGET
  '"\nSelect lines, arcs and/or polylines to extend: "
  (LIST 'QUOTE
    (LIST '"_:L"
(LIST
(CONS -4 "<OR")
        (CONS 0 "LINE,ARC")
        (CONS -4 "<AND")
        (CONS 0 "LWPOLYLINE")
        (CONS -4 "<NOT")
        (CONS -4 "&=")
        (CONS 70 1)
        (CONS -4 "NOT>")
        (CONS -4 "AND>")
        (CONS -4 "<AND")
        (CONS 0 "POLYLINE")
        (CONS -4 "<NOT")
        (CONS -4 "&=")
        (CONS 70 87)
        (CONS -4 "NOT>")
        (CONS -4 "AND>")
        (CONS -4 "OR>")
      )
    )
  )
)

You can also perform nested DisEval-uations on any code and then nestedly evaluate it:
Code: [Select]
_$ (eval (eval (eval (eval (eval (_DisEval (_DisEval (_DisEval (_DisEval '(ssget "_:L-I" '((0 . "CIRCLE")(8 . "0"))))))))))))
<Selection set: 2f>

Code: [Select]
_$ ( (lambda (n c / r ) (setq r c) (repeat n (setq r (_DisEval r))) r) 5
    '(defun AcDoc nil
      (vla-get-ActiveDocument (vlax-get-acad-object))
    )
  )
(LIST
  'LIST
  (LIST 'QUOTE 'LIST)
  (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'LIST))
  (LIST 'LIST
(LIST 'QUOTE 'LIST)
(LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
(LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'LIST))
  )
  (LIST 'LIST
(LIST 'QUOTE 'LIST)
(LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'LIST))
(LIST 'LIST
      (LIST 'QUOTE 'LIST)
      (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
      (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
)
(LIST 'LIST
      (LIST 'QUOTE 'LIST)
      (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
      (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'DEFUN))
)
  )
  (LIST 'LIST
(LIST 'QUOTE 'LIST)
(LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'LIST))
(LIST 'LIST
      (LIST 'QUOTE 'LIST)
      (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
      (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
)
(LIST 'LIST
      (LIST 'QUOTE 'LIST)
      (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
      (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'ACDOC))
)
  )
  nil
  (LIST
    'LIST
    (LIST 'QUOTE 'LIST)
    (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'LIST))
    (LIST 'LIST
  (LIST 'QUOTE 'LIST)
  (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
  (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'LIST))
    )
    (LIST 'LIST
  (LIST 'QUOTE 'LIST)
  (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'LIST))
  (LIST 'LIST
(LIST 'QUOTE 'LIST)
(LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
(LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
  )
  (LIST 'LIST
(LIST 'QUOTE 'LIST)
(LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
(LIST 'LIST
      (LIST 'QUOTE 'QUOTE)
      (LIST 'QUOTE 'vla-get-ActiveDocument)
)
  )
    )
    (LIST
      'LIST
      (LIST 'QUOTE 'LIST)
      (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'LIST))
      (LIST 'LIST
    (LIST 'QUOTE 'LIST)
    (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
    (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'LIST))
      )
      (LIST 'LIST
    (LIST 'QUOTE 'LIST)
    (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'LIST))
    (LIST 'LIST
  (LIST 'QUOTE 'LIST)
  (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
  (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
    )
    (LIST 'LIST
  (LIST 'QUOTE 'LIST)
  (LIST 'LIST (LIST 'QUOTE 'QUOTE) (LIST 'QUOTE 'QUOTE))
  (LIST 'LIST
(LIST 'QUOTE 'QUOTE)
(LIST 'QUOTE 'vlax-get-acad-object)
  )
    )
      )
    )
  )
)

Code: [Select]
_$ ( (lambda (n c / r ) (setq r c) (repeat n (setq r (eval r))) r) 7 ; try from 5 up to 7
  ( (lambda (n c / r ) (setq r c) (repeat n (setq r (_DisEval r))) r) 5
    '(defun AcDoc nil
      (vla-get-ActiveDocument (vlax-get-acad-object))
    )
  )
);; (lambda)
#<USUBR @0000001c064381b0 ACDOC>

You can also DisEval-uate and evaluate bunch of subs/code like so:
Code: [Select]
(mapcar 'eval
  (eval
    (eval
      (_DisEval
        '(list
          ;; Bunch of codes...
          (defun AcObj nil
            (vlax-get-acad-object)
          )
          (defun AcDoc nil
            (vla-get-ActiveDocument (vlax-get-acad-object))
          )
          (defun AlertHello nil
            (alert "Hello")
          )
          (defun SayHi nil
            (princ "Hi")
          )
          ;; ... bunch of codes
        ); list
      )
    )
  )
)

or like in this example that guarantees that it works properly -
Code: [Select]
(mapcar 'eval
  (eval
    (eval
      (_DisEval
        '(list
          ;; i.e. paste the whole lisp content from here:
          ;; http://www.lee-mac.com/lisp/html/2DProjectionV1-0.html
        ); list
      )
    )
  )
)


So in the end..
Code - Auto/Visual Lisp: [Select]
  1. _$ (_DisEval ;; Inspect 'n Pretty-print
  2.   '(defun mp-get-owner ( object / owner )
  3.     (vl-catch-all-apply
  4.       (function
  5.         (lambda nil
  6.           (setq owner
  7.             (vla-objectidtoobject
  8.               (vla-get-ownerid object)
  9.             )
  10.           )
  11.         )
  12.       )      
  13.     )
  14.     owner
  15.   )
  16. )
  17. >>
  18.   'DEFUN
  19.   'MP-GET-OWNER
  20.   (LIST 'OBJECT '/ 'OWNER)
  21.   (LIST
  22.     'VL-CATCH-ALL-APPLY
  23.     (LIST
  24.       'FUNCTION
  25.       (LIST
  26.         'LAMBDA
  27.         nil
  28.         (LIST
  29.           'SETQ
  30.           'OWNER
  31.           (LIST
  32.             'vla-ObjectIDToObject
  33.             (LIST 'vla-get-OwnerID 'OBJECT)
  34.           )
  35.         )
  36.       )
  37.     )
  38.   )
  39.   'OWNER
  40. )


Cheers!
(going back to my busy life, so sorry if I don't reply on time)
« Last Edit: October 24, 2020, 05:51:01 AM by Grrr1337 »
(apply ''((a b c)(a b c))
  '(
    (( f L ) (apply 'strcat (f L)))
    (( L ) (if L (cons (chr (car L)) (f (cdr L)))))
    (72 101 108 108 111 32 87 111 114 108 100)
  )
)
vevo.bg

JohnK

  • Administrator
  • Seagull
  • Posts: 10605
Re: DisEval
« Reply #1 on: October 23, 2020, 02:44:16 PM »
Pardon my ignorance but what is wrong with:
Code - Auto/Visual Lisp: [Select]
  1. (setq *acadobject*
  2.        (cond
  3.          (*acadobject*)
  4.          ((vlax-get-acad-object))) )

and on the lazy evaluation subject (I am unsure what "disevaluation" means) I've used a function I wrote a long time ago called "hook".
Code - Auto/Visual Lisp: [Select]
  1. (defun hook (func)
  2.   ;; hook
  3.   ;; this function will take an argument and turn it into a
  4.   ;; lambda expression to evaluate. (You can assign to a variable
  5.   ;; and exec it at a later time if you wish.)
  6.   ;;
  7.   ;; By: John (Se7en) K
  8.   ;;    (inspired by something I saw in one of
  9.   ;;     Vladimir Nesterovsky's procedures.)
  10.   ;;
  11.   ;; Ex: (hook '(+ 1 2))
  12.   ;;     > ((LAMBDA nil (+ 1 2)))
  13.   ;;    
  14.   (list (cons 'lambda (cons nil (list func)))) )

Code: [Select]
> Command: (setq a (hook '(+ 1 2)))
> ((LAMBDA nil (+ 1 2)))
>
> Command: (eval a)
> 3
>
> Command: (setq a (hook '(+ 1 2))
> (_>       b (hook '(+ 1 3))
> (_>       c (hook '(+ 1 4))
> (_>       d (hook '(+ 1 5))
> (_>       e (hook '(+ 1 6))
> (_>       f (hook '(+ 1 7)))
> ((LAMBDA nil (+ 1 7)))
>
> Command: (mapcar 'eval (mapcar 'eval '(a b c d e f)))
> (3 4 5 6 7 8)
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

MP

  • Seagull
  • Posts: 17750
  • Have thousands of dwgs to process? Contact me.
Re: DisEval
« Reply #2 on: October 23, 2020, 03:42:49 PM »
Yours uses a global variable set to acadapp, the alternative would encapsulate acadapp without a global.
Engineering Technologist • CAD Automation Practitioner
Automation ▸ Design ▸ Drafting ▸ Document Control ▸ Client
cadanalyst@gmail.com • http://cadanalyst.slack.com • http://linkedin.com/in/cadanalyst

JohnK

  • Administrator
  • Seagull
  • Posts: 10605
Re: DisEval
« Reply #3 on: October 23, 2020, 03:57:27 PM »
Sorry, running late!!


There is nothing wrong with global variables (in this respect) is there?

What would be the difference between a variable and a function (aren't they both "symbols" in AutoLisp)?

If defun-q is a list can you modify that defun-q list like any other list (were there any problems with modifying those defun-q lists and evaluations)?
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

MP

  • Seagull
  • Posts: 17750
  • Have thousands of dwgs to process? Contact me.
Re: DisEval
« Reply #4 on: October 23, 2020, 04:00:00 PM »
Personal preference that’s presumably ok with the lisp police.
Engineering Technologist • CAD Automation Practitioner
Automation ▸ Design ▸ Drafting ▸ Document Control ▸ Client
cadanalyst@gmail.com • http://cadanalyst.slack.com • http://linkedin.com/in/cadanalyst

Lee Mac

  • Seagull
  • Posts: 12906
  • London, England
Re: DisEval
« Reply #5 on: October 23, 2020, 05:39:55 PM »
Whilst I applaud the code you have shared, note that quoting every nested expression in the supplied list is not quite achieving the same result as both mine & MP's example functions, in which certain specific expressions are evaluated immediately as part of the function definition (e.g. references to objects, such as the active document object), whilst others are evaluated when the function itself is evaluated.

Consider the difference in the result of the following function definitions, as may be illustrated through the use of defun-q:
Code - Auto/Visual Lisp: [Select]
  1. LM:ACDOC
  2. _$ LM:acdoc
  3. (nil #<VLA-OBJECT IAcadDocument 0000000028473118>)
  4.  
  5. ACDOC
  6. _$ acdoc

JohnK

  • Administrator
  • Seagull
  • Posts: 10605
Re: DisEval
« Reply #6 on: October 23, 2020, 05:43:51 PM »
Personal preference that’s presumably ok with the lisp police.

I think the police are drunk now because some of my old code/methods were accused of being "too academic" (not fit for production) in the past [ "hygienic variables", "or is evil", "let", etc.] and was labeled as a heretic--this is way-out-there IMO. -i.e. I'd take the cond.
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

ronjonp

  • Needs a day job
  • Posts: 7526
Re: DisEval
« Reply #7 on: October 23, 2020, 06:04:10 PM »
FWIW I'd be inclined to use this over COND  :-)
Code - Auto/Visual Lisp: [Select]
  1. (or *acadobject* (setq *acadobject* (vlax-get-acad-object)))

Windows 11 x64 - AutoCAD /C3D 2023

Custom Build PC

JohnK

  • Administrator
  • Seagull
  • Posts: 10605
Re: DisEval
« Reply #8 on: October 23, 2020, 06:19:22 PM »
*sigh* ...I'm beginning to think people believe "obfuscation" equals "better".
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

MP

  • Seagull
  • Posts: 17750
  • Have thousands of dwgs to process? Contact me.
Re: DisEval
« Reply #9 on: October 23, 2020, 07:09:54 PM »
:o
Engineering Technologist • CAD Automation Practitioner
Automation ▸ Design ▸ Drafting ▸ Document Control ▸ Client
cadanalyst@gmail.com • http://cadanalyst.slack.com • http://linkedin.com/in/cadanalyst

MP

  • Seagull
  • Posts: 17750
  • Have thousands of dwgs to process? Contact me.
Re: DisEval
« Reply #10 on: October 23, 2020, 11:44:00 PM »
... specific expressions are evaluated <at declaration time> ... whilst others are evaluated <at run time> ...

Exactamundo.

/AHF
Engineering Technologist • CAD Automation Practitioner
Automation ▸ Design ▸ Drafting ▸ Document Control ▸ Client
cadanalyst@gmail.com • http://cadanalyst.slack.com • http://linkedin.com/in/cadanalyst

MP

  • Seagull
  • Posts: 17750
  • Have thousands of dwgs to process? Contact me.
Re: DisEval
« Reply #11 on: October 24, 2020, 12:20:05 AM »
Code: [Select]
(defun mp-get-owner-1 ( object / owner )
    (vl-catch-all-apply
        (function
            (lambda ( )
                (setq owner
                    (vla-objectidtoobject
                        (vla-get-activedocument (vlax-get-acad-object))
                        (vla-get-ownerid object)
                    )             
                )
            )
        )
    )
    owner
)

Code: [Select]
(defun mp-get-owner-2 ( object )
    (eval
        (list 'defun 'mp-get-owner-2 '( object / owner )
            (list 'vl-catch-all-apply
                (list 'function
                    (list 'lambda nil
                        (list 'setq 'owner
                            (list
                                'vla-objectidtoobject
                                (vla-get-activedocument (vlax-get-acad-object))
                                (list 'vla-get-ownerid 'object)
                            )
                        )
                    )
                )       
            )
           'owner
        )
    )
    (mp-get-owner-2 object )
)

Code: [Select]
(setq obj (vlax-ename->vla-object (car (entsel))))

(mp-benchmark
   '(
        (mp-get-owner-1 obj)
        (mp-get-owner-2 obj)
    )           
)

Elapsed milliseconds / relative speed for 32768 iteration(s):
(MP-GET-OWNER-2 OBJ).....1125 / 1.35 <35% faster>
(MP-GET-OWNER-1 OBJ).....1516 / 1.00 <slowest>

(did multiple tests, observed everything from 29-41% speed increase).

TLDR: Wifey says speed isn't everything.

An aside, I prefer encapsulating objects when applicable, eliminates superfluous processing and it's tidy; win.
Engineering Technologist • CAD Automation Practitioner
Automation ▸ Design ▸ Drafting ▸ Document Control ▸ Client
cadanalyst@gmail.com • http://cadanalyst.slack.com • http://linkedin.com/in/cadanalyst

ronjonp

  • Needs a day job
  • Posts: 7526
Re: DisEval
« Reply #12 on: October 24, 2020, 12:35:50 AM »
*sigh* ...I'm beginning to think people believe "obfuscation" equals "better".
It's not really that at all ... it's very legible to me  :? \disclaimeraside ... not catering to others.

Mute point really .. goes back to preference.
Quote
_$
Benchmarking ...................Elapsed milliseconds / relative speed for 65536 iteration(s):

    (OR *ACADOBJECT* (SETQ *ACADOBJECT* ...).....1344 / 1.06 <fastest>
    (SETQ *ACADOBJECT* (COND (*ACADOBJEC...).....1422 / 1.00 <slowest>

 
; 1 form loaded from #<editor "<Untitled-0> loading...">
_$
« Last Edit: October 24, 2020, 12:52:10 AM by ronjonp »

Windows 11 x64 - AutoCAD /C3D 2023

Custom Build PC

ronjonp

  • Needs a day job
  • Posts: 7526
Re: DisEval
« Reply #13 on: October 24, 2020, 12:41:13 AM »
Code: [Select]
(defun mp-get-owner-1 ( object / owner )
    (vl-catch-all-apply
        (function
            (lambda ( )
                (setq owner
                    (vla-objectidtoobject
                        (vla-get-activedocument (vlax-get-acad-object))
                        (vla-get-ownerid object)
                    )             
                )
            )
        )
    )
    owner
)

Code: [Select]
(defun mp-get-owner-2 ( object )
    (eval
        (list 'defun 'mp-get-owner-2 '( object / owner )
            (list 'vl-catch-all-apply
                (list 'function
                    (list 'lambda nil
                        (list 'setq 'owner
                            (list
                                'vla-objectidtoobject
                                (vla-get-activedocument (vlax-get-acad-object))
                                (list 'vla-get-ownerid 'object)
                            )
                        )
                    )
                )       
            )
           'owner
        )
    )
    (mp-get-owner-2 object )
)

Code: [Select]
(setq obj (vlax-ename->vla-object (car (entsel))))

(mp-benchmark
   '(
        (mp-get-owner-1 obj)
        (mp-get-owner-2 obj)
    )           
)

Elapsed milliseconds / relative speed for 32768 iteration(s):
(MP-GET-OWNER-2 OBJ).....1125 / 1.35 <35% faster>
(MP-GET-OWNER-1 OBJ).....1516 / 1.00 <slowest>

(did multiple tests, observed everything from 29-41% speed increase).

TLDR: Wifey says speed isn't everything.

An aside, I prefer encapsulating objects when applicable, eliminates superfluous processing and it's tidy; win.
I always wondered why you smart mother truckers did the fancy 'eval shite .. going to have to look into this closer. *beers*

Windows 11 x64 - AutoCAD /C3D 2023

Custom Build PC

MP

  • Seagull
  • Posts: 17750
  • Have thousands of dwgs to process? Contact me.
Engineering Technologist • CAD Automation Practitioner
Automation ▸ Design ▸ Drafting ▸ Document Control ▸ Client
cadanalyst@gmail.com • http://cadanalyst.slack.com • http://linkedin.com/in/cadanalyst