Skip to content

REFACTOR: move points cloud extension at project level #6004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/6004.miscellaneous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
move points cloud extension at project level
8 changes: 0 additions & 8 deletions doc/source/User_guide/pyaedt_extensions_doc/hfss/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,6 @@ HFSS extensions
Shielding effectiveness workflow in HFSS.


.. grid-item-card:: Point cloud generator
:link: point_cloud_generator
:link-type: doc
:margin: 2 2 0 0

Generate and import point list from a geometry in HFSS.


.. grid-item-card:: Move it
:link: move_it
:link-type: doc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,10 @@ Project extensions

Lear how to convert projects from 2022R2 to newer versions.

.. grid-item-card:: Points cloud generator
:link: points_cloud_generator
:link-type: doc
:margin: 2 2 0 0

Generate and import points list from a geometry.

Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Point cloud generator
=====================
Points cloud generator
======================

With this extension, you can generate point clouds that conform to objects and surfaces.
With this extension, you can generate points clouds that conform to objects and surfaces.

You can access the extension from the icon created on the **Automation** tab using the Extension Manager.

The following image shows the extension user interface:

.. image:: ../../../_static/extensions/point_cloud_ui.png
.. image:: ../../../_static/extensions/points_cloud_ui.png
:width: 800
:alt: Point Cloud Generator UI
:alt: Points Cloud Generator UI


Enables conformal domains for near-field calculations.
Expand Down
Binary file removed doc/source/_static/extensions/point_cloud_ui.png
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/ansys/aedt/core/workflows/hfss/toolkits_catalog.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ template = "run_pyaedt_toolkit_script"
pip = ""

[PointCloudGenerator]
name = "Point Cloud Generator"
script = "point_cloud.py"
name = "Points Cloud Generator"
script = "points_cloud.py"
icon = "images/large/cloud.png"
template = "run_pyaedt_toolkit_script"
pip = ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@
is_student = is_student()

# Extension batch arguments
extension_arguments = {"choice": "", "points": 1000}
extension_description = "Point cloud generator"
extension_arguments = {"choice": "", "points": 1000, "output_file": ""}
extension_description = "Points cloud generator"

Check warning on line 46 in src/ansys/aedt/core/workflows/project/points_cloud.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/workflows/project/points_cloud.py#L45-L46

Added lines #L45 - L46 were not covered by tests


def frontend(): # pragma: no cover
import tkinter
from tkinter import filedialog
import tkinter.ttk as ttk

import PIL.Image
Expand Down Expand Up @@ -75,13 +76,7 @@
project_name = active_project.GetName()
design_name = active_design.GetName()

hfss = get_pyaedt_app(project_name, design_name)

if hfss.design_type != "HFSS": # pragma: no cover
app.logger.error("Active design is not HFSS.")
hfss.release_desktop(False, False)
output_dict = {"choice": "", "file_path": ""}
return output_dict
aedtapp = get_pyaedt_app(project_name, design_name)

# Create UI
master = tkinter.Tk()
Expand Down Expand Up @@ -111,17 +106,17 @@
# Set background color of the window (optional)
master.configure(bg=theme.light["widget_bg"])

hfss.modeler.model_units = "mm"
hfss.modeler.set_working_coordinate_system(name="Global")
aedtapp.modeler.model_units = "mm"
aedtapp.modeler.set_working_coordinate_system(name="Global")

aedt_solids = hfss.modeler.get_objects_in_group("Solids")
aedt_sheets = hfss.modeler.get_objects_in_group("Sheets")
aedt_solids = aedtapp.modeler.get_objects_in_group("Solids")
aedt_sheets = aedtapp.modeler.get_objects_in_group("Sheets")

if not aedt_solids and aedt_sheets:
if not aedt_solids and not aedt_sheets:
msg = "No solids or sheets are defined in this design."
messagebox.showerror("Error", msg)
app.logger.error(msg)
hfss.release_desktop(False, False)
aedtapp.release_desktop(False, False)
output_dict = {}
return output_dict

Expand All @@ -130,20 +125,31 @@
label.grid(row=0, column=0, pady=10)

# Dropdown menu for objects and surfaces
combo = ttk.Combobox(master, width=30, style="PyAEDT.TCombobox", state="readonly")

values = ["--- Objects ---"]
if aedt_solids:
values.extend(aedt_solids)

values.append("--- Surfaces ---")
if aedt_sheets:
values.extend(aedt_sheets)
combo["values"] = values

