Skip to content

Commit

Permalink
Added is_movement_allowed to Point, updated docs
Browse files Browse the repository at this point in the history
  • Loading branch information
mlauer154 committed Feb 13, 2024
1 parent 4b73993 commit 48face8
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 83 deletions.
2 changes: 1 addition & 1 deletion docs/source/_templates/custom-class-template.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

.. autosummary::
{% for item in methods %}
{%- if item not in inherited_members %}
{%- if item not in inherited_members and item != "__init__" %}
~{{ name }}.{{ item }}
{%- endif %}
{%- endfor %}
Expand Down
2 changes: 1 addition & 1 deletion pymead/core/airfoil.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

class Airfoil(PymeadObj):
"""
This a primary class in `pymead`, which defines an airfoil by a leading edge, a trailing edge,
This is a primary class in `pymead`, which defines an airfoil by a leading edge, a trailing edge,
and optionally an upper-surface endpoint and a lower-surface endpoint in the case of a blunt airfoil. For the
purposes of single-airfoil evaluation method (such as XFOIL or the built-in panel code), instances of this class
are sufficient. For multi-element airfoil evaluation (such as MSES), instances of this class are stored in the
Expand Down
196 changes: 115 additions & 81 deletions pymead/core/point.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@


class Point(PymeadObj):
def __init__(self, x: float, y: float, name: str or None = None, setting_from_geo_col: bool = False,
fixed: bool = False):
def __init__(self, x: float, y: float, name: str or None = None, setting_from_geo_col: bool = False):
super().__init__(sub_container="points")
self._x = None
self._y = None
self._fixed = fixed
self._fixed_weak = False
self.gcs = None
self.root = False
self.rotation_handle = False
Expand All @@ -34,12 +31,6 @@ def x(self):
def y(self):
return self._y

def fixed(self):
return self._fixed

def fixed_weak(self):
return self._fixed_weak

def set_x(self, x: LengthParam or float):
self._x = x if isinstance(x, LengthParam) else LengthParam(
value=x, name=self.name() + ".x", setting_from_geo_col=self.setting_from_geo_col, point=self)
Expand All @@ -54,12 +45,6 @@ def set_y(self, y: LengthParam or float):
self._y.geo_objs.append(self)
self._y.point = self

def set_fixed(self, fixed: bool):
self._fixed = fixed

def set_fixed_weak(self, fixed_weak: bool):
self._fixed_weak = fixed_weak

def set_name(self, name: str):
# Rename the x and y parameters of the Point
if self.x() is not None:
Expand Down Expand Up @@ -102,77 +87,126 @@ def _is_symmetry_123_and_no_edges(self):
return False
return symmetry_constraints

def request_move(self, xp: float, yp: float, force: bool = False):
def is_movement_allowed(self) -> bool:
"""
This method determines if movement is allowed for the point. These cases are:
- Where the constraint solver has not been set or there are no geometric constraints attached
- Where the point is a root or rotation handle of a constraint cluster. If a rotation handle, movement is
allowed, but the movement gets translated to a rotation about the root point with a fixed distance to the
root point
- Where the point is one of the first three out of the four points in a symmetry constraint, and edges are
attached to this point in the constraint graph
Returns
-------
bool
``True`` if movement is allowed for this point, ``False`` otherwise
"""
if self.gcs is None:
return True
if self.gcs is not None and len(self.geo_cons) == 0:
return True
if self.gcs is not None and self.root:
return True
if self.gcs is not None and self.rotation_handle:
# In this case, movement is allowed, but movements get translated to a rotation about the root point with
# a fixed distance to the root point
return True
if self._is_symmetry_123_and_no_edges():
return True
return False

if (self.gcs is None or (self.gcs is not None and len(self.geo_cons) == 0) or force or
(self.gcs is not None and self.root) or (self.gcs is not None and self.rotation_handle)
or self._is_symmetry_123_and_no_edges()):

if self.root:
points_to_update = self.gcs.translate_cluster(self, dx=xp - self.x().value(), dy=yp - self.y().value())
constraints_to_update = []
for point in networkx.dfs_preorder_nodes(self.gcs, source=self):
for geo_con in point.geo_cons:
if geo_con not in constraints_to_update:
constraints_to_update.append(geo_con)

for geo_con in constraints_to_update:
if geo_con.canvas_item is not None:
geo_con.canvas_item.update()
elif self.rotation_handle:
points_to_update, root = self.gcs.rotate_cluster(self, xp, yp)
constraints_to_update = []
for point in networkx.dfs_preorder_nodes(self.gcs, source=root):
for geo_con in point.geo_cons:
if geo_con not in constraints_to_update:
constraints_to_update.append(geo_con)

for geo_con in constraints_to_update:
if geo_con.canvas_item is not None:
geo_con.canvas_item.update()
else:
self.x().set_value(xp)
self.y().set_value(yp)
points_to_update = [self]
symmetry_constraints = self._is_symmetry_123_and_no_edges()
if symmetry_constraints:
for symmetry_constraint in symmetry_constraints:
self.gcs.solve_symmetry_constraint(symmetry_constraint)
points_to_update.extend(symmetry_constraint.child_nodes)
points_to_update = list(set(points_to_update)) # Get only the unique points
for symmetry_constraint in symmetry_constraints:
if symmetry_constraint.canvas_item is not None:
symmetry_constraint.canvas_item.update()

# Update the GUI object, if there is one
if self.canvas_item is not None:
self.canvas_item.updateCanvasItem(self.x().value(), self.y().value())

curves_to_update = []
for point in points_to_update:
if point.canvas_item is not None:
point.canvas_item.updateCanvasItem(point.x().value(), point.y().value())

for curve in point.curves:
if curve not in curves_to_update:
curves_to_update.append(curve)

airfoils_to_update = []
for curve in curves_to_update:
if curve.airfoil is not None and curve.airfoil not in airfoils_to_update:
airfoils_to_update.append(curve.airfoil)
curve.update()

for airfoil in airfoils_to_update:
airfoil.update_coords()
if airfoil.canvas_item is not None:
airfoil.canvas_item.generatePicture()
def request_move(self, xp: float, yp: float, force: bool = False):
"""
Updates the location of the point and updates any curves and canvas items associated with the point movement.
Parameters
----------
xp: float
New :math:`x`-value for the point
yp: float
New :math:`y`-value for the point
force: bool
Force the movement of this point. Overrides ``is_movement_allowed``.
Warning
-------
The ``force`` keyword argument should **never** be called directly from the API, or unexpected behavior
may result. This argument is used in the backend code for the constraint solver in the symmetry and curvature
constraints.
"""

if not self.is_movement_allowed() and not force:
return

if self.root:
points_to_update = self.gcs.translate_cluster(self, dx=xp - self.x().value(), dy=yp - self.y().value())
constraints_to_update = []
for point in networkx.dfs_preorder_nodes(self.gcs, source=self):
for geo_con in point.geo_cons:
if geo_con not in constraints_to_update:
constraints_to_update.append(geo_con)

for geo_con in constraints_to_update:
if geo_con.canvas_item is not None:
geo_con.canvas_item.update()
elif self.rotation_handle:
points_to_update, root = self.gcs.rotate_cluster(self, xp, yp)
constraints_to_update = []
for point in networkx.dfs_preorder_nodes(self.gcs, source=root):
for geo_con in point.geo_cons:
if geo_con not in constraints_to_update:
constraints_to_update.append(geo_con)

for geo_con in constraints_to_update:
if geo_con.canvas_item is not None:
geo_con.canvas_item.update()
else:
self.x().set_value(xp)
self.y().set_value(yp)
points_to_update = [self]
symmetry_constraints = self._is_symmetry_123_and_no_edges()
if symmetry_constraints:
for symmetry_constraint in symmetry_constraints:
self.gcs.solve_symmetry_constraint(symmetry_constraint)
points_to_update.extend(symmetry_constraint.child_nodes)
points_to_update = list(set(points_to_update)) # Get only the unique points
for symmetry_constraint in symmetry_constraints:
if symmetry_constraint.canvas_item is not None:
symmetry_constraint.canvas_item.update()

# Update the GUI object, if there is one
if self.canvas_item is not None:
self.canvas_item.updateCanvasItem(self.x().value(), self.y().value())

curves_to_update = []
for point in points_to_update:
if point.canvas_item is not None:
point.canvas_item.updateCanvasItem(point.x().value(), point.y().value())

for curve in point.curves:
if curve not in curves_to_update:
curves_to_update.append(curve)

airfoils_to_update = []
for curve in curves_to_update:
if curve.airfoil is not None and curve.airfoil not in airfoils_to_update:
airfoils_to_update.append(curve.airfoil)
curve.update()

for airfoil in airfoils_to_update:
airfoil.update_coords()
if airfoil.canvas_item is not None:
airfoil.canvas_item.generatePicture()

def __repr__(self):
return f"Point {self.name()}<x={self.x().value():.6f}, y={self.y().value():.6f}>"

def get_dict_rep(self):
return {"x": float(self.x().value()), "y": float(self.y().value()), "fixed": self.fixed()}
return {"x": float(self.x().value()), "y": float(self.y().value())}


class PointSequence:
Expand Down
15 changes: 15 additions & 0 deletions pymead/core/pymead_obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ class PymeadObj(ABC, DualRep):
"""

def __init__(self, sub_container: str):
"""
Parameters
----------
sub_container: str
Sub-container where this object will be stored in the ``GeometryCollection``
"""
self.sub_container = sub_container
self._name = None
self.geo_col = None
Expand Down Expand Up @@ -70,4 +76,13 @@ def set_name(self, name: str):

@abstractmethod
def get_dict_rep(self) -> dict:
"""
Gets a dictionary representation of the pymead object. In general, this dictionary should consist of only
the required arguments for object instantiation. For example, the dictionary representation of a point looks
something like this: ``{"x": 0.3, "y": 0.5}``. If the argument requires a reference to a ``PymeadObj``
rather than a string or float value, the ``name()`` method should be the value that is stored. For
an example, see the overridden value of this method in ``pymead.core.airfoil.Airfoil``.
All subclasses of ``PymeadObj`` must implement this method, since it is the way pymead objects are stored in
saved instances of a ``GeometryCollection`` (``.jmea`` files).
"""
pass

0 comments on commit 48face8

Please sign in to comment.