Visual LISP, AutoLISP and General Customization
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Lsp routine to find and replace attribute values

31 REPLIES 31
SOLVED
Reply
Message 1 of 32
Bkndsdl
3051 Views, 31 Replies

Lsp routine to find and replace attribute values

Looking for a lisp routine that will change one block with multiple tag values; iow, I have one block that will have multiple tag values that need to be changed to new values.  I need a routine that will allow me to change the block with the tag value 10-1 to abc, the same block and tag value 10-2 to def, so & sf.  The routine will need to search the tag value to determine what the value is and change it accordingly.  Also need it to work with a scr file........I had this routine on an external hd that decided it doesn't want to work anymore.

 

HELP!!

31 REPLIES 31
Message 21 of 32
pbejse
in reply to: Gary_J_Orr

I guess you werent kidding when you issued this warning Gary.

 

<<Warning: I write very long code blocks with nested if's so I can handle many different conditions with one function as oppossed to having many "specialized short and sweet" functions so be forewarned...>>>

 

Anyhoo, i have not really tested your code thoroughly, but does it address the other condtion (or option) i posted at my last message?

 

<<..would that be "dog2" to "car2"? >>

 

Works much like the native FIND command using wildcards

 

'M just curios is all 

 

Message 22 of 32
Gary_J_Orr
in reply to: pbejse

pbejse...
if you're curious, try it...
I saw a post about a specific need... got to thinking about an old library file I had that needed some updating anyway, did some rearranging, a bit of tweaking, and ended up with a solution for the OP that served the need and upgraded my library function with a little more flexibility all at the same time...
And yes, it can be dog2 to cat2 or it can be any dog to this car... truth is, while the OP's problem has been solved I have further tweaked and modified it and am currently working on a option to reset the attribute(s) back to the original default value(s) if such is what is desired.

You're welcome to try it if you're curious, to use it if you like it, or further modify it if you think it has something that you can improve on, or ignore it in favor of your own methods... doesn't really matter to me one way or the other.

It isn't like I'm out here selling my tool and have to try to justify and provide support for it and answer questions of potential buyers... I solved someone's issue and ended up with an improved tool set and ideas for even further improvements, so my "task" is done and both myself and the OP are satisfied with the results
-G
Gary J. Orr
(Your Friendly Neighborhood) CADD/BIM/VDC Applications Manager
http://www.linkedin.com/in/garyorr

aka (current and past user names):
Gary_J_Orr (GOMO Stuff 2008-Present); OrrG (Forum Studio 2005-2008); Gary J. Orr (LHB Inc 2002-2005); Orr, Gary J. (Gossen Livingston 1997-2002)
Message 23 of 32
pbejse
in reply to: Gary_J_Orr


@Anonymous wrote:
pbejse...
if you're curious, try it...

.. And yes, it can be dog2 to cat2 or it can be any dog to this car... truth is, while the OP's problem has been solved I have further tweaked and modified it and am currently working on a option to reset the attribute(s) back to the original default value(s) if such is what is desired...
-G


 

My curiosity is brought about the fact that i'm impress with time you spent to tackle every possible conditions. Good for you and 2x as good for the OP.

 

As for dog2 to cat2 , can you show me the correct syntax to make it work for wildcards search .i.e  dog* cat* will change dog2 , dog5 and dog8 to cat2, cat5 and cat8? 

 


@Anonymous wrote:

... I solved someone's issue and ended up with an improved tool set and ideas for even further improvements, so my "task" is done and both myself and the OP are satisfied with the results
-G

Just to clarify. My first question was intened for the OP really [re:"Accept as solution" comment].and I am not questioning your intention G,

The forum is lucky to have you.

 

Cheers Smiley Happy

Message 24 of 32
Gary_J_Orr
in reply to: pbejse


@pbejse wrote:

@Anonymous wrote:
pbejse...
if you're curious, try it...

.. And yes, it can be dog2 to cat2 or it can be any dog to this car... truth is, while the OP's problem has been solved I have further tweaked and modified it and am currently working on a option to reset the attribute(s) back to the original default value(s) if such is what is desired...
-G


 

My curiosity is brought about the fact that i'm impress with time you spent to tackle every possible conditions. Good for you and 2x as good for the OP.

 

