Adding DC funtions - count_entities("")

Hello!

I’m currently working on a plugin to add functions to Dynamic Components (DCs) in order to simplify the process of feasibility studies. While I already have a DC that helps me and my students in our classes, we’ve realized that more functionalities could greatly improve our use and teaching processes. Currently, in order to calculate certain aspects of our studies, we need to create reports and manually input the data into software tables. However, if we can access more information through the DC functions, we could display most, if not all, of the data instantly in the component option window, thereby streamlining the design process and validation in our classes and freeing up more time for actual teaching.

One of our initial ideas is to add a function that can count the number of entities inside the DC by name. For instance, if we want to count the number of “floor 01” components, we would simply add the function “=count_entities(“type_floor_1”)”. This would enable us to relate the data with urban parameters and validate or invalidate the study using the native DC logic functions. While we have more needs, we plan to take one step at a time.

To accomplish this, I’ve begun writing the necessary Ruby code. However, I’m encountering difficulties in getting it to work. The code is as follows:

require 'sketchup'
require 'su_dynamiccomponents.rb'

if defined?($dc_observers)
  class DCFunctionsV1
    protected
    # DC Function Usage: =count_entities("ComponentDefinitionName")
    # or =count_entities() to count all instances
    def count_entities(*args)
      if args.empty?
        count = @source_entity.definition.instances.count
      else
        component_name = args.first
        definition = Sketchup.active_model.definitions.find { |d| d.name == component_name }
        count = definition ? definition.count_used_instances { |instance| instance.model == Sketchup.active_model } : 0
      end
      return count
    end
  end
end

Unfortunately, whenever I run the function, it always returns zero. I’ve tried various other versions of the code, including attempting to count by IfcClass name (IfcBuildingStorey), but the result is always the same. Can anyone help me with this issue?

i’m not sure these are scoped for inside a given object or not. i think definition instances are global across the project.

The count_instances method is used to count the number of unique component instances in a model using this component definition.

The count_used_instances method is used to count the total number of component instances in a model using this component definition.

1 Like

Thanks @glennmstanton! That’s correct. The definition.instances method returns all instances of a definition that are present in the entire model, not just the active model. So if we want to count the number of instances of a definition that are present in the active model, we will need to iterate through all the instances and count only those that belong to the active model. Can we use the model method on each instance to get the model it belongs to and compare it to the active model? Something like that:

def count(*args)
  if args.empty?
    count = @source_entity.definition.count_used_instances { |instance| instance.model == Sketchup.active_model }
  else
    component_name = args.first
    definition = Sketchup.active_model.definitions.find { |d| d.name == component_name }
    count = definition ? definition.count_used_instances { |instance| instance.model == Sketchup.active_model } : 0
  end
  return count
end

I try to add it. But still not work…

You are missing the fact that all component definitions in the DefinitionList already belong to the model, because the definitions collection belongs only to that model.

Secondly, both the #count_instances and #count_used_instances methods of the Sketchup::ComponentDefinition class are NOT block form Ruby iterative methods. They do their couniting on the C-side and there is no variable comparison test that you the consumer can define for these counts.

In other words, you seem to be thinking that these methods are equivalent to Ruby’s Enumerable#count. This is not the case. They do not accept the block that you’ve defined. Ie, it is silently ignored.

The API’s DefinitionList#[] method can do the equivalent. Ie …

definition = Sketchup.active_model.definitions[component_name]

EDIT: It just occurred to me that you already have a direct getter for the definition without searching for it a few lines above. Ie …

definition = @source_entity.definition

The problem may stem from the fact that your dynamic components have 3 or 4 separate names.

1 Like

Other persons have also thought this and proposed new functions for the Dynamic Components extension.

The main problem is that this extension is global and is currently distributed with every SketchUp install. This means that everyone is sharing the global classes used by this extension.

Your additions if released publicly would affect all dynamic components and might clash with other local modifications. This is a very big “no-no” and would be rejected by the Extension Warehouse.

If you keep this extension “in house” then locally you’ll be the moderator of any clashes between DC modifications. What I worry about is some of the students carrying the extension outside and posting it publicly.

3 Likes

Thank you so much, @DanRathbun! Your comment was very informative and helpful.

I was unaware of the issues with the “names” on DC and how it was causing confusion with my filter. Based on your feedback, I made some modifications to my code. I used the “DefinitionList” instead of the “find” method and added an instances method of the ComponentDefinition object to get an array of all instances of that component in the model. Then, I called the count method on that array to count the instances. Finally, I added a block to the count method to check if the instance’s model is the same as the active model. However, I encountered an error message from SketchUp saying “no implicit conversion of Array into integer.”

