diff --git a/cadquery/cq.py b/cadquery/cq.py index 30ee9ae1..0cf41694 100644 --- a/cadquery/cq.py +++ b/cadquery/cq.py @@ -285,7 +285,11 @@ class CQ(object): face/faces, if a face/faces was selected. If a vertex was selected, the origin will be at the vertex, and located on the face. The centerOption paramter sets how the center is defined. - Options are 'CenterOfMass', 'CenterOfBoundBox', or 'ProjectedOrigin'. + Options are 'CenterOfMass', 'CenterOfBoundBox', 'ProjectedOrigin', or + 'ProjectedGlobalOrigin'. 'CenterOfMass' and 'CenterOfBoundBox' are + in relation to the selected face/faces. 'ProjectedOrigin' uses the + current origin that may have been updated by calls to transformed or + center. 'ProjectedGlobalOrigin' projects the global (0,0,0) origin. * The Z direction will be normal to the plane of the face,computed at the center point. * The X direction will be parallel to the x-y plane. If the workplane is parallel to @@ -344,10 +348,12 @@ class CQ(object): if not all(_isCoPlanar(self.objects[0], f) for f in self.objects[1:]): raise ValueError("Selected faces must be co-planar.") - if centerOption == 'CenterOfMass' or centerOption == 'ProjectedOrigin': + if centerOption in {'CenterOfMass', 'ProjectedOrigin', 'ProjectedGlobalOrigin'}: center = Shape.CombinedCenter(self.objects) elif centerOption == 'CenterOfBoundBox': center = Shape.CombinedCenterOfBoundBox(self.objects) + else: + raise ValueError('Undefined value passed to centerOption') normal = self.objects[0].normalAt() xDir = _computeXdir(normal) @@ -356,7 +362,7 @@ class CQ(object): obj = self.objects[0] if isinstance(obj, Face): - if centerOption == 'CenterOfMass' or centerOption == 'ProjectedOrigin': + if centerOption in {'CenterOfMass', 'ProjectedOrigin', 'ProjectedGlobalOrigin'}: center = obj.Center() elif centerOption == 'CenterOfBoundBox': center = obj.CenterOfBoundBox() @@ -364,7 +370,7 @@ class CQ(object): xDir = _computeXdir(normal) else: if hasattr(obj, 'Center'): - if centerOption == 'CenterOfMass' or centerOption == 'ProjectedOrigin': + if centerOption in {'CenterOfMass', 'ProjectedOrigin', 'ProjectedGlobalOrigin'}: center = obj.Center() elif centerOption == 'CenterOfBoundBox': center = obj.CenterOfBoundBox() @@ -376,7 +382,9 @@ class CQ(object): # update center to projected origin if desired if centerOption == 'ProjectedOrigin': - center = Vector(0, 0, 0).projectToPlane(center, normal) + center = self.plane.origin.projectToPlane(Plane(center, xDir, normal)) + elif centerOption == 'ProjectedGlobalOrigin': + center = Vector(0,0,0).projectToPlane(Plane(center, xDir, normal)) # invert if requested if invert: diff --git a/cadquery/occ_impl/geom.py b/cadquery/occ_impl/geom.py index e8d54afc..804fdf06 100644 --- a/cadquery/occ_impl/geom.py +++ b/cadquery/occ_impl/geom.py @@ -143,31 +143,18 @@ class Vector(object): raise NotImplementedError( "Have not needed this yet, but FreeCAD supports it!") - def projectToPlane(self, *args): + def projectToPlane(self, plane): """ Vector is projected onto the plane provided as input. - :param args: Plane object or base and normal vectors that define the plane + :param args: Plane object - This method modifies the vector in place and returns the new vector. + Returns the projected vector. """ - if len(args) == 2: - base = args[0] - normal = args[1] - elif len(args) == 1: - base = args[0].origin - normal = args[0].zDir - else: - raise TypeError("Expected 1 or 2 arguments consisting of 1 Plane object \ -or a base Vector and a normal Vector object.") + base = plane.origin + normal = plane.zDir - result = self-normal*(((self-base).dot(normal))/normal.Length**2) - - self.x = result.x - self.y = result.y - self.z = result.z - - return self + return self-normal*(((self-base).dot(normal))/normal.Length**2) def __neg__(self): return self * -1 diff --git a/tests/TestCadObjects.py b/tests/TestCadObjects.py index 09efaa9b..53c432fe 100644 --- a/tests/TestCadObjects.py +++ b/tests/TestCadObjects.py @@ -161,24 +161,11 @@ class TestCadObjects(BaseTest): base = Vector(5, 7, 9) x_dir = Vector(1, 0, 0) - # test passing plane defined by base and normal - point = Vector(10, 11, 12).projectToPlane(base, normal) - self.assertTupleAlmostEquals(point.toTuple(), (59/7, 55/7, 51/7), - decimal_places) - - point = Vector(10, 11, 12).projectToPlane(base, normal.normalized()) - self.assertTupleAlmostEquals(point.toTuple(), (59/7, 55/7, 51/7), - decimal_places) - # test passing Plane object point = Vector(10, 11, 12).projectToPlane(Plane(base, x_dir, normal)) self.assertTupleAlmostEquals(point.toTuple(), (59/7, 55/7, 51/7), decimal_places) - # test wrong number of input arguments - with self.assertRaises(TypeError): - Vector(10,11.12).projectToPlane(1,1,1) - def testMatrixCreationAndAccess(self): def matrix_vals(m): return [[m[r,c] for c in range(4)] for r in range(4)] diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index 9efc2402..681a938b 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -1924,6 +1924,22 @@ class TestCadQuery(BaseTest): .plane.origin.toTuple() self.assertTupleAlmostEquals(origin, (45.0, 30.0, 10.0), decimal_places) + # test case where plane origin is shifted with center call + r = r.faces(">Z").workplane(centerOption='ProjectedOrigin').center(30,0) \ + .hole(90) + + origin = r.faces(">Z").workplane(centerOption='ProjectedOrigin') \ + .plane.origin.toTuple() + self.assertTupleAlmostEquals(origin, (30.0, 0.0, 10.0), decimal_places) + + origin = r.faces(">Z").workplane(centerOption='ProjectedGlobalOrigin') \ + .plane.origin.toTuple() + self.assertTupleAlmostEquals(origin, (0.0, 0.0, 10.0), decimal_places) + + with self.assertRaises(ValueError): + origin = r.faces(">Z").workplane(centerOption='undefined') + + # make sure projection works in all directions r = Workplane("YZ").polyline(pts).close().extrude(10.0) origin = r.faces(">X").workplane(centerOption='ProjectedOrigin') \ @@ -1951,6 +1967,3 @@ class TestCadQuery(BaseTest): origin = r.faces("