As for dog2 to cat2 , can you show me the correct syntax to make it work for wildcards search .i.e  dog* cat* will change dog2 , dog5 and dog8 to cat2, cat5 and cat8? 

 


What you seem to be talking about is a substring replacement... and that is something that I haven't even considered.
Not that it isn't possible to do, just that it would take a completely different approach to achieve. The concept of this tool is to work with a defined new value whereas you are talking about defining the value "in-process" by obtaining the new value as a derivative of the current value.

 

A modification to the "put-textstring" section could get you there.easily enough... by passing in a value list such as (("Dog*" . "Cat*")) ... upon finding a value that matches "dog*", test the "new Value"(ie, the "cat*") prior to setting it to see if it has a wildcard in it (BTW: this is exactly what I'm looking at doing for the "reset to dafaults" option that I'm considering only that will be  just "*")... if a wildcard is found (yes, another one of those nested If's 😉 )  then do a substitute text kind of thing (by parsing the strings), replacing the current value of the string (all characters other than the wildcard) with the new string (all characters except the wildcard)...

It wouldn't take all that much to add it really (a lot easier than the reset to defaults that I'm looking at) due to the way that I approach my coding methodologies (the "long-form" that I use makes it easy to modify my code without having to completely rework everything to add in a new option/idea) but I'm on a different computer right now doing some research in a different environment so I can't look at it right away.

 

-G

Gary J. Orr
(Your Friendly Neighborhood) CADD/BIM/VDC Applications Manager
http://www.linkedin.com/in/garyorr

aka (current and past user names):
Gary_J_Orr (GOMO Stuff 2008-Present); OrrG (Forum Studio 2005-2008); Gary J. Orr (LHB Inc 2002-2005); Orr, Gary J. (Gossen Livingston 1997-2002)
Message 25 of 32
Bkndsdl
in reply to: Gary_J_Orr

Gary, thanks for the help.  This will shave hours, possibly days, off of what we need to do.

 

Looking at the last exchange, and your last code post......is that mod going to allow me to change anything to anything?  In other words, I had planned on modifying the original routine you posted by changing the block and tag, saving it as another routine, and sticking that in my script.  Basically, there are other blocks that I have to change as well, and since the routine was specific to the block and tag, I wasn't going to bother anyone by asking them to modify the routine so the cons could work for any block; I would just modify the original for each block and tag I needed to change.  However, it looks like, with the discussion that ensued, that you have addressed this issue........would I be correct in my assumption, therefore I should update the original routine?

 

Thanks for everyone's input!

Message 26 of 32
Bkndsdl
in reply to: pbejse

@pbejse wrote:

 

Not taking anything away from Gary _J_Orr. But I notice  you have not tag this thread as "Accept as solution", hence my question above

 


lol..........is it ever finished??  When I was writing lisp back in the 90's and early 2k's, it didn't matter what I did, the EU always would say '.......but could you make it do this too?'..........lol........

 

I didn't want to 'accept the solution' because honestly I've never seen that in a forum before and didn't want to accept and then not be able to open the convo again.  My lack of forum knowledge I guess.

Message 27 of 32
Bkndsdl
in reply to: Bkndsdl

Ok, after further review, I see the code change, but forgive me, I'm confused........I don't understand if I need to load every instance of variables into the routine - essentially hard coding the routine -  or if I can simply replace what I need with the command line when I want the routine to change something?

Message 28 of 32
Gary_J_Orr
in reply to: Bkndsdl


@Bkndsdl wrote:

Ok, after further review, I see the code change, but forgive me, I'm confused........I don't understand if I need to load every instance of variables into the routine - essentially hard coding the routine -  or if I can simply replace what I need with the command line when I want the routine to change something?


using the fully corrected version that I posted on: 05-17-2014 08:37...

 

You can do a number of things. bear in mind that the sub function that I provided : (change-ec317-continuation-AttsByVal) was a specific example to tie the options directly to your examples. You can create any such subfunctions to cover any number of conditions, including a sub in which you ask the user for input, then send the appropriate "switches" to the GJO_ChangeAttVals function.

 

Viable options for the GJO_ChangeAttVals function:

ValList is REQUIRED but we'll save that for last...

whereas BlkName, AttTag, and CtabFlag can each be a specified value or passed as nil

so, calling GJO_ChangeAttVals can take any of the following forms:

 