combo.current(1)
combo.grid(row=0, column=1, pady=10, padx=5)
combo.focus_set()
# Determine the height of the ListBox
listbox_height = min(len(values), 6)
objects_list_frame = tkinter.Frame(master, width=20)
objects_list_frame.grid(row=0, column=1, pady=10, padx=10, sticky="ew")
objects_list_lb = tkinter.Listbox(
objects_list_frame,
selectmode=tkinter.MULTIPLE,
justify=tkinter.CENTER,
exportselection=False,
height=listbox_height,
)
objects_list_lb.pack(expand=True, fill=tkinter.BOTH, side=tkinter.LEFT)
if len(values) > 6:
scroll_bar = tkinter.Scrollbar(objects_list_frame, orient=tkinter.VERTICAL, command=objects_list_lb.yview)
scroll_bar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
objects_list_lb.config(yscrollcommand=scroll_bar.set, height=listbox_height)
for obj in values:
objects_list_lb.insert(tkinter.END, obj)

# Points entry
points_label = ttk.Label(master, text="Number of Points:", width=20, style="PyAEDT.TLabel")
Expand All @@ -153,6 +159,13 @@
points_entry.grid(row=1, column=1, pady=15, padx=10)
points_entry.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font)

# output file entry
output_file_label = ttk.Label(master, text="Output File:", width=20, style="PyAEDT.TLabel")
output_file_label.grid(row=2, column=0, padx=15, pady=10)
output_file_entry = tkinter.Text(master, width=40, height=1, wrap=tkinter.WORD)
output_file_entry.grid(row=2, column=1, pady=15, padx=10)
output_file_entry.configure(bg=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font)

def toggle_theme():
if master.theme == "light":
set_dark_theme()
Expand All @@ -166,15 +179,45 @@
points_entry.configure(
background=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font
)
objects_list_lb.configure(
background=theme.light["widget_bg"], foreground=theme.light["text"], font=theme.default_font
)
if len(values) > 6:
scroll_bar.configure(background=theme.light["widget_bg"])
output_file_entry.configure(
background=theme.light["pane_bg"], foreground=theme.light["text"], font=theme.default_font
)
theme.apply_light_theme(style)
change_theme_button.config(text="\u263D")

def set_dark_theme():
master.configure(bg=theme.dark["widget_bg"])
points_entry.configure(background=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font)
objects_list_lb.configure(
background=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font
)
if len(values) > 6:
scroll_bar.configure(background=theme.dark["widget_bg"])
output_file_entry.configure(
background=theme.dark["pane_bg"], foreground=theme.dark["text"], font=theme.default_font
)
theme.apply_dark_theme(style)
change_theme_button.config(text="\u2600")

def browse_location():
filename = filedialog.asksaveasfilename(
initialdir="/",
defaultextension=".pts",
filetypes=(("Points file", ".pts"), ("all files", "*.*")),
)
output_file_entry.insert(tkinter.END, filename)
master.file_path = output_file_entry.get("1.0", tkinter.END).strip()

output_file_button = ttk.Button(
master, text="Save as...", width=20, command=browse_location, style="PyAEDT.TButton"
)
output_file_button.grid(row=2, column=2, padx=0)

# Create a frame for the toggle button to position it correctly
button_frame = ttk.Frame(master, style="PyAEDT.TFrame", relief=tkinter.SUNKEN, borderwidth=2)
button_frame.grid(row=3, column=2, pady=10, padx=10)
Expand All @@ -188,40 +231,50 @@

def callback():
master.flag = True
selected_item = combo.get()
if selected_item in ["--- Objects ---", "--- Surfaces ---", ""]:
selected_objects = objects_list_lb.curselection()
master.assignment = [objects_list_lb.get(i) for i in selected_objects]
if not selected_objects or any(
element in selected_objects for element in ["--- Objects ---", "--- Surfaces ---", ""]
):
messagebox.showerror("Error", "Please select a valid object or surface.")
master.flag = False
aedtapp.release_desktop(False, False)
output_dict = {}
return output_dict

points = points_entry.get("1.0", tkinter.END).strip()
num_points = int(points)
if num_points <= 0:
master.flag = False
messagebox.showerror("Error", "Number of points must be greater than zero.")

master.assignment = selected_item
master.points = points

master.output_file = output_file_entry.get("1.0", tkinter.END).strip()
master.destroy()

