diff --git a/docs/source/todo.md b/docs/source/todo.md
index 65d59ea1..ffa957dc 100644
--- a/docs/source/todo.md
+++ b/docs/source/todo.md
@@ -14,9 +14,9 @@ Planned feature additions
- Add variable constraint GUI object positioning (add keyword arguments that are stored in the `.jmea` files)
- Add error handling for over-constrained/Jax shape/no solution in GCS
- Add an undo/redo framework to the GUI
-- Graphical highlighting of constraint parameters and constraints
+- Graphical highlighting of constraint parameters and constraints - make hoverEnter detection on constraint canvas items
+- Tie parameter hover to associated constraint hover events
- Add unit selection ComboBox
-- Add renaming to parameters
- Make the "Analysis" tab focused by default after an aerodynamics analysis (possibly implement a user option to
override this behavior)
- Write the XFOIL/MSES analysis code using the same `CPUBoundProcess` architecture used by optimization
@@ -40,6 +40,8 @@ Bug fixes
- Remove wave/viscous drag from XFOIL drag history plots (optimization)
- Fix symmetry constraint having switched target/tool points (perhaps automatically create the mirror point?)
- Toggle grid affects all the dock widgets
+- Apply theme to status bar widgets immediately on theme change
+- Correct dimensions having default colors before switching themes
Testing
-------
diff --git a/pymead/core/gcs2.py b/pymead/core/gcs2.py
index a2ad7ae1..12b66995 100644
--- a/pymead/core/gcs2.py
+++ b/pymead/core/gcs2.py
@@ -248,10 +248,10 @@ def solve(self, source: GeoCon):
d_angle = new_angle - old_angle
rotation_point = source.p2
- if edge_data_p21 and edge_data_p21["angle"] is source:
+ if edge_data_p21 and "angle" in edge_data_p21 and edge_data_p21["angle"] is source:
start = source.p1
# d_angle *= -1
- elif edge_data_p23 and edge_data_p23["angle"] is source:
+ elif edge_data_p23 and "angle" in edge_data_p23 and edge_data_p23["angle"] is source:
start = source.p3
d_angle *= -1
else:
@@ -279,9 +279,9 @@ def solve(self, source: GeoCon):
d_angle = new_angle - old_angle
rotation_point = source.vertex
- if edge_data_p21 and edge_data_p21["angle"] is source:
+ if edge_data_p21 and "angle" in edge_data_p21 and edge_data_p21["angle"] is source:
pass
- elif edge_data_p23 and edge_data_p23["angle"] is source:
+ elif edge_data_p23 and "angle" in edge_data_p23 and edge_data_p23["angle"] is source:
pass
d_angle *= -1
else:
diff --git a/pymead/gui/airfoil_canvas.py b/pymead/gui/airfoil_canvas.py
index 298110ed..a572f51f 100644
--- a/pymead/gui/airfoil_canvas.py
+++ b/pymead/gui/airfoil_canvas.py
@@ -2,6 +2,7 @@
import typing
import sys
from copy import deepcopy
+from functools import partial
import numpy as np
import pyqtgraph as pg
@@ -206,7 +207,7 @@ def addPymeadCanvasItem(self, pymead_obj: PymeadObj):
pymead_obj.canvas_item.sigPolyExit.connect(self.airfoil_exited)
@staticmethod
- def runSelectionEventLoop(drawing_object: str, starting_message: str):
+ def runSelectionEventLoop(drawing_object: str, starting_message: str, enter_callback: typing.Callable = None):
drawing_object = drawing_object
starting_message = starting_message
@@ -215,7 +216,11 @@ def wrapped(self, *args, **kwargs):
self.drawing_object = drawing_object
self.sigStatusBarUpdate.emit(starting_message, 0)
loop = QEventLoop()
- self.sigEnterPressed.connect(loop.quit)
+ connection = None
+ if enter_callback:
+ connection = self.sigEnterPressed.connect(partial(enter_callback, self))
+ else:
+ self.sigEnterPressed.connect(loop.quit)
self.sigEscapePressed.connect(loop.quit)
loop.exec()
# if len(self.geo_col.selected_objects["points"]) > 0:
@@ -226,6 +231,8 @@ def wrapped(self, *args, **kwargs):
# self.clearSelectedObjects()
self.drawing_object = None
self.sigStatusBarUpdate.emit("", 0)
+ if enter_callback:
+ self.sigEnterPressed.disconnect(connection)
return wrapped
return decorator
@@ -234,6 +241,18 @@ def wrapped(self, *args, **kwargs):
def drawPoints(self):
pass
+ def drawBezierNoEvent(self):
+ if len(self.geo_col.selected_objects["points"]) < 2:
+ msg = f"Choose at least 2 points to define a curve"
+ self.sigStatusBarUpdate.emit(msg, 2000)
+ return
+
+ point_sequence = PointSequence([pt for pt in self.geo_col.selected_objects["points"]])
+ self.geo_col.add_bezier(point_sequence=point_sequence)
+
+ self.clearSelectedObjects()
+ self.sigStatusBarUpdate.emit("Select the first Bezier control point of the next curve", 0)
+
@runSelectionEventLoop(drawing_object="Bezier", starting_message="Select the first Bezier control point")
def drawBezier(self):
if len(self.geo_col.selected_objects["points"]) < 2:
@@ -244,6 +263,14 @@ def drawBezier(self):
point_sequence = PointSequence([pt for pt in self.geo_col.selected_objects["points"]])
self.geo_col.add_bezier(point_sequence=point_sequence)
+ @runSelectionEventLoop(drawing_object="Beziers", starting_message="Select the first Bezier control point",
+ enter_callback=drawBezierNoEvent)
+ def drawBeziers(self):
+ if len(self.geo_col.selected_objects["points"]) < 2:
+ msg = f"Choose at least 2 points to define a curve"
+ self.sigStatusBarUpdate.emit(msg, 2000)
+ return
+
@runSelectionEventLoop(drawing_object="LineSegment", starting_message="Select the first line endpoint")
def drawLineSegment(self):
if len(self.geo_col.selected_objects["points"]) < 2:
@@ -254,8 +281,12 @@ def drawLineSegment(self):
point_sequence = PointSequence([pt for pt in self.geo_col.selected_objects["points"]])
self.geo_col.add_line(point_sequence=point_sequence)
+ @runSelectionEventLoop(drawing_object="LineSegments", starting_message="Select the first line endpoint")
def drawLines(self):
- self.drawLineSegment()
+ if len(self.geo_col.selected_objects["points"]) < 2:
+ msg = f"Choose at least 2 points to define a curve"
+ self.sigStatusBarUpdate.emit(msg, 2000)
+ return
@runSelectionEventLoop(drawing_object="Airfoil", starting_message="Select the leading edge point")
def generateAirfoil(self):
@@ -263,6 +294,7 @@ def generateAirfoil(self):
self.sigStatusBarUpdate.emit(
"Choose either 2 points (sharp trailing edge) or 4 points (blunt trailing edge)"
" to generate an airfoil", 4000)
+ return
le = self.geo_col.selected_objects["points"][0]
te = self.geo_col.selected_objects["points"][1]
@@ -291,6 +323,7 @@ def addLengthDimension(self):
self.sigStatusBarUpdate.emit("Choose either 2 points (no length parameter) or 3 points "
"(specified length parameter)"
" to add a length dimension", 4000)
+ return
tool_point = self.geo_col.selected_objects["points"][0]
target_point = self.geo_col.selected_objects["points"][1]
@@ -302,6 +335,7 @@ def addLengthDimension(self):
def addDistanceConstraint(self):
if len(self.geo_col.selected_objects["points"]) != 2:
self.sigStatusBarUpdate.emit("Choose exactly two points to define a distance constraint", 4000)
+ return
# p1 = self.geo_col.selected_objects["points"][0]
# p2 = self.geo_col.selected_objects["points"][1]
@@ -321,6 +355,7 @@ def addAngleDimension(self):
self.sigStatusBarUpdate.emit("Choose either 2 points (no angle parameter) or 3 points "
"(specified angle parameter)"
" to add an angle dimension", 4000)
+ return
tool_point = self.geo_col.selected_objects["points"][0]
target_point = self.geo_col.selected_objects["points"][1]
angle_param = None if len(self.geo_col.selected_objects["points"]) <= 2 else self.geo_col.selected_objects["points"][2]
@@ -424,14 +459,35 @@ def pointClicked(self, scatter_item, spot, ev, point_item):
self.geo_col.select_object(point_item.point)
n_ctrl_pts = len(self.geo_col.selected_objects["points"])
degree = n_ctrl_pts - 1
- msg = (f"Added control point to curve. Number of control points: {len(self.geo_col.selected_objects['points'])} "
+ msg = (f"Added control point to curve. Number of control points: "
+ f"{len(self.geo_col.selected_objects['points'])} "
+ f"(degree: {degree}). Press 'Enter' to generate the curve.")
+ self.sigStatusBarUpdate.emit(msg, 0)
+ elif self.drawing_object == "Beziers":
+ self.geo_col.select_object(point_item.point)
+ n_ctrl_pts = len(self.geo_col.selected_objects["points"])
+ degree = n_ctrl_pts - 1
+ msg = (f"Added control point to curve. Number of control points: "
+ f"{len(self.geo_col.selected_objects['points'])} "
f"(degree: {degree}). Press 'Enter' to generate the curve.")
self.sigStatusBarUpdate.emit(msg, 0)
elif self.drawing_object == "LineSegment":
if len(self.geo_col.selected_objects["points"]) < 2:
self.geo_col.select_object(point_item.point)
+ if len(self.geo_col.selected_objects["points"]) == 1:
+ self.sigStatusBarUpdate.emit("Next, choose the line's endpoint", 0)
if len(self.geo_col.selected_objects["points"]) == 2:
self.sigEnterPressed.emit() # Complete the line after selecting the second point
+ elif self.drawing_object == "LineSegments":
+ if len(self.geo_col.selected_objects["points"]) < 2:
+ self.geo_col.select_object(point_item.point)
+ if len(self.geo_col.selected_objects["points"]) == 1:
+ self.sigStatusBarUpdate.emit("Next, choose the line's endpoint", 0)
+ if len(self.geo_col.selected_objects["points"]) == 2:
+ point_sequence = PointSequence([pt for pt in self.geo_col.selected_objects["points"]])
+ self.geo_col.add_line(point_sequence=point_sequence)
+ self.clearSelectedObjects()
+ self.sigStatusBarUpdate.emit("Choose the next line's start point", 0)
elif self.adding_point_to_curve is not None:
if len(self.geo_col.selected_objects["points"]) < 2:
self.geo_col.select_object(point_item.point)
diff --git a/pymead/gui/gui.py b/pymead/gui/gui.py
index 6f49bedd..ba07f4e0 100644
--- a/pymead/gui/gui.py
+++ b/pymead/gui/gui.py
@@ -459,6 +459,14 @@ def set_theme(self, theme_name: str):
for cnstr in self.geo_col.container()["geocon"].values():
cnstr.canvas_item.setStyle(theme)
+ for button_name, button_setting in self.main_icon_toolbar.button_settings.items():
+ icon_name = button_setting["icon"]
+ if "dark" not in icon_name:
+ continue
+
+ image_path = os.path.join(ICON_DIR, icon_name.replace("dark", theme_name))
+ self.main_icon_toolbar.buttons[button_name]["button"].setIcon(QIcon(image_path))
+
def set_color_bar_style(self, new_values: dict = None):
if self.cbar is None:
return
diff --git a/pymead/gui/gui_settings/buttons.json b/pymead/gui/gui_settings/buttons.json
index 5f4157cf..96f411a8 100644
--- a/pymead/gui/gui_settings/buttons.json
+++ b/pymead/gui/gui_settings/buttons.json
@@ -13,17 +13,59 @@
"function": "change_background_color_button_toggled"
},
"draw-point": {
- "icon": "point.svg",
+ "icon": "point_dark.svg",
"status_tip": "Draw points",
"checkable": false,
"function": "on_draw_points_pressed"
},
"draw-line": {
- "icon": "line.svg",
+ "icon": "line_dark.svg",
"status_tip": "Draw lines",
"checkable": false,
"function": "on_draw_lines_pressed"
},
+ "draw-bezier": {
+ "icon": "bezier_dark.svg",
+ "status_tip": "Draw Bezier curves",
+ "checkable": false,
+ "function": "on_draw_bezier_pressed"
+ },
+ "add-distance-constraint": {
+ "icon": "distance_constraint_dark.svg",
+ "status_tip": "Add a distance constraint",
+ "checkable": false,
+ "function": "on_add_distance_constraint_pressed"
+ },
+ "add-rel-angle-constraint": {
+ "icon": "rel_angle_constraint_dark.svg",
+ "status_tip": "Add a relative angle constraint",
+ "checkable": false,
+ "function": "on_add_rel_angle_constraint_pressed"
+ },
+ "add-perp-constraint": {
+ "icon": "perp3_constraint_dark.svg",
+ "status_tip": "Add a perpendicular constraint",
+ "checkable": false,
+ "function": "on_add_perp_constraint_pressed"
+ },
+ "add-anti-parallel-constraint": {
+ "icon": "anti_parallel_constraint_dark.svg",
+ "status_tip": "Add an anti-parallel constraint",
+ "checkable": false,
+ "function": "on_add_anti_parallel_constraint_pressed"
+ },
+ "add-symmetry-constraint": {
+ "icon": "symmetry_constraint_dark.svg",
+ "status_tip": "Add a symmetry constraint",
+ "checkable": false,
+ "function": "on_add_symmetry_constraint_pressed"
+ },
+ "add-roc-constraint": {
+ "icon": "roc_constraint_dark.svg",
+ "status_tip": "Add a radius of curvature constraint",
+ "checkable": false,
+ "function": "on_add_roc_constraint_pressed"
+ },
"stop-optimization": {
"icon": "stop.png",
"status_tip": "Terminate optimization",
diff --git a/pymead/gui/main_icon_toolbar.py b/pymead/gui/main_icon_toolbar.py
index 1c5d28bc..a5aebb3d 100644
--- a/pymead/gui/main_icon_toolbar.py
+++ b/pymead/gui/main_icon_toolbar.py
@@ -81,5 +81,26 @@ def on_draw_points_pressed(self):
def on_draw_lines_pressed(self):
self.parent.airfoil_canvas.drawLines()
+ def on_draw_bezier_pressed(self):
+ self.parent.airfoil_canvas.drawBeziers()
+
+ def on_add_distance_constraint_pressed(self):
+ self.parent.airfoil_canvas.addDistanceConstraint()
+
+ def on_add_rel_angle_constraint_pressed(self):
+ self.parent.airfoil_canvas.addRelAngle3Constraint()
+
+ def on_add_perp_constraint_pressed(self):
+ self.parent.airfoil_canvas.addPerp3Constraint()
+
+ def on_add_anti_parallel_constraint_pressed(self):
+ self.parent.airfoil_canvas.addAntiParallel3Constraint()
+
+ def on_add_symmetry_constraint_pressed(self):
+ self.parent.airfoil_canvas.addSymmetryConstraint()
+
+ def on_add_roc_constraint_pressed(self):
+ self.parent.airfoil_canvas.addROCurvatureConstraint()
+
def on_help_button_pressed(self):
self.parent.show_help()
diff --git a/pymead/icons/anti_parallel_constraint_dark.svg b/pymead/icons/anti_parallel_constraint_dark.svg
new file mode 100644
index 00000000..38237dff
--- /dev/null
+++ b/pymead/icons/anti_parallel_constraint_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/anti_parallel_constraint_light.svg b/pymead/icons/anti_parallel_constraint_light.svg
new file mode 100644
index 00000000..26852ffc
--- /dev/null
+++ b/pymead/icons/anti_parallel_constraint_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/bezier_dark.svg b/pymead/icons/bezier_dark.svg
new file mode 100644
index 00000000..f9a9057d
--- /dev/null
+++ b/pymead/icons/bezier_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/bezier_light.svg b/pymead/icons/bezier_light.svg
new file mode 100644
index 00000000..140af7e6
--- /dev/null
+++ b/pymead/icons/bezier_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/distance_constraint_dark.svg b/pymead/icons/distance_constraint_dark.svg
new file mode 100644
index 00000000..6be7a0e9
--- /dev/null
+++ b/pymead/icons/distance_constraint_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/distance_constraint_light.svg b/pymead/icons/distance_constraint_light.svg
new file mode 100644
index 00000000..95051f51
--- /dev/null
+++ b/pymead/icons/distance_constraint_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/line.svg b/pymead/icons/line.svg
deleted file mode 100644
index 37a4a6e1..00000000
--- a/pymead/icons/line.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/pymead/icons/line_dark.svg b/pymead/icons/line_dark.svg
new file mode 100644
index 00000000..6eb89f6e
--- /dev/null
+++ b/pymead/icons/line_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/line_light.svg b/pymead/icons/line_light.svg
new file mode 100644
index 00000000..3513d62c
--- /dev/null
+++ b/pymead/icons/line_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/perp3_constraint_dark.svg b/pymead/icons/perp3_constraint_dark.svg
new file mode 100644
index 00000000..a2672cff
--- /dev/null
+++ b/pymead/icons/perp3_constraint_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/perp3_constraint_light.svg b/pymead/icons/perp3_constraint_light.svg
new file mode 100644
index 00000000..75995923
--- /dev/null
+++ b/pymead/icons/perp3_constraint_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/point.svg b/pymead/icons/point_dark.svg
similarity index 92%
rename from pymead/icons/point.svg
rename to pymead/icons/point_dark.svg
index 936f190a..572ec0ed 100644
--- a/pymead/icons/point.svg
+++ b/pymead/icons/point_dark.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/pymead/icons/point_light.svg b/pymead/icons/point_light.svg
new file mode 100644
index 00000000..48794e23
--- /dev/null
+++ b/pymead/icons/point_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/rel_angle_constraint_dark.svg b/pymead/icons/rel_angle_constraint_dark.svg
new file mode 100644
index 00000000..f537fed0
--- /dev/null
+++ b/pymead/icons/rel_angle_constraint_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/rel_angle_constraint_light.svg b/pymead/icons/rel_angle_constraint_light.svg
new file mode 100644
index 00000000..b8315a53
--- /dev/null
+++ b/pymead/icons/rel_angle_constraint_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/roc_constraint_dark.svg b/pymead/icons/roc_constraint_dark.svg
new file mode 100644
index 00000000..995b1ee5
--- /dev/null
+++ b/pymead/icons/roc_constraint_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/roc_constraint_light.svg b/pymead/icons/roc_constraint_light.svg
new file mode 100644
index 00000000..a4529e23
--- /dev/null
+++ b/pymead/icons/roc_constraint_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/symmetry_constraint.drawio.png b/pymead/icons/symmetry_constraint.drawio.png
deleted file mode 100644
index 7fcb5949..00000000
Binary files a/pymead/icons/symmetry_constraint.drawio.png and /dev/null differ
diff --git a/pymead/icons/symmetry_constraint_dark.svg b/pymead/icons/symmetry_constraint_dark.svg
new file mode 100644
index 00000000..27ade121
--- /dev/null
+++ b/pymead/icons/symmetry_constraint_dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pymead/icons/symmetry_constraint_light.svg b/pymead/icons/symmetry_constraint_light.svg
new file mode 100644
index 00000000..61411675
--- /dev/null
+++ b/pymead/icons/symmetry_constraint_light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file