-> process only those blocks named "BlockName" and only those attributes with the tag name of  "AttributeTag" in the current layout tab

(GJO_ChangeAttVals ChangeList "BlockName" "AttributeTag" T)

 

-> process only those blocks named "BlockName" and only those attributes with the tag name of  "AttributeTag" in any layout tab

(GJO_ChangeAttVals ChangeList "BlockName" "AttributeTag" nil)

 

-> process only those blocks named "BlockName" and all attributes with a matching value regardless of the attribute tag name in any layout tab

(GJO_ChangeAttVals ChangeList "BlockName" nil nil)

 

-> process all blocks regardless of name and all attributes with a matching value regardless of the attribute tag name in any layout tab

(GJO_ChangeAttVals ChangeList nil nil nil)

 

-> process all blocks regardless of name but only those attributes with the tag name of  "AttributeTag" in any layout tab

(GJO_ChangeAttVals ChangeList nil "AttributeTag" nil)

 

either or both of the BlkName or AttTag strings can contain multiple names:

(GJO_ChangeAttVals ChangeList "BlockName1,BlockName2" "AttributeTag1,AttributeTag2" nil)

 

or valid ssget wildcards (the attribute tag checker portion of the code works similar to ssget for wildcards)

(GJO_ChangeAttVals ChangeList "BlockName*" "AttributeTag*" nil)

 

Now to the ValList variable (which I was setting to "changelist" within the example function prior to passing to the GJO_ChangeAttVals function)...

 

-> passing this list would change all attribute values (regardless of the current value):

*** warning***

Passing the next list along with nil for the block name and nil for the attribute tag would set EVERY attribute in EVERY block REGARDLESS of it's current value to "Everything is Equal"... a very dangerous combination... so be sure that you are supplying either block name or attribute tag or both with this example

*** warning***

(list   (cons "*" "Everything is Equal")   )

 

in the remaining examples the "value to replace" (the first part of each cons list) is not case sensitive but the new string will be exactly what is in the second part of each cons list.

 

(list
  ;if the string is exactly "dog" replace it with "Animal"
  (cons "dog" "Animal")
  ;if the string is exactly "fish" replace it with "Seafood"
  (cons "fish" "Seafood")
  ;if the string is exactly "car" replace it with "Transportation"
  (cons "car" "Transportation")
  )

(list
  ;if the string starts with "dog" replace it with "Animal"
  (cons "dog*" "Animal")
  ;if the string ends with "fish" replace it with "Seafood"
  (cons "*fish" "Seafood")
  ;if the string contains "car" replace it with "Transportation"
  (cons "*car*" "Transportation")
  )

(list
  ;if the string contains exactly "dog" or "cat" or "pig" replace it with "Animal"
  (cons "dog,cat,pig" "Animal")
  ;if the string contains exactly "fish" or "lobster" or "crab" replace it with "Seafood"
  (cons "Fish,lobster,cRab" "Seafood")
  ;if the string contains exactly "car" or "truck" or "Train" replace it with "Transportation"
  (cons "Car,TrUCK,train" "Transportation")
  )

(list
  ;if the string begins with "dog" or "cat" or "pig" replace it with "Animal"
  (cons "dog*,cat*,pig*" "Animal")
  ;if the string ends with "fish" or "lobster" or "crab" replace it with "Seafood"
  (cons "*Fish,*lobster,*cRab" "Seafood")
  ;if the string contains "car" or "truck" or "Train" anywhere within it, replace it with "Transportation"
  (cons "*Car*,*TrUCK*,*train*" "Transportation")
  )

 

As you can see there are many, many different ways to use the single function...

Since you're scripting the routine you will need to pre-define the options and variables at some point, either within the routine as sub functions (as with the example that I provided), or by setting lisp variables with the script prior to calling the function, or by writing the writing the options during the function call...but how you choose to call the function and pass the ValList and other options is entirely up to you..

 

If you want to define the variables within the script instead of hard-coding and defining specific functions within the lisp file (and remember that I am not familiar with scripting so all I can do is outline the options) I imagine that it would go something like this:

open a drawing

load the lisp file

define a changeList in the document with something like:  (setq ChangeList (list (cons "dog" "car") (cons "cat" "truck") (cons "fish" "train")))