def preview():
try:
selected_item = combo.get()
if selected_item in ["--- Objects ---", "--- Surfaces ---", ""]:
selected_objects = [objects_list_lb.get(i) for i in objects_list_lb.curselection()]
if not selected_objects or any(
element in selected_objects for element in ["--- Objects ---", "--- Surfaces ---", ""]
):
messagebox.showerror("Error", "Please select a valid object or surface.")
return None
master.flag = False
aedtapp.release_desktop(False, False)
output_dict = {}
return output_dict
points = points_entry.get("1.0", tkinter.END).strip()
num_points = int(points)
if num_points <= 0:
messagebox.showerror("Error", "Number of points must be greater than zero.")
return None

# Export the mesh and generate point cloud
output_file = hfss.post.export_model_obj(assignment=selected_item)
output_file = aedtapp.post.export_model_obj(assignment=selected_objects)

if not output_file or not Path(output_file[0][0]).is_file():
messagebox.showerror("Error", "Object could not be exported.")
hfss.release_desktop(False, False)
aedtapp.release_desktop(False, False)
output_dict = {"choice": "", "file_path": ""}
return output_dict

Expand All @@ -239,7 +292,7 @@

except Exception as e:
messagebox.showerror("Error", str(e))
hfss.release_desktop(False, False)
aedtapp.release_desktop(False, False)
output_dict = {}
return output_dict

Expand All @@ -253,11 +306,12 @@

assignment = getattr(master, "assignment", extension_arguments["choice"])
points = getattr(master, "points", extension_arguments["points"])
output_file = getattr(master, "output_file", extension_arguments["output_file"])

hfss.release_desktop(False, False)
aedtapp.release_desktop(False, False)
output_dict = {}
if master.flag:
output_dict = {"choice": assignment, "points": points}
output_dict = {"choice": assignment, "points": points, "output_file": output_file}
return output_dict


Expand All @@ -279,19 +333,14 @@
else:
design_name = active_design.GetName()

hfss = get_pyaedt_app(project_name, design_name)

if hfss.design_type != "HFSS": # pragma: no cover
app.logger.error("Active design is not HFSS.")
if not extension_args["is_test"]:
app.release_desktop(False, False)
return False
aedtapp = get_pyaedt_app(project_name, design_name)

Check warning on line 336 in src/ansys/aedt/core/workflows/project/points_cloud.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/workflows/project/points_cloud.py#L336

Added line #L336 was not covered by tests

assignment = extension_args.get("choice", extension_arguments["choice"])
points = extension_args.get("points", extension_arguments["points"])
output_file = extension_args.get("output_file", extension_arguments["output_file"])

Check warning on line 340 in src/ansys/aedt/core/workflows/project/points_cloud.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/workflows/project/points_cloud.py#L340

Added line #L340 was not covered by tests

# Export the mesh and generate point cloud
output_file = hfss.post.export_model_obj(assignment=assignment)
output_file = aedtapp.post.export_model_obj(assignment=assignment, export_path=Path(output_file).parent)

Check warning on line 343 in src/ansys/aedt/core/workflows/project/points_cloud.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/workflows/project/points_cloud.py#L343

Added line #L343 was not covered by tests

goemetry_file = output_file[0][0]

Expand All @@ -300,8 +349,9 @@
model_plotter.add_object(goemetry_file)
point_values = model_plotter.point_cloud(points=int(points))

for input_file, _ in point_values.values():
_ = hfss.insert_near_field_points(input_file=input_file)
if aedtapp.design_type == "HFSS":
for input_file, _ in point_values.values():
_ = aedtapp.insert_near_field_points(input_file=input_file)

Check warning on line 354 in src/ansys/aedt/core/workflows/project/points_cloud.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/aedt/core/workflows/project/points_cloud.py#L352-L354

Added lines #L352 - L354 were not covered by tests

if not extension_args["is_test"]: # pragma: no cover
app.release_desktop(False, False)
Expand Down
4 changes: 2 additions & 2 deletions tests/system/visualization/test_45_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,10 +651,10 @@ def test_19_shielding_effectiveness(self, add_app, local_scratch):
def test_20_point_cloud(self, add_app, local_scratch):
aedtapp = add_app(project_name=point_cloud_generator, subfolder=test_subfolder)

from ansys.aedt.core.workflows.hfss.point_cloud import main
from ansys.aedt.core.workflows.project.points_cloud import main

# No choice
assert main({"is_test": True, "choice": "Torus1", "points": 1000})
assert main({"is_test": True, "choice": "Torus1", "points": 1000, "output_file": local_scratch.path})

aedtapp.close_project(aedtapp.project_name)

Expand Down