As coding is not my strong suit, I decided to do some research on SketchUcation forums and found a plugin that could be useful for my purpose. The plugin, developed by TommyK, implements a similar function to DC that counts attributes instead of names. You can find it on:
http://sketchucation.com/forums/viewtopic.php?f=323&t=52969
Additionally, the plugin can sum attribute values, giving the total of subcomponent values sum.

Therefore, I will abandon the development of a “count” function. But, I still have some questions about the possible development of this function, out of curiosity:

  1. It seems that the “plural names” of entities make it almost impossible to create a count filter that looks only at the active model. Do you have any suggestions on how we can create a filter that makes it functional? I found an article on ThomThom’s blog discussing a similar problem (Definitions and Instances in SketchUp | Procrastinators Revolt!) and tried to incorporate his solution into my code, but it still doesn’t work.
  2. Loking at TommyK plugin, would it be simpler to reach this count via IfcClass?
  3. I read that

Your additions if released publicly would affect all dynamic components and might clash with other local modifications. This is a very big “no-no” and would be rejected by the Extension Warehouse.

Does this mean that if we need more DC functions, the best way is to request them directly from Trimble? However, with the live components, it seems that DC is not one of Trimble’s favorite features to increase right now.
4. It seems that we really have a problem in terms of compatibility with those DC extensions. The Calculate Nested Attributes Plugin isn’t on the Extension Warehouse, and the control of that plugin or a possible Cont DC plugin are out of the authors’ hands. It looks like the best authors can do is to warn users and hope for the best.

Empirical evidence suggests Trimble does not wish to put any time or money into the DC code.
Despite asking, whining, crying, shouting, bitchin’, pleading, etc., … etc. it takes 2 to 3 years for them to fix even the simplest of bugs, that is if they even care to.

It’s quite obvious that no new features will be going into the Dynamic Components extension. (I once handed them the code for new feature and they were less than enthused for it. Rejecting it out of hand as coming from outside.)

They seem to feel that Live Components is the future. But they seem much more complex than DCs and require a “live” internet connection as the engine is “in the cloud” rather than local as integral to the SketchUp application.

I am of the opinion that a company should fix the current product’s known issues, then work on new features. As a result, I’m not participating in the Live Components beta. Ie … “voting by abstinence”.

RE, the DC extension, I expect they’ll sunset it and stop distributing it with SketchUp making it an optional warehouse download.

I hope that they open source it so we can finally improve it and add features via a community project on GitHub.

7 Likes

Totally agree

1 Like

Because it would be rejected per the EW terms by using an “undocumented API”, ie …
the DC extension is a proprietary “native” Trimble extension and it’s code is encrypted.

I was never much a fan of publicly teaching coders to “hack” the DC function class over at SketchUcation. I think I said so there in one of the topics.

I do remember explaining that the DC components class and the DC Functions class were designed to be subclassed that components of later “dynamic versions” would automatically use. (Currently all dynamic components are _formatversion = 1.0, which means they use the global instance of DynamicComponentsV1 and DCFunctionsV1.)
The code does take this into account, ie the authoring dialog will set _formatversion to the latest DC class loaded and registered, if memory serves.
(This is a brute force pattern which might cause issues for components that only use v1 functionality that attempt to be used on a machine that does not have v2 or later installed. I think that a warning dialog will open up asking the user to update the Dynamic Components extension if it encounters newer version components than it knows how to handle.)

Well, the main issue here is that the authors are not supposed to be modifying any Ruby Core class or library, nor any SketchUp API class or library.

We still occasionally have new coders that get all enthusiastic with Ruby’s dynamism and start redefining and adding methods to global or API classes. This has always been a big no-no.

  • Since SketchUp began shipping with Ruby 2.x (v2014), virtual modifications can be used via the “virtual subclassing” called Ruby Refinements. So any “improvements” you might do must be a refinement.
    But these refinements only apply to the code within your files that use the refinements. Since the DC code files do not, your refinements would not be in effect within the DC extension code.

The DC classes are defined in the top level Ruby ObjectSpace like any other shared core class and should be considered “native no-touch” API.

But people want to have the features they need to do the work, so yes some major extensions have modified how the DCs work. So a user sometimes must make a choice which of two clashing extensions to use.

I’ve also seen the same DC function name “hacked” but with different code. Whichever of the two gets loaded last will redefine the previous leading to strange results.
I’ve seen a few authors prefix function names uniquely to avoid clashing functions.

I’d like to see a community project on GitHub for a one-stop set of new DC functions at least.

1 Like

Aside - Please unsolve the discussion as it’s a pain to see the warning “This topic has been solved” … etc. warning in the rightside view pain. This discussion is very much still ongoing.

I hope this means that you just did a …

number = @source_entity.definition.instances.count

