def execute(self, fp): if not fp.Base or not fp.Tools: return fp.Proxy = None if fp.CutDirection == Vector(0.0, 0.0, 0.0): bbox = self.extractCompounds([fp.Base])[0].Shape.BoundBox v = Vector(1, 1, 1) if bbox.XLength < bbox.YLength and bbox.XLength < bbox.ZLength: v.x = 0 elif bbox.YLength <= bbox.XLength and bbox.YLength < bbox.ZLength: v.y = 0 else: v.z = 0 bbox = self.extractCompounds(fp.Tools)[0].Shape.BoundBox if bbox.XLength < bbox.YLength and bbox.XLength < bbox.ZLength: v.x = 0 elif bbox.YLength <= bbox.XLength and bbox.YLength < bbox.ZLength: v.y = 0 else: v.z = 0 fp.CutDirection = v * fp.CutDepth / 50 fp.Proxy = self self.cutNotches(fp)
def cutNotches(self, fp): shapes = [] halfsize = fp.CutDirection / 2 for obj in self.extractCompounds([fp.Base]): useSubPart = obj.TypeId == 'Part::Extrusion' and len( obj.Base.Shape.Faces) > 0 if useSubPart: bShapes = obj.Base else: bShapes = obj for bShape in self.extractShapes([bShapes]): cutcubes = [] for tool in self.extractShapes(fp.Tools): tbox = tool.optimalBoundingBox() common = tool.common(bShape) cbox = common.BoundBox if cbox.XLength + cbox.YLength + cbox.ZLength > epsilon: cbox = common.optimalBoundingBox() vSize = Vector(cbox.XLength, cbox.YLength, cbox.ZLength) vPlace = Vector(cbox.XMin, cbox.YMin, cbox.ZMin) if vSize.x < epsilon or vSize.x > tbox.XLength: vSize.x = tbox.XLength vPlace.x = tbox.XMin if vSize.y < epsilon or vSize.y > tbox.YLength: vSize.y = tbox.YLength vPlace.y = tbox.YMin if vSize.z < epsilon or vSize.z > tbox.ZLength: vSize.z = tbox.ZLength vPlace.z = tbox.ZMin cutcube = Part.makeBox(vSize.x, vSize.y, vSize.z) cutcube.Placement.Base = vPlace cutcube.Placement.Base.x += cbox.XLength * halfsize.x cutcube.Placement.Base.y += cbox.YLength * halfsize.y cutcube.Placement.Base.z += cbox.ZLength * halfsize.z cutcubes.append(cutcube) if len(cutcubes) > 0: try: cutted = bShape.cut(cutcubes) except: cutted = bShape else: cutted = bShape if useSubPart: cutted.Placement.Base -= obj.Dir * float(obj.LengthRev) ext = cutted.extrude(obj.Dir * float(obj.LengthFwd + obj.LengthRev)) shapes.append(ext) else: shapes.append(cutted) fp.Shape = Part.makeCompound(shapes)
def cutNotches(self, fp): shapes = [] halfsize = fp.CutDirection / 2 for obj in self.extractCompounds([fp.Base]): isExtrude = hasattr(obj, "LengthFwd") and hasattr(obj, "Base") if isExtrude: bShapes = obj.Base else: bShapes = obj for bShape in self.extractShapes([bShapes]): cutcubes = [] for tool in self.extractShapes(fp.Tools): tbox = tool.BoundBox common = tool.common(bShape) cbox = common.BoundBox if cbox.XLength + cbox.YLength + cbox.ZLength > epsilon: vSize = Vector(cbox.XLength, cbox.YLength, cbox.ZLength) vPlace = Vector(cbox.XMin, cbox.YMin, cbox.ZMin) if vSize.x < epsilon or vSize.x > tbox.XLength: vSize.x = tbox.XLength vPlace.x = tbox.XMin if vSize.y < epsilon or vSize.y > tbox.YLength: vSize.y = tbox.YLength vPlace.y = tbox.YMin if vSize.z < epsilon or vSize.z > tbox.ZLength: vSize.z = tbox.ZLength vPlace.z = tbox.ZMin cutcube = Part.makeBox(vSize.x, vSize.y, vSize.z) cutcube.Placement.Base = vPlace cutcube.Placement.Base.x += cbox.XLength * halfsize.x cutcube.Placement.Base.y += cbox.YLength * halfsize.y cutcube.Placement.Base.z += cbox.ZLength * halfsize.z cutcubes.append(cutcube) if len(cutcubes) > 0: cutted = bShape.cut(cutcubes) else: cutted = bShape if isExtrude: cutted.Placement.Base -= obj.Dir * float(obj.LengthRev) ext = cutted.extrude(obj.Dir * float(obj.LengthFwd + obj.LengthRev)) shapes.append(ext) else: shapes.append(cutted) if len(shapes) == 1: fp.Shape = shapes[0] elif len(shapes) > 1: fp.Shape = Part.makeCompound(shapes)
def __vol_cog(shape): vol = 0.0 cog = Vector() for solid in shape.Solids: vol += solid.Volume sCoG = solid.CenterOfMass cog.x = cog.x + sCoG.x * solid.Volume cog.y = cog.y + sCoG.y * solid.Volume cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol return cog, vol
def scaleByBoundbox(shape, boundbox, doScaleXYZ, copy=True): basebbox = shape.BoundBox #basebbox=[1.0,1.0,1.0] scalevec = Vector(1, 1, 1) if doScaleXYZ[0] and basebbox.XLength > epsilon: scalevec.x = boundbox.XLength / basebbox.XLength if doScaleXYZ[1] and basebbox.YLength > epsilon: scalevec.y = boundbox.YLength / basebbox.YLength if doScaleXYZ[2] and basebbox.ZLength > epsilon: scalevec.z = boundbox.ZLength / basebbox.ZLength scalevec.x = boundbox.XLength scalevec.y = boundbox.YLength scalevec.z = boundbox.ZLength if scalevec.x < epsilon: if doScaleXYZ[0]: scalevec.x = epsilon else: scalevec.x = 1 if scalevec.y < epsilon: if doScaleXYZ[1]: scalevec.y = epsilon else: scalevec.y = 1 if scalevec.z < epsilon: if doScaleXYZ[2]: scalevec.z = epsilon else: scalevec.z = 1 _rib = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Rib00") print("Scale in scaleByBoundbox") print(scalevec) WingRib( _rib, "/Users/fredericnivoix/Library/Preferences/FreeCAD/Mod/AirPlaneDesign/wingribprofil/naca/naca2412.dat", False, 0, scalevec.x, 0, 0, 0, 0, 0, 0) ViewProviderWingRib(_rib.ViewObject) _rib.Placement = shape.Placement #dolly = scale(shape, scalevec, basebbox.Center, copy) #dolly.Placement = shape.Placement if doScaleXYZ[0]: _rib.Placement.Base.x += boundbox.XMin - basebbox.XMin * scalevec.x if doScaleXYZ[1]: _rib.Placement.Base.y += boundbox.YMin - basebbox.YMin * scalevec.y if doScaleXYZ[2]: _rib.Placement.Base.z += boundbox.ZMin - basebbox.ZMin * scalevec.z return _rib #dolly
def getCoG(self, fp, vol, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Return the fluid volume center of gravity, provided the volume of fluid inside the tank. The returned center of gravity is referred to the untransformed ship. Keyword arguments: fp -- Part::FeaturePython object affected. vol -- Volume of fluid. roll -- Ship roll angle. trim -- Ship trim angle. If the fluid volume is bigger than the total tank one, it will be conveniently clamped. """ if vol <= 0.0: return Vector() if vol >= fp.Shape.Volume: vol = 0.0 cog = Vector() for solid in fp.Shape.Solids: vol += solid.Volume sCoG = solid.CenterOfMass cog.x = cog.x + sCoG.x * solid.Volume cog.y = cog.y + sCoG.y * solid.Volume cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol return cog shape = self.getFluidShape(fp, vol, roll, trim) # Get the center of gravity vol = 0.0 cog = Vector() if len(shape.Solids) > 0: for solid in shape.Solids: vol += solid.Volume sCoG = solid.CenterOfMass cog.x = cog.x + sCoG.x * solid.Volume cog.y = cog.y + sCoG.y * solid.Volume cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol return cog
def pointInversion(circle, point): """Circle inversion of a point. pointInversion(Circle, Vector) Will calculate the inversed point an return it. If the given point is equal to the center of the circle "None" will be returned. See also: http://en.wikipedia.org/wiki/Inversive_geometry """ if (geomType(circle) == "Circle") and isinstance(point, FreeCAD.Vector): cen = circle.Curve.Center rad = circle.Curve.Radius if DraftVecUtils.equals(cen, point): return None # Inverse the distance of the point # dist(cen -> P) = r^2 / dist(cen -> invP) dist = DraftVecUtils.dist(point, cen) invDist = rad**2 / d invPoint = Vector(0, 0, point.z) invPoint.x = cen.x + (point.x - cen.x) * invDist / dist invPoint.y = cen.y + (point.y - cen.y) * invDist / dist return invPoint else: FreeCAD.Console.PrintMessage("debug: pointInversion bad parameters!\n") return None
def scaleByBoundbox2(shape, boundbox, doScaleXYZ): basebbox = shape.BoundBox scalevec = Vector(1, 1, 1) x = shape.Placement.Base.x y = shape.Placement.Base.y z = shape.Placement.Base.z if doScaleXYZ[0] and basebbox.XLength > epsilon: scalevec.x = boundbox.XLength / basebbox.XLength if doScaleXYZ[1] and basebbox.YLength > epsilon: scalevec.y = boundbox.YLength / basebbox.YLength if doScaleXYZ[2] and basebbox.ZLength > epsilon: scalevec.z = boundbox.ZLength / basebbox.ZLength scalevec.x = boundbox.XLength scalevec.y = boundbox.YLength scalevec.z = boundbox.ZLength if scalevec.x < epsilon: if doScaleXYZ[0]: scalevec.x = epsilon else: scalevec.x = 1 if scalevec.y < epsilon: if doScaleXYZ[1]: scalevec.y = epsilon else: scalevec.y = 1 if scalevec.z < epsilon: if doScaleXYZ[2]: scalevec.z = epsilon else: scalevec.z = 1 if doScaleXYZ[0]: x += boundbox.XMin - basebbox.XMin * scalevec.x if doScaleXYZ[1]: y += boundbox.YMin - basebbox.YMin * scalevec.y if doScaleXYZ[2]: z += boundbox.ZMin - basebbox.ZMin * scalevec.z return x, y, z, scalevec.x, scalevec.y, scalevec.z
def scaleByBoundbox(shape, boundbox, doScaleXYZ, copy=True): basebbox = shape.BoundBox scalevec = Vector(1, 1, 1) if basebbox.XLength > epsilon: scalevec.x = boundbox.XLength / basebbox.XLength if basebbox.YLength > epsilon: scalevec.y = boundbox.YLength / basebbox.YLength if basebbox.ZLength > epsilon: scalevec.z = boundbox.ZLength / basebbox.ZLength if scalevec.x < epsilon: if doScaleXYZ[0]: scalevec.x = epsilon else: scalevec.x = 1 if scalevec.y < epsilon: if doScaleXYZ[1]: scalevec.y = epsilon else: scalevec.y = 1 if scalevec.z < epsilon: if doScaleXYZ[2]: scalevec.z = epsilon else: scalevec.z = 1 dolly = scale(shape, scalevec, basebbox.Center, copy) dolly.Placement = shape.Placement if doScaleXYZ[0]: dolly.Placement.Base.x += boundbox.XMin - basebbox.XMin * scalevec.x if doScaleXYZ[1]: dolly.Placement.Base.y += boundbox.YMin - basebbox.YMin * scalevec.y if doScaleXYZ[2]: dolly.Placement.Base.z += boundbox.ZMin - basebbox.ZMin * scalevec.z return dolly
def create_compartment(box, direction, offset, materialWidth, notchWidth, drawSides=[True, True, True, True, True, True], doc = app.activeDocument()): cpos = direction * offset mybox = None if len(box) == 1 and hasattr(box[0], 'Links'): parts = box[0].Links mybox = box[0] elif isinstance(box, list): parts = box else: parts = [box] boxsize = Vector(0, 0, 0) for side in parts: if hasattr(side, 'Shape'): bbox = side.Shape.BoundBox if bbox.XLength > boxsize.x: boxsize.x = bbox.XLength if bbox.YLength > boxsize.y: boxsize.y = bbox.YLength if bbox.ZLength > boxsize.z: boxsize.z = bbox.ZLength holes = [] if direction == Vector(1, 0, 0): if boxsize.z == 0 or boxsize.y == 0: app.Console.PrintError("select a box first !\n") return compartment = draw_left(doc, 'compartmentX' + str(offset), materialWidth, boxsize.z, boxsize.y, notchWidth, drawSides) if drawSides[4] or drawSides[5]: holes += draw_holes(boxsize.y, notchWidth, materialWidth, Vector(0, 0, 90)) if drawSides[2] or drawSides[3]: holes += draw_holes(boxsize.z, notchWidth, materialWidth, Vector(90, 0, 90)) for h in holes: h.Placement.Base.x += offset + materialWidth elif direction == Vector(0, 1, 0): if boxsize.z == 0 or boxsize.x == 0: app.Console.PrintError("select a box first !\n") return sides = [drawSides[0], drawSides[1], drawSides[4], drawSides[5], drawSides[2], drawSides[3]] compartment = draw_left(doc, 'compartmentY' + str(offset), materialWidth, boxsize.z, boxsize.x, notchWidth, sides) doc.recompute() Draft.rotate([compartment], 270.0, Vector(0, 0, 0), axis=Vector(0.0, 0.0, 1.0), copy=False) doc.recompute() Draft.move([compartment], Vector(0, materialWidth, 0), copy=False) doc.recompute() if drawSides[0] or drawSides[1]: holes += draw_holes(boxsize.x, notchWidth, materialWidth, Vector(0, 0, 0)) if drawSides[2] or drawSides[3]: holes += draw_holes(boxsize.z, notchWidth, materialWidth, Vector(0, 270, 0)) for h in holes: h.Placement.Base.y += offset elif direction == Vector(0, 0, 1): if boxsize.x == 0 or boxsize.y == 0: app.Console.PrintError("select a box first !\n") return sides = [drawSides[2], drawSides[3], drawSides[0], drawSides[1], drawSides[4], drawSides[5]] compartment = draw_left(doc, 'compartmentZ' + str(offset), materialWidth, boxsize.x, boxsize.y, notchWidth, sides) doc.recompute() Draft.rotate([compartment], 270.0, Vector(boxsize.x, 0, 0), axis=Vector(0.0, 1.0, 0.0), copy=False) doc.recompute() Draft.move([compartment], Vector(0, 0, boxsize.x), copy=False) doc.recompute() if drawSides[0] or drawSides[1]: holes += draw_holes(boxsize.x, notchWidth, materialWidth, Vector(270, 0, 0)) if drawSides[4] or drawSides[5]: holes += draw_holes(boxsize.y, notchWidth, materialWidth, Vector(0, 270, 90)) for h in holes: h.Placement.Base.z += offset + materialWidth else: return None Draft.move([compartment], cpos, copy=False) doc.recompute() addLinesToBoxSide(parts, holes) if mybox: compartment.adjustRelativeLinks(mybox) mybox.ViewObject.dropObject(compartment, None, '', []) doc.recompute() return mybox doc.recompute() return compartment
def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0): """ Compute the ship displacement. @param ship Ship instance. @param draft Ship draft. @param roll Ship roll angle. @param trim Ship trim angle. @param yaw Ship yaw angle. Ussually you don't want to use this value. @return [disp, B, Cb], \n - disp = Ship displacement [ton]. - B = Bouyance center [m]. - Cb = Block coefficient. @note Bouyance center will returned as a FreeCAD.Vector instance. @note Returned Bouyance center is in the non modified ship coordinates """ # We will take a duplicate of ship shape in order to conviniently # manipulate it shape = ship.Shape.copy() shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value)) shape.rotate(Vector(0.0, 0.0, 0.0), Vector(1.0, 0.0, 0.0), roll) shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, 0.0, 1.0), yaw) bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax # Create the "sea" box to intersect the ship L = xmax - xmin B = bbox.YMax - bbox.YMin p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0) try: box = Part.makeBox(3.0*L, 3.0*B, - bbox.ZMin + 1.0, p) except: return [0.0, Vector(), 0.0] vol = 0.0 cog = Vector() for solid in shape.Solids: # Compute the common part of the "sea" with the ship try: common = box.common(solid) except: continue # Get the data vol = vol + common.Volume / Units.Metre.Value**3 for s in common.Solids: sCoG = s.CenterOfMass cog.x = cog.x + sCoG.x * s.Volume / Units.Metre.Value**4 cog.y = cog.y + sCoG.y * s.Volume / Units.Metre.Value**4 cog.z = cog.z + sCoG.z * s.Volume / Units.Metre.Value**4 cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol Vol = L * B * abs(bbox.ZMin) / Units.Metre.Value**3 # Undo the transformations B = Vector() B.x = cog.x * math.cos(math.radians(-yaw)) - \ cog.y * math.sin(math.radians(-yaw)) B.y = cog.x * math.sin(math.radians(-yaw)) + \ cog.y * math.cos(math.radians(-yaw)) B.z = cog.z cog.x = B.x * math.cos(math.radians(-trim)) - \ B.z * math.sin(math.radians(-trim)) cog.y = B.y cog.z = B.x * math.sin(math.radians(-trim)) + \ B.z * math.cos(math.radians(-trim)) B.x = cog.x B.y = cog.y * math.cos(math.radians(-roll)) - \ cog.z * math.sin(math.radians(-roll)) B.z = cog.y * math.sin(math.radians(-roll)) + \ cog.z * math.cos(math.radians(-roll)) B.z = B.z + draft # Return the computed data dens = 1.025 # [tons/m3], salt water return [dens*vol, B, vol/Vol]
def findContactVoxelSurfaces(face, condIndex, gbbox, delta, voxelSpace=None, createShell=False): ''' Find the voxel surface sides corresponding to the given contact surface (face) of an object. The object must have already been voxelized. 'face' is the object face 'condIndex' (integer) is the index of the object to which the face belongs. It defines the object conductivity. 'gbbox' (FreeCAD.BoundBox) is the overall bounding box 'delta' is the voxels size length 'voxelSpace' (Numpy 3D array) is the voxel tensor of the overall space 'createShell' (bool) creates a shell out of the contact faces Returns a list of surfaces in the format [x,y,z,voxside] where x, y, z are the voxel position indexes, while voxside is '+x', '-x', '+y', '-y', '+z', '-z' according the the impacted surface of the voxel ''' if voxelSpace == None: return surfList = [] contactList = [] # get the face's bbox bbox = face.BoundBox # now must find the voxel set that contains the face bounding box # with a certain slack - it could be the next voxel, # if the surface is at the boundary between voxels. # Find the voxel that contains the bbox min point min_x = int((bbox.XMin - gbbox.XMin) / delta) - 1 min_y = int((bbox.YMin - gbbox.YMin) / delta) - 1 min_z = int((bbox.ZMin - gbbox.ZMin) / delta) - 1 # find the voxel that contains the bbox max point max_x = int((bbox.XMax - gbbox.XMin) / delta) + 1 max_y = int((bbox.YMax - gbbox.YMin) / delta) + 1 max_z = int((bbox.ZMax - gbbox.ZMin) / delta) + 1 # debug #print(str(min_x)+" "+str(min_y)+" "+str(min_z)+" "+str(max_x)+" "+str(max_y)+" "+str(max_z)) # create a Part.Vertex that we can use to test the distance # to the face (as it is a TopoShape) vec = FreeCAD.Vector(0, 0, 0) testVertex = Part.Vertex(vec) # this is half the side of the voxel halfdelta = delta / 2.0 # small displacement w.r.t. delta epsdelta = delta / 100.0 # array to find the six neighbour sides = [(1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)] # string describing the side sideStrs = ['+x', '-x', '+y', '-y', '+z', '-z'] # centers of the sides, with respect to the lower corner (with the smallest coordinates) sideCenters = [ Vector(delta, halfdelta, halfdelta), Vector(0.0, halfdelta, halfdelta), Vector(halfdelta, delta, halfdelta), Vector(halfdelta, 0.0, halfdelta), Vector(halfdelta, halfdelta, delta), Vector(halfdelta, halfdelta, 0.0) ] # vertexes of the six faces (with a slight offset) vertexes = [[ Vector(delta + epsdelta, 0, 0), Vector(delta + epsdelta, delta, 0), Vector(delta + epsdelta, delta, delta), Vector(delta + epsdelta, 0, delta) ], [ Vector(-epsdelta, 0, 0), Vector(-epsdelta, 0, delta), Vector(-epsdelta, delta, delta), Vector(-epsdelta, delta, 0) ], [ Vector(0, delta + epsdelta, 0), Vector(0, delta + epsdelta, delta), Vector(delta, delta + epsdelta, delta), Vector(delta, delta + epsdelta, 0) ], [ Vector(0, -epsdelta, 0), Vector(delta, -epsdelta, 0), Vector(delta, -epsdelta, delta), Vector(0, -epsdelta, delta) ], [ Vector(0, 0, delta + epsdelta), Vector(delta, 0, delta + epsdelta), Vector(delta, delta, delta + epsdelta), Vector(0, delta, delta + epsdelta) ], [ Vector(0, 0, -epsdelta), Vector(0, delta, -epsdelta), Vector(delta, delta, -epsdelta), Vector(delta, 0, -epsdelta) ]] # and now iterate to find which voxel is inside the bounding box of the 'face', vbase = Vector(gbbox.XMin + min_x * delta, gbbox.YMin + min_y * delta, gbbox.ZMin + min_z * delta) for step_x in range(min_x, max_x + 1): vbase.y = gbbox.YMin + min_y * delta for step_y in range(min_y, max_y + 1): vbase.z = gbbox.ZMin + min_z * delta for step_z in range(min_z, max_z + 1): # check if voxel is belonging to the given object if voxelSpace[step_x, step_y, step_z] == condIndex: # scan the six neighbour voxels, to see if they are belonging to the same conductor or not. # If they are not belonging to the same conductor, or if the voxel space is finished, the current voxel # side in the direction of the empty voxel is an external surface for side, sideStr, sideCenter, vertex in zip( sides, sideStrs, sideCenters, vertexes): is_surface = False nextVoxelIndexes = [ step_x + side[0], step_y + side[1], step_z + side[2] ] if (nextVoxelIndexes[0] > max_x or nextVoxelIndexes[0] < 0 or nextVoxelIndexes[1] > max_y or nextVoxelIndexes[1] < 0 or nextVoxelIndexes[2] > max_z or nextVoxelIndexes[2] < 0): is_surface = True else: if voxelSpace[nextVoxelIndexes[0], nextVoxelIndexes[1], nextVoxelIndexes[2]] != condIndex: is_surface = True if is_surface == True: # debug #print("pos_x="+str(vbase.x)+" pos_y="+str(vbase.y)+" pos_z="+str(vbase.z)) testVertex.Placement.Base = vbase + sideCenter # if the point is close enough to the face, we consider # the voxel surface as belonging to the voxelized face dist = testVertex.distToShape(face) # debug #print(str(dist)) if abs(dist[0]) < halfdelta: contactList.append( [step_x, step_y, step_z, sideStr]) if createShell: # create the face # calculate the vertexes v11 = vbase + vertex[0] v12 = vbase + vertex[1] v13 = vbase + vertex[2] v14 = vbase + vertex[3] # now make the face poly = Part.makePolygon( [v11, v12, v13, v14, v11]) contFace = Part.Face(poly) surfList.append(contFace) vbase.z += delta vbase.y += delta vbase.x += delta contactShell = None if createShell: if surfList != []: # create a shell. Does not need to be solid. contactShell = Part.makeShell(surfList) return [contactList, contactShell]
def createVoxelShell(obj, condIndex, gbbox, delta, voxelSpace=None): ''' Creates a shell composed by the external faces of a voxelized object. 'obj' is the object whose shell must be created 'condIndex' (integer) is the index of the object. It defines the object conductivity. 'gbbox' (FreeCAD.BoundBox) is the overall bounding box 'delta' is the voxels size length 'voxelSpace' (Numpy 3D array) is the voxel tensor of the overall space ''' if voxelSpace == None: return if not hasattr(obj, "Shape"): return surfList = [] # get the object's bbox bbox = obj.Shape.BoundBox # now must find the voxel set that contains the object bounding box # find the voxel that contains the bbox min point min_x = int((bbox.XMin - gbbox.XMin) / delta) min_y = int((bbox.YMin - gbbox.YMin) / delta) min_z = int((bbox.ZMin - gbbox.ZMin) / delta) # find the voxel that contains the bbox max point max_x = int((bbox.XMax - gbbox.XMin) / delta) max_y = int((bbox.YMax - gbbox.YMin) / delta) max_z = int((bbox.ZMax - gbbox.ZMin) / delta) # this is half the side of the voxel halfdelta = delta / 2.0 # array to find the six neighbour sides = [(1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)] # vertexes of the six faces vertexes = [[ Vector(delta, 0, 0), Vector(delta, delta, 0), Vector(delta, delta, delta), Vector(delta, 0, delta) ], [ Vector(0, 0, 0), Vector(0, 0, delta), Vector(0, delta, delta), Vector(0, delta, 0) ], [ Vector(0, delta, 0), Vector(0, delta, delta), Vector(delta, delta, delta), Vector(delta, delta, 0) ], [ Vector(0, 0, 0), Vector(delta, 0, 0), Vector(delta, 0, delta), Vector(0, 0, delta) ], [ Vector(0, 0, delta), Vector(delta, 0, delta), Vector(delta, delta, delta), Vector(0, delta, delta) ], [ Vector(0, 0, 0), Vector(0, delta, 0), Vector(delta, delta, 0), Vector(delta, 0, 0) ]] # and now iterate to find which voxel is inside the object 'obj', # sampling based on the voxel centers vbase = Vector(gbbox.XMin + min_x * delta, gbbox.YMin + min_y * delta, gbbox.ZMin + min_z * delta) for step_x in range(min_x, max_x + 1): vbase.y = gbbox.YMin + min_y * delta for step_y in range(min_y, max_y + 1): vbase.z = gbbox.ZMin + min_z * delta for step_z in range(min_z, max_z + 1): # check if voxel is belonging to the given object if voxelSpace[step_x, step_y, step_z] == condIndex: # scan the six neighbour voxels, to see if they are belonging to the same conductor or not. # If they are not belonging to the same conductor, or if the voxel space is finished, the current voxel # side in the direction of the empty voxel is an external surface for side, vertex in zip(sides, vertexes): is_surface = False nextVoxelIndexes = [ step_x + side[0], step_y + side[1], step_z + side[2] ] if (nextVoxelIndexes[0] > max_x or nextVoxelIndexes[0] < 0 or nextVoxelIndexes[1] > max_y or nextVoxelIndexes[1] < 0 or nextVoxelIndexes[2] > max_z or nextVoxelIndexes[2] < 0): is_surface = True else: if voxelSpace[nextVoxelIndexes[0], nextVoxelIndexes[1], nextVoxelIndexes[2]] != condIndex: is_surface = True if is_surface == True: # debug #print("pos_x="+str(vbase.x)+" pos_y="+str(vbase.y)+" pos_z="+str(vbase.z)) # create the face # calculate the vertexes v11 = vbase + vertex[0] v12 = vbase + vertex[1] v13 = vbase + vertex[2] v14 = vbase + vertex[3] # now make the face poly = Part.makePolygon([v11, v12, v13, v14, v11]) face = Part.Face(poly) surfList.append(face) vbase.z += delta vbase.y += delta vbase.x += delta # create a shell. Does not need to be solid. objShell = Part.makeShell(surfList) return objShell
def getCoG(self, fp, vol, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Return the fluid volume center of gravity, provided the volume of fluid inside the tank. The returned center of gravity is refered to the untransformed ship. Keyword arguments: fp -- Part::FeaturePython object affected. vol -- Volume of fluid. roll -- Ship roll angle. trim -- Ship trim angle. If the fluid volume is bigger than the total tank one, it will be conveniently clamped. """ # Change the units of the volume, and clamp the value if vol <= 0.0: return Vector() if vol >= fp.Shape.Volume: vol = 0.0 for solid in fp.Shape.Solids: vol += solid.Volume sCoG = solid.CenterOfMass cog.x = cog.x + sCoG.x * solid.Volume cog.y = cog.y + sCoG.y * solid.Volume cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol return cog # Get a first estimation of the level level = vol.Value / fp.Shape.Volume # Transform the tank shape current_placement = fp.Placement m = current_placement.toMatrix() m.rotateX(roll.getValueAs("rad")) m.rotateY(-trim.getValueAs("rad")) fp.Placement = Placement(m) # Iterate to find the fluid shape for i in range(COMMON_BOOLEAN_ITERATIONS): shape = self.getVolume(fp, level, return_shape=True) error = (vol.Value - shape.Volume) / fp.Shape.Volume if abs(error) < 0.01: break level += error # Get the center of gravity vol = 0.0 cog = Vector() if len(shape.Solids) > 0: for solid in shape.Solids: vol += solid.Volume sCoG = solid.CenterOfMass cog.x = cog.x + sCoG.x * solid.Volume cog.y = cog.y + sCoG.y * solid.Volume cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol # Untransform the object to retrieve the original position fp.Placement = current_placement p = Part.Point(cog) m = Matrix() m.rotateY(trim.getValueAs("rad")) m.rotateX(-roll.getValueAs("rad")) p.rotate(Placement(m)) return Vector(p.X, p.Y, p.Z)
def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0): """ Compute the ship displacement. @param ship Ship instance. @param draft Ship draft. @param roll Ship roll angle. @param trim Ship trim angle. @param yaw Ship yaw angle. Ussually you don't want to use this value. @return [disp, B, Cb], \n - disp = Ship displacement [ton]. - B = Bouyance center [m]. - Cb = Block coefficient. @note Bouyance center will returned as a FreeCAD.Vector instance. @note Returned Bouyance center is in the non modified ship coordinates """ # We will take a duplicate of ship shape in order to conviniently # manipulate it shape = ship.Shape.copy() shape.translate(Vector(0.0, 0.0, -draft * Units.Metre.Value)) shape.rotate(Vector(0.0, 0.0, 0.0), Vector(1.0, 0.0, 0.0), roll) shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, 0.0, 1.0), yaw) bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax # Create the "sea" box to intersect the ship L = xmax - xmin B = bbox.YMax - bbox.YMin p = Vector(-1.5 * L, -1.5 * B, bbox.ZMin - 1.0) try: box = Part.makeBox(3.0 * L, 3.0 * B, -bbox.ZMin + 1.0, p) except: return [0.0, Vector(), 0.0] vol = 0.0 cog = Vector() for solid in shape.Solids: # Compute the common part of the "sea" with the ship try: common = box.common(solid) except: continue # Get the data vol = vol + common.Volume / Units.Metre.Value**3 for s in common.Solids: sCoG = s.CenterOfMass cog.x = cog.x + sCoG.x * s.Volume / Units.Metre.Value**4 cog.y = cog.y + sCoG.y * s.Volume / Units.Metre.Value**4 cog.z = cog.z + sCoG.z * s.Volume / Units.Metre.Value**4 cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol Vol = L * B * abs(bbox.ZMin) / Units.Metre.Value**3 # Undo the transformations B = Vector() B.x = cog.x * math.cos(math.radians(-yaw)) - \ cog.y * math.sin(math.radians(-yaw)) B.y = cog.x * math.sin(math.radians(-yaw)) + \ cog.y * math.cos(math.radians(-yaw)) B.z = cog.z cog.x = B.x * math.cos(math.radians(-trim)) - \ B.z * math.sin(math.radians(-trim)) cog.y = B.y cog.z = B.x * math.sin(math.radians(-trim)) + \ B.z * math.cos(math.radians(-trim)) B.x = cog.x B.y = cog.y * math.cos(math.radians(-roll)) - \ cog.z * math.sin(math.radians(-roll)) B.z = cog.y * math.sin(math.radians(-roll)) + \ cog.z * math.cos(math.radians(-roll)) B.z = B.z + draft # Return the computed data dens = 1.025 # [tons/m3], salt water return [dens * vol, B, vol / Vol]
def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0): """ Compute ship displacement. @param ship Ship instance. @param draft Ship draft. @param roll Ship roll angle. @param trim Ship trim angle. @param yaw Ship yaw angle. Ussually you don't want to use this value. @return [disp, B, Cb], \n disp = Ship displacement [ton]. B = Bouyance center [m]. Cb = Block coefficient. @note Bouyance center will returned as FreeCAD.Vector class. @note Returned Bouyance center is in non modified ship coordinates """ # We will take a duplicate of ship shape in order to place it shape = ship.Shape.copy() shape.translate(Vector(0.0, 0.0, -draft)) # Rotations composition is Roll->Trim->Yaw shape.rotate(Vector(0.0, 0.0, 0.0), Vector(1.0, 0.0, 0.0), roll) shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, -1.0, 0.0), trim) shape.rotate(Vector(0.0, 0.0, 0.0), Vector(0.0, 0.0, 1.0), yaw) # Now we need to know box dimensions bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax # Create the box L = xmax - xmin B = bbox.YMax - bbox.YMin p = Vector(-1.5 * L, -1.5 * B, bbox.ZMin - 1.0) box = Part.makeBox(3.0 * L, 3.0 * B, -bbox.ZMin + 1.0, p) vol = 0.0 cog = Vector() for solid in shape.Solids: # Compute common part with ship try: common = box.common(solid) except: continue # Get data vol = vol + common.Volume for s in common.Solids: sCoG = s.CenterOfMass cog.x = cog.x + sCoG.x * s.Volume cog.y = cog.y + sCoG.y * s.Volume cog.z = cog.z + sCoG.z * s.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol Vol = L * B * abs(bbox.ZMin) # Undo transformations B = Vector() B.x = cog.x * math.cos(math.radians(-yaw)) - cog.y * math.sin( math.radians(-yaw)) B.y = cog.x * math.sin(math.radians(-yaw)) + cog.y * math.cos( math.radians(-yaw)) B.z = cog.z cog.x = B.x * math.cos(math.radians(-trim)) - B.z * math.sin( math.radians(-trim)) cog.y = B.y cog.z = B.x * math.sin(math.radians(-trim)) + B.z * math.cos( math.radians(-trim)) B.x = cog.x B.y = cog.y * math.cos(math.radians(-roll)) - cog.z * math.sin( math.radians(-roll)) B.z = cog.y * math.sin(math.radians(-roll)) + cog.z * math.cos( math.radians(-roll)) B.z = B.z + draft # Return data dens = 1.025 # [tons/m3], salt water return [dens * vol, B, vol / Vol]
def displacement(ship, draft, roll=0.0, trim=0.0, yaw=0.0): """ Compute ship displacement. @param ship Ship instance. @param draft Ship draft. @param roll Ship roll angle. @param trim Ship trim angle. @param yaw Ship yaw angle. Ussually you don't want to use this value. @return [disp, B, Cb], \n disp = Ship displacement [ton]. B = Bouyance center [m]. Cb = Block coefficient. @note Bouyance center will returned as FreeCAD.Vector class. @note Returned Bouyance center is in non modified ship coordinates """ # We will take a duplicate of ship shape in order to place it shape = ship.Shape.copy() shape.translate(Vector(0.0,0.0,-draft)) # Rotations composition is Roll->Trim->Yaw shape.rotate(Vector(0.0,0.0,0.0), Vector(1.0,0.0,0.0), roll) shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,-1.0,0.0), trim) shape.rotate(Vector(0.0,0.0,0.0), Vector(0.0,0.0,1.0), yaw) # Now we need to know box dimensions bbox = shape.BoundBox xmin = bbox.XMin xmax = bbox.XMax # Create the box L = xmax - xmin B = bbox.YMax - bbox.YMin p = Vector(-1.5*L, -1.5*B, bbox.ZMin - 1.0) box = Part.makeBox(3.0*L, 3.0*B, -bbox.ZMin + 1.0, p) vol = 0.0 cog = Vector() for solid in shape.Solids: # Compute common part with ship try: common = box.common(solid) except: continue # Get data vol = vol + common.Volume for s in common.Solids: sCoG = s.CenterOfMass cog.x = cog.x + sCoG.x*s.Volume cog.y = cog.y + sCoG.y*s.Volume cog.z = cog.z + sCoG.z*s.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol Vol = L*B*abs(bbox.ZMin) # Undo transformations B = Vector() B.x = cog.x*math.cos(math.radians(-yaw)) - cog.y*math.sin(math.radians(-yaw)) B.y = cog.x*math.sin(math.radians(-yaw)) + cog.y*math.cos(math.radians(-yaw)) B.z = cog.z cog.x = B.x*math.cos(math.radians(-trim)) - B.z*math.sin(math.radians(-trim)) cog.y = B.y cog.z = B.x*math.sin(math.radians(-trim)) + B.z*math.cos(math.radians(-trim)) B.x = cog.x B.y = cog.y*math.cos(math.radians(-roll)) - cog.z*math.sin(math.radians(-roll)) B.z = cog.y*math.sin(math.radians(-roll)) + cog.z*math.cos(math.radians(-roll)) B.z = B.z + draft # Return data dens = 1.025 # [tons/m3], salt water return [dens*vol, B, vol/Vol]
def displacement(ship, draft=None, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Compute the ship displacement Position arguments: ship -- Ship object (see createShip) Keyword arguments: draft -- Ship draft (Design ship draft by default) roll -- Roll angle (0 degrees by default) trim -- Trim angle (0 degrees by default) Returned values: disp -- The ship displacement (a density of the water of 1025 kg/m^3 is assumed) B -- Bouyance application point, i.e. Center of mass of the underwater side Cb -- Block coefficient The Bouyance center is refered to the original ship position. """ if draft is None: draft = ship.Draft shape, base_z = placeShipShape(ship.Shape.copy(), draft, roll, trim) shape = getUnderwaterSide(shape) vol = 0.0 cog = Vector() if len(shape.Solids) > 0: for solid in shape.Solids: vol += solid.Volume sCoG = solid.CenterOfMass cog.x = cog.x + sCoG.x * solid.Volume cog.y = cog.y + sCoG.y * solid.Volume cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol bbox = shape.BoundBox Vol = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin) * abs(bbox.ZMin) # Undo the transformations on the bouyance point B = Part.Point(Vector(cog.x, cog.y, cog.z)) m = Matrix() m.move(Vector(0.0, 0.0, draft)) m.move(Vector(-draft * math.sin(trim.getValueAs("rad")), 0.0, 0.0)) m.rotateY(trim.getValueAs("rad")) m.move(Vector(0.0, -draft * math.sin(roll.getValueAs("rad")), base_z)) m.rotateX(-roll.getValueAs("rad")) B.transform(m) try: cb = vol / Vol except ZeroDivisionError: msg = QtGui.QApplication.translate( "ship_console", "ZeroDivisionError: Null volume found during the displacement" " computation!", None, QtGui.QApplication.UnicodeUTF8) App.Console.PrintError(msg + '\n') cb = 0.0 # Return the computed data return (DENS * Units.Quantity(vol, Units.Volume), Vector(B.X, B.Y, B.Z), cb)
def displacement(ship, draft=None, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Compute the ship displacement Position arguments: ship -- Ship object (see createShip) Keyword arguments: draft -- Ship draft (Design ship draft by default) roll -- Roll angle (0 degrees by default) trim -- Trim angle (0 degrees by default) Returned values: disp -- The ship displacement (a density of the water of 1025 kg/m^3 is assumed) B -- Bouyance application point, i.e. Center of mass of the underwater side Cb -- Block coefficient The Bouyance center is referred to the original ship position. """ if draft is None: draft = ship.Draft shape, base_z = placeShipShape(ship.Shape.copy(), draft, roll, trim) shape = getUnderwaterSide(shape) vol = 0.0 cog = Vector() if len(shape.Solids) > 0: for solid in shape.Solids: vol += solid.Volume sCoG = solid.CenterOfMass cog.x = cog.x + sCoG.x * solid.Volume cog.y = cog.y + sCoG.y * solid.Volume cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol bbox = shape.BoundBox Vol = (bbox.XMax - bbox.XMin) * (bbox.YMax - bbox.YMin) * abs(bbox.ZMin) # Undo the transformations on the bouyance point B = Part.Point(Vector(cog.x, cog.y, cog.z)) m = Matrix() m.move(Vector(0.0, 0.0, draft)) m.move(Vector(-draft * math.sin(trim.getValueAs("rad")), 0.0, 0.0)) m.rotateY(trim.getValueAs("rad")) m.move(Vector(0.0, -draft * math.sin(roll.getValueAs("rad")), base_z)) m.rotateX(-roll.getValueAs("rad")) B.transform(m) try: cb = vol / Vol except ZeroDivisionError: msg = QtGui.QApplication.translate( "ship_console", "ZeroDivisionError: Null volume found during the displacement" " computation!", None) App.Console.PrintError(msg + '\n') cb = 0.0 # Return the computed data return (DENS * Units.Quantity(vol, Units.Volume), Vector(B.X, B.Y, B.Z), cb)
def getCoG(self, fp, vol, roll=Units.parseQuantity("0 deg"), trim=Units.parseQuantity("0 deg")): """Return the fluid volume center of gravity, provided the volume of fluid inside the tank. The returned center of gravity is referred to the untransformed ship. Keyword arguments: fp -- Part::FeaturePython object affected. vol -- Volume of fluid. roll -- Ship roll angle. trim -- Ship trim angle. If the fluid volume is bigger than the total tank one, it will be conveniently clamped. """ # Change the units of the volume, and clamp the value if vol <= 0.0: return Vector() if vol >= fp.Shape.Volume: vol = 0.0 for solid in fp.Shape.Solids: vol += solid.Volume sCoG = solid.CenterOfMass cog.x = cog.x + sCoG.x * solid.Volume cog.y = cog.y + sCoG.y * solid.Volume cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol return cog # Get a first estimation of the level level = vol.Value / fp.Shape.Volume # Transform the tank shape current_placement = fp.Placement m = current_placement.toMatrix() m.rotateX(roll.getValueAs("rad")) m.rotateY(-trim.getValueAs("rad")) fp.Placement = Placement(m) # Iterate to find the fluid shape for i in range(COMMON_BOOLEAN_ITERATIONS): shape = self.getVolume(fp, level, return_shape=True) error = (vol.Value - shape.Volume) / fp.Shape.Volume if abs(error) < 0.01: break level += error # Get the center of gravity vol = 0.0 cog = Vector() if len(shape.Solids) > 0: for solid in shape.Solids: vol += solid.Volume sCoG = solid.CenterOfMass cog.x = cog.x + sCoG.x * solid.Volume cog.y = cog.y + sCoG.y * solid.Volume cog.z = cog.z + sCoG.z * solid.Volume cog.x = cog.x / vol cog.y = cog.y / vol cog.z = cog.z / vol # Untransform the object to retrieve the original position fp.Placement = current_placement p = Part.Point(cog) m = Matrix() m.rotateY(trim.getValueAs("rad")) m.rotateX(-roll.getValueAs("rad")) p.rotate(Placement(m)) return Vector(p.X, p.Y, p.Z)
def makeRibs(self, obj): pl = obj.Placement ribs = [] curvebox = FreeCAD.BoundBox(float("-inf"), float("-inf"), float("-inf"), float("inf"), float("inf"), float("inf")) for n in range(0, len(obj.Hullcurves)): cbbx = obj.Hullcurves[n].Shape.BoundBox if self.doScaleXYZ[n][0]: if cbbx.XMin > curvebox.XMin: curvebox.XMin = cbbx.XMin if cbbx.XMax < curvebox.XMax: curvebox.XMax = cbbx.XMax if self.doScaleXYZ[n][1]: if cbbx.YMin > curvebox.YMin: curvebox.YMin = cbbx.YMin if cbbx.YMax < curvebox.YMax: curvebox.YMax = cbbx.YMax if self.doScaleXYZ[n][2]: if cbbx.ZMin > curvebox.ZMin: curvebox.ZMin = cbbx.ZMin if cbbx.ZMax < curvebox.ZMax: curvebox.ZMax = cbbx.ZMax if curvebox.XMin == float("-inf"): curvebox.XMin = obj.Hullcurves[0].Shape.BoundBox.XMin if curvebox.XMax == float("inf"): curvebox.XMax = obj.Hullcurves[0].Shape.BoundBox.XMax if curvebox.YMin == float("-inf"): curvebox.YMin = obj.Hullcurves[0].Shape.BoundBox.YMin if curvebox.YMax == float("inf"): curvebox.YMax = obj.Hullcurves[0].Shape.BoundBox.YMax if curvebox.ZMin == float("-inf"): curvebox.ZMin = obj.Hullcurves[0].Shape.BoundBox.ZMin if curvebox.ZMax == float("inf"): curvebox.ZMax = obj.Hullcurves[0].Shape.BoundBox.ZMax areavec = Vector(curvebox.XLength, curvebox.YLength, curvebox.ZLength) deltavec = areavec.scale( obj.Axis.x, obj.Axis.y, obj.Axis.z) - (obj.OffsetStart + obj.OffsetEnd) * obj.Axis sections = int(obj.Items) startvec = Vector(curvebox.XMin, curvebox.YMin, curvebox.ZMin) if obj.Axis.x < 0: startvec.x = curvebox.XMax if obj.Axis.y < 0: startvec.y = curvebox.YMax if obj.Axis.z < 0: startvec.z = curvebox.ZMax pos0 = startvec + (obj.OffsetStart * obj.Axis) if (not hasattr(obj, "Positions") or len(obj.Positions) == 0): for x in range(0, sections): if sections > 1: d = CurvedShapes.distribute(x / (sections - 1), obj.Distribution, obj.DistributionReverse) posvec = pos0 + (deltavec * d) else: posvec = pos0 dolly = self.makeRib(obj, posvec) if dolly: if not obj.Twist == 0: dolly.rotate( dolly.BoundBox.Center, obj.Axis, obj.Twist * posvec.Length / areavec.Length) ribs.append(dolly) else: for p in obj.Positions: posvec = pos0 + (deltavec * p) dolly = self.makeRib(obj, posvec) if dolly: if not obj.Twist == 0: dolly.rotate( dolly.BoundBox.Center, obj.Axis, obj.Twist * posvec.Length / areavec.Length) ribs.append(dolly) if (obj.Surface or obj.Solid) and obj.Items > 1: obj.Shape = CurvedShapes.makeSurfaceSolid(ribs, obj.Solid) else: obj.Shape = Part.makeCompound(ribs) obj.Placement = pl if self.extract: CompoundTools.Explode.explodeCompound(obj) obj.ViewObject.hide()