Python (learning) trying to accept lists and repeating

import random

NumberOfPoints = IN[0]
MinimumDistance =IN[1]
MaximumDistance = IN[2]
RepeatNumber=IN[3]
outputList=[]

def RandomNumberRange(NumberOfPoints,MinimumDistance,MaximumDistance):
    
    d = sorted(random.randint(0, MaximumDistance-1) for _ in range(NumberOfPoints))
    o = [b - a for a, b in zip([0] + d[:-1], d)]

    RandomNumbers=([i * MinimumDistance + sum(o[:i + 1]) for i in range(NumberOfPoints)])
    return RandomNumbers


if not isinstance(NumberOfPoints, list):
	NumberOfPoints = [NumberOfPoints]
	

for n in NumberOfPoints:
	processed = RandomNumberRange(NumberOfPoints,MinimumDistance,MaximumDistance)
    outputList.append(processed)


OUT = outputList

I am trying to understand python but i am at a point i am taking two steps forward but also two steps back.
At the moment i am trying that my script accepts the list, so i get a list of 20 random points and a list of 21 random points.
The next step, (but i can’t get over this first hurdle) is that i want list 0 to have 4 sublists of 20 random numbers and list 1 to have 6 sublists of 21 random number.

i know my problem is with the range…i need something of an extra for i in… but how

Breaking down the list comprehensions to normal for loops will help you understand each step in the script. Then you can make changes to the actual function piece by piece.

1 Like

then don’t help…pff… been trying for hours now need a good tip…remarks like this don’t help. I went back to the part that worked… not all my mistakes.
I am trying to do this myself, but i am stuck.

Hey Arno, I didn’t mean to give you some half hearted tip for your problem. I assumed you’d like to learn python (based on the title) and gave a pointer on how you will get closer to the solution by yourself, simplifying your script. Learning this concept will help you more.

If you start understanding your RandomNumberRange, you can change it to a RandomPointRange function. Trying to change the function and posting your steps (even if it didn’t work) would show effort and clarify where you’re stuck.

I understand your frustration but i don’t appreciate your reaction.

Kibar, i am sorry, thank you for your help…but have been on this for hours and do want to learn, but sometimes you just want some progress as well… and a tip that sends me just back to the woods i have been wondering in for hours isn’t motivating. But i didn’t mean to bite your head of…sorry for my frustration

1 Like

I will have more time tonight, but for now you can try working with what I have started with.
So far I have only broken down the list comprehensions, renamed variables and commented on what is actually happening. Took 5 minutes and makes it easier to read/understand.

NumberOfPoints = IN[0]
MinimumDistance =IN[1]
MaximumDistance = IN[2]
RepeatNumber=IN[3]
outputList=[]

def RandomNumberRange(NumberOfPoints,MinimumDistance,MaximumDistance):
    """
    AIM:
        - Create a random Point within a minimum and maximum distance(?)
    STEPS:
        - Get 3 random numbers (X, Y, and Z coordinate)
        - Create a Point
        - Return this point
    """        


    # Create a random number between 0 and MaximumDistance
    # for each point
    random_numbers = []
    for num in range(NumberOfPoints):
        random_num = random.randint(0, MaximumDistance-1)
        random_numbers.append(random_num)

    # Sort random_numbers
    # (Do I need it?)
    random_numbers = sorted(random_numbers)

    # (what happens here? Do I need it?)
    substracts = []
    for backwards, forwards in zip(random_numbers[:-1], random_numbers):
        num = backwards - forwards
        substracts.append(num)

    # (what happens here? Do I need it?)
    output = []
    for num in range(NumberOfPoints):
        mult_distance = num * MinimumDistance
        sum_numbers = sum(substracts[:num+1], MaximumDistance-1)
        output.append( mult_distance + sum_numbers)

    return output


if not isinstance(NumberOfPoints, list):
    NumberOfPoints = [NumberOfPoints]
    

for n in NumberOfPoints:
    processed = RandomNumberRange(NumberOfPoints,MinimumDistance,MaximumDistance)
    outputList.append(processed)

It does not change what the python script / dynamo graph did before, but now you can add comments and ask/search for what you want to achieve.
Very important from a learning point of view and you will understand whats happening, when you come back/reuse the code.

