Cutting a 3D form into small cubes then doing a random selection

Hey ! I’m working on a project of two steps but i need some help with grasshopper because i’m just learning.

  1. I’m trying to cut a brep -modelised at first on rhino (let’s say a cube)- into small cubes (or points) but I’m struggling to actually cut them and not the surfaces.

  2. Because I want after that to do a random selection of those cubes that are in contact of each others in order to highlight news 3D forms. I will add some variables like the number of selection, or the number of cubes in Z direction for example but i think i can manage that. (hopefully ahah)

I don’t know if I should work only with the components of grasshopper (i think it’s do-able) or add a python script.
A friend helped me coding a program that is working and doing mostly what i want but it would be better if i can translate it on grasshopper.

If you can help me even with just a part it would be very appreciated ! I hope it’s clear and if you have any questions please ask.

Here is the code and the result showed through polyscope.

import numpy as np
import polyscope as ps
from copy import deepcopy
import random as rd


DIR = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 1, 0), (1, 0, 0), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, -1, 0), (-1, 0, 0)]

ps.init()


def init_cube_points(side_length):
    points = set()
    for i in range(side_length + 1):
        for j in range(side_length + 1):
            for k in range(side_length + 1):
                points.add((i, j, k))
    return points


def translate_point(point, vec):
    return (point[0] + vec[0], point[1] + vec[1], point[2] + vec[2])


def vec_from_points(p_1, p_2):
    return (p_2[0] - p_1[0], p_2[1] - p_1[1], p_2[2] - p_1[2])


class point_cloud:

    def __init__(
        self,
        points=None,
    ):
        self._origin = (0, 0, 0)
        if points is not None:
            self._points = points
        else:
            self._points = set()

    def get_points(self):
        return {translate_point(p, self._origin) for p in self._points}

    def get_origin(self):
        return self._origin

    def get_relative_bounding_box(self):
        x_max, y_max, z_max = 0, 0, 0
        for p in self._points:
            if p[0] > x_max:
                x_max = p[0]
            if p[1] > y_max:
                y_max = p[1]
            if p[2] > z_max:
                z_max = p[2]
        return (x_max, y_max, z_max)

    def translate(self, vec):
        self._origin = translate_point(self._origin, vec)

    def is_intersecting_with(self, pc):
        translate_vec = vec_from_points(pc._origin, self._origin)
        for p in self._points:
            if translate_point(p, translate_vec) in pc._points:
                return True
        return False

    def is_removable(self, pc):
        bounding_box = self.get_relative_bounding_box()
        act_origin = (0, 0, 0)

        def parcours(
            self,
            pc,
            prev_origin,
            act_origin,
            bounding_box,
        ):
            if prev_origin is not None:
                translate_vec = vec_from_points(prev_origin, act_origin)
            else:
                translate_vec = (0, 0, 0)
            pc_copy = deepcopy(pc)
            pc_copy.translate(translate_vec)
            if self.is_intersecting_with(pc_copy):
                return False
            if (
                abs(act_origin[0]) > bounding_box[0]
                or abs(act_origin[1]) > bounding_box[1]
                or abs(act_origin[2]) > bounding_box[2]
            ):
                return True
            for d in DIR:
                pot_next_origin = translate_point(act_origin, d)
                pc_next = deepcopy(pc_copy)
                if prev_origin is None or pot_next_origin != prev_origin:
                    if parcours(
                        self, pc_next, act_origin, pot_next_origin, bounding_box
                    ):
                        return True
            return False

        return parcours(self, pc, None, act_origin, bounding_box)

    def to_numpy_points(self):
        return np.array(list(self.get_points()))

    def separate_point_cloud(self, n_remove):
        bounding_box = self.get_relative_bounding_box()
        object_to_remove = []
        points_copy = deepcopy(self._points)
        point_list = list(self._points)
        rd.shuffle(point_list)
        for p in point_list:
            if (
                0 in p
                or p[0] == bounding_box[0]
                or p[1] == bounding_box[1]
                or p[2] == bounding_box[2]
            ):
                starting_point = p
                object_to_remove.append(starting_point)
                points_copy.remove(starting_point)
                break
        cpt = 0
        while len(object_to_remove) != n_remove and cpt < 100000:
            cpt += 1
            rd_point = rd.choice(object_to_remove)
            rd_d = rd.choice(DIR)
            pot_neigh = translate_point(rd_point, rd_d)
            if pot_neigh in points_copy:
                points_copy.remove(pot_neigh)
                object_to_remove.append(pot_neigh)
                # if not point_cloud(points_copy).is_removable(
                #     point_cloud(set(object_to_remove))
                # ):
                #     points_copy.add(object_to_remove.pop())
        return point_cloud(set(object_to_remove)), point_cloud(points_copy)


# Lenght of the big cube side
side_length = 10

# Size of removed objects
object_removed_size = 450

