Python script killing grasshopper - crazy slow

Hi Guys.

I need help with python in grasshopper.

I have a client who would like to build a bench by prompting a user with questions. The script will run on Shapediver.

I wrote a script in Python 3 as a start ( I’m new to python) and I got it to work just fine, See the attached file called Dictionary_vs2.py

I then went and stripped out all the fluff and made it run in python 2.7 so that I can use it in grasshopper. I passed the script into a python component it just brings grasshopper to an almost complete standstill. The script is not crazy big or complex.

I have also attached the grasshopper file.

I’m open for suggestions on how to resolve this but also if anyone can think of a better way to do this I.m all ears. The Python file includes a brief description of how the script works. 2019_08_28_python.gh (23.1 KB)
Dictionary_vs2.py (6.1 KB)

Hi @thys,

Yes, the script is not very complex, but there are many ways in which you could optimise your code.

The first thing that comes to mind is that calling raw_input() or input() to fetch user information from Rhino is not a good idea. These methods are meant to be called from a shell, not Rhino. Instead you should use rhinoscriptsyntax (i.e. rs.GetString()), when coding in RhinoPython. This also works in GHPython, however it’s not ideal, since your component has to stop, contact Rhino, wait for a Rhino response, and continue, each time that you call for a user input. This can become quite a bottleneck, when processing data!
Since your client wants to run the definition in Shapediver, getting information from Rhino is probably not even necessary? I haven’t worked with Shapediver yet, but it seems to communicate with Grasshopper directly, which means that your input information will come from Shapediver and go directly to the GHPython or whatever component. This means that you could input information directly into GHPython. The question is now how you want to structure it all? Which information will the user input through Shapediver? What will the GHPython do with this information? …

Here’s a restructured version of some of your GHPython code:

import rhinoscriptsyntax as rs
import time
import ast

###############################################################################

def bench_type_simple(choices_dict):
    """Asks for a Rhino user input (i.e. '1', '2') and gets the simple bench 
        types (i.e. 'A', 'B') from a dictionary of bench type choices.
    
    Args:
      choices_dict (dict): A dictionary of available bench types that maps 
        bench choices (i.e. 'A: Small', 'B: Medium') as keys to alphabetical 
        characters (i.e. 'A', 'B') as values.
      retries (int): An optional maximum number of retries to get input data,
        if the user doesn't make a choice.
    Returns:
      The simple bench type (i.e. 'A', 'B')
    """
    # Get a list of ordered choice_dict keys
    choices = [k for k in sorted(choices_dict.keys())]
    # Parse an input message from available choices
    msg = "Choose a bench type or press <Enter> to abort:\n"
    for i in range(len(choices)):
        msg += "\n {} ({})".format(i+1, choices[i].split(" : ")[-1])
    # Get a user input
    btype = rs.GetInteger(message=msg, minimum=1, maximum=len(choices))
    if btype == None:
        return
    return choices_dict[choices[btype-1]]
        

def get_keys_by_value(dictionary, search_value):
    """Gets the keys from a dictionary, mapped to values equal to 
        the search value.
    
    Args:
      dictionary (dict): A dictionary to search.
      search_value (misc): A value to find.
    Returns:
      A list of found keys, if multiple keys were found, 
        a single key, if only one key was a match, or None. 
    """
    found_keys = []
    for key, val in dictionary.items():
        if val == search_value:
            found_keys.append(key)
    if len(found_keys) > 0:
        if len(found_keys) == 1:
            return found_keys[0]
        else:
            return found_keys
    else:
        return


# Convert the input string dictionaries to dictionaries
choice_dict = ast.literal_eval(choiceDict)
bench_dict = ast.literal_eval(benchDict)
my_dict = ast.literal_eval(myDict)

bench_type = bench_type_simple(choice_dict)
bench_size = get_keys_by_value(choice_dict, bench_type)

print bench_size.split(" : ")[-1]

bench_length = bench_dict[bench_type]
print bench_length

The dictionaries that you input from panels are no dictionaries, but strings. First you need to converted these back to dictionaries to be of any use.