call the function with something like: (GJO_ChangeAttVals ChangeList "BlockName" "AttributeTag" nil)

save the drawing

repeat...

 

Yet another option (and my recommended method): define a "temporary" lisp/script file for this run that defines your specific options and calls,

then your main script would look something like

open a drawing

load the main lisp file

load the temporary lisp/script file

if you enclosed the calls within a function in a temporary lisp file, call the function from the temporary lisp file

save the drawing

repeat...

 

 

If what you are trying to do gets too complicated then you can run the main function a couple of times, once with one set of options and/or ValList to do one specific replacement task (defined and called with one predefined sub function), then a second (or more) time(s) with a different set of options and/or valList to further modify the results (defined with additional predefined sub functions). With the use of a temporary lisp file for the specific needs of "this run" (as recommended) you can define many functions to run with each desired processing option and call each of them, or a single function that calls the main function many times, each time with different options, all without "cluttering up" your original reusable main lisp file.

 

Message me privately if you would like for additional explanations.

-Gary

 

BTW: I hope the formatting comes through... this stupid Discussion Group decided that my creds weren't valid anymore when I went to submit it and I had to rebuild the post by copying to the contents to notepad then putting it all back together.

Gary J. Orr
(Your Friendly Neighborhood) CADD/BIM/VDC Applications Manager
http://www.linkedin.com/in/garyorr

aka (current and past user names):
Gary_J_Orr (GOMO Stuff 2008-Present); OrrG (Forum Studio 2005-2008); Gary J. Orr (LHB Inc 2002-2005); Orr, Gary J. (Gossen Livingston 1997-2002)
Message 29 of 32
darrell1gregg
in reply to: Gary_J_Orr

I would use the "cond" LISP function rather than a bunch of nested "IF"s...

The COND function is pretty easily mananged and makes it just plain easier to see the conditions, the tests, and the resultant expression you want executed when that condition is true.

 

 

Message 30 of 32
Gary_J_Orr
in reply to: darrell1gregg


@darrell1gregg wrote:

I would use the "cond" LISP function rather than a bunch of nested "IF"s...

The COND function is pretty easily mananged and makes it just plain easier to see the conditions, the tests, and the resultant expression you want executed when that condition is true.

 

 


