def extrude(obj, vector, solid=False): """extrude(object, vector, [solid]) Create a Part::Extrusion object from a given object. Parameters ---------- obj : vector : Base.Vector The extrusion direction and module. solid : bool TODO: describe. """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return newobj = App.ActiveDocument.addObject("Part::Extrusion", "Extrusion") newobj.Base = obj newobj.Dir = vector newobj.Solid = solid if App.GuiUp: obj.ViewObject.Visibility = False gui_utils.format_object(newobj, obj) gui_utils.select(newobj) return newobj
def make_point_array(base, ptlst): """make_point_array(base,pointlist) Make a Draft PointArray object. Parameters ---------- base : TODO: describe plist : TODO: describe """ obj = App.ActiveDocument.addObject("Part::FeaturePython", "PointArray") PointArray(obj, base, ptlst) obj.Base = base obj.PointList = ptlst if App.GuiUp: ViewProviderDraftArray(obj.ViewObject) base.ViewObject.hide() gui_utils.formatObject(obj, obj.Base) if len(obj.Base.ViewObject.DiffuseColor) > 1: obj.ViewObject.Proxy.resetColors(obj.ViewObject) gui_utils.select(obj) return obj
def make_facebinder(selectionset, name="Facebinder"): """makeFacebinder(selectionset, [name]) Creates a Facebinder object from a selection set. Parameters ---------- selectionset : Only faces will be added. name : string (default = "Facebinder") Name of the created object """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return if not isinstance(selectionset, list): selectionset = [selectionset] fb = App.ActiveDocument.addObject("Part::FeaturePython", name) Facebinder(fb) if App.GuiUp: ViewProviderFacebinder(fb.ViewObject) faces = [] # unused variable? fb.Proxy.addSubobjects(fb, selectionset) gui_utils.select(fb) return fb
def make_shape2dview(baseobj, projectionVector=None, facenumbers=[]): """make_shape2dview(object, [projectionVector], [facenumbers]) Add a 2D shape to the document, which is a 2D projection of the given object. Parameters ---------- object : TODO: Describe projectionVector : Base.Vector Custom vector for the projection facenumbers : [] TODO: Describe A list of face numbers to be considered in individual faces mode. """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", "Shape2DView") Shape2DView(obj) if App.GuiUp: ViewProviderDraftAlt(obj.ViewObject) obj.Base = baseobj if projectionVector: obj.Projection = projectionVector if facenumbers: obj.FaceNumbers = facenumbers gui_utils.select(obj) return obj
def make_shapestring(String, FontFile, Size=100, Tracking=0): """ShapeString(Text,FontFile,[Height],[Track]) Turns a text string into a Compound Shape Parameters ---------- majradius : Major radius of the ellipse. """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", "ShapeString") ShapeString(obj) obj.String = String obj.FontFile = FontFile obj.Size = Size obj.Tracking = Tracking if App.GuiUp: ViewProviderShapeString(obj.ViewObject) gui_utils.format_object(obj) obrep = obj.ViewObject if "PointSize" in obrep.PropertiesList: obrep.PointSize = 1 # hide the segment end points gui_utils.select(obj) obj.recompute() return obj
def cut(object1, object2): """Return a cut object made from the difference of the 2 given objects. Parameters ---------- object1: Part::Feature Any object with a `Part::TopoShape`. object2: Part::Feature Any object with a `Part::TopoShape`. Returns ------- Part::Cut The resulting cut object. None If there is a problem and the new object can't be created. """ if not App.activeDocument(): _err(_tr("No active document. Aborting.")) return obj = App.activeDocument().addObject("Part::Cut", "Cut") obj.Base = object1 obj.Tool = object2 if App.GuiUp: gui_utils.format_object(obj, object1) gui_utils.select(obj) object1.ViewObject.Visibility = False object2.ViewObject.Visibility = False return obj
def make_rectangle(length, height=0, placement=None, face=None, support=None): """make_rectangle(length, width, [placement], [face]) Creates a Rectangle object with length in X direction and height in Y direction. Parameters ---------- length, height : dimensions of the rectangle placement : Base.Placement If a placement is given, it is used. face : Bool If face is False, the rectangle is shown as a wireframe, otherwise as a face. Rectangles can also be constructed by giving them a list of four vertices as first argument: make_rectangle(list_of_vertices, face=...) but you are responsible to check yourself that these 4 vertices are ordered and actually form a rectangle, otherwise the result might be wrong. Placement is ignored when constructing a rectangle this way (face argument is kept). """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return if isinstance(length, (list, tuple)) and (len(length) == 4): verts = length xv = verts[1].sub(verts[0]) yv = verts[3].sub(verts[0]) zv = xv.cross(yv) rr = App.Rotation(xv, yv, zv, "XYZ") rp = App.Placement(verts[0], rr) return make_rectangle(xv.Length, yv.Length, rp, face, support) if placement: utils.type_check([(placement, App.Placement)], "make_rectangle") obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", "Rectangle") Rectangle(obj) obj.Length = length obj.Height = height obj.Support = support if face != None: obj.MakeFace = face if placement: obj.Placement = placement if App.GuiUp: ViewProviderRectangle(obj.ViewObject) gui_utils.format_object(obj) gui_utils.select(obj) return obj
def make_label(targetpoint=None, target=None, direction=None, distance=None, labeltype=None, placement=None): """ make_label(targetpoint, target, direction, distance, labeltype, placement) Function to create a Draft Label annotation object Parameters ---------- targetpoint : App::Vector To be completed target : LinkSub To be completed direction : String Straight direction of the label ["Horizontal","Vertical","Custom"] distance : Quantity Length of the straight segment of label leader line labeltype : String Label type in ["Custom","Name","Label","Position", "Length","Area","Volume","Tag","Material"] placement : Base::Placement To be completed Returns ------- obj : App::DocumentObject Newly created label object """ obj = App.ActiveDocument.addObject("App::FeaturePython", "dLabel") Label(obj) if App.GuiUp: ViewProviderLabel(obj.ViewObject) if targetpoint: obj.TargetPoint = targetpoint if target: obj.Target = target if direction: obj.StraightDirection = direction if distance: obj.StraightDistance = distance if labeltype: obj.LabelType = labeltype if placement: obj.Placement = placement if App.GuiUp: gui_utils.format_object(obj) gui_utils.select(obj) return obj
def make_point(X=0, Y=0, Z=0, color=None, name="Point", point_size=5): """ makePoint(x,y,z ,[color(r,g,b),point_size]) or makePoint(Vector,color(r,g,b),point_size]) - Creates a Draft Point in the current document. Parameters ---------- X : float -> X coordinate of the point Base.Vector -> Ignore Y and Z coordinates and create the point from the vector. Y : float Y coordinate of the point Z : float Z coordinate of the point color : (R, G, B) Point color as RGB example to create a colored point: make_point(0,0,0,(1,0,0)) # color = red example to change the color, make sure values are floats: p1.ViewObject.PointColor =(0.0,0.0,1.0) """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return obj = App.ActiveDocument.addObject("Part::FeaturePython", name) if isinstance(X, App.Vector): Z = X.z Y = X.y X = X.x Point(obj, X, Y, Z) # TODO: Check if this is a repetition: obj.X = X obj.Y = Y obj.Z = Z if App.GuiUp: ViewProviderPoint(obj.ViewObject) if hasattr(Gui, "draftToolBar") and (not color): color = Gui.draftToolBar.getDefaultColor('ui') obj.ViewObject.PointColor = (float(color[0]), float(color[1]), float(color[2])) obj.ViewObject.PointSize = point_size obj.ViewObject.Visibility = True select(obj) return obj
def make_ellipse(majradius, minradius, placement=None, face=True, support=None): """make_ellipse(majradius, minradius, [placement], [face], [support]) Makes an ellipse with the given major and minor radius, and optionally a placement. Parameters ---------- majradius : Major radius of the ellipse. minradius : Minor radius of the ellipse. placement : Base.Placement If a placement is given, it is used. face : Bool If face is False, the rectangle is shown as a wireframe, otherwise as a face. support : TODO: Describe. """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", "Ellipse") Ellipse(obj) if minradius > majradius: majradius, minradius = minradius, majradius obj.MajorRadius = majradius obj.MinorRadius = minradius obj.Support = support if face != None: obj.MakeFace = face if placement: obj.Placement = placement if App.GuiUp: ViewProviderDraft(obj.ViewObject) gui_utils.format_object(obj) gui_utils.select(obj) return obj
def make_polygon(nfaces, radius=1, inscribed=True, placement=None, face=None, support=None): """makePolgon(edges,[radius],[inscribed],[placement],[face]) Creates a polygon object with the given number of edges and radius. Parameters ---------- edges : int Number of edges of the polygon. radius : Radius of the control circle. inscribed : bool Defines is the polygon is inscribed or not into the control circle. placement : Base.Placement If placement is given, it is used. face : bool If face is True, the resulting shape is displayed as a face, otherwise as a wireframe. support : TODO: Describe """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return if nfaces < 3: return None obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", "Polygon") Polygon(obj) obj.FacesNumber = nfaces obj.Radius = radius if face != None: obj.MakeFace = face if inscribed: obj.DrawMode = "inscribed" else: obj.DrawMode = "circumscribed" obj.Support = support if placement: obj.Placement = placement if App.GuiUp: ViewProviderDraft(obj.ViewObject) format_object(obj) select(obj) return obj
def make_text(stringslist, point=App.Vector(0, 0, 0), screen=False): """makeText(strings, point, screen) Creates a Text object containing the given strings. The current color and text height and font specified in preferences are used. Parameters ---------- stringlist : List Given list of strings, one string by line (strings can also be one single string) point : App::Vector insert point of the text screen : Bool If screen is True, the text always faces the view direction. """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return utils.type_check([(point, App.Vector)], "makeText") if not isinstance(stringslist, list): stringslist = [stringslist] obj = App.ActiveDocument.addObject("App::FeaturePython", "Text") Text(obj) obj.Text = stringslist obj.Placement.Base = point if App.GuiUp: ViewProviderText(obj.ViewObject) if screen: obj.ViewObject.DisplayMode = "3D text" h = utils.get_param("textheight", 0.20) if screen: h = h * 10 obj.ViewObject.FontSize = h obj.ViewObject.FontName = utils.get_param("textfont", "") obj.ViewObject.LineSpacing = 1 gui_utils.format_object(obj) gui_utils.select(obj) return obj
def finish(self, closed=False): """Terminate the operation of the Trimex tool.""" super(Trimex, self).finish() self.force = None if self.ui: if self.linetrack: self.linetrack.finalize() if self.ghost: for g in self.ghost: g.finalize() if self.obj: self.obj.ViewObject.Visibility = True if self.color: self.obj.ViewObject.LineColor = self.color if self.width: self.obj.ViewObject.LineWidth = self.width gui_utils.select(self.obj)
def cut(object1, object2): """cut(oject1,object2) Returns a cut object made from the difference of the 2 given objects. """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return obj = App.ActiveDocument.addObject("Part::Cut", "Cut") obj.Base = object1 obj.Tool = object2 object1.ViewObject.Visibility = False object2.ViewObject.Visibility = False if App.GuiUp: gui_utils.format_object(obj, object1) gui_utils.select(obj) return obj
def fuse(object1, object2): """fuse(oject1, object2) Returns an object made from the union of the 2 given objects. If the objects are coplanar, a special Draft Wire is used, otherwise we use a standard Part fuse. """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return import Part import DraftGeomUtils # testing if we have holes: holes = False fshape = object1.Shape.fuse(object2.Shape) fshape = fshape.removeSplitter() for f in fshape.Faces: if len(f.Wires) > 1: holes = True if DraftGeomUtils.isCoplanar(object1.Shape.fuse( object2.Shape).Faces) and not holes: obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", "Fusion") Wire(obj) if App.GuiUp: ViewProviderWire(obj.ViewObject) obj.Base = object1 obj.Tool = object2 elif holes: # temporary hack, since Part::Fuse objects don't remove splitters obj = App.ActiveDocument.addObject("Part::Feature", "Fusion") obj.Shape = fshape else: obj = App.ActiveDocument.addObject("Part::Fuse", "Fusion") obj.Base = object1 obj.Tool = object2 if App.GuiUp: object1.ViewObject.Visibility = False object2.ViewObject.Visibility = False gui_utils.format_object(obj, object1) gui_utils.select(obj) return obj
def make_rectangle(length, height, placement=None, face=None, support=None): """makeRectangle(length, width, [placement], [face]) Creates a Rectangle object with length in X direction and height in Y direction. Parameters ---------- length, height : dimensions of the rectangle placement : Base.Placement If a placement is given, it is used. face : Bool If face is False, the rectangle is shown as a wireframe, otherwise as a face. """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return if placement: type_check([(placement, App.Placement)], "make_rectangle") obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", "Rectangle") Rectangle(obj) obj.Length = length obj.Height = height obj.Support = support if face != None: obj.MakeFace = face if placement: obj.Placement = placement if App.GuiUp: ViewProviderRectangle(obj.ViewObject) format_object(obj) select(obj) return obj
def make_block(objectslist): """make_block(objectslist) Creates a Draft Block from the given objects. Parameters ---------- objectlist : list Major radius of the ellipse. """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", "Block") Block(obj) obj.Components = objectslist if App.GuiUp: ViewProviderDraftPart(obj.ViewObject) for o in objectslist: o.ViewObject.Visibility = False select(obj) return obj
def make_angular_dimension(center, angles, p3, normal=None): """makeAngularDimension(center,angle1,angle2,p3,[normal]): creates an angular Dimension from the given center, with the given list of angles, passing through p3. """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return obj = App.ActiveDocument.addObject("App::FeaturePython", "Dimension") AngularDimension(obj) if App.GuiUp: ViewProviderAngularDimension(obj.ViewObject) obj.Center = center for a in range(len(angles)): if angles[a] > 2 * math.pi: angles[a] = angles[a] - (2 * math.pi) obj.FirstAngle = math.degrees(angles[1]) obj.LastAngle = math.degrees(angles[0]) obj.Dimline = p3 if not normal: if hasattr(App, "DraftWorkingPlane"): normal = App.DraftWorkingPlane.axis else: normal = App.Vector(0, 0, 1) if App.GuiUp: # invert the normal if we are viewing it from the back vnorm = gui_utils.get3DView().getViewDirection() if vnorm.getAngle(normal) < math.pi / 2: normal = normal.negative() obj.Normal = normal if App.GuiUp: gui_utils.format_object(obj) gui_utils.select(obj) return obj
def scale(objectslist, scale=App.Vector(1, 1, 1), center=App.Vector(0, 0, 0), copy=False): """scale(objects, scale, [center], copy) Scales the objects contained in objects (that can be a list of objects or an object) of the given around given center. Parameters ---------- objectlist : list scale : Base.Vector Scale factors defined by a given vector (in X, Y, Z directions). objectlist : Base.Vector Center of the scale operation. copy : bool If copy is True, the actual objects are not scaled, but copies are created instead. Return ---------- The objects (or their copies) are returned. """ if not isinstance(objectslist, list): objectslist = [objectslist] newobjlist = [] for obj in objectslist: if copy: newobj = make_copy.make_copy(obj) else: newobj = obj if hasattr(obj, 'Shape'): scaled_shape = obj.Shape.copy() m = App.Matrix() m.move(center.negative()) m.scale(scale.x, scale.y, scale.z) m.move(center) scaled_shape = scaled_shape.transformGeometry(m) if utils.get_type(obj) == "Rectangle": p = [] for v in scaled_shape.Vertexes: p.append(v.Point) pl = obj.Placement.copy() pl.Base = p[0] diag = p[2].sub(p[0]) bb = p[1].sub(p[0]) bh = p[3].sub(p[0]) nb = DraftVecUtils.project(diag, bb) nh = DraftVecUtils.project(diag, bh) if obj.Length < 0: l = -nb.Length else: l = nb.Length if obj.Height < 0: h = -nh.Length else: h = nh.Length newobj.Length = l newobj.Height = h tr = p[0].sub(obj.Shape.Vertexes[0].Point) # unused? newobj.Placement = pl elif utils.get_type(obj) == "Wire" or utils.get_type(obj) == "BSpline": for index, point in enumerate(newobj.Points): scale_vertex(newobj, index, scale, center) elif hasattr(obj, 'Shape'): newobj.Shape = scaled_shape elif (obj.TypeId == "App::Annotation"): factor = scale.y * obj.ViewObject.FontSize newobj.ViewObject.FontSize = factor d = obj.Position.sub(center) newobj.Position = center.add( App.Vector(d.x * scale.x, d.y * scale.y, d.z * scale.z)) if copy: gui_utils.format_object(newobj, obj) newobjlist.append(newobj) if copy and utils.get_param("selectBaseObjects", False): gui_utils.select(objectslist) else: gui_utils.select(newobjlist) if len(newobjlist) == 1: return newobjlist[0] return newobjlist
def make_circle(radius, placement=None, face=None, startangle=None, endangle=None, support=None): """make_circle(radius, [placement, face, startangle, endangle]) or make_circle(edge,[face]): Creates a circle object with given parameters. Parameters ---------- radius : the radius of the circle. placement : If placement is given, it is used. face : Bool If face is False, the circle is shown as a wireframe, otherwise as a face. startangle : start angle of the arc (in degrees) endangle : end angle of the arc (in degrees) if startangle and endangle are equal, a circle is created, if they are different an arc is created edge : edge.Curve must be a 'Part.Circle' the circle is created from the given edge support : TODO: Describe """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return if placement: type_check([(placement,App.Placement)], "make_circle") if startangle != endangle: _name = "Arc" else: _name = "Circle" obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", _name) Circle(obj) if face != None: obj.MakeFace = face if isinstance(radius,Part.Edge): edge = radius if DraftGeomUtils.geomType(edge) == "Circle": obj.Radius = edge.Curve.Radius placement = App.Placement(edge.Placement) delta = edge.Curve.Center.sub(placement.Base) placement.move(delta) # Rotation of the edge rotOk = App.Rotation(edge.Curve.XAxis, edge.Curve.YAxis, edge.Curve.Axis, "ZXY") placement.Rotation = rotOk if len(edge.Vertexes) > 1: v0 = edge.Curve.XAxis v1 = (edge.Vertexes[0].Point).sub(edge.Curve.Center) v2 = (edge.Vertexes[-1].Point).sub(edge.Curve.Center) # Angle between edge.Curve.XAxis and the vector from center to start of arc a0 = math.degrees(App.Vector.getAngle(v0, v1)) # Angle between edge.Curve.XAxis and the vector from center to end of arc a1 = math.degrees(App.Vector.getAngle(v0, v2)) obj.FirstAngle = a0 obj.LastAngle = a1 else: obj.Radius = radius if (startangle != None) and (endangle != None): if startangle == -0: startangle = 0 obj.FirstAngle = startangle obj.LastAngle = endangle obj.Support = support if placement: obj.Placement = placement if App.GuiUp: ViewProviderDraft(obj.ViewObject) format_object(obj) select(obj) return obj
def make_angular_dimension(center=App.Vector(0, 0, 0), angles=[0, 90], dim_line=App.Vector(10, 10, 0), normal=None): """Create an angular dimension from the given center and angles. Parameters ---------- center: Base::Vector3, optional It defaults to the origin `Vector(0, 0, 0)`. Center of the dimension line, which is a circular arc. angles: list of two floats, optional It defaults to `[0, 90]`. It is a list of two angles, given in degrees, that determine the aperture of the dimension line, that is, of the circular arc. It is drawn counter-clockwise. :: angles = [0 90] angles = [330 60] # the arc crosses the X axis angles = [-30 60] # same angle dim_line: Base::Vector3, optional It defaults to `Vector(10, 10, 0)`. This is a point through which the extension of the dimension line will pass. This defines the radius of the dimension line, the circular arc. normal: Base::Vector3, optional It defaults to `None`, in which case the `normal` is taken from the currently active `App.DraftWorkingPlane.axis`. If the working plane is not available, then the `normal` defaults to +Z or `Vector(0, 0, 1)`. Returns ------- App::FeaturePython A scripted object of type `'AngularDimension'`. This object does not have a `Shape` attribute, as the text and lines are created on screen by Coin (pivy). None If there is a problem it will return `None`. """ _name = "make_angular_dimension" utils.print_header(_name, "Angular dimension") found, doc = utils.find_doc(App.activeDocument()) if not found: _err(_tr("No active document. Aborting.")) return None _msg("center: {}".format(center)) try: utils.type_check([(center, App.Vector)], name=_name) except TypeError: _err(_tr("Wrong input: must be a vector.")) return None _msg("angles: {}".format(angles)) try: utils.type_check([(angles, (tuple, list))], name=_name) if len(angles) != 2: _err(_tr("Wrong input: must be a list with two angles.")) return None ang1, ang2 = angles utils.type_check([(ang1, (int, float)), (ang2, (int, float))], name=_name) except TypeError: _err(_tr("Wrong input: must be a list with two angles.")) return None # If the angle is larger than 360 degrees, make sure # it is smaller than 360 for n in range(len(angles)): if angles[n] > 360: angles[n] = angles[n] - 360 _msg("dim_line: {}".format(dim_line)) try: utils.type_check([(dim_line, App.Vector)], name=_name) except TypeError: _err(_tr("Wrong input: must be a vector.")) return None _msg("normal: {}".format(normal)) if normal: try: utils.type_check([(dim_line, App.Vector)], name=_name) except TypeError: _err(_tr("Wrong input: must be a vector.")) return None if not normal: if hasattr(App, "DraftWorkingPlane"): normal = App.DraftWorkingPlane.axis else: normal = App.Vector(0, 0, 1) new_obj = App.ActiveDocument.addObject("App::FeaturePython", "Dimension") AngularDimension(new_obj) new_obj.Center = center new_obj.FirstAngle = angles[0] new_obj.LastAngle = angles[1] new_obj.Dimline = dim_line if App.GuiUp: ViewProviderAngularDimension(new_obj.ViewObject) # Invert the normal if we are viewing it from the back. # This is determined by the angle between the current # 3D view and the provided normal being below 90 degrees vnorm = gui_utils.get3DView().getViewDirection() if vnorm.getAngle(normal) < math.pi / 2: normal = normal.negative() new_obj.Normal = normal if App.GuiUp: gui_utils.format_object(new_obj) gui_utils.select(new_obj) return new_obj
def make_dimension(p1, p2, p3=None, p4=None): """Create one of three types of dimension objects. In all dimensions the p3 parameter defines a point through which the dimension line will go through. The current line width and color will be used. Linear dimension ---------------- - (p1, p2, p3): a simple linear dimension from p1 to p2 - (object, i1, i2, p3): creates a linked dimension to the provided object (edge), measuring the distance between its vertices indexed i1 and i2 Circular dimension ------------------ - (arc, i1, mode, p3): creates a linked dimension to the given arc object, i1 is the index of the arc edge that will be measured; mode is either "radius" or "diameter". """ if not App.ActiveDocument: _err("No active document. Aborting") return None new_obj = App.ActiveDocument.addObject("App::FeaturePython", "Dimension") LinearDimension(new_obj) if App.GuiUp: ViewProviderLinearDimension(new_obj.ViewObject) if isinstance(p1, App.Vector) and isinstance(p2, App.Vector): # Measure a straight distance between p1 and p2 new_obj.Start = p1 new_obj.End = p2 if not p3: p3 = p2.sub(p1) p3.multiply(0.5) p3 = p1.add(p3) elif isinstance(p2, int) and isinstance(p3, int): # p1 is an object, and measure the distance between vertices p2 and p3 # of this object linked = [] idx = (p2, p3) linked.append((p1, "Vertex" + str(p2 + 1))) linked.append((p1, "Vertex" + str(p3 + 1))) new_obj.LinkedGeometry = linked new_obj.Support = p1 # p4, and now p3, is the point through which the dimension line # will go through p3 = p4 if not p3: # When used from the GUI command, this will never run # because p4 will always be assigned to a vector, # so p3 will never be `None`. # Moreover, `new_obj.Base` doesn't exist, and certainly `Shape` # doesn't exist, so if this ever runs it will be an error. v1 = new_obj.Base.Shape.Vertexes[idx[0]].Point v2 = new_obj.Base.Shape.Vertexes[idx[1]].Point p3 = v2.sub(v1) p3.multiply(0.5) p3 = v1.add(p3) elif isinstance(p3, str): # If the original p3 is a string, we are measuring a circular arc # p2 should be an integer number starting from 0 linked = [] linked.append((p1, "Edge" + str(p2 + 1))) if p3 == "radius": # linked.append((p1, "Center")) if App.GuiUp: new_obj.ViewObject.Override = "R $dim" new_obj.Diameter = False elif p3 == "diameter": # linked.append((p1, "Diameter")) if App.GuiUp: new_obj.ViewObject.Override = "Ø $dim" new_obj.Diameter = True new_obj.LinkedGeometry = linked new_obj.Support = p1 # p4, and now p3, is the point through which the dimension line # will go through p3 = p4 if not p3: p3 = p1.Shape.Edges[p2].Curve.Center.add(App.Vector(1, 0, 0)) # This p3 is the point through which the dimension line will pass, # but this may not be the original p3, it could have been p4 # depending on the first three parameter values new_obj.Dimline = p3 if hasattr(App, "DraftWorkingPlane"): normal = App.DraftWorkingPlane.axis else: normal = App.Vector(0, 0, 1) if App.GuiUp: # invert the normal if we are viewing it from the back vnorm = gui_utils.get3DView().getViewDirection() if vnorm.getAngle(normal) < math.pi / 2: normal = normal.negative() new_obj.Normal = normal if App.GuiUp: gui_utils.format_object(new_obj) gui_utils.select(new_obj) return new_obj
def make_bezcurve(pointslist, closed=False, placement=None, face=None, support=None, degree=None): """make_bezcurve(pointslist, [closed], [placement]) Creates a Bezier Curve object from the given list of vectors. Parameters ---------- pointlist : [Base.Vector] List of points to create the polyline. Instead of a pointslist, you can also pass a Part Wire. TODO: Change the name so! closed : bool If closed is True or first and last points are identical, the created BSpline will be closed. placement : Base.Placement If a placement is given, it is used. face : Bool If face is False, the rectangle is shown as a wireframe, otherwise as a face. support : TODO: Describe degree : int Degree of the BezCurve """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return if not isinstance(pointslist,list): nlist = [] for v in pointslist.Vertexes: nlist.append(v.Point) pointslist = nlist if placement: utils.type_check([(placement,App.Placement)], "make_bezcurve") if len(pointslist) == 2: fname = "Line" else: fname = "BezCurve" obj = App.ActiveDocument.addObject("Part::Part2DObjectPython",fname) BezCurve(obj) obj.Points = pointslist if degree: obj.Degree = degree else: import Part obj.Degree = min((len(pointslist)-(1 * (not closed))), Part.BezierCurve().MaxDegree) obj.Closed = closed obj.Support = support if face != None: obj.MakeFace = face obj.Proxy.resetcontinuity(obj) if placement: obj.Placement = placement if App.GuiUp: ViewProviderBezCurve(obj.ViewObject) # if not face: obj.ViewObject.DisplayMode = "Wireframe" # obj.ViewObject.DisplayMode = "Wireframe" gui_utils.format_object(obj) gui_utils.select(obj) return obj
def move(objectslist, vector, copy=False): """move(objects,vector,[copy]) Move the objects contained in objects (that can be an object or a list of objects) in the direction and distance indicated by the given vector. Parameters ---------- objectslist : list vector : Base.Vector Delta Vector to move the clone from the original position. copy : bool If copy is True, the actual objects are not moved, but copies are created instead. Return ---------- The objects (or their copies) are returned. """ utils.type_check([(vector, App.Vector), (copy, bool)], "move") if not isinstance(objectslist, list): objectslist = [objectslist] objectslist.extend(utils.get_movable_children(objectslist)) newobjlist = [] newgroups = {} objectslist = utils.filter_objects_for_modifiers(objectslist, copy) for obj in objectslist: newobj = None # real_vector have been introduced to take into account # the possibility that object is inside an App::Part # TODO: Make Move work also with App::Link if hasattr(obj, "getGlobalPlacement"): v_minus_global = obj.getGlobalPlacement().inverse( ).Rotation.multVec(vector) real_vector = obj.Placement.Rotation.multVec(v_minus_global) else: real_vector = vector if utils.get_type(obj) == "Point": if copy: newobj = make_copy(obj) else: newobj = obj newobj.X = obj.X + real_vector.x newobj.Y = obj.Y + real_vector.y newobj.Z = obj.Z + real_vector.z elif obj.isDerivedFrom("App::DocumentObjectGroup"): pass elif hasattr(obj, 'Shape'): if copy: newobj = make_copy(obj) else: newobj = obj pla = newobj.Placement pla.move(real_vector) elif utils.get_type(obj) == "Annotation": if copy: newobj = make_copy(obj) else: newobj = obj newobj.Position = obj.Position.add(real_vector) elif utils.get_type(obj) == "Text": if copy: newobj = make_copy(obj) else: newobj = obj newobj.Placement.Base = obj.Placement.Base.add(real_vector) elif utils.get_type(obj) in ["Dimension", "LinearDimension"]: if copy: newobj = make_copy(obj) else: newobj = obj newobj.Start = obj.Start.add(real_vector) newobj.End = obj.End.add(real_vector) newobj.Dimline = obj.Dimline.add(real_vector) elif utils.get_type(obj) in ["AngularDimension"]: if copy: newobj = make_copy(obj) else: newobj = obj newobj.Center = obj.Start.add(real_vector) elif "Placement" in obj.PropertiesList: if copy: newobj = make_copy(obj) else: newobj = obj pla = obj.Placement pla.move(real_vector) if newobj is not None: newobjlist.append(newobj) if copy: for p in obj.InList: if p.isDerivedFrom("App::DocumentObjectGroup") and ( p in objectslist): g = newgroups.setdefault( p.Name, App.ActiveDocument.addObject(p.TypeId, p.Name)) g.addObject(newobj) break if utils.get_type(p) == "Layer": p.Proxy.addObject(p, newobj) if copy and utils.get_param("selectBaseObjects", False): gui_utils.select(objectslist) else: gui_utils.select(newobjlist) if len(newobjlist) == 1: return newobjlist[0] return newobjlist
def offset(obj, delta, copy=False, bind=False, sym=False, occ=False): """offset(object,delta,[copymode],[bind]) Offset the given wire by applying the given delta Vector to its first vertex. Parameters ---------- obj : delta : Base.Vector or list of Base.Vector If offsetting a BSpline, the delta must not be a Vector but a list of Vectors, one for each node of the spline. copy : bool If copymode is True, another object is created, otherwise the same object gets offsetted. copy : bool If bind is True, and provided the wire is open, the original and the offset wires will be bound by their endpoints, forming a face. sym : bool if sym is True, bind must be true too, and the offset is made on both sides, the total width being the given delta length. """ import Part import DraftGeomUtils newwire = None delete = None if utils.get_type(obj).startswith("Part::") or utils.get_type( obj).startswith("Sketcher::"): copy = True print( "the offset tool is currently unable to offset a non-Draft object directly - Creating a copy" ) def getRect(p, obj): """returns length,height,placement""" pl = obj.Placement.copy() pl.Base = p[0] diag = p[2].sub(p[0]) bb = p[1].sub(p[0]) bh = p[3].sub(p[0]) nb = DraftVecUtils.project(diag, bb) nh = DraftVecUtils.project(diag, bh) if obj.Length.Value < 0: l = -nb.Length else: l = nb.Length if obj.Height.Value < 0: h = -nh.Length else: h = nh.Length return l, h, pl def getRadius(obj, delta): """returns a new radius for a regular polygon""" an = math.pi / obj.FacesNumber nr = DraftVecUtils.rotate(delta, -an) nr.multiply(1 / math.cos(an)) nr = obj.Shape.Vertexes[0].Point.add(nr) nr = nr.sub(obj.Placement.Base) nr = nr.Length if obj.DrawMode == "inscribed": return nr else: return nr * math.cos(math.pi / obj.FacesNumber) newwire = None if utils.get_type(obj) == "Circle": pass elif utils.get_type(obj) == "BSpline": pass else: if sym: d1 = App.Vector(delta).multiply(0.5) d2 = d1.negative() n1 = DraftGeomUtils.offsetWire(obj.Shape, d1) n2 = DraftGeomUtils.offsetWire(obj.Shape, d2) else: if isinstance(delta, float) and (len(obj.Shape.Edges) == 1): # circle c = obj.Shape.Edges[0].Curve nc = Part.Circle(c.Center, c.Axis, delta) if len(obj.Shape.Vertexes) > 1: nc = Part.ArcOfCircle(nc, obj.Shape.Edges[0].FirstParameter, obj.Shape.Edges[0].LastParameter) newwire = Part.Wire(nc.toShape()) p = [] else: newwire = DraftGeomUtils.offsetWire(obj.Shape, delta) if DraftGeomUtils.hasCurves(newwire) and copy: p = [] else: p = DraftGeomUtils.getVerts(newwire) if occ: newobj = App.ActiveDocument.addObject("Part::Feature", "Offset") newobj.Shape = DraftGeomUtils.offsetWire(obj.Shape, delta, occ=True) gui_utils.formatObject(newobj, obj) if not copy: delete = obj.Name elif bind: if not DraftGeomUtils.isReallyClosed(obj.Shape): if sym: s1 = n1 s2 = n2 else: s1 = obj.Shape s2 = newwire if s1 and s2: w1 = s1.Edges w2 = s2.Edges w3 = Part.LineSegment(s1.Vertexes[0].Point, s2.Vertexes[0].Point).toShape() w4 = Part.LineSegment(s1.Vertexes[-1].Point, s2.Vertexes[-1].Point).toShape() newobj = App.ActiveDocument.addObject("Part::Feature", "Offset") newobj.Shape = Part.Face(Part.Wire(w1 + [w3] + w2 + [w4])) else: print("Draft.offset: Unable to bind wires") else: newobj = App.ActiveDocument.addObject("Part::Feature", "Offset") newobj.Shape = Part.Face(obj.Shape.Wires[0]) if not copy: delete = obj.Name elif copy: newobj = None if sym: return None if utils.get_type(obj) == "Wire": if p: newobj = make_wire(p) newobj.Closed = obj.Closed elif newwire: newobj = App.ActiveDocument.addObject("Part::Feature", "Offset") newobj.Shape = newwire else: print("Draft.offset: Unable to duplicate this object") elif utils.get_type(obj) == "Rectangle": if p: length, height, plac = getRect(p, obj) newobj = make_rectangle(length, height, plac) elif newwire: newobj = App.ActiveDocument.addObject("Part::Feature", "Offset") newobj.Shape = newwire else: print("Draft.offset: Unable to duplicate this object") elif utils.get_type(obj) == "Circle": pl = obj.Placement newobj = make_circle(delta) newobj.FirstAngle = obj.FirstAngle newobj.LastAngle = obj.LastAngle newobj.Placement = pl elif utils.get_type(obj) == "Polygon": pl = obj.Placement newobj = make_polygon(obj.FacesNumber) newobj.Radius = getRadius(obj, delta) newobj.DrawMode = obj.DrawMode newobj.Placement = pl elif utils.get_type(obj) == "BSpline": newobj = make_bspline(delta) newobj.Closed = obj.Closed else: # try to offset anyway try: if p: newobj = make_wire(p) newobj.Closed = obj.Shape.isClosed() except Part.OCCError: pass if (not newobj) and newwire: newobj = App.ActiveDocument.addObject("Part::Feature", "Offset") newobj.Shape = newwire if not newobj: print("Draft.offset: Unable to create an offset") if newobj: gui_utils.formatObject(newobj, obj) else: newobj = None if sym: return None if utils.get_type(obj) == "Wire": if obj.Base or obj.Tool: App.Console.PrintWarning("Warning: object history removed\n") obj.Base = None obj.Tool = None obj.Placement = App.Placement( ) # p points are in the global coordinate system obj.Points = p elif utils.get_type(obj) == "BSpline": #print(delta) obj.Points = delta #print("done") elif utils.get_type(obj) == "Rectangle": length, height, plac = getRect(p, obj) obj.Placement = plac obj.Length = length obj.Height = height elif utils.get_type(obj) == "Circle": obj.Radius = delta elif utils.get_type(obj) == "Polygon": obj.Radius = getRadius(obj, delta) elif utils.get_type(obj) == 'Part': print("unsupported object") # TODO newobj = obj if copy and utils.get_param("selectBaseObjects", False): gui_utils.select(newobj) else: gui_utils.select(obj) if delete: App.ActiveDocument.removeObject(delete) return newobj
def upgrade(objects, delete=False, force=None): """Upgrade the given objects. This is a counterpart to `downgrade`. Parameters ---------- objects: Part::Feature or list A single object to upgrade or a list containing various such objects. delete: bool, optional It defaults to `False`. If it is `True`, the old objects are deleted, and only the resulting object is kept. force: str, optional It defaults to `None`. Its value can be used to force a certain method of upgrading. It can be any of: `'makeCompound'`, `'closeGroupWires'`, `'makeSolid'`, `'closeWire'`, `'turnToParts'`, `'makeFusion'`, `'makeShell'`, `'makeFaces'`, `'draftify'`, `'joinFaces'`, `'makeSketchFace'`, `'makeWires'`. Returns ------- tuple A tuple containing two lists, a list of new objects and a list of objects to be deleted. None If there is a problem it will return `None`. See Also -------- downgrade """ _name = "upgrade" utils.print_header(_name, "Upgrade objects") if not isinstance(objects, list): objects = [objects] delete_list = [] add_list = [] doc = App.ActiveDocument # definitions of actions to perform def turnToLine(obj): """Turn an edge into a Draft Line.""" p1 = obj.Shape.Vertexes[0].Point p2 = obj.Shape.Vertexes[-1].Point newobj = make_line.make_line(p1, p2) add_list.append(newobj) delete_list.append(obj) return newobj def makeCompound(objectslist): """Return a compound object made from the given objects.""" newobj = make_block.make_block(objectslist) add_list.append(newobj) return newobj def closeGroupWires(groupslist): """Close every open wire in the given groups.""" result = False for grp in groupslist: for obj in grp.Group: newobj = closeWire(obj) # add new objects to their respective groups if newobj: result = True grp.addObject(newobj) return result def makeSolid(obj): """Turn an object into a solid, if possible.""" if obj.Shape.Solids: return None sol = None try: sol = Part.makeSolid(obj.Shape) except Part.OCCError: return None else: if sol: if sol.isClosed(): newobj = doc.addObject("Part::Feature", "Solid") newobj.Shape = sol add_list.append(newobj) delete_list.append(obj) return newobj else: _err(_tr("Object must be a closed shape")) else: _err(_tr("No solid object created")) return None def closeWire(obj): """Close a wire object, if possible.""" if obj.Shape.Faces: return None if len(obj.Shape.Wires) != 1: return None if len(obj.Shape.Edges) == 1: return None if is_straight_line(obj.Shape) == True: return None if utils.get_type(obj) == "Wire": obj.Closed = True return True else: w = obj.Shape.Wires[0] if not w.isClosed(): edges = w.Edges p0 = w.Vertexes[0].Point p1 = w.Vertexes[-1].Point if p0 == p1: # sometimes an open wire can have the same start # and end points (OCCT bug); in this case, # although it is not closed, the face works. f = Part.Face(w) newobj = doc.addObject("Part::Feature", "Face") newobj.Shape = f else: edges.append(Part.LineSegment(p1, p0).toShape()) w = Part.Wire(Part.__sortEdges__(edges)) newobj = doc.addObject("Part::Feature", "Wire") newobj.Shape = w add_list.append(newobj) delete_list.append(obj) return newobj else: return None def turnToParts(meshes): """Turn given meshes to parts.""" result = False for mesh in meshes: sh = Arch.getShapeFromMesh(mesh.Mesh) if sh: newobj = doc.addObject("Part::Feature", "Shell") newobj.Shape = sh add_list.append(newobj) delete_list.append(mesh) result = True return result def makeFusion(obj1, obj2=None): """Make a Draft or Part fusion between 2 given objects.""" if not obj2 and isinstance(obj1, (list, tuple)): obj1, obj2 = obj1[0], obj1[1] newobj = fuse.fuse(obj1, obj2) if newobj: add_list.append(newobj) return newobj return None def makeShell(objectslist): """Make a shell or compound with the given objects.""" params = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") preserveFaceColor = params.GetBool("preserveFaceColor") # True preserveFaceNames = params.GetBool("preserveFaceNames") # True faces = [] facecolors = [[], []] if preserveFaceColor else None for obj in objectslist: faces.extend(obj.Shape.Faces) if App.GuiUp and preserveFaceColor: # at this point, obj.Shape.Faces are not in same order as the # original faces we might have gotten as a result # of downgrade, nor do they have the same hashCode(). # Nevertheless, they still keep reference to their original # colors, capture that in facecolors. # Also, cannot use ShapeColor here, we need a whole array # matching the colors of the array of faces per object, # only DiffuseColor has that facecolors[0].extend(obj.ViewObject.DiffuseColor) facecolors[1] = faces sh = Part.makeShell(faces) if sh: if sh.Faces: newobj = doc.addObject("Part::Feature", str(sh.ShapeType)) newobj.Shape = sh if preserveFaceNames: firstName = objectslist[0].Label nameNoTrailNumbers = re.sub(r"\d+$", "", firstName) newobj.Label = "{} {}".format(newobj.Label, nameNoTrailNumbers) if App.GuiUp and preserveFaceColor: # At this point, sh.Faces are completely new, # with different hashCodes and different ordering # from obj.Shape.Faces. Since we cannot compare # via hashCode(), we have to iterate and use a different # criteria to find the original matching color colarray = [] for ind, face in enumerate(newobj.Shape.Faces): for fcind, fcface in enumerate(facecolors[1]): if (face.Area == fcface.Area and face.CenterOfMass == fcface.CenterOfMass): colarray.append(facecolors[0][fcind]) break newobj.ViewObject.DiffuseColor = colarray add_list.append(newobj) delete_list.extend(objectslist) return newobj return None def joinFaces(objectslist, coplanarity=False, checked=False): """Make one big face from selected objects, if possible.""" faces = [] for obj in objectslist: faces.extend(obj.Shape.Faces) # check coplanarity if needed if not checked: coplanarity = DraftGeomUtils.is_coplanar(faces, 1e-3) if not coplanarity: _err(_tr("Faces must be coplanar to be refined")) return None # fuse faces fuse_face = faces.pop(0) for face in faces: fuse_face = fuse_face.fuse(face) face = DraftGeomUtils.concatenate(fuse_face) # to prevent create new object if concatenate fails if face.isEqual(fuse_face): face = None if face: # several coplanar and non-curved faces, # they can become a Draft Wire if (not DraftGeomUtils.hasCurves(face) and len(face.Wires) == 1): newobj = make_wire.make_wire(face.Wires[0], closed=True, face=True) # if not possible, we do a non-parametric union else: newobj = doc.addObject("Part::Feature", "Union") newobj.Shape = face add_list.append(newobj) delete_list.extend(objectslist) return newobj return None def makeSketchFace(obj): """Make a face from a sketch.""" face = Part.makeFace(obj.Shape.Wires, "Part::FaceMakerBullseye") if face: newobj = doc.addObject("Part::Feature", "Face") newobj.Shape = face add_list.append(newobj) if App.GuiUp: obj.ViewObject.Visibility = False return newobj return None def makeFaces(objectslist): """Make a face from every closed wire in the list.""" result = False for o in objectslist: for w in o.Shape.Wires: try: f = Part.Face(w) except Part.OCCError: pass else: newobj = doc.addObject("Part::Feature", "Face") newobj.Shape = f add_list.append(newobj) result = True if o not in delete_list: delete_list.append(o) return result def makeWires(objectslist): """Join edges in the given objects list into wires.""" edges = [] for object in objectslist: for edge in object.Shape.Edges: edges.append(edge) try: sorted_edges = Part.sortEdges(edges) if _DEBUG: for item_sorted_edges in sorted_edges: for e in item_sorted_edges: print("Curve: {}".format(e.Curve)) print("first: {}, last: {}".format( e.Vertexes[0].Point, e.Vertexes[-1].Point)) wires = [Part.Wire(e) for e in sorted_edges] except Part.OCCError: return None else: for wire in wires: newobj = doc.addObject("Part::Feature", "Wire") newobj.Shape = wire add_list.append(newobj) # delete object only if there are no links to it # TODO: A more refined criteria to delete object for object in objectslist: if object.InList: if App.GuiUp: object.ViewObject.Visibility = False else: delete_list.append(object) return True return None # analyzing what we have in our selection edges = [] wires = [] openwires = [] faces = [] groups = [] parts = [] curves = [] facewires = [] loneedges = [] meshes = [] for ob in objects: if ob.TypeId == "App::DocumentObjectGroup": groups.append(ob) elif hasattr(ob, 'Shape'): parts.append(ob) faces.extend(ob.Shape.Faces) wires.extend(ob.Shape.Wires) edges.extend(ob.Shape.Edges) for f in ob.Shape.Faces: facewires.extend(f.Wires) wirededges = [] for w in ob.Shape.Wires: if len(w.Edges) > 1: for e in w.Edges: wirededges.append(e.hashCode()) if not w.isClosed(): openwires.append(w) for e in ob.Shape.Edges: if DraftGeomUtils.geomType(e) != "Line": curves.append(e) if not e.hashCode() in wirededges and not e.isClosed(): loneedges.append(e) elif ob.isDerivedFrom("Mesh::Feature"): meshes.append(ob) objects = parts if _DEBUG: print("objects: {}, edges: {}".format(objects, edges)) print("wires: {}, openwires: {}".format(wires, openwires)) print("faces: {}".format(faces)) print("groups: {}, curves: {}".format(groups, curves)) print("facewires: {}, loneedges: {}".format(facewires, loneedges)) if force: all_func = { "makeCompound": makeCompound, "closeGroupWires": closeGroupWires, "makeSolid": makeSolid, "closeWire": closeWire, "turnToParts": turnToParts, "makeFusion": makeFusion, "makeShell": makeShell, "makeFaces": makeFaces, "draftify": ext_draftify.draftify, "joinFaces": joinFaces, "makeSketchFace": makeSketchFace, "makeWires": makeWires, "turnToLine": turnToLine } if force in all_func: result = all_func[force](objects) else: _msg(_tr("Upgrade: Unknown force method:") + " " + force) result = None else: # checking faces coplanarity # The precision needed in Part.makeFace is 1e-7. Here we use a # higher value to let that function throw the exception when # joinFaces is called if the precision is insufficient if faces: faces_coplanarity = DraftGeomUtils.is_coplanar(faces, 1e-3) # applying transformations automatically result = None # if we have a group: turn each closed wire inside into a face if groups: result = closeGroupWires(groups) if result: _msg(_tr("Found groups: closing each open object inside")) # if we have meshes, we try to turn them into shapes elif meshes: result = turnToParts(meshes) if result: _msg(_tr("Found meshes: turning into Part shapes")) # we have only faces here, no lone edges elif faces and (len(wires) + len(openwires) == len(facewires)): # we have one shell: we try to make a solid if len(objects) == 1 and len(faces) > 3 and not faces_coplanarity: result = makeSolid(objects[0]) if result: _msg(_tr("Found 1 solidifiable object: solidifying it")) # we have exactly 2 objects: we fuse them elif len(objects) == 2 and not curves and not faces_coplanarity: result = makeFusion(objects[0], objects[1]) if result: _msg(_tr("Found 2 objects: fusing them")) # we have many separate faces: we try to make a shell or compound elif len(objects) >= 2 and len(faces) > 1 and not loneedges: result = makeShell(objects) if result: _msg( _tr("Found several objects: creating a " + str(result.Shape.ShapeType))) # we have faces: we try to join them if they are coplanar elif len(objects) == 1 and len(faces) > 1: result = joinFaces(objects, faces_coplanarity, True) if result: _msg( _tr("Found object with several coplanar faces: " "refine them")) # only one object: if not parametric, we "draftify" it elif (len(objects) == 1 and not objects[0].isDerivedFrom("Part::Part2DObjectPython")): result = ext_draftify.draftify(objects[0]) if result: _msg( _tr("Found 1 non-parametric objects: " "draftifying it")) # in the following cases there are no faces elif not faces: # we have only closed wires if wires and not openwires and not loneedges: # we have a sketch: extract a face if (len(objects) == 1 and objects[0].isDerivedFrom("Sketcher::SketchObject")): result = makeSketchFace(objects[0]) if result: _msg( _tr("Found 1 closed sketch object: " "creating a face from it")) # only closed wires else: result = makeFaces(objects) if result: _msg(_tr("Found closed wires: creating faces")) # wires or edges: we try to join them elif len(wires) > 1 or len(loneedges) > 1: result = makeWires(objects) if result: _msg(_tr("Found several wires or edges: wiring them")) # TODO: improve draftify function # only one object: if not parametric, we "draftify" it # elif (len(objects) == 1 # and not objects[0].isDerivedFrom("Part::Part2DObjectPython")): # result = ext_draftify.draftify(objects[0]) # if result: # _msg(_tr("Found 1 non-parametric objects: " # "draftifying it")) # special case, we have only one open wire. We close it, # unless it has only 1 edge! elif len(objects) == 1 and len(openwires) == 1: result = closeWire(objects[0]) _msg(_tr("trying: closing it")) if result: _msg(_tr("Found 1 open wire: closing it")) # we have only one object that contains one edge # TODO: this case should be considered in draftify elif len(objects) == 1 and len(edges) == 1: # turn to Draft Line e = objects[0].Shape.Edges[0] if isinstance(e.Curve, (Part.LineSegment, Part.Line)): result = turnToLine(objects[0]) if result: _msg(_tr("Found 1 linear object: converting to line")) # only points, no edges elif not edges and len(objects) > 1: result = makeCompound(objects) if result: _msg(_tr("Found points: creating compound")) # all other cases, if more than 1 object, make a compound elif len(objects) > 1: result = makeCompound(objects) if result: _msg( _tr("Found several non-treatable objects: " "creating compound")) # no result has been obtained if not result: _msg(_tr("Unable to upgrade these objects.")) if delete: names = [] for o in delete_list: names.append(o.Name) delete_list = [] for n in names: doc.removeObject(n) gui_utils.select(add_list) return add_list, delete_list
def make_dimension(p1, p2, p3=None, p4=None): """makeDimension(p1,p2,[p3]) or makeDimension(object,i1,i2,p3) or makeDimension(objlist,indices,p3): Creates a Dimension object with the dimension line passign through p3.The current line width and color will be used. There are multiple ways to create a dimension, depending on the arguments you pass to it: - (p1,p2,p3): creates a standard dimension from p1 to p2 - (object,i1,i2,p3): creates a linked dimension to the given object, measuring the distance between its vertices indexed i1 and i2 - (object,i1,mode,p3): creates a linked dimension to the given object, i1 is the index of the (curved) edge to measure, and mode is either "radius" or "diameter". """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return obj = App.ActiveDocument.addObject("App::FeaturePython", "Dimension") LinearDimension(obj) if App.GuiUp: ViewProviderLinearDimension(obj.ViewObject) if isinstance(p1, App.Vector) and isinstance(p2, App.Vector): obj.Start = p1 obj.End = p2 if not p3: p3 = p2.sub(p1) p3.multiply(0.5) p3 = p1.add(p3) elif isinstance(p2, int) and isinstance(p3, int): l = [] idx = (p2, p3) l.append((p1, "Vertex" + str(p2 + 1))) l.append((p1, "Vertex" + str(p3 + 1))) obj.LinkedGeometry = l obj.Support = p1 p3 = p4 if not p3: v1 = obj.Base.Shape.Vertexes[idx[0]].Point v2 = obj.Base.Shape.Vertexes[idx[1]].Point p3 = v2.sub(v1) p3.multiply(0.5) p3 = v1.add(p3) elif isinstance(p3, str): l = [] l.append((p1, "Edge" + str(p2 + 1))) if p3 == "radius": #l.append((p1,"Center")) if App.GuiUp: obj.ViewObject.Override = "R $dim" obj.Diameter = False elif p3 == "diameter": #l.append((p1,"Diameter")) if App.GuiUp: obj.ViewObject.Override = "Ø $dim" obj.Diameter = True obj.LinkedGeometry = l obj.Support = p1 p3 = p4 if not p3: p3 = p1.Shape.Edges[p2].Curve.Center.add(App.Vector(1, 0, 0)) obj.Dimline = p3 if hasattr(App, "DraftWorkingPlane"): normal = App.DraftWorkingPlane.axis else: normal = App.Vector(0, 0, 1) if App.GuiUp: # invert the normal if we are viewing it from the back vnorm = gui_utils.get3DView().getViewDirection() if vnorm.getAngle(normal) < math.pi / 2: normal = normal.negative() obj.Normal = normal if App.GuiUp: gui_utils.format_object(obj) gui_utils.select(obj) return obj
def rotate(objectslist, angle, center=App.Vector(0,0,0), axis=App.Vector(0,0,1), copy=False): """rotate(objects,angle,[center,axis,copy]) Rotates the objects contained in objects (that can be a list of objects or an object) of the given angle (in degrees) around the center, using axis as a rotation axis. Parameters ---------- objectlist : list angle : list center : Base.Vector axis : Base.Vector If axis is omitted, the rotation will be around the vertical Z axis. copy : bool If copy is True, the actual objects are not moved, but copies are created instead. Return ---------- The objects (or their copies) are returned. """ import Part utils.type_check([(copy,bool)], "rotate") if not isinstance(objectslist,list): objectslist = [objectslist] objectslist.extend(groups.get_movable_children(objectslist)) newobjlist = [] newgroups = {} objectslist = utils.filter_objects_for_modifiers(objectslist, copy) for obj in objectslist: newobj = None # real_center and real_axis are introduced to take into account # the possibility that object is inside an App::Part if hasattr(obj, "getGlobalPlacement"): ci = obj.getGlobalPlacement().inverse().multVec(center) real_center = obj.Placement.multVec(ci) ai = obj.getGlobalPlacement().inverse().Rotation.multVec(axis) real_axis = obj.Placement.Rotation.multVec(ai) else: real_center = center real_axis = axis if copy: newobj = make_copy.make_copy(obj) else: newobj = obj if obj.isDerivedFrom("App::Annotation"): # TODO: this is very different from how move handle annotations # maybe we can uniform the two methods if axis.normalize() == App.Vector(1,0,0): newobj.ViewObject.RotationAxis = "X" newobj.ViewObject.Rotation = angle elif axis.normalize() == App.Vector(0,1,0): newobj.ViewObject.RotationAxis = "Y" newobj.ViewObject.Rotation = angle elif axis.normalize() == App.Vector(0,-1,0): newobj.ViewObject.RotationAxis = "Y" newobj.ViewObject.Rotation = -angle elif axis.normalize() == App.Vector(0,0,1): newobj.ViewObject.RotationAxis = "Z" newobj.ViewObject.Rotation = angle elif axis.normalize() == App.Vector(0,0,-1): newobj.ViewObject.RotationAxis = "Z" newobj.ViewObject.Rotation = -angle elif utils.get_type(obj) == "Point": v = App.Vector(obj.X,obj.Y,obj.Z) rv = v.sub(real_center) rv = DraftVecUtils.rotate(rv, math.radians(angle), real_axis) v = real_center.add(rv) newobj.X = v.x newobj.Y = v.y newobj.Z = v.z elif obj.isDerivedFrom("App::DocumentObjectGroup"): pass elif hasattr(obj,"Placement"): #FreeCAD.Console.PrintMessage("placement rotation\n") shape = Part.Shape() shape.Placement = obj.Placement shape.rotate(DraftVecUtils.tup(real_center), DraftVecUtils.tup(real_axis), angle) newobj.Placement = shape.Placement elif hasattr(obj,'Shape') and (utils.get_type(obj) not in ["WorkingPlaneProxy","BuildingPart"]): #think it make more sense to try first to rotate placement and later to try with shape. no? shape = obj.Shape.copy() shape.rotate(DraftVecUtils.tup(real_center), DraftVecUtils.tup(real_axis), angle) newobj.Shape = shape if copy: gui_utils.formatObject(newobj,obj) if newobj is not None: newobjlist.append(newobj) if copy: for p in obj.InList: if p.isDerivedFrom("App::DocumentObjectGroup") and (p in objectslist): g = newgroups.setdefault(p.Name, App.ActiveDocument.addObject(p.TypeId, p.Name)) g.addObject(newobj) break gui_utils.select(newobjlist) if len(newobjlist) == 1: return newobjlist[0] return newobjlist
def make_clone(obj, delta=None, forcedraft=False): """clone(obj,[delta,forcedraft]) Makes a clone of the given object(s). The clone is an exact, linked copy of the given object. If the original object changes, the final object changes too. Parameters ---------- obj : delta : Base.Vector Delta Vector to move the clone from the original position. forcedraft : bool If forcedraft is True, the resulting object is a Draft clone even if the input object is an Arch object. """ prefix = get_param("ClonePrefix", "") cl = None if prefix: prefix = prefix.strip() + " " if not isinstance(obj, list): obj = [obj] if (len(obj) == 1) and obj[0].isDerivedFrom("Part::Part2DObject"): cl = App.ActiveDocument.addObject("Part::Part2DObjectPython", "Clone2D") cl.Label = prefix + obj[0].Label + " (2D)" elif (len(obj) == 1) and (hasattr(obj[0], "CloneOf") or (get_type( obj[0]) == "BuildingPart")) and (not forcedraft): # arch objects can be clones import Arch if get_type(obj[0]) == "BuildingPart": cl = Arch.makeComponent() else: try: clonefunc = getattr(Arch, "make" + obj[0].Proxy.Type) except: pass # not a standard Arch object... Fall back to Draft mode else: cl = clonefunc() if cl: base = getCloneBase(obj[0]) cl.Label = prefix + base.Label cl.CloneOf = base if hasattr(cl, "Material") and hasattr(obj[0], "Material"): cl.Material = obj[0].Material if get_type(obj[0]) != "BuildingPart": cl.Placement = obj[0].Placement try: cl.Role = base.Role cl.Description = base.Description cl.Tag = base.Tag except: pass if App.GuiUp: format_object(cl, base) cl.ViewObject.DiffuseColor = base.ViewObject.DiffuseColor if get_type(obj[0]) in ["Window", "BuildingPart"]: ToDo.delay(Arch.recolorize, cl) select(cl) return cl # fall back to Draft clone mode if not cl: cl = App.ActiveDocument.addObject("Part::FeaturePython", "Clone") cl.addExtension("Part::AttachExtensionPython", None) cl.Label = prefix + obj[0].Label Clone(cl) if App.GuiUp: ViewProviderClone(cl.ViewObject) cl.Objects = obj if delta: cl.Placement.move(delta) elif (len(obj) == 1) and hasattr(obj[0], "Placement"): cl.Placement = obj[0].Placement format_object(cl, obj[0]) if hasattr(cl, "LongName") and hasattr(obj[0], "LongName"): cl.LongName = obj[0].LongName if App.GuiUp and (len(obj) > 1): cl.ViewObject.Proxy.resetColors(cl.ViewObject) select(cl) return cl
def downgrade(objects, delete=False, force=None): """Downgrade the given objects. This is a counterpart to `upgrade`. Parameters ---------- objects: Part::Feature or list A single object to downgrade or a list containing various such objects. delete: bool, optional It defaults to `False`. If it is `True`, the old objects are deleted, and only the resulting object is kept. force: str, optional It defaults to `None`. Its value can be used to force a certain method of downgrading. It can be any of: `'explode'`, `'shapify'`, `'subtr'`, `'splitFaces'`, `'cut2'`, `'getWire'`, `'splitWires'`, or `'splitCompounds'`. Returns ------- tuple A tuple containing two lists, a list of new objects and a list of objects to be deleted. None If there is a problem it will return `None`. See Also -------- ugrade """ _name = "downgrade" utils.print_header(_name, "Downgrade objects") if not isinstance(objects, list): objects = [objects] delete_list = [] add_list = [] doc = App.ActiveDocument # actions definitions def explode(obj): """Explode a Draft block.""" pl = obj.Placement newobj = [] for o in obj.Components: o.Placement = o.Placement.multiply(pl) if App.GuiUp: o.ViewObject.Visibility = True if newobj: delete_list(obj) return newobj return None def cut2(objects): """Cut first object from the last one.""" newobj = cut.cut(objects[0], objects[1]) if newobj: add_list.append(newobj) return newobj return None def splitCompounds(objects): """Split solids contained in compound objects into new objects.""" result = False for o in objects: if o.Shape.Solids: for s in o.Shape.Solids: newobj = doc.addObject("Part::Feature", "Solid") newobj.Shape = s add_list.append(newobj) result = True delete_list.append(o) return result def splitFaces(objects): """Split faces contained in objects into new objects.""" result = False params = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") preserveFaceColor = params.GetBool("preserveFaceColor") # True preserveFaceNames = params.GetBool("preserveFaceNames") # True for o in objects: if App.GuiUp and preserveFaceColor and o.ViewObject: voDColors = o.ViewObject.DiffuseColor else: voDColors = None oLabel = o.Label if hasattr(o, 'Label') else "" if o.Shape.Faces: for ind, f in enumerate(o.Shape.Faces): newobj = doc.addObject("Part::Feature", "Face") newobj.Shape = f if preserveFaceNames: newobj.Label = "{} {}".format(oLabel, newobj.Label) if App.GuiUp and preserveFaceColor and voDColors: # At this point, some single-color objects might have # just a single value in voDColors for all faces, # so we handle that if ind < len(voDColors): tcolor = voDColors[ind] else: tcolor = voDColors[0] # does is not applied visually on its own # just in case newobj.ViewObject.DiffuseColor[0] = tcolor # this gets applied, works by itself too newobj.ViewObject.ShapeColor = tcolor add_list.append(newobj) result = True delete_list.append(o) return result def subtr(objects): """Subtract objects from the first one.""" faces = [] for o in objects: if o.Shape.Faces: faces.extend(o.Shape.Faces) delete_list.append(o) u = faces.pop(0) for f in faces: u = u.cut(f) if not u.isNull(): newobj = doc.addObject("Part::Feature", "Subtraction") newobj.Shape = u add_list.append(newobj) return newobj return None def getWire(obj): """Get the wire from a face object.""" result = False for w in obj.Shape.Faces[0].Wires: newobj = doc.addObject("Part::Feature", "Wire") newobj.Shape = w add_list.append(newobj) result = True delete_list.append(obj) return result def splitWires(objects): """Split the wires contained in objects into edges.""" result = False for o in objects: if o.Shape.Edges: for e in o.Shape.Edges: newobj = doc.addObject("Part::Feature", "Edge") newobj.Shape = e add_list.append(newobj) delete_list.append(o) result = True return result # analyzing objects faces = [] edges = [] onlyedges = True parts = [] solids = [] result = None for o in objects: if hasattr(o, 'Shape'): for s in o.Shape.Solids: solids.append(s) for f in o.Shape.Faces: faces.append(f) for e in o.Shape.Edges: edges.append(e) if o.Shape.ShapeType != "Edge": onlyedges = False parts.append(o) objects = parts if force: if force in ("explode", "shapify", "subtr", "splitFaces", "cut2", "getWire", "splitWires"): # TODO: Using eval to evaluate a string is not ideal # and potentially a security risk. # How do we execute the function without calling eval? # Best case, a series of if-then statements. shapify = utils.shapify result = eval(force)(objects) else: _msg(_tr("Upgrade: Unknown force method:") + " " + force) result = None else: # applying transformation automatically # we have a block, we explode it if len(objects) == 1 and utils.get_type(objects[0]) == "Block": result = explode(objects[0]) if result: _msg(_tr("Found 1 block: exploding it")) # we have one multi-solids compound object: extract its solids elif (len(objects) == 1 and hasattr(objects[0], 'Shape') and len(solids) > 1): result = splitCompounds(objects) # print(result) if result: _msg(_tr("Found 1 multi-solids compound: exploding it")) # special case, we have one parametric object: we "de-parametrize" it elif (len(objects) == 1 and hasattr(objects[0], 'Shape') and hasattr(objects[0], 'Base')): result = utils.shapify(objects[0]) if result: _msg( _tr("Found 1 parametric object: " "breaking its dependencies")) add_list.append(result) # delete_list.append(objects[0]) # we have only 2 objects: cut 2nd from 1st elif len(objects) == 2: result = cut2(objects) if result: _msg(_tr("Found 2 objects: subtracting them")) elif len(faces) > 1: # one object with several faces: split it if len(objects) == 1: result = splitFaces(objects) if result: _msg(_tr("Found several faces: splitting them")) # several objects: remove all the faces from the first one else: result = subtr(objects) if result: _msg( _tr("Found several objects: " "subtracting them from the first one")) # only one face: we extract its wires elif len(faces) > 0: result = getWire(objects[0]) if result: _msg(_tr("Found 1 face: extracting its wires")) # no faces: split wire into single edges elif not onlyedges: result = splitWires(objects) if result: _msg(_tr("Found only wires: extracting their edges")) # no result has been obtained if not result: _msg(_tr("No more downgrade possible")) if delete: names = [] for o in delete_list: names.append(o.Name) delete_list = [] for n in names: doc.removeObject(n) gui_utils.select(add_list) return add_list, delete_list