Furthermore, working with global variables inside of functions is not exactly forbidden, but it’s usually recommended to pass everything that you need inside the function, as arguments to it instead. It makes it more straightforward to respect the scope of things and to structure your code. Also global variables are usually capitalised in Python (i.e. GLOBAL_VARIABLE) and written above classes and functions (which you respected).

Variable, function names, and pretty much everything else are written in lowercase letters divided by underscores (i.e. my_variable, my_function()). Classes are written in camelback notation (i.e. MyClass).

I didn’t really understand the role of the bench_SMod() and bench_FMod() functions. What’s your goal here?

Hi @thys, I had started to look at your script when @p1r4t3b0y replied. In order for us to provide you with meaningful help, please explain in more detail what you want to achieve for your client:

Did I understand correctly that you are looking for a way to build a configurator that asks a sequence of questions to the end user?

Thank you, thank you, thank you.

I appreciate your effort. I will work through the code and make sense as I go a long.

Thanks for the advice on the naming conventions. Like I said I’m brand new to python and still finding my feet.

1 Like

Hi Alexander.

You understand correct.

I want to build configurator that gives the user a choice of bench size - small, med, large or extra large. each bench is made up with modules, and only specific modules can be connected to each other. Therefor the users response needs to be checked to make sure it will work

To keep the thing as simple as possible, I’m limiting the user by only allowing them to build from left to right and you can never make a full 360 deg.

Let’s say a small bench is chosen. It is made up out of 4 modules.

start - middle - middle - end.

The start and end are given. That can not be changed. But the middle is open to the user to select, and if the module is straight, the user can also give it a dimension.

Hope the simple line drawings below helps. I will then use the output from the code to generate the bench.

The python code is used to write the recipe for the bench. If that makes sense

The bench_FMod() is used to give the user the initial choice of all the available moduals, the bench_SMod() checks the users resonce and then only provide them with the relevant choices.

OK, so the numbered parts are curved modules and the ABC parts are the straight modules, but how does the bench size relate to this? Does it initially define the overall module size?

What information does the user actually input?

What output do you need to reference your geometry? A code like this “A4C1A” as a string or something different?

Hi @thys, now I understood what you want to achieve. You will need to follow a completely different approach. This help article will be a good point to start from understanding and experimenting.

The main points you need to mind:

  • You can not store state in your Grasshopper model. When running on ShapeDiver, there is no one to one relationship between an end user interacting with your model, and an instance of Rhino/Grasshopper. Such a one to one relationship only exists when you are working with Grasshopper on your local computer.
  • The state of a configuration exists as part of the session that is established between the ShapeDiver viewer and the ShapeDiver backend. This session state therefore exists in the browser of the end user, where a web application can make use of it to enable a step by step configurator.
  • If you want to achieve such a configurator, you will therefore need to implement a web application that embeds the ShapeDiver viewer, makes use of its API, presents your step by step questions to the end user, and reacts according to the user’s input.

In case web development is out of your reach, we can direct you to suitable agencies, or you contact a local web developer and point them here.

Hi p1r4t3b0y

I employed the bench size option to give the end-user direction and to limit the number of modules needed to build the bench. If I don’t do this, the user will have the option to create a bench for London all to Paris.

I found if you set the boundaries beforehand and guide the user, the outcome will be more reliable

The ability to provide a length to the module will allow the user to create a product that fits into the space available. It will allow the bench to fill the space if it needs to.

Output, Yes that is what I had in mind.

Looking at Alexanders response I will have to change my approach, Thank you for your help.

Hi Alexander.

Thank you for the input. I will look in to all the point above and take it from there.

Not necessarily in my opinion! In standard Grasshopper-Shapediver-workflows, you simply drive the procedural part of the design by sliders or other value inputs that you predefine as such in Grasshopper, before uploading your file to their website.
So figuring this part out simply stays necessary and will be enough for preview purposes.

Now, since your client may want to implement Shapediver on his/her website and offer for instance to the user to be able to store iterations of his/her design online, you’ll need an approach similar to the one proposed by @snabela.
If you still want to go the Python route, you could easily output bench configurations as text files in order to save for example a user history. This would also be pretty light in terms of storage, since you only save small strings.