diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c7b8017dfb..a8c516d88a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * Added implementation of `RhinoBrep.fillet()` and `RhinoBrep.filleted()` to `compas_rhino`. +* Added `Frame.invert` and `Frame.inverted`. +* Added `Frame.flip` and `Frame.flipped` as alias for invert and inverted. +* Added `Vector.flip` and `Vector.flipped` as alias for invert and inverted. ### Changed * Fixed `native_edge` property of `RhinoBrepEdge`. +* Expose the parameters `radius` and `nmax` from `compas.topology._face_adjacency` to `compas.topology.face_adjacency` and further propagate them to `unify_cycles` and `Mesh.unify_cycles`. +* Modify `face_adjacency` to avoid using `compas.topology._face_adjacency` by default when there are more than 100 faces, unless one of the parameters `radius`, `nmax` is passed. +* Changed `unify_cycles` to use the first face in the list as root if no root is provided. ### Removed @@ -32,8 +38,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed * Fixed `PluginNotInstalledError` when using `Brep.from_boolean_*` in Rhino. -* Expose the parameters `radius` and `nmax` from `compas.topology._face_adjacency` to `compas.topology.face_adjacency` and further propagate them to `unify_cycles` and `Mesh.unify_cycles`. -* Modify `face_adjacency` to avoid using `compas.topology._face_adjacency` by default when there are more than 100 faces, unless one of the parameters `radius`, `nmax` is passed * Added support for `Polyline` as input for `compas_rhino.Brep.from_extrusion`. ### Removed diff --git a/src/compas/geometry/frame.py b/src/compas/geometry/frame.py index 6f2d703990c..9dc0de6a173 100644 --- a/src/compas/geometry/frame.py +++ b/src/compas/geometry/frame.py @@ -604,6 +604,21 @@ def to_transformation(self): # Methods # ========================================================================== + def invert(self): + """Invert the frame while keeping the X axis fixed.""" + self._yaxis = self.yaxis * -1 + self._zaxis = None + + flip = invert + + def inverted(self): + """Return an inverted copy of the frame.""" + frame = self.copy() # type: Frame + frame.invert() + return frame + + flipped = inverted + def interpolate_frame(self, other, t): """Interpolates between two frames at a given parameter t in the range [0, 1] diff --git a/src/compas/geometry/vector.py b/src/compas/geometry/vector.py index a3b9ea4d07e..d3aac48593f 100644 --- a/src/compas/geometry/vector.py +++ b/src/compas/geometry/vector.py @@ -679,6 +679,8 @@ def invert(self): """ self.scale(-1.0) + flip = invert + def inverted(self): """Returns a inverted copy of this vector @@ -697,6 +699,8 @@ def inverted(self): """ return self.scaled(-1.0) + flipped = inverted + def scale(self, n): """Scale this vector by a factor n. diff --git a/src/compas/topology/orientation.py b/src/compas/topology/orientation.py index b6e735fc3c6..72da1b21e36 100644 --- a/src/compas/topology/orientation.py +++ b/src/compas/topology/orientation.py @@ -2,8 +2,6 @@ from __future__ import division from __future__ import print_function -import random - from compas.geometry import centroid_points from compas.itertools import pairwise from compas.topology import breadth_first_traverse @@ -193,6 +191,11 @@ def unify_cycles(vertices, faces, root=None, nmax=None, max_distance=None): Exception If no all faces are included in the unnification process. + Notes + ----- + The cycles of the faces will be aligned with the cycle direction of the root face. + If no root face is specified, the first face in the list will be used. + """ def unify(node, nbr): @@ -210,7 +213,8 @@ def unify(node, nbr): return if root is None: - root = random.choice(list(range(len(faces)))) + # root = random.choice(list(range(len(faces)))) + root = 0 adj = face_adjacency(vertices, faces, nmax=nmax, max_distance=max_distance) # this is the only place where the vertex coordinates are used diff --git a/tests/compas/geometry/test_frame.py b/tests/compas/geometry/test_frame.py index 8d67d6b4b84..0752762e29b 100644 --- a/tests/compas/geometry/test_frame.py +++ b/tests/compas/geometry/test_frame.py @@ -92,3 +92,35 @@ def test_interpolate_frame_start_end(): three_quarter_frame = frame1.interpolate_frame(frame2, 0.75) assert TOL.is_allclose([math.degrees(three_quarter_frame.axis_angle_vector.y)], [-67.5], atol=TOL.angular) + + +def test_frame_invert(): + frame = Frame([0, 0, 0]) + + assert TOL.is_close(frame.xaxis.dot([1, 0, 0]), 1.0) + assert TOL.is_close(frame.yaxis.dot([0, 1, 0]), 1.0) + assert TOL.is_close(frame.zaxis.dot([0, 0, 1]), 1.0) + + frame.invert() + + assert TOL.is_close(frame.xaxis.dot([1, 0, 0]), 1.0) + assert TOL.is_close(frame.yaxis.dot([0, -1, 0]), 1.0) + assert TOL.is_close(frame.zaxis.dot([0, 0, -1]), 1.0) + + +def test_frame_inverted(): + frame = Frame([0, 0, 0]) + + assert TOL.is_close(frame.xaxis.dot([1, 0, 0]), 1.0) + assert TOL.is_close(frame.yaxis.dot([0, 1, 0]), 1.0) + assert TOL.is_close(frame.zaxis.dot([0, 0, 1]), 1.0) + + other = frame.inverted() + + assert TOL.is_close(frame.xaxis.dot([1, 0, 0]), 1.0) + assert TOL.is_close(frame.yaxis.dot([0, 1, 0]), 1.0) + assert TOL.is_close(frame.zaxis.dot([0, 0, 1]), 1.0) + + assert TOL.is_close(other.xaxis.dot([1, 0, 0]), 1.0) + assert TOL.is_close(other.yaxis.dot([0, -1, 0]), 1.0) + assert TOL.is_close(other.zaxis.dot([0, 0, -1]), 1.0) diff --git a/tests/compas/topology/test_unify_cycles.py b/tests/compas/topology/test_unify_cycles.py index 310b166bf24..390ab8f0a37 100644 --- a/tests/compas/topology/test_unify_cycles.py +++ b/tests/compas/topology/test_unify_cycles.py @@ -14,6 +14,7 @@ def test_unify_cycles(): if compas.IPY: return + test_data = compas.json_load(os.path.join(HERE, "..", "fixtures", "topology", "vertices_faces.json")) vertices = test_data["vertices"] faces = test_data["faces"] @@ -25,12 +26,15 @@ def test_unify_cycles(): # no parameters unify_cycles(vertices, faces) assert TOL.is_close(volume, volume_polyhedron((vertices, faces))) + # only max_nbrs unify_cycles(vertices, faces, nmax=max_nbrs) assert TOL.is_close(volume, volume_polyhedron((vertices, faces))) + # only max_distance unify_cycles(vertices, faces, max_distance=max_edge_length) assert TOL.is_close(volume, volume_polyhedron((vertices, faces))) + # both parameters unify_cycles(vertices, faces, nmax=max_nbrs, max_distance=max_edge_length) assert TOL.is_close(volume, volume_polyhedron((vertices, faces)))