4 Likes

Start with a python tutorial. There are a good many for this and a dozen or so instances of this on the forum. My personal favorite was from SoloLearn where the web app allowed me to make progress on my train ride. I have also heard good things about code academy as well.

From there start getting into the tutorials for Dynamo specific Python implementations. Haven’t run all the way through it but this link has some good stuff in that front. Untangling Python: A Crash Course on Dynamo‘s Python Node | Autodesk University. @john_pierson’s excellent AU course on the topic will also help you get going pretty quickly, and should not be missed: https://www.autodesk.com/autodesk-university/class/Hack-It-til-You-Crack-It-Python-Dynamo-Beginners-2018

After that you can get into trying to unravel the python implementations in other people’s code. A great resource for this is the Dynamo Python repository from @solamour which can be found here: GitHub - Amoursol/dynamoPython: Python Modules for Dynamo

One thing that helps is to ALWAYS comment your code as you go. No matter how basic it feels today, or how simple it seems. Even if you crib it from somewhere else (actually make that especially if you crib it from somewhere else), write down what every single line does in the code. Skip nothing. This helps commit the concepts to your memory.

Lastly know that Python isn’t always ideal for ‘in graph’ uses (no bindings, nearly impossible to maintain at scale, require a higher degree of ‘prerequisite knowledge’ to customize for new list structures, etc), and as a result most of the ‘how do I loop over multiple lists of differing levels’ issues can be instantly avoided by making a very simple custom node at which point list levels and lacing will solve the problem for you (though perhaps not in this case - I am not at my laptop so I am not able to help you debug this code).

4 Likes

I have looked more into it and also found your previous thread and can understand your frustration.

This stack overflow post includes your RandomNumberRange in its comments and some other ways of achieving such a list, but I think you could achieve what you´re looking for by a workflow you started before:

You had a graph in which you´ve used Surface.PointAtParameter and checked if the geometries intersect. I´ve built up on it and added a while loop.

import clr
clr.AddReference("ProtoGeometry")
from Autodesk.DesignScript.Geometry import *
import sys
sys.path.append("C:\\Program Files (x86)\\IronPython 2.7\\Lib")
import random

surface = IN[0]
number_of_points = IN[1]
max_distance = IN[2]

# Output
outlist = []

def distance_check(point, point_list, max):
	"""
	Checks the distance between one point and
	all points in point_list
	
	Returns True if distance < max
	"""

	flag = True
	for p in points:
		if p == point:
			flag = False
			break
		# Length of line between points
		if Line.ByStartPointEndPoint(p, point).Length < max:
			flag = False
			break
	return flag

if not isinstance(number_of_points, list):
	number_of_points = [number_of_points]

for num in number_of_points:
	points = []
	count = 0
	# Fallback counter
	# -> prevents infinitive loop
	iterations = 0
	max_iterations = 10000

	while count < num and iterations < max_iterations:
		u_value = random.uniform(0,1)
		v_value = random.uniform(0,1)
		point = surface.PointAtParameter(u_value, v_value)
	
		if Geometry.DoesIntersect(point,surface) and distance_check(point, points, max_distance):
			count += 1
			points.append(point)

		iterations += 1

	outlist.append(points)

OUT = outlist

The while loop runs until you have as many points as you looked for, OR until the max number of iterations is reached. The second condition is preventing an infinitve loop, which could happen if no more points can be fit on the surface. This could also mean that you have less points than you aimed for… you could put up the number to 10000 or 10000000.

If you aim to include cross checking the distances between the lists of points, you would need to add to the distance_check() function.

Here is the workspace:

Besides all the great resources listed by @jacob.small , you could look into code challenges from codewars and project euler and of course automate the boring stuff.

3 Likes

@Kibar ibar, thank you for your patience and help, this had brought me a step further, still tearing your script apart to figure out how it works exactly. But your script also brought me a step further in my approach.

thanks for your help

@jacob.small thanks for the list of resources

1 Like

@Kibar i totally understand your code, but i am getting empty lists and i can’t figure out why.

got one step further if i remove the Geometry.DoesIntersect(point, surface) and
Then i get all the points visible (ofcourse also the points outside the surface)

Try to find a case when it actually works, then scale it slowly up to your live model.

