diff --git a/fury/actor.py b/fury/actor.py index 199190bfc..ec1269f40 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -191,7 +191,190 @@ def box( mat = _create_mesh_material(material=material, enable_picking=enable_picking) obj = create_mesh(geometry=geo, material=mat) obj.local.position = centers[0] + obj.prim_count = prim_count + return obj + + +def cylinder( + centers, + *, + colors=(1, 1, 1), + height=1, + sectors=36, + radii=0.5, + scales=(1, 1, 1), + directions=(0, 1, 0), + capped=True, + opacity=None, + material="phong", + enable_picking=True, +): + """Visualize one or many cylinders with different features. + + Parameters + ---------- + centers : ndarray, shape (N, 3) + Box positions. + colors : ndarray (N,3) or (N, 4) or tuple (3,) or tuple (4,), optional + RGB or RGBA (for opacity) R, G, B and A should be at the range [0, 1]. + height: float, optional + The height of the cylinder. Default is 1. + sectors: int, optional + The number of divisions around the cylinder's circumference . + Higher values produce smoother cylinders. Default is 36. + radii : float or ndarray (N,) or tuple, optional + The radius of the base of the cylinders, single value applies to all cylinders, + while an array specifies a radius for each cylinder individually. Default:0.5. + scales : int or ndarray (N, 3) or tuple (3,), optional + Scaling factors for the cylinders in the (x, y, z) dimensions. + Default is uniform scaling (1, 1, 1). + directions : ndarray, shape (N, 3), optional + The orientation vector of the box. + capped : bool, optional + Whether to add caps (circular ends) to the cylinders. Default is True. + opacity : float, optional + Takes values from 0 (fully transparent) to 1 (opaque). + If both `opacity` and RGBA are provided, the final alpha will be: + final_alpha = alpha_in_RGBA * opacity + material : str, optional + The material type for the boxes. Options are 'phong' and 'basic'. + enable_picking : bool, optional + Whether the boxes should be pickable in a 3D scene. + Returns + ------- + mesh_actor : Actor + A mesh actor containing the generated boxes, with the specified + material and properties. + + Examples + -------- + >>> from fury import window, actor + >>> import numpy as np + >>> scene = window.Scene() + >>> centers = np.random.rand(5, 3) * 10 + >>> colors = np.random.rand(5, 3) + >>> cylinder_actor = actor.cylinder(centers=centers, colors=colors) + >>> scene.add(cylinder_actor) + >>> show_manager = window.ShowManager(scene=scene, size=(600, 600)) + >>> show_manager.start() + """ + + vertices, faces = fp.prim_cylinder( + radius=radii, height=height, sectors=sectors, capped=capped + ) + res = fp.repeat_primitive( + vertices, + faces, + directions=directions, + centers=centers, + colors=colors, + scales=scales, + ) + big_vertices, big_faces, big_colors, _ = res + prim_count = len(centers) + big_colors = big_colors / 255.0 + + if isinstance(opacity, (int, float)): + if big_colors.shape[1] == 3: + big_colors = np.hstack( + (big_colors, np.full((big_colors.shape[0], 1), opacity)) + ) + else: + big_colors[:, 3] *= opacity + + geo = buffer_to_geometry( + indices=big_faces.astype("int32"), + positions=big_vertices.astype("float32"), + texcoords=big_vertices.astype("float32"), + colors=big_colors.astype("float32"), + ) + mat = _create_mesh_material(material=material, enable_picking=enable_picking) + obj = create_mesh(geometry=geo, material=mat) + obj.local.position = centers[0] obj.prim_count = prim_count + return obj + +def square( + centers, + *, + directions=(0, 0, 0), + colors=(1, 1, 1), + scales=(1, 1, 1), + opacity=None, + material="phong", + enable_picking=True, +): + """Visualize one or many boxes with different features. + + Parameters + ---------- + centers : ndarray, shape (N, 3) + Box positions. + directions : ndarray, shape (N, 3), optional + The orientation vector of the box. + colors : ndarray (N,3) or (N, 4) or tuple (3,) or tuple (4,), optional + RGB or RGBA (for opacity) R, G, B and A should be at the range [0, 1]. + scales : int or ndarray (N,3) or tuple (3,), optional + The size of the box in each dimension. If a single value is provided, + the same size will be used for all boxes. + opacity : float, optional + Takes values from 0 (fully transparent) to 1 (opaque). + If both `opacity` and RGBA are provided, the final alpha will be: + final_alpha = alpha_in_RGBA * opacity + material : str, optional + The material type for the boxes. Options are 'phong' and 'basic'. + enable_picking : bool, optional + Whether the boxes should be pickable in a 3D scene. + + Returns + ------- + mesh_actor : Actor + A mesh actor containing the generated boxes, with the specified + material and properties. + + Examples + -------- + >>> from fury import window, actor + >>> import numpy as np + >>> scene = window.Scene() + >>> centers = np.random.rand(5, 3) * 10 + >>> colors = np.random.rand(5, 3) + >>> square_actor = actor.square(centers=centers, colors=colors) + >>> scene.add(square_actor) + >>> show_manager = window.ShowManager(scene=scene, size=(600, 600)) + >>> show_manager.start() + """ + vertices, faces = fp.prim_square() + res = fp.repeat_primitive( + vertices, + faces, + directions=directions, + centers=centers, + colors=colors, + scales=scales, + ) + big_vertices, big_faces, big_colors, _ = res + prim_count = len(centers) + big_colors = big_colors / 255.0 + + if isinstance(opacity, (int, float)): + if big_colors.shape[1] == 3: + big_colors = np.hstack( + (big_colors, np.full((big_colors.shape[0], 1), opacity)) + ) + else: + big_colors[:, 3] *= opacity + + geo = buffer_to_geometry( + indices=big_faces.astype("int32"), + positions=big_vertices.astype("float32"), + texcoords=big_vertices.astype("float32"), + colors=big_colors.astype("float32"), + ) + mat = _create_mesh_material(material=material, enable_picking=enable_picking) + obj = create_mesh(geometry=geo, material=mat) + obj.local.position = centers[0] + obj.prim_count = prim_count return obj diff --git a/fury/tests/test_actor.py b/fury/tests/test_actor.py index c39e6d425..a29b3631d 100644 --- a/fury/tests/test_actor.py +++ b/fury/tests/test_actor.py @@ -91,3 +91,85 @@ def test_box(): assert box_actor.prim_count == 1 scene.remove(box_actor) + + +def test_cylinder(): + scene = window.Scene() + centers = np.array([[0, 0, 0]]) + colors = np.array([[1, 0, 0]]) + sectors = 36 + capped = True + + cylinder_actor = actor.cylinder( + centers=centers, colors=colors, sectors=sectors, capped=capped + ) + scene.add(cylinder_actor) + + npt.assert_array_equal(cylinder_actor.local.position, centers[0]) + + mean_vertex = np.mean(cylinder_actor.geometry.positions.view, axis=0) + npt.assert_array_almost_equal(mean_vertex, centers[0], decimal=2) + + assert cylinder_actor.prim_count == 1 + + # window.snapshot(scene=scene, fname="cylinder_test_1.png") + + # img = Image.open("cylinder_test_1.png") + # img_array = np.array(img) + + # mean_r, mean_g, mean_b, mean_a = np.mean( + # img_array.reshape(-1, img_array.shape[2]), axis=0 + # ) + + # assert mean_r > mean_b and mean_r > mean_g + # assert 0 < mean_r < 255 and 0 < mean_g < 255 and 0 <= mean_b < 255 + + # middle_pixel = img_array[img_array.shape[0] // 2, img_array.shape[1] // 2] + # r, g, b, a = middle_pixel + # assert r > g and r > b + # assert g == b + # assert r > 0 and g > 0 and b > 0 + + scene.remove(cylinder_actor) + + # cylinder_actor_2 = actor.cylinder( + # centers=centers, colors=colors, sectors=sectors, capped=capped, + # material="basic" + # ) + # scene.add(cylinder_actor_2) + # window.snapshot(scene=scene, fname="cylinder_test_2.png") + + # img = Image.open("cylinder_test_2.png") + # img_array = np.array(img) + + # mean_r, mean_g, mean_b, mean_a = np.mean( + # img_array.reshape(-1, img_array.shape[2]), axis=0 + # ) + + # assert mean_r > mean_b and mean_r > mean_g + # assert 0 < mean_r < 255 + # assert mean_g == 0 and mean_b == 0 + + # middle_pixel = img_array[img_array.shape[0] // 2, img_array.shape[1] // 2] + # r, g, b, a = middle_pixel + # assert r > g and r > b + # assert g == 0 and b == 0 + # assert r == 255 + # scene.remove(cylinder_actor_2) + + +def test_square(): + scene = window.Scene() + centers = np.array([[0, 0, 0]]) + colors = np.array([[1, 0, 0]]) + + square_actor = actor.square(centers=centers, colors=colors) + scene.add(square_actor) + + npt.assert_array_equal(square_actor.local.position, centers[0]) + + mean_vertex = np.mean(square_actor.geometry.positions.view, axis=0) + npt.assert_array_almost_equal(mean_vertex, centers[0]) + + assert square_actor.prim_count == 1 + scene.remove(square_actor)