… and did not define a new #instances method in the ComponentDefinition class.
The term “added an instances method of the ComponentDefinition object” is making me nervous.

Scratchin’ my head here. The API is meant to operate upon the active model and will always do so on Windows platform. The Mac platform can have more than 1 model open, but there are easy followed patterns to be sure that a model reference is always using the active model.

As I said above, if you have hold of (a reference) to an instance, or it’s definition or the definition list collection to which it belongs, all of these things must belong to the same model. The DC extension and it’s observer callbacks (ie it’s functionality) only operates upon the active model object.

So trying to filter instances from a model’s definition’s instances collection on a active model comparison is frivolous. You might as well just check at a higher level, ie…

@instance.model == Sketchup.active_model
@instance.definition.model == Sketchup.active_model
@instance.definition.parent.model == Sketchup.active_model
@instance.definition.parent == Sketchup.active_model.definitions

Do you understand? If the component definition belongs to the active model, then it is a member of that model’s DefinitionList collection, and so ALL of it’s instances must belong to the active model.
There is no need to go through the collection checking each instance for it’s model ownership.

I also hope you are not confounding component files in disk storage with component Ruby objects that are loaded into a specific model’s DefinitionList collection. (Some users have in the past.)
A component file can be loaded into any number of models, but each definition is unique to their parent model, and so then is their instances unique to the model in which the definition is loaded.
In other words, the defintion.instances method can only return a set of instances for the definition’s model. It can never return an array of instances mixed between various open models (on the Mac) or even model files not open still on disk. (Some users have wanted to know where all the instances of a particular component file are in every model file they have on disk. The SketchUp API does not work this way across files.)

I do not have time to interrogate this plugin. IfcClasses are likely in different attribute dictionaries then the “dynamic_attributes” dictionary, so I don’t know how this would help.

I do not know what you mean by " ‘plural names’ of entities ", and again there is no problem accessing objects that belong only to the active model.

This article is 11 years old ! It’s from 2012, back in the v8 days when Google owned SketchUp.

Some of the bugs and workaround code in this article are no longer necessary as the bugs have been fixed.

I am really at a loss as to understand what it is you need to count.

Can you describe in a simple list the filters you wish to apply to a count ?

1 Like

In both of your attempts, you are using the splat operator ( * ) for args.
args is already an array, not a parameter list that needs to be collected into an array,
therefore the splat operator will expand the args array into a parameter list.

In all of the DC “hacks” I have copies of, the splat operator is not used. Ie …

def count(args) # <------- NO SPLAT OPERATOR
  if args.empty?
    count = @source_entity.definition.count_used_instances
  else
    component_name = args.first
    definition = Sketchup.active_model.definitions[ component_name ]
    count = definition ? definition.count_used_instances : 0
  end
  return count
end
1 Like

Hello @DanRathbun,

Thank you so much for all your answers and your patience in explaining the things that I misunderstood in my previous questions.

  1. I understood the points related to extending DC functionality and Trimble’s favorite new feature. I tried to start messing with the live components creator tool, but I felt like I did 10 years ago when I abandoned Revit because it was too bureaucratic to use and completely impossible to personalize and automate the design without being a professional visual programmer in Dynamo. DC is so lovely to use. It’s like a 3D Excel with simple (and sometimes intuitive) tools. It’s really sad news to me to read that Trimble “does not wish to put any time or money into the DC code.”
  2. “I’d like to see a community project on GitHub for a one-stop set of new DC functions at least.” It looks like this will be the best way to integrate the upgrade functionalities, considering that new improvements may never come from Trimble.
  3. I didn’t understand everything you said about code improvements and functionalities, not because you didn’t make it clear, but because I’m not really a programmer, and Ruby isn’t the primary coding language I use. I only use Ruby to make really simple routines in SketchUp. But it seems like an opportunity to learn more. Again, thank you for taking the time and having the patience to explain these things! I’m going to search more about these points on SketchUp Ruby documentation.
3 Likes

Ruby is, in my opinion, the best dynamic interpretive language for learning. Python is also very good, but has some quirks that make it a second choice. Again, a subjective opinion. (Not meaning to start a flame war over coding language.)

I created lists here (in this category) of Ruby learning resources …


Agreed. Right now I just don’t have the time or energy to spend on learning it’s workflow.
And being a programmer, until it has an API for coding, I have less interest.

2 Likes

Just for example sake, the topic linked below shows a coder dealing with another extension in which the base Ruby Array#sum method has been redefined, causing issues for other extensions.

1 Like

Absolutely this 100%. If you can’t, won’t, or don’t know how to fix it at least let the community have a go at improving it. From a Trimble point of view, it would add value to Sketchup at no cost to Trimble…

3 Likes