Merge pull request #113 from hyOzd/multiface-workplane
create workplane from multiple co-planar faces
This commit is contained in:
		| @ -262,7 +262,6 @@ class CQ(object): | ||||
|  | ||||
|     def workplane(self, offset=0.0, invert=False): | ||||
|         """ | ||||
|  | ||||
|         Creates a new 2-D workplane, located relative to the first face on the stack. | ||||
|  | ||||
|         :param offset:  offset for the work plane in the Z direction. Default | ||||
| @ -271,14 +270,18 @@ class CQ(object): | ||||
|         :type invert: boolean or None=False | ||||
|         :rtype: Workplane object ( which is a subclass of CQ ) | ||||
|  | ||||
|         The first element on the stack must be a face, or a vertex.  If a vertex, then the parent | ||||
|         item on the chain immediately before the vertex must be a face. | ||||
|         The first element on the stack must be a face, a set of | ||||
|         co-planar faces or a vertex.  If a vertex, then the parent | ||||
|         item on the chain immediately before the vertex must be a | ||||
|         face. | ||||
|  | ||||
|         The result will be a 2-d working plane | ||||
|         with a new coordinate system set up as follows: | ||||
|  | ||||
|            * The origin will be located in the *center* of the face, if a face was selected. If a | ||||
|              vertex was selected, the origin will be at the vertex, and located on the face. | ||||
|            * The origin will be located in the *center* of the | ||||
|              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 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 | ||||
| @ -298,11 +301,21 @@ class CQ(object): | ||||
|             For now you can work around by creating a workplane and then offsetting the center | ||||
|             afterwards. | ||||
|         """ | ||||
|         if len(self.objects) > 1: | ||||
|             raise ValueError("Workplane cannot be created if more than" | ||||
|                              " 1 object is selected.") | ||||
|         def _isCoPlanar(f0, f1): | ||||
|             """Test if two faces are on the same plane.""" | ||||
|             p0 = f0.Center() | ||||
|             p1 = f1.Center() | ||||
|             n0 = f0.normalAt() | ||||
|             n1 = f1.normalAt() | ||||
|  | ||||
|         obj = self.objects[0] | ||||
|             # test normals (direction of planes) | ||||
|             if not ((abs(n0.x-n1.x) < self.ctx.tolerance) or | ||||
|                     (abs(n0.y-n1.y) < self.ctx.tolerance) or | ||||
|                     (abs(n0.z-n1.z) < self.ctx.tolerance)): | ||||
|                 return False | ||||
|  | ||||
|             # test if p1 is on the plane of f0 (offset of planes) | ||||
|             return abs(n0.dot(p0.sub(p1)) < self.ctx.tolerance) | ||||
|  | ||||
|         def _computeXdir(normal): | ||||
|             """ | ||||
| @ -317,18 +330,23 @@ class CQ(object): | ||||
|                 xd = Vector(1, 0, 0) | ||||
|             return xd | ||||
|  | ||||
|         faceToBuildOn = None | ||||
|         center = None | ||||
|         #if isinstance(obj,Vertex): | ||||
|         #    f = self.parent.objects[0] | ||||
|         #    if f != None and isinstance(f,Face): | ||||
|         #        center = obj.Center() | ||||
|         #        normal = f.normalAt(center) | ||||
|         #        xDir = _computeXdir(normal) | ||||
|         #    else: | ||||
|         #        raise ValueError("If a vertex is selected, a face must be the immediate parent") | ||||
|         if len(self.objects) > 1: | ||||
|             # are all objects 'PLANE'? | ||||
|             if not all(o.geomType() == 'PLANE' for o in self.objects): | ||||
|                 raise ValueError("If multiple objects selected, they all must be planar faces.") | ||||
|  | ||||
|             # are all faces co-planar with each other? | ||||
|             if not all(_isCoPlanar(self.objects[0], f) for f in self.objects[1:]): | ||||
|                 raise ValueError("Selected faces must be co-planar.") | ||||
|  | ||||
|             center = Shape.CombinedCenter(self.objects) | ||||
|             normal = self.objects[0].normalAt() | ||||
|             xDir = _computeXdir(normal) | ||||
|  | ||||
|         else: | ||||
|             obj = self.objects[0] | ||||
|  | ||||
|             if isinstance(obj, Face): | ||||
|             faceToBuildOn = obj | ||||
|                 center = obj.Center() | ||||
|                 normal = obj.normalAt(center) | ||||
|                 xDir = _computeXdir(normal) | ||||
|  | ||||
| @ -101,7 +101,7 @@ class Vector(object): | ||||
|         return self.wrapped.dot(v.wrapped) | ||||
|  | ||||
|     def sub(self,v): | ||||
|         return self.wrapped.sub(v.wrapped) | ||||
|         return Vector(self.wrapped.sub(v.wrapped)) | ||||
|  | ||||
|     def add(self,v): | ||||
|         return Vector( self.wrapped.add(v.wrapped)) | ||||
|  | ||||
| @ -198,6 +198,22 @@ class Shape(object): | ||||
|         else: | ||||
|             raise ValueError("Cannot find the center of %s object type" % str(type(self.Solids()[0].wrapped))) | ||||
|  | ||||
|     @staticmethod | ||||
|     def CombinedCenter(objects): | ||||
|         """ | ||||
|         Calculates the center of mass of multiple objects. | ||||
|  | ||||
|         :param objects: a list of objects with mass | ||||
|         """ | ||||
|         total_mass = sum(o.wrapped.Mass for o in objects) | ||||
|         weighted_centers = [o.wrapped.CenterOfMass.multiply(o.wrapped.Mass) for o in objects] | ||||
|  | ||||
|         sum_wc = weighted_centers[0] | ||||
|         for wc in weighted_centers[1:] : | ||||
|             sum_wc = sum_wc.add(wc) | ||||
|  | ||||
|         return Vector(sum_wc.multiply(1./total_mass)) | ||||
|  | ||||
|     def Closed(self): | ||||
|         return self.wrapped.Closed | ||||
|  | ||||
|  | ||||
| @ -480,6 +480,19 @@ class TestCadQuery(BaseTest): | ||||
|         self.saveModel(r) | ||||
|         self.assertEqual(7,r.faces().size() ) | ||||
|  | ||||
|     def testMultiFaceWorkplane(self): | ||||
|         """ | ||||
|         Test Creation of workplane from multiple co-planar face | ||||
|         selection. | ||||
|         """ | ||||
|         s = Workplane('XY').box(1,1,1).faces('>Z').rect(1,0.5).cutBlind(-0.2) | ||||
|  | ||||
|         w = s.faces('>Z').workplane() | ||||
|         o = w.objects[0] # origin of the workplane | ||||
|         self.assertAlmostEqual(o.x, 0., 3) | ||||
|         self.assertAlmostEqual(o.y, 0., 3) | ||||
|         self.assertAlmostEqual(o.z, 0.5, 3) | ||||
|  | ||||
|     def testTriangularPrism(self): | ||||
|         s = Workplane("XY").lineTo(1,0).lineTo(1,1).close().extrude(0.2) | ||||
|         self.saveModel(s) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user