Jump to content

symbols in geometrical boundary count via tag


Recommended Posts

I'm not sure if this is possible in Vectorworks yet, but there are times when I need to count a number of objects/symbols inside the geometrical boundary of another object such as a rectangle or square. My question is - Is something like this possible to do ? Count the number of fixtures inside a geometrical figure and post it into a 'tag' and / or worksheet?

 

 

 

image.png

Edited by Samuel Derenboim
Link to comment

You need the LOC Criteria.

 

Draw a polygon and give it a Name at the bottom of the OIP. You can then use a criteria of

 

LOC is Name to return only the items that are contained within the area.  It can be a rectangle, polygon, circle, arc. Pretty much any closed 2D object.

 

It can be combined with other criteria to limit what you are counting even further:

 

LOC is Name

TYPE is SYMBOL

etc.

 

Ask if you need more details.

Link to comment

LOC is a criteria and can be used in Worksheets and scripts.

 

It does not look like LOC or COUNT can be implemented in Tags. 

 

You should write it up as a request in the Wish List Forum.

 

You could probably write a script that would count the objects in the LOC and store that to a Record.Field and then use the tag to display that data. But you would have to remember to rerun the script when the counts changed. 😞

 

 

Link to comment

Here is a starting point. Edit this to use your record and field names.  As set now it expects a Record named Counts with fields of Rect and Circle of type Number.

 

Also remember if the Center of a regular object is inside the LOC object it is counted. Outside and it is not counted, so you need to be careful about where your LOCs fall

 

Fairly well commented, but ask if you need help.  Without the comments, error checking and Var definitions, the code is only about 12 lines.

 

Procedure FillCountsRecord;

{January 24, 2020}
{©2020 Patrick Stanford pat@coviana.com}
{Licensed under the GNU Lesser General Public License}

{Example script showing how to attach a record containing the Count of objects meeting a set of}
{criteria inside a LOC (location) object so that the data can be accessed in a Data Tag.}
{It takes the first selected object on the active layer and if it has a name it attempts}
{to count the object meeting the criteria defined in CriteriaString AND are positioned in }
{the LOC named by the selected object}
{Edit the RecName, FieldName, and CriteriaString in the script for what you need}
{Do Not inlcude a LOC criteria in CriteriaString. It is autmatically added by the script.}
{This sample shows how you can duplicate the code to save the count of multiple criteria}
{into multiple fields with a single execution of the script.}
{If you only need a single count your can delete the second block.}
{The second block also shows a more complicated criteria.}
{I recommend using the Criteria Builder in the Custom Selection tool using the}
{Save as Vectorscript option to build and get a copyable version of the criteria to paste}
{into the CriteriaString assignment. The criteria number be between single quotes.}

{No warranty expressed or implied. Use at your own risk.}
{There be dragons. Backup, Backup, Backup, then Backup again.}
{Test on an inconspicuous area before full application.}
{Do not use if you are allergic to poorly protected code.}
{Very lightly tested. Test suitability for your use.}


Var	H1				:Handle;
	LocName			:String;
	RecName			:String;
	FieldName		:String;
	CriteriaString	:String;
	S1				:String;
	TheCount		:LongInt;
	
	Procedure Execute(H2:Handle);		{This is the procedure called by the ForEachObject command}
		Begin
			TheCount:=TheCount+1;		{Count the object}
			SetRField(H1,RecName,FieldName,Concat(TheCount)); {Store the count in the record}
		End;
		