Maybe put the number of points from 20 to 1, and the maximum number of iterations to 100000. If that does not return a point, try to scale down the surface. I saw that there was a problem with DoesIntersect, that has been resolved back in 2017.

I won’t have time to try it myself until tonight /tomorrow, but you could upload a sample file (with a surface as large as yours) for me and others to try find the problem.

@Kibar i am making progress and will wrestle with it a bit, need to learn it as well. Will keep you up to date… thanks


hitting a small block again, i added code so i can add multiple surfaces,
and i changed the input numbers, based on area of the surface and the mindistance, this way you can keep the calculations based on true values… don’t have to run a 1000 points if the area is only 5m2.

But because i added some loops i now get, 4 pointLists on each surface… so somewhere in my loop for the surface i need to tell it it needs to take an index of the pointList and use that and not all the points from all the lists.

import clr
clr.AddReference("ProtoGeometry")
from Autodesk.DesignScript.Geometry import *
import sys
sys.path.append("C:\\Program Files (x86)\\IronPython 2.7\\Lib")
import random

surface = IN[0]
number_of_points = IN[1]
max_distance = IN[2]


# Output
outlistPoints = []
outlistSurface = []

def distance_check(point, point_list, max):
    """
    Checks the distance between one point and
    all points in point_list
	
	Returns True if distance < max
	"""

    flag = True
    for p in points:
	    if p == point:
		    flag = False
		    break
    	# Length of line between points
	    if Line.ByStartPointEndPoint(p, point).Length < max:
		    flag = False
		    break
	    
    return flag

if not isinstance(number_of_points, list):
	number_of_points = [number_of_points]
    

if not isinstance(surface, list):
    surface = [surface]

for sur in surface:
    for num in number_of_points:
	    points = []
	    count = 0
	    # Fallback counter
	    # -> prevents infinitive loop
	    iterations = 0
    	max_iterations = 10000

    	while count < num and iterations < max_iterations:
	    	u_value = random.uniform(0,1)
		    v_value = random.uniform(0,1)
		    point = sur.PointAtParameter(u_value, v_value)
    
	    	#if Geometry.DoesIntersect(point,surface) and distance_check(point, points, max_distance):
    		if distance_check(point, points, max_distance):
	    		count += 1
		    	points.append(point)

    		iterations += 1

    	outlistPoints.append(points)
	outlistSurface.append(outlistPoints)

OUT = [outlistSurface]

so how to litarate through a list and in that litaration use an index of another list.

Have a look at pythons **zip() ** function to iterate over the surfaces and numbers at the same time.

import clr
clr.AddReference("ProtoGeometry")
from Autodesk.DesignScript.Geometry import *
import sys
sys.path.append("C:\\Program Files (x86)\\IronPython 2.7\\Lib")
import random

surface = IN[0]
number_of_points = IN[1]
max_distance = IN[2]

# Output
outlistPoints = []
outlistSurface = []

def distance_check(point, point_list, max):
    """
	Checks the distance between one point and
    all points in point_list

    Returns True if distance < max
    """

    flag = True
	for p in points:
		if p == point:
			flag = False
			break
		# Length of line between points
		if Line.ByStartPointEndPoint(p, point).Length < max:
			flag = False
			break
	
	return flag

if not isinstance(number_of_points, list):
	number_of_points = [number_of_points]
    

if not isinstance(surface, list):
	surface = [surface]

for sur,num in zip(surface, number_of_points):
	points = []
    count = 0
	# Fallback counter
    # -> prevents infinitive loop
    iterations = 0
	max_iterations = 10000

    while count < num and iterations < max_iterations:
    	u_value = random.uniform(0,1)
	    v_value = random.uniform(0,1)
	    point = sur.PointAtParameter(u_value, v_value)

		#if Geometry.DoesIntersect(point,surface) and distance_check(point, points, max_distance):
    	if distance_check(point, points, max_distance):
	    	count += 1 
			points.append(point)

    	iterations += 1

	outlistPoints.append(points)


OUT = outlistPoints

in my head this should work but output is empty list…

I can’t try it out myself at the moment, but it seems like I have made a mistake in the distance_check function. It returns false if the distance is smaller than max. Could you try flipping the operator? >

got it working… will post the final script later…