Normally I would be the first to agree with you... in this case the nested if's are often buried within while or foreach statements to make a decision at that specific point that can't be tested at the beginning of the function... they are also used as "branching" functions based upon those "at that point" values. Once you rule out the initial If (which simply allows an error free exit with a statement to the command line as to why it didn't do anything) and rule out those values that are dynamically changing in throughout the course of the routine, there aren't that many places in which the cond stmt makes sense in this particular function...

But hey, you're welcome to rebuild it with your suggestion... maybe you're right and it would be better... I say "go for it". In the meantime, I'm quite happy with it as it stands... well, except for the options that I'm currently working on adding...

 

-Gary

Gary J. Orr
(Your Friendly Neighborhood) CADD/BIM/VDC Applications Manager
http://www.linkedin.com/in/garyorr

aka (current and past user names):
Gary_J_Orr (GOMO Stuff 2008-Present); OrrG (Forum Studio 2005-2008); Gary J. Orr (LHB Inc 2002-2005); Orr, Gary J. (Gossen Livingston 1997-2002)
Message 31 of 32
Gary_J_Orr
in reply to: pbejse


@pbejse wrote:

My curiosity is brought about the fact that i'm impress with time you spent to tackle every possible conditions. Good for you and 2x as good for the OP.

 

As for dog2 to cat2 , can you show me the correct syntax to make it work for wildcards search .i.e  dog* cat* will change dog2 , dog5 and dog8 to cat2, cat5 and cat8? 

 


pbejse,

I now have a module that does a wildcard replace as you mentioned in the quoted post... As usual, I took it to extreems... had a lot of fun (and a bit of hair pulling) to get it done to cover nearly any matched pairs of wildcards, even one option that allows for rearranging the text string (ie if you have "001-tag" and want to change it to "Mark: 001" you can pass this pair of from-to "*tag" "Mark: *" as well as a host of more complicated substring replacements.

 

Spoiler
;|
;Test expected valid strings:
(setq Test (GJO_ReplaceWCString "my car*chevy" "My Friend\'s Car*Ford" "My Car is a Chevy"))
(setq Test (GJO_ReplaceWCString "*Truck*Dodge Ram*18*" "*Bike*Harley-Davidson*42*" "My truck is a dodge ram that gets 18 Miles Per Gallon"))
(setq Test (GJO_ReplaceWCString "My truck*dodge ram*18 MILES Per Gallon" "My Bike*Harley-Davidson*42mpg" "My Truck is a Dodge Ram that gets 18 Miles Per Gallon"))
(setq Test (GJO_ReplaceWCString "*Car**chevy*35*" "*Truck*Dodge Ram**18*" "My Car is a Chevy that gets 35 Miles Per Gallon"))
(setq Test (GJO_ReplaceWCString "*gets*" "*averages*" "My Car is a Chevy that gets 35mpg"))
(setq Test (GJO_ReplaceWCString "*35 Miles Per Gallon" "*35mpg" "My Car is a Chevy that gets 35 Miles Per Gallon"))
(setq Test (GJO_ReplaceWCString "my car*" "My Truck*" "My Car is a Chevy"))
(setq Test (GJO_ReplaceWCString "*car*" "*Truck*" "My Car is a chevy"))
(setq Test (GJO_ReplaceWCString "*-Tag" "Tag-*" "001-Tag"))
(setq Test (GJO_ReplaceWCString "*-TAG" "Mark: *" "001-Tag"))
(setq Test (GJO_ReplaceWCString "Tag-*" "*-Tag" "Tag-001"))
(setq Test (GJO_ReplaceWCString "mark: *" "* - Index" "Mark: 001"))

;test expected failure strings
(setq Test (GJO_ReplaceWCString "*car" "Truck*" "My Car is a Chevy"))
(setq Test (GJO_ReplaceWCString "car*" "*Truck" "My Car is a Chevy"))
(setq Test (GJO_ReplaceWCString "car*" "*Truck*" "My Car is a Chevy"))
(setq Test (GJO_ReplaceWCString "*car*" "Truck*" "My Car is a Chevy"))
(setq Test (GJO_ReplaceWCString "My Car*chevy" "*Truck*Dodge Ram" "My Car is a chevy"))
(setq Test (GJO_ReplaceWCString "*Car*chevy*35*" "*Truck*Dodge Ram*" "My Car is a Chevy that gets 35 Miles Per Gallon"))
(setq Test (GJO_ReplaceWCString "*Car*a chevy" "My Truck*Dodge Ram*" "My Car is a chevy"))

|;

(defun GJO_ReplaceWCString (TestStr NewStr CurrVal / Local_Set-WCVars ReturnStr
			    TestList TestWCCnt TestFirst TestLast NewList NewWCCnt NewFirst NewLast
			    Index TestVal Len Pos ReplaceVal)
  (princ (strcat "\n" CurrVal))
  ;a quick verification of wcmatch in case I use it somewhere else and forget to verify first
  (if (wcmatch (strcase CurrVal) (strcase TestStr))
    (progn
      ;define local function to generate variables due to repetition
      (defun Local_Set-WCVars (Str NameValList NameCntr NameFirst NameLast
			       / WCCnt pos ValList)
	(setq WCCnt 0)
	;check for a leading wildcard
	(if (set (read NameFirst) (wcmatch Str "['*]*"))
	  (progn (setq Str (substr Str 2)) (setq WCCnt (1+ WCCnt)))
	  )
	;check for a leading wildcard
	(if (set (read NameLast) (wcmatch Str "*['*]"))
	  (progn (setq Str (substr Str 1 (1- (strlen Str)))) (setq WCCnt (1+ WCCnt)))
	  )
	;check for mid string wildcards
	(while (setq pos (vl-string-position (ascii "*") Str))
	  (setq WCCnt (1+ WCCnt))
	  ;trap possibility of two wildcards together without text between "**"
	  (if (> (strlen (substr Str 1 pos)) 0)
	    (setq ValList (cons (substr Str 1 pos) ValList))
	    )
	  (setq Str (substr Str (+ 2 pos)))
	  )
	(set (read NameValList) (reverse (cons Str ValList)))
	(set (read NameCntr) WCCnt)
	);defun Local_Set-WCVars

      ;set our vars
      (Local_Set-WCVars (strcase TestStr) "TestList" "TestWCCnt" "TestFirst" "TestLast")
      (Local_Set-WCVars NewStr "NewList" "NewWCCnt" "NewFirst" "NewLast")

      ;get to work
      (cond
	;mismatched structures: return specific reason to command line
	((/= (length TestList) (length NewList))
	 (princ "\nReplacement Values Mismatch: differing number of string parts in TestVal and NewVal...")
	 );cond - mismatched structures
	;mismatched structures: return specific reason to command line
	((/= TestWCCnt NewWCCnt)
	 (princ "\nWildcard Mismatch: differing number of wildcards in TestVal and NewVal...")
	 );cond - mismatched structures
	;mismatched structures: return specific reason to command line
	((and (> TestWCCnt 1) (> NewWCCnt 1)
	      (or
		(and TestFirst (not NewFirst))
		(and NewFirst (not TestFirst))
		(and TestLast (not NewLast))
		(and NewLast (not TestLast))
		);or
	      );and
	 (princ "\nWildcard Mismatch: Too many wildcards for Restructure option...")
	 );cond - first/last mismatch
	;Restructure leading to trailing (keep leading value but place at end)
	((and TestFirst NewLast (= 1 TestWCCnt) (= 1 NewWCCnt))
	 (progn
	   (princ "\nRestructure: leading to trailing")
	   (setq TestVal (nth 0 TestList))
	   (setq Len (strlen TestVal))
	   (setq Pos (- (strlen CurrVal) Len))
	   (setq CurrVal (substr CurrVal 1 Pos))
	   (setq ReturnStr (strcat (nth 0 NewList) CurrVal))
	   )
	 );cond: (and TestLeading NewTrailing)
	;Restructure trailing to leading (keep trailing value but place at beginning)
	((and TestLast NewFirst (= 1 TestWCCnt) (= 1 NewWCCnt))
	 (progn
	   (princ "\nRestructure: trailing to leading")
	   (setq TestVal (nth 0 TestList))
	   (setq Len (strlen TestVal))
	   (setq CurrVal (substr CurrVal (+ 1 Len)))
	   (setq ReturnStr (strcat CurrVal (nth 0 NewList)))
	   )
	 );cond: (and TestTrailing NewLeading)
	;Process anything that has passed the failure tests and/or not already processed handled
	(T 
	 (progn
	   (princ "\nProcessing WildCard Replacement...")
	   (setq Index 0)
	   (repeat (length TestList)
	     (setq TestVal (nth Index TestList))
	     (setq Len (strlen TestVal))
	     (setq Pos (vl-string-search TestVal (strcase CurrVal)))
	     (setq ReplaceVal (substr CurrVal (1+ Pos) Len))
	     (setq CurrVal (vl-string-subst (nth Index NewList) ReplaceVal CurrVal Pos))
	     (setq Index (1+ Index))
	     );repeat
	   (setq ReturnStr CurrVal)
	   );progn
	 );cond - passed the failovers
	);cond stmt
      );progn wcmatch true
    (princ "\nCurrVal and TestVal are not a WC Match...")
    );if wcmatch
  
  ;ensure a return value
  (if ReturnStr
    ReturnStr
    CurrVal
    );if returnstr
  );GJO_ReplaceWCString
;-------------------------------------------------

 

 

The code block includes a bunch of commented lines at the beginning that I used to test it with...

Man, I need a job, I'm obviously getting bored with nothing else to do to generate such a monster from something that could have been so simple *ReallyBigGrin*

 

-Gary

Gary J. Orr
(Your Friendly Neighborhood) CADD/BIM/VDC Applications Manager
http://www.linkedin.com/in/garyorr

aka (current and past user names):
Gary_J_Orr (GOMO Stuff 2008-Present); OrrG (Forum Studio 2005-2008); Gary J. Orr (LHB Inc 2002-2005); Orr, Gary J. (Gossen Livingston 1997-2002)
Message 32 of 32
EC-CAD
in reply to: Bkndsdl

Bkndsdl,

You are in luck. Here's a Freebee from bobscadshop.com

The T_ means it needs to be 'tuned' / edited before use. Set the Blockname, TagName, and Value into the List.

Enjoy

 

Gees, can't attach the Lisp. What's with that ?

Bob

 

 

Tags (1)

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk Design & Make Report

”Boost