Begin

	H1:=FSActLayer;
	LocName:=GetName(H1);
	
	If ((H1<>Nil) & (LocName<>'')) then
		Begin
			
		RecName:='Counts';				{Enter the Record Name you want to store count in}
		SetRecord(H1, RecName);
	
		{This block count the objects that meet the Criteria in CriteriaString. Use the criteria Builder to}
		{specify the objects to count in CriteriaString. Don't specify the LOC criteria as that is determined}
		{by the object selected when the script is run.}
	
		TheCount:=0;					{This must start at zero in each block for proper count}
		FieldName:='Rect';  			{Enter the Field Name you want to store the count in}

		CriteriaString:='((T=RECT))';   {Us the Criteria builder to generate the criteria string you want and store here}
										{Do not include the LOC criteria as that is defined by the named object}
										{selected when the script is run}

		S1:=Concat('((LOC=', LocName, ') & ', CriteriaString, ')');	 {Do not change this line, only change Criteria String}
		ForEachObject(Execute,S1);
		
		{************ End Block *************}
	
		{You can fill more than one field by adding a second block and changing the Field Name and Criteria}
	
		TheCount:=0;					{This must start at zero in each block for proper count}
		FieldName:='Circle';  			{Enter the Field Name you want to store the count in}

		CriteriaString:='((INSYMBOL & INVIEWPORT & (ST=CIRCLE) & (PF=1237)))';   {Us the Criteria builder }
										{to generate the criteria string you want and store here between single quotes}
										{Do not include the LOC criteria as that is defined by the named object}
										{selected when the script is run}

		S1:=Concat('((LOC=', LocName, ') & ', CriteriaString, ')');	 {Do not change this line, only change Criteria String}
		ForEachObject(Execute,S1);
	
		{************ End Block *************}
		End
		Else AlrtDialog('Something went wrong. Either no object was selected or the LOC object was not named in the OIP.');
		
		SetDSelect(H1);				{Forces Refresh of OIP}
		SetSelect(H1);				{Resets to original selection state}
End;

Run(FillCountsRecord);

 

Link to comment

Thanks Samuel, I am nearly always up to help anyone who wants to learn to script. I specifically tried to write this one as a tutorial so you and others can see some of what is possible.

 

The biggest trick that I failed to point out is building the entire Criteria string into S1 before using it. Most functions don't handle criteria that only partially includes a variable very well, but if you build the entire sting into a single variable they work just fine.

 

For many of these types of functions ForEachObject is the best way to go. That way you can write a very simple Execute function to only operate on a single object at a time. You do need to be careful about getting your Global and Local variables declared and used properly. Since H2 is declared as part of Execute, it will not have a valid value after Execute returns. Since TheCount is declared globally, it is persistent through each run of Execute.

 

And be careful of non-serif fonts. 😉. I used to use a procedure name of DoIt. Until someone read it with a SansSerif font and thought I was insulting them.

 

image.thumb.png.14d19828e80c8c8beec34434d491912b.png

  • Laugh 1
Link to comment

regarding LOC='' in worksheets - the function only allows me to select one zone by name in the criteria selection, not all of them. Is there a criteria that can include all objects that have a record, and then print out all objects within them?

 

i've tried multiple variable condition (if) statements without success. i've included a sample file with lighting zones with object names as Zone 1, 2, & 3, but the count reports the incorrect number of fixtures in each after moving them around.

count by zone test1.vwx

Edited by Samuel Derenboim
Link to comment

Nope, Loc is a one way criteria. You might be able to do it with columns

 

Set the criteria to be just Lights.

 

Enter a formula in Column C of =((LOC='Zone 1'))

 

Enter a formula in Column D of =IF(((LOC='Zone 2')), 'Zone 2', 'Not Zone 2') 

 

Enter a formula in Column E of =Count

 

These show you can use LOC to get the LOC an item is in, but you need to use the IF to report the name.

 

If you are only going to have 3 zones you could reasonably use a nested If to get a single column that would display the Zone name. If you get beyond 5 or 10 then nested IFs will be a nightmare.

 

If you Summarize first on the Instrument type in Column B. Then Summarize by Column D. You should get a count for each instrument type in Zone 2 and Not in Zone 2.

 

Try it and ask again.

Link to comment

I can see what you mean. With lighting you can have 10-20 zones easy (if i were to use the criteria energy code for example) per story, which is why i wanted to use a nested value rather than sorting it by different columns. Same goes for other criteria i wanted to list for 'building code' objects that would be located in separate tenant spaces. Those criteria can also exceed 20 objects per LOC....

 

the way i wanted to get around it was to create a zone that didn't have anything in it and say LOC<>'Zone 3' to include all objects in all other zones, but alas it still reports an incorrect number.

 

I'm guessing the script is the only other way to go? or to create a separate database table for each zone....which is also cumbersome

Edited by Samuel Derenboim
Link to comment

And to add to the complication, LOCs can be overlapping. And a smaller LOC inside the larger LOC may be counted as an object inside the LOC. The number of objects within the LOC is not an issue. The issue if the number of LOCs.

 

What about a separate database for each LOC? They can be in contiguous rows so you only need a single label row above them all. It would take manual editing of the criteria row, but you could specify a cell (or range of cells) that would contain the LOC names so you could change them without having to change the actual criteria. Or a script to set  the database formulas based on a list of location names?

 

Lots of ways to skin the cat.

 

=IF(((LOC<>'Zone 3')), 'Other Zone', 'Zone 3'). works for me in Column D.  With the double sort I get 7 for 1a and 3 for 1b in other zones and 2 for 1b in Zone 3. Your criteria is for both lights and Light labels, so you have more than the 12 lights that are being counted.

Link to comment

yes, creating several database tables is possible for each zone. I thought it would be easier putting this in the criteria of the database, however, it isolates zones from the light fixtures in the space. So yes, the only other choice is to create a separate database for every zone, and thats only for one story. if i wanted to include a worksheet with all stories, and they had a different number of zones, thats when the worksheet will get a bit cumbersome to read as some stories that have less zones would return a blank record (unless database tables would be specified for every zone, and every floor).

 

right now im working on a 5 story, 80 thousand square foot building, approximately 6-7 tenant spaces per story, and probably upwards of 5-6 lighting zones per tenant. it would be easier to match the name record between light fixtures and zones than to do it this way. or not use LOC altogether unfortunately.

 

file attached is my second attempt. I tried to list all zones in one database row, and reference them for the zones and light fixtures in another. It separates the rows and the zones in the database row in the 1st one.

count by zone test2.vwx

Edited by Samuel Derenboim
Link to comment

I will look more later, but the simplest explanation as to why you can't do what you want is that there is no link between the objects and the zone. The objects don't ever know that they are in the zone. And the zone does not know what objects are inside it unless you specifically run something using LOC.

 

Perhaps what you really want is a script that will go through every named object (or named in a certain class, or named with a certain record attached) and attach a record to the objects that match the LOC for each object. It should also remove the record for all objects first.

 

Then you have a directly (though fragile) link between the object and the zone and will have a lot more flexibility in what you want to do. You just have to remember to rerun the script when objects get moved into, out of, or between zones.

Link to comment

I wrote the nested script in a matter of minutes once i figured out that i have to create a reference name for the zone name in the conditional statement...

I'm thinking of just using an excel script to create a script for me that includes the maximum amount of zones the building house in one story. Turns out, it works on multiple stories !! I'm thinking about how i can do this in a simple manner and... will this crash VW?

 

attached file below.

 

Also, do you know if referencing the object name (=N) with a record using the datamanager would eliminate all these complications?

 

count by zone nested-working.vwx

Edited by Samuel Derenboim
Link to comment

I think i accidentally found a bug. i just noticed in the file attached above - the geometry  was duplicated from one floor to the next. However, the name of the boundary was not modified, yet it still calculates the correct number of objects on the 2nd story when moving the fixtures from one boundary to another

 

correct me if I'm wrong?

 

the downside to doing everything by LOC function , turns out that every boundary object has to be unique in the entire building, and then create a nested conditional script that computes all of them, unless of course, yes, certain operations would be done by class or operation.

The kicker is this, i often use id's that can be identical to different spaces that use the same id tag so long as it is subdivided by the =story condition.

so ID=1 between one and another story will be printed separately if I invoke the =story command that separates their conditions.

 

Here, you cannot do this. but it accidently worked when a boundary object is duplicated. don't know why. ☹️

 

Update:

Followup question - is it possible to simply list all the LOC zones and their names?

Edited by Samuel Derenboim
Link to comment

LOCs are generic. It does not care what story or layer it is on. Nor does it care what story or layer the other objects are on. If the physical locations are identical from layer to layer you can just specify the criteria to only look at that layer and reuses the same LOC for multiple layers.

 

Since any named object can be used as a LOC, no you can't report just LOC objects. You can report named objects. And classes objects, and objects with a record attached. So put all of your LOCs into a class or attache a record and you can then report on them.

 

LOC is a virtual construct. There is no intrinsic relationship between a LOC and anything. Any named object can be used as a LOC. It probably does not make any sense to use a line or a locus as a LOC, but I don't think it would error if you tried, it would just return nothing since there is no area.

 

On your bug, please double check. If you duplicated a named object and it did not clear the name, then that is a VERY BAD THING™. My guess is that the poly was duplicated but the name was cleared. But since the original and duplicate were in the same location in space the LOC criteria used the named original and happened to give the correct result.

Link to comment

Pat, that's exactly it. it is a duplicate object, that wasn't renamed, but the object name was not there. VW picked up that it was the same name and it didn't rename it, it left it blank instead. It however, was in the same exact location as another LOC object, hence why it picks up the information. but i'm guessing this might lead to some problems down the road? Should i submit a bug report?

 

Note - i duplicated the layer, and it didn't copy the name. If i copy and paste the object, then it auto renames

Edited by Samuel Derenboim
Link to comment

If you report a bug it will be returned as WAD (Working as Designed).  Named objects must have unique names in a VW file. When you duplicate a named object the name if cleared.

 

The formula you are using is still specifying the name of the first object. Since that object still exists and has the name specified, it is being used. 

 

Again, the LOC is not a thing. A LOC is a criteria that can be used to determine is an object is inside the boundaries of another object. There is NO link between either the contained objects or the boundary object and the LOC. None, zero, zip.

 

X + Y = Z.  If I tell you here is the + sign, what are X and Y, you can't do it.  If I tell you X is 2 , Y is 2 and Z is 4  and then ask you what function the + sign has you can easily tell me it is addition.

 

Similarly, If I give you a LOC, you can check is an object is in that LOC. But if I give you an object the only way to tell what LOC it is in is to test every LOC (named object) in the file. And remember that a given object can be in multiple LOCs at the same time.

 

If you want to do LOCs on different Layers they will have to have different names. And you will have to use different criteria in the database to account for the different names.

 

LOC is ephemeral.

  • Like 1
Link to comment

I think i understand. Sounds like LOC function would be easier to use using a marionette of scripting function rather than a worksheet callout. Seems like keeping track of the names of the boundaries can prove to be a hurdle.

 

I'm trying to use that script you made to study it a bit, but i get this error :

image.thumb.png.f13b38283fb221911ab9c76c5b86482a.png

 

Would the right parenthesis go somewhere in here?

 

S1:=Concat('((LOC=', LocName, ') & ', CriteriaString, '))');

Link to comment

Wow, this is beautiful. Works like a charm.

 

In order for this script to have integrated information, it would be easier to simply copy the zone information in an attached record from the boundary onto all symbols in that zone rather than counting the objects. That way, i know if the zone matches between the objects and the zone itself, they could be aligned in the worksheet easily and specify any required specific information about that zone (like wattage, lumens, etc..)

 

there was a script you wrote once regarding counting the number of people in a space object  by using the space object area and dividing it by a separate record. maybe that can give me a hint as to how to take information from one record and copy it to all the objects (or symbols) with the LOC of the boundary.

 

Edited by Samuel Derenboim
Link to comment

First Attempt.

 

Procedure FillCountsRecord;

Var    H1                :Handle;
    H2                :Handle;
    LocName            :String;
    LocName2        :String;
    RecName            :String;
    RecName2        :String;
    FieldName        :String;
    FieldName2        :String;
    CriteriaString    :String;
    CriteriaString2    :String;
    S1                :String;
    S2                :String;
    TheCount        :LongInt;
    
    Procedure Execute(H3:Handle);        {This is the procedure called by the ForEachObject command}
        Begin
            SetRField(H2,RecName,FieldName));
            SetRField(H1,RecName2,FieldName)); {Store the count in the record}
        End;
        
        