# Number of pieces to extract
num_object = 3

points = init_cube_points(side_length)
cube = point_cloud(points)

for i in range(num_object):
    removed_object, cube_left = cube.separate_point_cloud(object_removed_size)
   # while not removed_object.is_removable(cube_left):
    #    removed_object, cube_left = cube.separate_point_cloud(object_removed_size)
    cube_even = ps.register_point_cloud(f"Object {i}", removed_object.to_numpy_points())
    cube = cube_left

ps.init()
cube_left = ps.register_point_cloud("Cube left", cube_left.to_numpy_points())
ps.show()

Hi Amelie,

Here is a GH script that I think do what you mean.
You can define another brep for the input.
The point attractors can also moved freely in the rhino files.
Hope this helps!


am.3dm (209.8 KB)
am.gh (26.8 KB)

Thanks a lot !! I will try this !

you are not really taking advantage of Grasshopper by doing things in cascade, like iterating the same operation without taking advantage of data trees :slight_smile:

another important point, Voronoi yields cells that encapsulate the space/volume that is closest to a point than any other point in the set: creating 3D Voronoi cells, then checking if a given point lies inside a given cell or not, is equal to using Pull Point with Closest = True, but much more complicated and very much slower

by using Pull Point, its “i” output will contain the indexes of the Closest Point, which is the equivalent index of the theoretical Voronoi3D cell that would contain that point:

if I had to create a 3D Voronoi using those very same points, I would get exactly the very same cell/colors (doing it on top, as demonstration):

fill brep with points and partition with attractors.gh (240.0 KB)

1 Like

Wow. That’s amazing inno. I’ve never thought of that before. Thanks!
This is a learning for me too.

Thank you for your help as well !!

I have a new question ahah.
It’s indeed working well ! But if I want to set another kind of form into the brep, it’s not always following. I think I undestood how the script works but I’m not sure what to change in order to force the points to follow the shape ?

For example, it is not working with this one.


Do you think you can help me ?

Can you sharw your GH files so that we could see what went wrong?

yes sure ! I actually just set an other brep for the beginning. It’s a form that has specific mesurement and I think it’s a smaller scale than the cube original maybe it 's a problem ?

Also it’s quite slow executing but I think it might just be my computer.

am.3dm (845.4 KB)
am.gh (31.3 KB)

fill brep with points and partition with attractors_2.gh (20.1 KB)

if you want boxes instead of MeshSpheres:

fill brep with points and partition with attractors_2_boxes.gh (24.9 KB)

1 Like

Wow yea, that’s really nice thank you so much !

Hey, going further with the project since I’m learning a lot ! I was wondering. Why is there a difference of results between your two scripts about the randomly selected points ?

I’m explaining, @Mudita_Lau 's one is selecting points that can intersect with others creating pieces that can be recessed with an other one whereas @inno 's points are next to each others but without being recessed.

The purpose of my project is to take the pieces generated and see how they can be recessed with each others in a given 3D form (the brep)

I tried by setting different attrators points but it’s not changing anything :confused:
For example, I really like the idea of having this kind of results :

Is there a way of having this into your script @inno ? Since the other script doesn’t always follow the brep set unfortunately.

it depends on the kind of final result you are looking for

for instance, in my script the point division is done Voronoi-like, or let’s say closest point: you set some key-points points, and each sphere/cube gets assigned/grouped to its closest key-point

so partition boundaries tend to be straight, because there’s a Line (in 2D) or a Plane (in 3D) that defines the boundary between what is closer to point A and what is closer to point B

what is the 3D space rule (or set of rules) that partitioning follows in your original concept?

Okay I understand,

Well to be more precise, I actually need to have parts that fit into each other and it’s this result that interests me.

I am a student in product design and the interest of this program is to cut randomly built-in shapes in a given 3d shape in order to then interpret them as seats (stools/chairs).
So maybe the point division using voronoi is not the best way to achieve that right ?

Hello
you can use also K-Means Clustering, there is now a tool in Lunch Box ML plugin and in Ngon

I think a good way to achieve what you want maybe still Voronoi-like clusterization (or K-means, at the end they are somehow related in the final shapes of the cells) but then those cells get combined again together

to show an example in 2D, which is maybe easier to catch:
here the final array of points on which spheres/cubes are centered is not visualized, this is just the division of the space inside your initial Brep

first you divide space by proximity to random points:

then you cluster those cells based on proximity (of the points that generated those) to a new layer of (fewer) points:

ending up in something like this (which is a very nice theoretical principle you can appreciate in many of Laurent’s clusterings works)

2D_Voronoi_clustering.gh (21.0 KB)

translated in 3D it would look something like this (spheres are willingly smaller to let you see what happens inside the brep)

2 layers proximity clustering.gh (29.6 KB)

4 Likes

@inno very nice way of doing

Funny thing it is also possible to use Cell Noise to get

3 Likes