Jump to content

MullinRJ

Member
  • Posts

    2,004
  • Joined

  • Last visited

Posts posted by MullinRJ

  1. Hi Dave,

       Essentially vectors are a Structure of 3 numbers, or an Array of 3 numbers, under one variable name. There's lots of material online, but if you give me a call I can get you started quite quickly, especially in ways to use them in VW. PM me and we can setup a Skype, Zoom, or "other" conference call at your convenience. 

     

    Seriously, call me,

    Raymond

  2. Hi @Pat Stanford, @DCarpenter,

       Here's an example using vectors, as requested, and a little more,  😉,   though HScale2D() may be quicker for lines. Where this method may be more useful is when you are working with a Polygon and want to move a vertex without changing anything else. As with anything, "Actual mileage may vary."

     

     

       Scale a line using a vector approach:

    PROCEDURE xxx;
    { Test script to set new line length using vectors. }
    { 22 April 2024 - Raymond Mullin }
    VAR
    	H :Handle;
    	NewLength :Real;
    
    
    	procedure SetLineLength(H :Handle; NewLength :Real);
    	{ Sets length of line H to new value NewLength. Point P1 is the anchor point.}
    	Var
    		P1, P2 :Vector;
    	Begin
    		GetSegPt1(H, P1.x, P1.y);
    		GetSegPt2(H, P2.x, P2.y);
    		P2 := P1 + UnitVec(P2-P1) * NewLength;
    		SetSegPt2(H, P2.x, P2.y);	
    	End;		{ SetLineLength }
    	
    	
    BEGIN
    	H := FSActLayer;
    	NewLength := RealDialog('How long?', concat(hLength(H)));
    	if not DidCancel then 
    		SetLineLength(H, NewLength);	{ changes line length to new value }
    	SysBeep;	{ make noise when done }
    END;
    Run(xxx);

     

       If you want to scale the line in the other direction, swap the vectors P1 & P2 as shown in the following line.   

            P1 := P2 + UnitVec(P1-P2) * NewLength;

     

       Here's an explanation of the expression:  P2 := P1 + UnitVec(P2-P1) * NewLength;

     

    1) Where P1 and P2 are both vectors, (P2-P1) is the vector that points from point P1 to point P2. Likewise, (P1-P2) is the opposite vector that points from point P2 to point P1.

    2) UnitVec() is a function that scales a vector to have a length of "1".

    3) A constant times a vector scales the vector, so scaling a unit vector by the new length results in a vector of the desired length.

           Essentially, the length is changed, but the direction remains the same.

    4) Since vectors are positionless, adding a vector to a known point will return the point that is the correct distance and the correct direction away from the known point.

     

    PS - If you are wondering why I used Vector instead of Point as the variable's data type, The UnitVec() function does not accept Point type data. Essentially I am only working with 2D data, and Point and Vector are similar, but Vector data types work in all of the Vectorworks expressions, so I always use the Vector data type. In this case the z-component of the vector is always "0" and therefor does not contribute to any of the calculations. Using Vectors for 2D calculations saves having to do data conversions in the middle of a script between Points and Vectors. Feel free to try other ways as mine is not the only one.

     

    Raymond

     

     

     

     

    • Like 2
  3. 2 hours ago, FMA said:

    I hope that didn't happen because of overloading your machine with VS Functions

     

    😁 Good morning, @FMA. No, if that were the case it would have died from self inflicted abuse on my part years ago. 

     

       As to your more frequent crashing, it can be caused by developing scripts. Restarting VW should clear most of your issues, as long as your code is eventually running smoothly, bu it would never hurt to restart your computer once and a while in the middle of doing a lot of development work. My experience is that excessive crashing goes away once I get my scripts running smoothly. YES – BACKUP OFTEN, especially when you are scripting. I will say one thing, if you lose a script because you crashed and it's not backed up, it's amazing how fast you can recreate it from memory versus create it from scratch. Been there, done that.

     

    Raymond

  4. @FMA,

       Like Pat, I, too, have often wondered if a BBox check would speed up processing for doing massive AddSurface operations, and like Pat, I never pursued it. After I posted last night I started to look at doing just that, but sleep happened before success and sleep won out.

     

       Today, I can confirm that testing bounding boxes for potential overlap is MUCH faster than using IntersectSurface() for the same evaluation. I ran a collection of 968 objects in a few seconds as compared to "hours?" as I presumed previously. And, I would post an example now, but my data hard drive died this afternoon. So until I get a new hard drive and restore the contents of my old one I'm going dark for a while. If you get a solution before then, post back. If not, maybe I can help you later.

     

    Raymond

    • Like 1
  5. Hello @FMA,
       It looks like you pretty much nailed it. 

     

       Two things – the first is just kibitzing. You can express your IF statements more concisely by using:
    IF ( temphandle <> NIL ) THEN BEGIN


        instead of:

    IF NOT ( temphandle = NIL ) THEN BEGIN

     

       OR NOT. Your code is syntactically correct and if it reads better to you, then leave it as is.

     

     

       The second – you should add a NIL test for your object in the inner loop. Change:
    IF NOT ( shadeOBJ[iOBJ] = shadeOBJ[iOBJn] ) THEN BEGIN
     

       to:
    IF NOT ( shadeOBJ[iOBJn] = NIL ) & NOT ( shadeOBJ[iOBJ] = shadeOBJ[iOBJn] ) THEN BEGIN

     

       The "&" symbol means "AND", so both clauses have to be TRUE to proceed. This way, you are only processing valid objects from your array.

     

       The shorter way to write it is:

    IF ( shadeOBJ[iOBJn] <> NIL ) & ( shadeOBJ[iOBJ] <> shadeOBJ[iOBJn] ) THEN BEGIN

     


       I don't know how many objects you typically want to process, but I timed a sample drawing with 1000 objects. It seemed to hang, so I restarted and selected 155 objects. The script took about 20 seconds. With 320 objects selected it took about 120 seconds. With that in mind you probably want to limit the number of objects to process to <300, and run your script multiple times on smaller selections. Actual times may vary.


    Nicely done,
    Raymond

     

  6. For my own benefit, I converted the first example above to Python. This may not be the most elegant function, but it works. If anyone wants to improve on it, please post an example. Thanks.

     

    def FSymFolder():
    	# Return a handle to the First Symbol Folder, if one exists, otherwise return Nil.
    	# 20 Apr 2024 - Raymond Mullin
    
    	SymFolderType = 92
    	Nil = vs.Handle(0)
    	SymHnd = vs.FSymDef()	# Symbol or Folder
    	if (SymHnd != Nil):
    		while (vs.GetTypeN(SymHnd) != SymFolderType):
    			SymHnd = vs.NextObj(SymHnd)
    	return SymHnd

     

    Raymond

    • Like 1
  7. 5 hours ago, FMA said:

    ... that sounds like it would take even longer than creating the intersection object and deleting it again. It would be great if Vectorworks could add a command that just checks for the intersection.

     

    Whether you do it, or the engineers in the Mothership do it, it could potentially take a very long time to execute. For objects that have multiple intersection points, returning the answer(s) could also be quite unwieldy. Can you describe more specifically what you are trying to achieve? Some problems are much easier to solve than others. Trying to solve all possible combinations of all possible intersections is usually reason for finding a workaround or another approach.

     

    All the best,

    Raymond

  8. Hi @Letti R,

       Of course your folders are nested. Why would life be easy?

     

       The way to get the first folder is to look at everything in the Symbol Library and skip the symbols. Here are two Pascal routines that will return a handle to the First SymFolder. Hopefully you can convert them to Python easily. If not, write back and I can help.

     

    	function FSymFolder :Handle;
    	{ Get a handle to the First Symbol Folder in the Symbol Library. }
    	{ 19 Apr 2024 - Raymond Mullin }
    	Const
    		SymFolderType = 92;
    	Var
    		SymHnd :Handle;
    	Begin
    		SymHnd := FSymDef;		{ Symbol or Folder }
    		if (SymHnd <> nil) then
    			while (GetTypeN(SymHnd) <> SymFolderType) do
    				SymHnd := NextObj(SymHnd);
    		FSymFolder := SymHnd;
    	End;		{ FSymFolder }

     

    The same routine using ForEachObjectInList().

    	function FSymFolder :Handle;
    	{ Return a handle to the First Symbol Folder in the Symbol Library if one exists, otherwise return NIL. }
    	{ 19 Apr 2024 - Raymond Mullin }
    	Const
    		SymFolderType = 92;
    	Var
    		FldrHnd :Handle;
    		
    		function isFolder(H :Handle) :Boolean;
    		Begin
    			if (GetTypeN(H) = SymFolderType) then
    				FldrHnd := H;
    			isFolder := FldrHnd <> nil;		{ return True to stop }
    		End;		{ isFolder }
    		
    	Begin	{ FSymFolder }
    		FldrHnd := nil;		{ Folder handle }
    		ForEachObjectInList(isFolder, 0, 0, FSymDef);		{ All objects, Shallow }
    		FSymFolder := FldrHnd;
    	End;		{ FSymFolder }

     

       Finding the Next Symbol Folder is similar to finding the first, but you start looking using a handle to the next object after the first Symbol Folder.

     

    Raymond

     

    • Like 1
  9. Hello @Letti R,

       Yes, it is definitely a bug in VW 2024. I just filed a bug report for this, VB-203576, and cited this Forum post. If/when I hear anything I'll post back.


       For the moment you will need to shuffle resources between the old folders and the newly created nested folders. However, you don't need to shuffle things around initially as much as you describe above. Instead, create your new nested folder structure with dummy names, then move everything from your old folders straight into the new folders with SetParent(), which does work with moving resources between folders. When everything is moved, delete the two old folders and rename the dummy folders with the old folders' names.


       There is a caveat, there is always a caveat, if either of old folders contain nested folders you will have to create the finished folder hierarchy before you start shuffling anything, then very carefully move everything from its starting position to its final position, which can get tricky. I hope your two starting folders are flat (only contain resources.)

     

    Best wishes,

    Raymond

    • Like 1
  10. Hello @DCarpenter,

       Immediately after EndXtrd,  get the handle to your new object with LNewObj.

     

       Create two new variables: "Hxtrd" to store the handle to the Extrude; and "Hdup" to store the handle to the extrude duplicate.

     

    PROCEDURE xxx;
    VAR
    	Hxtrd, Hdup :Handle;
    BEGIN
    	BeginXtrd(5",  10");
    		Rect(0, 0, 24, 12);
    	EndXtrd;
    	Hxtrd := LNewObj;			{ handle to the extrude }
    
    	Hdup := hDuplicate(Hxtrd, 0, 0);	{ handle to a duplicate extrude }
    	Move3DObj(Hxtrd, 40, 20, 30);		{ move it anywhere you want }
    	SysBeep;	{ make noise when done }
    END;
    Run(xxx);

     

    Raymond

  11. Hello @FMA,

       I don't believe you can write to your open READ file. You will need to close it first, and reopen it with Rewrite(), or Append(). Then you can write to it. Rewrite() will write over any existing data, and Append() will add to the end of existing data. Don't forget to close it again when you are finished writing.

     

    HTH,

    Raymond

    • Like 1
  12. Hello @Arnold_Hoekstra,

       I tried opening your file in several versions of VW and MiniCAD (VW12, VW8, MC7, MC6, MC5, & MC4) to no avail. It appears the file is damaged. Do you have another copy?

     

       When I downloaded the file, it had no File Type or Creator codes assigned. I added MC+4 / CDP3 for MiniCAD 4 &5 files, which did not work. Then I tried MC6d / CDP3 for MiniCAD 6 files. This also did not work. I got one of two messages, "This file is damaged!", or "Resource Manager error!". If this is your only copy, you might want to send it to the VW engineers to see if they can resurrect it.

     

    Good luck,

    Raymond

    • Like 1
  13. Hi @Dave Donley,

       I just took a peek at the file you uploaded today. It opens in VW 2024, but there is nothing showing. The Resource Manager has 10 LineStyles, and two Textures (Water Bright, and Water Dark.) Also, there is one Design Layer, visible, and two Classes (None and Dimension), both visible. Would you please verify on you end? 

     

    Thank you,

    Raymond

  14. Hello @ge25yak,

       There may be other ways to set the wall height, but I found this works, so I stopped looking for other ways. The commands below always use mm, so you have to convert your document units to mm before setting the wall height values, hence the use of the scale factor variable "SF". This overly simple script will change the selected wall height to 5 (document units). I assumed meters in this case. Using your document units change the "newWallHt" value to your liking.

     

    import vs
    # Sample script to set the height (top elevation) of a selected wall.
    
    newWallHt = 5		# document units
    
    WallStartHeightTop	= 604
    WallEndHeightTop	= 606
    
    WallStartHeightBot	= 605
    WallEndHeightBot	= 607
    
    SF = vs.GetPrefReal(152) / 25.4		# units per mm
    H = vs.FSActLayer()
    
    vs.SetObjectVariableReal(H, WallStartHeightTop, newWallHt/SF)	# starting height in mm
    vs.SetObjectVariableReal(H, WallEndHeightTop, newWallHt/SF)	# ending height in mm
    vs.ResetObject(H)
    
    vs.SysBeep()

     

    HTH,

    Raymond

    • Like 1
  15. Hi @Andreas,

       Yes, that is the call to insert a symbol/pio into a wall.

     

       Using VW to help reverse engineer the code, I placed a Window in Wall and exported the VW file to a script file that defines the geometry. Use menu File>Export>Export Script...

     

       This doesn't always work for complicated files, but for simple things it's a great way to see what calls are used and how they are arranged.

     

        Here's a small script I built by copying the Wall and Window from the text dump (Export Script...). I found the relevant calls in the middle of the file and copied them to a new file, then rearranged a few calls and renamed the temp handles to make it easier to follow. Comments were also added for readability. To see what this represents, copy and paste this code into a script resource and run it. Then change some number, rerun, and notice the effects of the changes. It's a great learning tool for new commands. 

     

    PROCEDURE xxx;
    { Sample script to insert a Window into a Wall. The units are in mm. }
    VAR
    	WallHandle, WindowHandle :Handle;
    	boolResult :Boolean;
    BEGIN
    	{ set pen and fill attributes }
    	PenSize(1);
    	PenPatN(2);
    	FillPat(1);
    	PenFore(0, 0, 0);
    	PenBack(65535, 65535, 65535);
    	FillFore(0, 0, 0);
    	FillBack(65535, 65535, 65535);
    
    	NameClass('None');	{ set class to use }
    	Wall(-5400, 2000, 5000, 2000);				{ create a wall }
    	SetObjectVariableReal(LNewObj, 604, 3048);		{ Wall Start Height Top }
    	SetObjectVariableReal(LNewObj, 605, 0);			{ Wall Start Height Bottom }
    	SetObjectVariableReal(LNewObj, 606, 3048);		{ Wall End Height Top }
    	SetObjectVariableReal(LNewObj, 607, 0);			{ Wall End Height Bottom }
    	SetObjectVariableReal(LNewObj, 621, 3048);		{ Wall Overall Height Top }
    	SetObjectVariableReal(LNewObj, 622, 0);			{ Wall Overall Height Bottom }
    	SetObjectVariableReal(LNewObj, 618, 152.4);		{ Set Wall Width }
    	DoubLines(152.4);
    	ClearCavities;						{ start with an empty wall }
    	WallHandle := LNewObj;					{ save handle to the Wall }
    	WallCap(FALSE, TRUE, FALSE, 0, 0);
    	AddSymToWallEdge(LNewObj,  5300, 0, FALSE, FALSE, 'Window', 0);	{ insert object into wall }
    	boolResult := SetObjWallInsLocOff(LNewObj,  WallHandle,  0);	{ shifts object to inside/outside of wall }
    	WallCap(TRUE, TRUE, FALSE, 0, 0);
    	ResetObject(WallHandle);				{ reset the Wall object }
    	WindowHandle := LNewObj;				{ save handle to the Window - not used, but could be used later }
    
    	SysBeep;		{ make noise when done }
    END;
    Run(xxx);

     

     

    Here's the same thing in Python. Again, the code was gathered from text dump from the program using Export Script.

     

    import vs
    # Sample Python script to insert a Window into a Wall. The units are in mm.
    
    # set pen and fill attributes
    vs.PenSize(1)
    vs.PenPatN(2)
    vs.FillPat(1)
    vs.PenFore((0, 0, 0))
    vs.PenBack((65535, 65535, 65535))
    vs.FillFore((0, 0, 0))
    vs.FillBack((65535, 65535, 65535))
    
    vs.NameClass('None')					# set class to use
    vs.Wall(-5400, 2000, 5000, 2000)			# create a wall
    vs.SetObjectVariableReal(vs.LNewObj(), 604, 3048)	# WallStartHeightTop
    vs.SetObjectVariableReal(vs.LNewObj(), 605, 0)		# WallStartHeightBottom
    vs.SetObjectVariableReal(vs.LNewObj(), 606, 3048)	# WallEndHeightTop
    vs.SetObjectVariableReal(vs.LNewObj(), 607, 0)		# WallEndHeightBottom
    vs.SetObjectVariableReal(vs.LNewObj(), 621, 3048)	# WallOverallHeightTop
    vs.SetObjectVariableReal(vs.LNewObj(), 622, 0)		# WallOverallHeightBottom
    vs.SetObjectVariableReal(vs.LNewObj(), 618, 152.4)	# SetWallWidth
    
    vs.DoubLines(152.4)
    vs.ClearCavities()			# start with an empty Wall
    WallHandle = vs.LNewObj()		# save handle to the Wall
    vs.WallCap(False, True, False, 0, 0)
    vs.AddSymToWallEdge(vs.LNewObj(), 5300, 0, False, False, 'Window', 0)	# insert object into wall
    boolResult = vs.SetObjWallInsLocOff(vs.LNewObj(),  WallHandle,  0)	# shifts object to inside/outside of wall
    vs.WallCap(True, True, False, 0, 0)
    vs.ResetObject(WallHandle)		# reset the Wall object
    WindowHandle = vs.LNewObj()		#  save handle to the Window - not used, but could be used later 
    
    vs.SysBeep()				# make noise when done

     

       One caveat to keep in mind, LNewObj is a function that returns a handle to the last object created (usually — but Groups don't follow this rule when they get created.) Think of LNewObj as a temporary handle. If you want to save a handle for use later in the script, assign LNewObj to a program variable and keep on drawing. Notice "WallHandle" is used this way.

     

    HTH,

    Raymond

    • Like 1
  16. Hi @mgvwx,

       I have not used the GIS calls in any of my scripts yet, and I don't know how much you know about scripting, but these are the commands in the GIS section of the Script Reference.

     

    BindLayerToArcGISFS		GISDimStringToMM
    BindLayerToWFSFS		IsGeoreferenced
    EditGeorefWithUI		LegacyShapefileExp
    GeogCoordToVW			LegacyShapefileImp
    GeogCoordToVWN			RemoveGeoref
    GetAngleToNorth			SetDocGeoRefByUsrOrg
    GetGISOrigin			SetGISLayer
    GetGISOriginN			SetProjectElevation
    GetProjectElevation		UpdateFeatureLayer
    GetProjectionLocName		UpdateLayerFromFS
    GetProjectionProj4		VWCoordToGeog
    GetProjectionWKT		VWCoordToGeogN

     

    You can view them in the Developer WIKI online https://developer.vectorworks.net/index.php/VS:Function_Reference,

    or in the HTML Script Reference located in you application folder .../Vectorworks 2024/VWHelp/Script Reference/ScriptFunctionReference.html

     

    Do you have a sample file you can post? Smaller is better. It might help people try out some of these calls against actual data and give you some feedback.

     

    Raymond

  17. 11 hours ago, Pat Stanford said:

    So the first object drawn will be at the bottom of the stacking order and will be returned by LSActLayer.  The last object drawn will be at the top of the stacking order and will be returned by FSActLayer.

     Hi @Pat Stanford,

       It is the other way around. The first object drawn - at the bottom of the stack, yes - is returned by FSActLayer (or FActLayer, when selection is ignored.) The object at the top of the stack is returned by LSActLayer (or LActLayer, when selection is ignored.)   Everything else is correct.

     

    Good night,

    Raymond 😉

    • Like 3
  18. In the same vane as @Pat Stanford's example, but using abs(a-b <Tol) to combine the (a < b+Tol) and (a > b-Tol) into one expression, you will need three comparisons.

     

     

     

    This compares Rotation to Ang+0, Ang+180, and Ang-180, and tests them against a tolerance. 

    Tol = 0.001
    if (abs(ObjAng - Rotation) < Tol) or (abs(ObjAng - Rotation + 180) < Tol) or (abs(ObjAng - Rotation - 180) < Tol):

     

    I think this covers all options. Change the tolerance to change the sensitivity of acceptable values. If you want to include values at the tolerance limit, then use "<=" in place of "<".

     

    HTH,

    Raymond

    • Like 1
  19. Hi @Pat Stanford,

       OOPS! Yes, Pref 21, not 12. Thank you. I corrected it above.

     

       It may be a bug, but as far as I can remember it has always worked this way. Annoying, it is! If you can get it scheduled to be "fixed", I and a lot of others will rejoice.

     

    Raymond

     

     

  20. You can use $INCLUDE statements in Palette Scripts, and in Plug-in Scripts. You can also set VW to recompile on each execution (VW Pref 21 – Developer Mode, or VW Pref 407 – ImmediateVSMode), but it is only "automatic" for scripts run inside Plug-ins. If you want to recompile a script that has an $INCLUDE statement in a Palette Script, then after you make changes to the disk file you will have to open the Script Editor for the script in the palette (Opt/Alt–Double Click) and then close the Editor with the OK button. No changes to the file are necessary. If you use the ESC key or click the Cancel button, nothing happens and the script will not be recompiled. 

     

    If you have your $INCLUDE statement inside a Plug-in and set VW to recompile for each execution, VW will automatically recompile before each execution, so you can make edits in your disk file and rerun the Plug-in. Your disk changes will be recompiled.

     

    Bottom Line: The Automatic Recompile option for Palette Scripts is meaningless. It only applies to Plug-in scripts.

     

    HTH,

    Raymond

×
×
  • Create New...