Begin

    H1:=FSActLayer;
    LocName:=GetName(H1);
    
    If ((H1<>Nil) & (H2<>Nil) & (LocName<>'') & (LocName2<>'')) then
        Begin
            
        RecName:='!Ltlegend';                {Enter the Record Name you want to store}
        SetRecord(H1, RecName);
        FieldName:='Zone';
        
        RecName2:='!Lights';                {Enter the Record2 Name you want write to}
        SetRecord(H2, RecName);
        FieldName:='LZone';
                            
        CriteriaString1:='(((R IN ['!LtLegend']) & (ALL)))';   {Us the Criteria builder to generate the criteria string you want and store here}
        
        CriteriaString2:='(((R IN ['!Lights']) & (T=SYMBOL)))';   {Do not include the LOC criteria as that is defined by the named object}
                                        {selected when the script is run}

        S1:=Concat('((LOC=', LocName, ') & ', CriteriaString, ')');     {Do not change this line, only change Criteria String}
        ForEachObject(Execute,S1);
        S2:=Concat('((LOC=', LocName, ') & ', CriteriaString, ')');
        ForEachObject(Execute,S2);
        
        End
        Else AlrtDialog('Something went wrong. Either no object was selected or the LOC object was not named in the OIP.');
        
        SetDSelect(H1);                {Forces Refresh of OIP}
        SetSelect(H1);        
        SetDSelect(H2);                {Forces Refresh of OIP}
        SetSelect(H2);        {Resets to original selection state}
End;

Run(FillCountsRecord);

Edited by Samuel Derenboim
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...