Example #1
0
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
Example #2
0
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
Example #3
0
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
Example #4
0
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
Example #5
0
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
Example #6
0
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
Example #7
0
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
Example #8
0
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
Example #9
0
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
Example #10
0
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
Example #11
0
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
Example #12
0
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
Example #13
0
 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)
Example #14
0
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
Example #15
0
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
Example #16
0
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
Example #17
0
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
Example #18
0
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
Example #19
0
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
Example #20
0
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
Example #21
0
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
Example #22
0
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
Example #23
0
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
Example #24
0
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
Example #25
0
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
Example #26
0
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
Example #27
0
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
Example #28
0
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
Example #29
0
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
Example #30
0
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