예제 #1
0
def _make_ortho_array(base_object,
                      v_x=App.Vector(10, 0, 0),
                      v_y=App.Vector(0, 10, 0),
                      v_z=App.Vector(0, 0, 10),
                      n_x=2,
                      n_y=2,
                      n_z=1,
                      use_link=True):
    """Create an orthogonal array from the given object.

    This is a simple wrapper of the `draftmake.make_array.make_array`
    function to be used by the different orthogonal arrays.

    - `make_ortho_array`
    - `make_ortho_array2d`, no Z direction
    - `make_rect_array`, strictly rectangular
    - `make_rect_array2d`, strictly rectangular, no Z direction

    This function has no error checking, nor does it display messages.
    This should be handled by the subfunctions that use this one.
    """
    _name = "_make_ortho_array"
    utils.print_header(_name, _tr("Internal orthogonal array"), debug=False)

    new_obj = make_array.make_array(base_object,
                                    arg1=v_x,
                                    arg2=v_y,
                                    arg3=v_z,
                                    arg4=n_x,
                                    arg5=n_y,
                                    arg6=n_z,
                                    use_link=use_link)
    return new_obj
예제 #2
0
def make_rect_array2d(obj, d_x=10, d_y=10, n_x=2, n_y=2, use_link=True):
    """Create a 2D rectangular array from the given object.

    This function wraps around `make_ortho_array2d`
    to produce strictly rectangular arrays, in which
    the displacement vectors `v_x` and `v_y`
    only have their respective components in X and Y.

    Parameters
    ----------
    obj: Part::Feature
        Any type of object that has a `Part::TopoShape`
        that can be duplicated.
        This means most 2D and 3D objects produced
        with any workbench.

    d_x, d_y: Base::Vector3, optional
        Displacement of elements in the corresponding X and Y directions.

    n_x, n_y: int, optional
        Number of elements in the corresponding X and Y directions.

    use_link: bool, optional
        If it is `True`, create `App::Link` array.
        See `make_ortho_array`.

    Returns
    -------
    Part::FeaturePython
        A scripted object with `Proxy.Type='Array'`.
        Its `Shape` is a compound of the copies of the original object.

    See Also
    --------
    make_ortho_array, make_ortho_array2d, make_rect_array
    """
    _name = "make_rect_array2d"
    utils.print_header(_name, _tr("Rectangular array 2D"))

    _msg("d_x: {}".format(d_x))
    _msg("d_y: {}".format(d_y))

    try:
        utils.type_check([(d_x, (int, float)), (d_y, (int, float))],
                         name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a number."))
        return None

    new_obj = make_ortho_array2d(obj,
                                 v_x=App.Vector(d_x, 0, 0),
                                 v_y=App.Vector(0, d_y, 0),
                                 n_x=n_x,
                                 n_y=n_y,
                                 use_link=use_link)
    return new_obj
예제 #3
0
def convert_draft_texts(textslist=None):
    """Convert the given Annotation to a Draft text.

    In the past, the `Draft Text` object didn't exist; text objects
    were of type `App::Annotation`. This function was introduced
    to convert those older objects to a `Draft Text` scripted object.

    This function was already present at splitting time during v0.19.

    Parameters
    ----------
    textslist: list of objects, optional
        It defaults to `None`.
        A list containing `App::Annotation` objects or a single of these
        objects.
        If it is `None` it will convert all objects in the current document.
    """
    _name = "convert_draft_texts"
    utils.print_header(_name, "Convert Draft texts")

    found, doc = utils.find_doc(App.activeDocument())
    if not found:
        _err(_tr("No active document. Aborting."))
        return None

    if not textslist:
        textslist = list()
        for obj in doc.Objects:
            if obj.TypeId == "App::Annotation":
                textslist.append(obj)

    if not isinstance(textslist, list):
        textslist = [textslist]

    to_delete = []

    for obj in textslist:
        label = obj.Label
        obj.Label = label + ".old"

        # Create a new Draft Text object
        new_obj = make_text(obj.LabelText, placement=obj.Position)
        new_obj.Label = label
        to_delete.append(obj)

        # Move the new object to the group which contained the old object
        for in_obj in obj.InList:
            if in_obj.isDerivedFrom("App::DocumentObjectGroup"):
                if obj in in_obj.Group:
                    group = in_obj.Group
                    group.append(new_obj)
                    in_obj.Group = group

    for obj in to_delete:
        doc.removeObject(obj.Name)
예제 #4
0
def make_point_array(base_object, point_object, extra=None):
    """Make a Draft PointArray object.

    Distribute copies of a `base_object` in the points
    defined by `point_object`.

    Parameters
    ----------
    base_object: Part::Feature or str
        Any of object that has a `Part::TopoShape` that can be duplicated.
        This means most 2D and 3D objects produced with any workbench.
        If it is a string, it must be the `Label` of that object.
        Since a label is not guaranteed to be unique in a document,
        it will use the first object found with this label.

    point_object: Part::Feature or str
        An object that is a type of container for holding points.
        This object must have one of the following properties `Geometry`,
        `Links`, or `Components`, which themselves must contain objects
        with `X`, `Y`, and `Z` properties.

        This object could be:

        - A `Sketcher::SketchObject`, as it has a `Geometry` property.
          The sketch can contain different elements but it must contain
          at least one `Part::GeomPoint`.

        - A `Part::Compound`, as it has a `Links` property. The compound
          can contain different elements but it must contain at least
          one object that has `X`, `Y`, and `Z` properties,
          like a `Draft Point` or a `Part::Vertex`.

        - A `Draft Block`, as it has a `Components` property. This `Block`
          behaves essentially the same as a `Part::Compound`. It must
          contain at least a point or vertex object.

    extra: Base::Placement, Base::Vector3, or Base::Rotation, optional
        It defaults to `None`.
        If it is provided, it is an additional placement that is applied
        to each copy of the array.
        The input could be a full placement, just a vector indicating
        the additional translation, or just a rotation.

    Returns
    -------
    Part::FeaturePython
        A scripted object of type `'PointArray'`.
        Its `Shape` is a compound of the copies of the original object.

    None
        If there is a problem it will return `None`.
    """
    _name = "make_point_array"
    utils.print_header(_name, "Point array")

    found, doc = utils.find_doc(App.activeDocument())
    if not found:
        _err(_tr("No active document. Aborting."))
        return None

    if isinstance(base_object, str):
        base_object_str = base_object

    found, base_object = utils.find_object(base_object, doc)
    if not found:
        _msg("base_object: {}".format(base_object_str))
        _err(_tr("Wrong input: object not in document."))
        return None

    _msg("base_object: {}".format(base_object.Label))

    if isinstance(point_object, str):
        point_object_str = point_object

    found, point_object = utils.find_object(point_object, doc)
    if not found:
        _msg("point_object: {}".format(point_object_str))
        _err(_tr("Wrong input: object not in document."))
        return None

    _msg("point_object: {}".format(point_object.Label))
    if (not hasattr(point_object, "Geometry")
            and not hasattr(point_object, "Links")
            and not hasattr(point_object, "Components")):
        _err(
            _tr("Wrong input: point object doesn't have "
                "'Geometry', 'Links', or 'Components'."))
        return None

    _msg("extra: {}".format(extra))
    if not extra:
        extra = App.Placement()
    try:
        utils.type_check([(extra, (App.Placement, App.Vector, App.Rotation))],
                         name=_name)
    except TypeError:
        _err(
            _tr("Wrong input: must be a placement, a vector, "
                "or a rotation."))
        return None

    # Convert the vector or rotation to a full placement
    if isinstance(extra, App.Vector):
        extra = App.Placement(extra, App.Rotation())
    elif isinstance(extra, App.Rotation):
        extra = App.Placement(App.Vector(), extra)

    new_obj = doc.addObject("Part::FeaturePython", "PointArray")
    PointArray(new_obj)
    new_obj.Base = base_object
    new_obj.PointObject = point_object
    new_obj.ExtraPlacement = extra

    if App.GuiUp:
        ViewProviderDraftArray(new_obj.ViewObject)
        gui_utils.formatObject(new_obj, new_obj.Base)

        if hasattr(new_obj.Base.ViewObject, "DiffuseColor"):
            if len(new_obj.Base.ViewObject.DiffuseColor) > 1:
                new_obj.ViewObject.Proxy.resetColors(new_obj.ViewObject)

        new_obj.Base.ViewObject.hide()
        gui_utils.select(new_obj)

    return new_obj
예제 #5
0
def make_ortho_array(obj,
                     v_x=App.Vector(10, 0, 0),
                     v_y=App.Vector(0, 10, 0),
                     v_z=App.Vector(0, 0, 10),
                     n_x=2,
                     n_y=2,
                     n_z=1,
                     use_link=True):
    """Create an orthogonal array from the given object.

    Parameters
    ----------
    obj: Part::Feature
        Any type of object that has a `Part::TopoShape`
        that can be duplicated.
        This means most 2D and 3D objects produced
        with any workbench.

    v_x, v_y, v_z: Base::Vector3, optional
        The vector indicating the vector displacement between two elements
        in the specified orthogonal direction X, Y, Z.

        By default:
        ::
            v_x = App.Vector(10, 0, 0)
            v_y = App.Vector(0, 10, 0)
            v_z = App.Vector(0, 0, 10)

        Given that this is a vectorial displacement
        the next object can appear displaced in one, two or three axes
        at the same time.

        For example
        ::
            v_x = App.Vector(10, 5, 0)

        means that the next element in the X direction will be displaced
        10 mm in X, 5 mm in Y, and 0 mm in Z.

        A traditional "rectangular" array is obtained when
        the displacement vector only has its corresponding component,
        like in the default case.

        If these values are entered as single numbers instead
        of vectors, the single value is expanded into a vector
        of the corresponding direction, and the other components are assumed
        to be zero.

        For example
        ::
            v_x = 15
            v_y = 10
            v_z = 1
        becomes
        ::
            v_x = App.Vector(15, 0, 0)
            v_y = App.Vector(0, 10, 0)
            v_z = App.Vector(0, 0, 1)

    n_x, n_y, n_z: int, optional
        The number of copies in the specified orthogonal direction X, Y, Z.
        This number includes the original object, therefore, it must be
        at least 1.

        The values of `n_x` and `n_y` default to 2,
        while `n_z` defaults to 1.
        This means the array by default is a planar array.

    use_link: bool, optional
        It defaults to `True`.
        If it is `True` the produced copies are not `Part::TopoShape` copies,
        but rather `App::Link` objects.
        The Links repeat the shape of the original `obj` exactly,
        and therefore the resulting array is more memory efficient.

        Also, when `use_link` is `True`, the `Fuse` property
        of the resulting array does not work; the array doesn't
        contain separate shapes, it only has the original shape repeated
        many times, so there is nothing to fuse together.

        If `use_link` is `False` the original shape is copied many times.
        In this case the `Fuse` property is able to fuse
        all copies into a single object, if they touch each other.

    Returns
    -------
    Part::FeaturePython
        A scripted object with `Proxy.Type='Array'`.
        Its `Shape` is a compound of the copies of the original object.

    See Also
    --------
    make_ortho_array2d, make_rect_array, make_rect_array2d
    """
    _name = "make_ortho_array"
    utils.print_header(_name, _tr("Orthogonal array"))

    _msg("v_x: {}".format(v_x))
    _msg("v_y: {}".format(v_y))
    _msg("v_z: {}".format(v_z))

    try:
        utils.type_check([(v_x, (int, float, App.Vector)),
                          (v_y, (int, float, App.Vector)),
                          (v_z, (int, float, App.Vector))],
                         name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a number or vector."))
        return None

    _text = "Input: single value expanded to vector."
    if not isinstance(v_x, App.Vector):
        v_x = App.Vector(v_x, 0, 0)
        _wrn(_tr(_text))
    if not isinstance(v_y, App.Vector):
        v_y = App.Vector(0, v_y, 0)
        _wrn(_tr(_text))
    if not isinstance(v_z, App.Vector):
        v_z = App.Vector(0, 0, v_z)
        _wrn(_tr(_text))

    _msg("n_x: {}".format(n_x))
    _msg("n_y: {}".format(n_y))
    _msg("n_z: {}".format(n_z))

    try:
        utils.type_check([(n_x, int), (n_y, int), (n_z, int)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be an integer number."))
        return None

    _text = ("Input: number of elements must be at least 1. "
             "It is set to 1.")
    if n_x < 1:
        _wrn(_tr(_text))
        n_x = 1
    if n_y < 1:
        _wrn(_tr(_text))
        n_y = 1
    if n_z < 1:
        _wrn(_tr(_text))
        n_z = 1

    _msg("use_link: {}".format(bool(use_link)))

    # new_obj = make_array.make_array()
    new_obj = Draft.makeArray(obj,
                              arg1=v_x,
                              arg2=v_y,
                              arg3=v_z,
                              arg4=n_x,
                              arg5=n_y,
                              arg6=n_z,
                              use_link=use_link)
    return new_obj
예제 #6
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
예제 #7
0
def make_linear_dimension_obj(edge_object, i1=1, i2=2, dim_line=None):
    """Create a linear dimension from an object.

    Parameters
    ----------
    edge_object: Part::Feature
        The object which has an edge which will be measured.
        It must have a `Part::TopoShape`, and at least one element
        in `Shape.Vertexes`, to be able to measure a distance.

    i1: int, optional
        It defaults to `1`.
        It is the index of the first vertex in `edge_object` from which
        the measurement will be taken.
        The minimum value should be `1`, which will be interpreted
        as `'Vertex1'`.

        If the value is below `1`, it will be set to `1`.

    i2: int, optional
        It defaults to `2`, which will be converted to `'Vertex2'`.
        It is the index of the second vertex in `edge_object` that determines
        the endpoint of the measurement.

        If it is the same value as `i1`, the resulting measurement will be
        made from the origin `(0, 0, 0)` to the vertex indicated by `i1`.

        If the value is below `1`, it will be set to the last vertex
        in `edge_object`.

        Then to measure the first and last, this could be used
        ::
            make_linear_dimension_obj(edge_object, i1=1, i2=-1)

    dim_line: Base::Vector3
        It defaults to `None`.
        This is a point through which the extension of the dimension line
        will pass.
        This point controls how close or how far the dimension line is
        positioned from the measured segment in `edge_object`.

        If it is `None`, this point will be calculated from the intermediate
        distance betwwen the vertices defined by `i1` and `i2`.

    Returns
    -------
    App::FeaturePython
        A scripted object of type `'LinearDimension'`.
        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_linear_dimension_obj"
    utils.print_header(_name, "Linear dimension")

    found, doc = utils.find_doc(App.activeDocument())
    if not found:
        _err(_tr("No active document. Aborting."))
        return None

    if isinstance(edge_object, str):
        edge_object_str = edge_object

    if isinstance(edge_object, (list, tuple)):
        _msg("edge_object: {}".format(edge_object))
        _err(_tr("Wrong input: object must not be a list."))
        return None

    found, edge_object = utils.find_object(edge_object, doc)
    if not found:
        _msg("edge_object: {}".format(edge_object_str))
        _err(_tr("Wrong input: object not in document."))
        return None

    _msg("edge_object: {}".format(edge_object.Label))
    if not hasattr(edge_object, "Shape"):
        _err(_tr("Wrong input: object doesn't have a 'Shape' to measure."))
        return None
    if (not hasattr(edge_object.Shape, "Vertexes")
            or len(edge_object.Shape.Vertexes) < 1):
        _err(
            _tr("Wrong input: object doesn't have at least one element "
                "in 'Vertexes' to use for measuring."))
        return None

    _msg("i1: {}".format(i1))
    try:
        utils.type_check([(i1, int)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be an integer."))
        return None

    if i1 < 1:
        i1 = 1
        _wrn(_tr("i1: values below 1 are not allowed; will be set to 1."))

    vx1 = edge_object.getSubObject("Vertex" + str(i1))
    if not vx1:
        _err(_tr("Wrong input: vertex not in object."))
        return None

    _msg("i2: {}".format(i2))
    try:
        utils.type_check([(i2, int)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a vector."))
        return None

    if i2 < 1:
        i2 = len(edge_object.Shape.Vertexes)
        _wrn(
            _tr("i2: values below 1 are not allowed; "
                "will be set to the last vertex in the object."))

    vx2 = edge_object.getSubObject("Vertex" + str(i2))
    if not vx2:
        _err(_tr("Wrong input: vertex not in object."))
        return None

    _msg("dim_line: {}".format(dim_line))
    if dim_line:
        try:
            utils.type_check([(dim_line, App.Vector)], name=_name)
        except TypeError:
            _err(_tr("Wrong input: must be a vector."))
            return None
    else:
        diff = vx2.Point.sub(vx1.Point)
        diff.multiply(0.5)
        dim_line = vx1.Point.add(diff)

    # TODO: the internal function expects an index starting with 0
    # so we need to decrease the value here.
    # This should be changed in the future in the internal function.
    i1 -= 1
    i2 -= 1

    new_obj = make_dimension(edge_object, i1, i2, dim_line)

    return new_obj
예제 #8
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
예제 #9
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
예제 #10
0
def make_circular_array(obj,
                        r_distance=100, tan_distance=100,
                        number=2, symmetry=1,
                        axis=App.Vector(0, 0, 1), center=App.Vector(0, 0, 0),
                        use_link=True):
    """Create a circular array from the given object.

    Parameters
    ----------
    obj: Part::Feature
        Any type of object that has a `Part::TopoShape`
        that can be duplicated.

    r_distance: float, optional
        It defaults to `100`.
        Radial distance to the next ring of circular arrays.

    tan_distance: float, optional
        It defaults to `100`.
        The tangential distance between two elements located
        in the same circular ring.
        The tangential distance together with the radial distance
        determine how many copies are created.

    number: int, optional
        It defaults to 2.
        The number of layers or rings of repeated objects.
        The original object stays at the center, and is counted
        as a layer itself. So, if you want at least one layer of circular
        copies, this number must be at least 2.

    symmetry: int, optional
        It defaults to 1.
        It indicates how many lines of symmetry the entire circular pattern
        has. That is, with 1, the array is symmetric only after a full
        360 degrees rotation.

        When it is 2, the array is symmetric at 0 and 180 degrees.
        When it is 3, the array is symmetric at 0, 120, and 240 degrees.
        When it is 4, the array is symmetric at 0, 90, 180, and 270 degrees.
        Et cetera.

    axis: Base::Vector3, optional
        It defaults to `App.Vector(0, 0, 1)` or the `+Z` axis.
        The unit vector indicating the axis of rotation.

    center: Base::Vector3, optional
        It defaults to `App.Vector(0, 0, 0)` or the global origin.
        The point through which the `axis` passes to define
        the axis of rotation.

    use_link: bool, optional
        It defaults to `True`.
        If it is `True` the produced copies are not `Part::TopoShape` copies,
        but rather `App::Link` objects.
        The Links repeat the shape of the original `obj` exactly,
        and therefore the resulting array is more memory efficient.

        Also, when `use_link` is `True`, the `Fuse` property
        of the resulting array does not work; the array doesn't
        contain separate shapes, it only has the original shape repeated
        many times, so there is nothing to fuse together.

        If `use_link` is `False` the original shape is copied many times.
        In this case the `Fuse` property is able to fuse
        all copies into a single object, if they touch each other.

    Returns
    -------
    Part::FeaturePython
        A scripted object with `Proxy.Type='Array'`.
        Its `Shape` is a compound of the copies of the original object.
    """
    _name = "make_circular_array"
    utils.print_header(_name, _tr("Circular array"))

    _msg("r_distance: {}".format(r_distance))
    _msg("tan_distance: {}".format(tan_distance))

    try:
        utils.type_check([(r_distance, (int, float, App.Units.Quantity)),
                          (tan_distance, (int, float, App.Units.Quantity))],
                         name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a number or quantity."))
        return None

    _msg("number: {}".format(number))
    _msg("symmetry: {}".format(symmetry))

    try:
        utils.type_check([(number, int),
                          (symmetry, int)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be an integer number."))
        return None

    _msg("axis: {}".format(axis))
    _msg("center: {}".format(center))

    try:
        utils.type_check([(axis, App.Vector),
                          (center, App.Vector)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a vector."))
        return None

    _msg("use_link: {}".format(bool(use_link)))

    new_obj = Draft.makeArray(obj,
                              arg1=r_distance, arg2=tan_distance,
                              arg3=axis, arg4=center,
                              arg5=number, arg6=symmetry,
                              use_link=use_link)
    return new_obj
예제 #11
0
def make_fillet(objs, radius=100, chamfer=False, delete=False):
    """Create a fillet between two lines or Part.Edges.

    Parameters
    ----------
    objs: list
        List of two objects of type wire, or edges.

    radius: float, optional
        It defaults to 100. The curvature of the fillet.

    chamfer: bool, optional
        It defaults to `False`. If it is `True` it no longer produces
        a rounded fillet but a chamfer (straight edge)
        with the value of the `radius`.

    delete: bool, optional
        It defaults to `False`. If it is `True` it will delete
        the pair of objects that are used to create the fillet.
        Otherwise, the original objects will still be there.

    Returns
    -------
    Part::Part2DObjectPython
        The object of Proxy type `'Fillet'`.
        It returns `None` if it fails producing the object.
    """
    _name = "make_fillet"
    utils.print_header(_name, "Fillet")

    if len(objs) != 2:
        _err(_tr("Two elements are needed."))
        return None

    e1, e2 = _extract_edges(objs)

    edges = DraftGeomUtils.fillet([e1, e2], radius, chamfer)
    if len(edges) < 3:
        _err(_tr("Radius is too large") + ", r={}".format(radius))
        return None

    lengths = [edges[0].Length, edges[1].Length, edges[2].Length]
    _msg(_tr("Segment") + " 1, " + _tr("length:") + " {}".format(lengths[0]))
    _msg(_tr("Segment") + " 2, " + _tr("length:") + " {}".format(lengths[1]))
    _msg(_tr("Segment") + " 3, " + _tr("length:") + " {}".format(lengths[2]))

    try:
        wire = Part.Wire(edges)
    except Part.OCCError:
        return None

    _doc = App.activeDocument()
    obj = _doc.addObject("Part::Part2DObjectPython", "Fillet")
    fillet.Fillet(obj)
    obj.Shape = wire
    obj.Length = wire.Length
    obj.Start = wire.Vertexes[0].Point
    obj.End = wire.Vertexes[-1].Point
    obj.FilletRadius = radius

    if delete:
        _doc.removeObject(objs[0].Name)
        _doc.removeObject(objs[1].Name)
        _msg(_tr("Removed original objects."))

    if App.GuiUp:
        view_fillet.ViewProviderFillet(obj.ViewObject)
        gui_utils.format_object(obj)
        gui_utils.select(obj)
        gui_utils.autogroup(obj)

    return obj
예제 #12
0
def make_label(target_point=App.Vector(0, 0, 0),
               placement=App.Vector(30, 30, 0),
               target_object=None, subelements=None,
               label_type="Custom", custom_text="Label",
               direction="Horizontal", distance=-10,
               points=None):
    """Create a Label object containing different types of information.

    The current color and text height and font specified in preferences
    are used.

    Parameters
    ----------
    target_point: Base::Vector3, optional
        It defaults to the origin `App.Vector(0, 0, 0)`.
        This is the point which is pointed to by the label's leader line.
        This point can be adorned with a marker like an arrow or circle.

    placement: Base::Placement, Base::Vector3, or Base::Rotation, optional
        It defaults to `App.Vector(30, 30, 0)`.
        If it is provided, it defines the base point of the textual
        label.
        The input could be a full placement, just a vector indicating
        the translation, or just a rotation.

    target_object: Part::Feature or str, optional
        It defaults to `None`.
        If it exists it should be an object which will be used to provide
        information to the label, as long as `label_type` is different
        from `'Custom'`.

        If it is a string, it must be the `Label` of that object.
        Since a `Label` is not guaranteed to be unique in a document,
        it will use the first object found with this `Label`.

    subelements: str, optional
        It defaults to `None`.
        If `subelements` is provided, `target_object` should be provided
        as well, otherwise it is ignored.

        It should be a string indicating a subelement name, either
        `'VertexN'`, `'EdgeN'`, or `'FaceN'` which should exist
        within `target_object`.
        In this case `'N'` is an integer that indicates the specific number
        of vertex, edge, or face in `target_object`.

        Both `target_object` and `subelements` are used to link the label
        to a particular object, or to the particular vertex, edge, or face,
        and get information from them.
        ::
            make_label(..., target_object=App.ActiveDocument.Box)
            make_label(..., target_object="My box", subelements="Face3")

        These two parameters can be can be obtained from the `Gui::Selection`
        module.
        ::
            sel_object = Gui.Selection.getSelectionEx()[0]
            target_object = sel_object.Object
            subelements = sel_object.SubElementNames[0]

    label_type: str, optional
        It defaults to `'Custom'`.
        It can be `'Custom'`, `'Name'`, `'Label'`, `'Position'`,
        `'Length'`, `'Area'`, `'Volume'`, `'Tag'`, or `'Material'`.
        It indicates the type of information that will be shown in the label.

        Only `'Custom'` allows you to manually set the text
        by defining `custom_text`. The other types take their information
        from the object included in `target`.

        - `'Position'` will show the base position of the target object,
          or of the indicated `'VertexN'` in `target`.
        - `'Length'` will show the `Length` of the target object's `Shape`,
          or of the indicated `'EdgeN'` in `target`.
        - `'Area'` will show the `Area` of the target object's `Shape`,
          or of the indicated `'FaceN'` in `target`.

    custom_text: str, optional
        It defaults to `'Label'`.
        It is the text that will be displayed by the label when
        `label_type` is `'Custom'`.

    direction: str, optional
        It defaults to `'Horizontal'`.
        It can be `'Horizontal'`, `'Vertical'`, or `'Custom'`.
        It indicates the direction of the straight segment of the leader line
        that ends up next to the textual label.

        If `'Custom'` is selected, the leader line can be manually drawn
        by specifying the value of `points`.
        Normally, the leader line has only three points, but with `'Custom'`
        you can specify as many points as needed.

    distance: int, float, Base::Quantity, optional
        It defaults to -10.
        It indicates the length of the horizontal or vertical segment
        of the leader line.

        The leader line is composed of two segments, the first segment is
        inclined, while the second segment is either horizontal or vertical
        depending on the value of `direction`.
        ::
            T
            |
            |
            o------- L text

        The `oL` segment's length is defined by `distance`
        while the `oT` segment is automatically calculated depending
        on the values of `placement` (L) and `distance` (o).

        This `distance` is oriented, meaning that if it is positive
        the segment will be to the right and above of the textual
        label, depending on if `direction` is `'Horizontal'` or `'Vertical'`,
        respectively.
        If it is negative, the segment will be to the left
        and below of the text.

    points: list of Base::Vector3, optional
        It defaults to `None`.
        It is a list of vectors defining the shape of the leader line;
        the list must have at least two points.
        This argument must be used together with `direction='Custom'`
        to display this custom leader.

        However, notice that if the Label's `StraightDirection` property
        is later changed to `'Horizontal'` or `'Vertical'`,
        the custom point list will be overwritten with a new,
        automatically calculated three-point list.

        For the object to use custom points, `StraightDirection`
        must remain `'Custom'`, and then the `Points` property
        can be overwritten by a suitable list of points.

    Returns
    -------
    App::FeaturePython
        A scripted object of type `'Label'`.
        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_label"
    utils.print_header(_name, "Label")

    found, doc = utils.find_doc(App.activeDocument())
    if not found:
        _err(_tr("No active document. Aborting."))
        return None

    _msg("target_point: {}".format(target_point))
    if not target_point:
        target_point = App.Vector(0, 0, 0)
    try:
        utils.type_check([(target_point, App.Vector)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a vector."))
        return None

    _msg("placement: {}".format(placement))
    if not placement:
        placement = App.Placement()
    try:
        utils.type_check([(placement, (App.Placement,
                                       App.Vector,
                                       App.Rotation))], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a placement, a vector, "
                 "or a rotation."))
        return None

    # Convert the vector or rotation to a full placement
    if isinstance(placement, App.Vector):
        placement = App.Placement(placement, App.Rotation())
    elif isinstance(placement, App.Rotation):
        placement = App.Placement(App.Vector(), placement)

    if isinstance(target_object, str):
        target_object_str = target_object

    if target_object:
        if isinstance(target_object, (list, tuple)):
            _msg("target_object: {}".format(target_object))
            _err(_tr("Wrong input: object must not be a list."))
            return None

        found, target_object = utils.find_object(target_object, doc)
        if not found:
            _msg("target_object: {}".format(target_object_str))
            _err(_tr("Wrong input: object not in document."))
            return None

        _msg("target_object: {}".format(target_object.Label))

    if target_object and subelements:
        _msg("subelements: {}".format(subelements))
        try:
            # Make a list
            if isinstance(subelements, str):
                subelements = [subelements]

            utils.type_check([(subelements, (list, tuple, str))],
                             name=_name)
        except TypeError:
            _err(_tr("Wrong input: must be a list or tuple of strings. "
                     "Or a single string."))
            return None

        # The subelements list is used to build a special list
        # called a LinkSub, which includes the target_object
        # and the subelements.
        # Single: (target_object, "Edge1")
        # Multiple: (target_object, ("Edge1", "Edge2"))
        for sub in subelements:
            _sub = target_object.getSubObject(sub)
            if not _sub:
                _err("subelement: {}".format(sub))
                _err(_tr("Wrong input: subelement not in object."))
                return None

    _msg("label_type: {}".format(label_type))
    if not label_type:
        label_type = "Custom"
    try:
        utils.type_check([(label_type, str)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a string, "
                 "'Custom', 'Name', 'Label', 'Position', "
                 "'Length', 'Area', 'Volume', 'Tag', or 'Material'."))
        return None

    if label_type not in ("Custom", "Name", "Label", "Position",
                          "Length", "Area", "Volume", "Tag", "Material"):
        _err(_tr("Wrong input: must be a string, "
                 "'Custom', 'Name', 'Label', 'Position', "
                 "'Length', 'Area', 'Volume', 'Tag', or 'Material'."))
        return None

    _msg("custom_text: {}".format(custom_text))
    if not custom_text:
        custom_text = "Label"
    try:
        utils.type_check([(custom_text, str)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a string."))
        return None

    _msg("direction: {}".format(direction))
    if not direction:
        direction = "Horizontal"
    try:
        utils.type_check([(direction, str)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a string, "
                 "'Horizontal', 'Vertical', or 'Custom'."))
        return None

    if direction not in ("Horizontal", "Vertical", "Custom"):
        _err(_tr("Wrong input: must be a string, "
                 "'Horizontal', 'Vertical', or 'Custom'."))
        return None

    _msg("distance: {}".format(distance))
    if not distance:
        distance = 1
    try:
        utils.type_check([(distance, (int, float))], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a number."))
        return None

    if points:
        _msg("points: {}".format(points))

        _err_msg = _tr("Wrong input: must be a list of at least two vectors.")
        try:
            utils.type_check([(points, (tuple, list))], name=_name)
        except TypeError:
            _err(_err_msg)
            return None

        if len(points) < 2:
            _err(_err_msg)
            return None

        if not all(isinstance(p, App.Vector) for p in points):
            _err(_err_msg)
            return None

    new_obj = doc.addObject("App::FeaturePython",
                            "dLabel")
    Label(new_obj)

    new_obj.TargetPoint = target_point
    new_obj.Placement = placement
    if target_object:
        if subelements:
            new_obj.Target = [target_object, subelements]
        else:
            new_obj.Target = [target_object, []]

    new_obj.LabelType = label_type
    new_obj.CustomText = custom_text

    new_obj.StraightDirection = direction
    new_obj.StraightDistance = distance
    if points:
        if direction != "Custom":
            _wrn(_tr("Direction is not 'Custom'; "
                     "points won't be used."))
        new_obj.Points = points

    if App.GuiUp:
        ViewProviderLabel(new_obj.ViewObject)
        h = utils.get_param("textheight", 0.20)
        new_obj.ViewObject.TextSize = h

        gui_utils.format_object(new_obj)
        gui_utils.select(new_obj)

    return new_obj
예제 #13
0
def make_polar_array(obj,
                     number=4,
                     angle=360,
                     center=App.Vector(0, 0, 0),
                     use_link=True):
    """Create a polar array from the given object.

    Parameters
    ----------
    obj: Part::Feature
        Any type of object that has a `Part::TopoShape`
        that can be duplicated.
        This means most 2D and 3D objects produced
        with any workbench.

    number: int, optional
        It defaults to 4.
        The number of copies produced in the circular pattern.

    angle: float, optional
        It defaults to 360.
        The magnitude in degrees swept by the polar pattern.

    center: Base::Vector3, optional
        It defaults to the origin `App.Vector(0, 0, 0)`.
        The vector indicating the center of rotation of the array.

    use_link: bool, optional
        It defaults to `True`.
        If it is `True` the produced copies are not `Part::TopoShape` copies,
        but rather `App::Link` objects.
        The Links repeat the shape of the original `obj` exactly,
        and therefore the resulting array is more memory efficient.

        Also, when `use_link` is `True`, the `Fuse` property
        of the resulting array does not work; the array doesn't
        contain separate shapes, it only has the original shape repeated
        many times, so there is nothing to fuse together.

        If `use_link` is `False` the original shape is copied many times.
        In this case the `Fuse` property is able to fuse
        all copies into a single object, if they touch each other.

    Returns
    -------
    Part::FeaturePython
        A scripted object with `Proxy.Type='Array'`.
        Its `Shape` is a compound of the copies of the original object.
    """
    _name = "make_polar_array"
    utils.print_header(_name, _tr("Polar array"))

    _msg("Number: {}".format(number))
    _msg("Angle: {}".format(angle))
    _msg("Center: {}".format(center))

    try:
        utils.type_check([(number, int)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be an integer number."))
        return None

    try:
        utils.type_check([(angle, (int, float))], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a number."))
        return None

    try:
        utils.type_check([(center, App.Vector)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a vector."))
        return None

    _msg("use_link: {}".format(bool(use_link)))

    new_obj = Draft.makeArray(obj,
                              arg1=center,
                              arg2=angle,
                              arg3=number,
                              use_link=use_link)
    return new_obj
예제 #14
0
def make_layer(name=None,
               line_color=None,
               shape_color=None,
               line_width=2.0,
               draw_style="Solid",
               transparency=0):
    """Create a Layer object in the active document.

    If a layer container named `'LayerContainer'` does not exist,
    it is created with this name.

    A layer controls the view properties of the objects inside the layer,
    so all parameters except for `name` only apply if the graphical interface
    is up.

    Parameters
    ----------
    name: str, optional
        It is used to set the layer's `Label` (user editable).
        It defaults to `None`, in which case the `Label`
        is set to `'Layer'` or to its translation in the current language.

    line_color: tuple, optional
        It defaults to `None`, in which case it uses the value of the parameter
        `User parameter:BaseApp/Preferences/View/DefaultShapeLineColor`.
        If it is given, it should be a tuple of three
        floating point values from 0.0 to 1.0.

    shape_color: tuple, optional
        It defaults to `None`, in which case it uses the value of the parameter
        `User parameter:BaseApp/Preferences/View/DefaultShapeColor`.
        If it is given, it should be a tuple of three
        floating point values from 0.0 to 1.0.

    line_width: float, optional
        It defaults to 2.0.
        It determines the width of the edges of the objects contained
        in the layer.

    draw_style: str, optional
        It defaults to `'Solid'`.
        It determines the style of the edges of the objects contained
        in the layer.
        If it is given, it should be 'Solid', 'Dashed', 'Dotted',
        or 'Dashdot'.

    transparency: int, optional
        It defaults to 0.
        It should be an integer value from 0 (completely opaque)
        to 100 (completely transparent).

    Return
    ------
    App::FeaturePython
        A scripted object of type `'Layer'`.
        This object does not have a `Shape` attribute.
        Modifying the view properties of this object will affect the objects
        inside of it.

    None
        If there is a problem it will return `None`.
    """
    _name = "make_layer"
    utils.print_header(_name, translate("draft", "Layer"))

    found, doc = utils.find_doc(App.activeDocument())
    if not found:
        _err(translate("draft", "No active document. Aborting."))
        return None

    if name:
        _msg("name: {}".format(name))
        try:
            utils.type_check([(name, str)], name=_name)
        except TypeError:
            _err(translate("draft", "Wrong input: it must be a string."))
            return None
    else:
        name = translate("draft", "Layer")

    if line_color:
        _msg("line_color: {}".format(line_color))
        try:
            utils.type_check([(line_color, tuple)], name=_name)
        except TypeError:
            _err(
                translate(
                    "draft",
                    "Wrong input: must be a tuple of three floats 0.0 to 1.0.")
            )
            return None

        if not all(isinstance(color, (int, float)) for color in line_color):
            _err(
                translate(
                    "draft",
                    "Wrong input: must be a tuple of three floats 0.0 to 1.0.")
            )
            return None
    else:
        c = view_group.GetUnsigned("DefaultShapeLineColor", 255)
        line_color = (((c >> 24) & 0xFF) / 255, ((c >> 16) & 0xFF) / 255,
                      ((c >> 8) & 0xFF) / 255)

    if shape_color:
        _msg("shape_color: {}".format(shape_color))
        try:
            utils.type_check([(shape_color, tuple)], name=_name)
        except TypeError:
            _err(
                translate(
                    "draft",
                    "Wrong input: must be a tuple of three floats 0.0 to 1.0.")
            )
            return None

        if not all(isinstance(color, (int, float)) for color in shape_color):
            _err(
                translate(
                    "draft",
                    "Wrong input: must be a tuple of three floats 0.0 to 1.0.")
            )
            return None
    else:
        c = view_group.GetUnsigned("DefaultShapeColor", 4294967295)
        shape_color = (((c >> 24) & 0xFF) / 255, ((c >> 16) & 0xFF) / 255,
                       ((c >> 8) & 0xFF) / 255)

    _msg("line_width: {}".format(line_width))
    try:
        utils.type_check([(line_width, (int, float))], name=_name)
        line_width = float(abs(line_width))
    except TypeError:
        _err(translate("draft", "Wrong input: must be a number."))
        return None

    _msg("draw_style: {}".format(draw_style))
    try:
        utils.type_check([(draw_style, str)], name=_name)
    except TypeError:
        _err(
            translate(
                "draft",
                "Wrong input: must be 'Solid', 'Dashed', 'Dotted', or 'Dashdot'."
            ))
        return None

    if draw_style not in ('Solid', 'Dashed', 'Dotted', 'Dashdot'):
        _err(
            translate(
                "draft",
                "Wrong input: must be 'Solid', 'Dashed', 'Dotted', or 'Dashdot'."
            ))
        return None

    _msg("transparency: {}".format(transparency))
    try:
        utils.type_check([(transparency, (int, float))], name=_name)
        transparency = int(abs(transparency))
    except TypeError:
        _err(
            translate("draft",
                      "Wrong input: must be a number between 0 and 100."))
        return None

    new_obj = doc.addObject("App::FeaturePython", "Layer")
    Layer(new_obj)

    new_obj.Label = name

    if App.GuiUp:
        ViewProviderLayer(new_obj.ViewObject)

        new_obj.ViewObject.LineColor = line_color
        new_obj.ViewObject.ShapeColor = shape_color
        new_obj.ViewObject.LineWidth = line_width
        new_obj.ViewObject.DrawStyle = draw_style
        new_obj.ViewObject.Transparency = transparency

    container = get_layer_container()
    container.addObject(new_obj)

    return new_obj
예제 #15
0
def make_path_twisted_array(base_object,
                            path_object,
                            count=15,
                            rot_factor=0.25,
                            use_link=True):
    """Create a Path twisted array."""
    _name = "make_path_twisted_array"
    utils.print_header(_name, "Path twisted array")

    found, doc = utils.find_doc(App.activeDocument())
    if not found:
        _err(_tr("No active document. Aborting."))
        return None

    if isinstance(base_object, str):
        base_object_str = base_object

    found, base_object = utils.find_object(base_object, doc)
    if not found:
        _msg("base_object: {}".format(base_object_str))
        _err(_tr("Wrong input: object not in document."))
        return None

    _msg("base_object: {}".format(base_object.Label))

    if isinstance(path_object, str):
        path_object_str = path_object

    found, path_object = utils.find_object(path_object, doc)
    if not found:
        _msg("path_object: {}".format(path_object_str))
        _err(_tr("Wrong input: object not in document."))
        return None

    _msg("path_object: {}".format(path_object.Label))

    _msg("count: {}".format(count))
    try:
        utils.type_check([(count, (int, float))], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a number."))
        return None
    count = int(count)

    use_link = bool(use_link)
    _msg("use_link: {}".format(use_link))

    if use_link:
        # The PathTwistedArray class must be called in this special way
        # to make it a PathTwistLinkArray
        new_obj = doc.addObject("Part::FeaturePython", "PathTwistedArray",
                                PathTwistedArray(None), None, True)
    else:
        new_obj = doc.addObject("Part::FeaturePython", "PathTwistedArray")
        PathTwistedArray(new_obj)

    new_obj.Base = base_object
    new_obj.PathObject = path_object
    new_obj.Count = count
    new_obj.RotationFactor = rot_factor

    if App.GuiUp:
        if use_link:
            ViewProviderDraftLink(new_obj.ViewObject)
        else:
            ViewProviderDraftArray(new_obj.ViewObject)
            gui_utils.formatObject(new_obj, new_obj.Base)

        if hasattr(new_obj.Base.ViewObject, "DiffuseColor"):
            if len(new_obj.Base.ViewObject.DiffuseColor) > 1:
                new_obj.ViewObject.Proxy.resetColors(new_obj.ViewObject)

        new_obj.Base.ViewObject.hide()
        gui_utils.select(new_obj)

    return new_obj
예제 #16
0
def make_path_array(base_object,
                    path_object,
                    count=4,
                    extra=App.Vector(0, 0, 0),
                    subelements=None,
                    align=False,
                    align_mode="Original",
                    tan_vector=App.Vector(1, 0, 0),
                    force_vertical=False,
                    vertical_vector=App.Vector(0, 0, 1),
                    use_link=True):
    """Make a Draft PathArray object.

    Distribute copies of a `base_object` along `path_object`
    or `subelements` from `path_object`.

    Parameters
    ----------
    base_object: Part::Feature or str
        Any of object that has a `Part::TopoShape` that can be duplicated.
        This means most 2D and 3D objects produced with any workbench.
        If it is a string, it must be the `Label` of that object.
        Since a label is not guaranteed to be unique in a document,
        it will use the first object found with this label.

    path_object: Part::Feature or str
        Path object like a polyline, B-Spline, or bezier curve that should
        contain edges.
        Just like `base_object` it can also be `Label`.

    count: int, float, optional
        It defaults to 4.
        Number of copies to create along the `path_object`.
        It must be at least 2.
        If a `float` is provided, it will be truncated by `int(count)`.

    extra: Base.Vector3, optional
        It defaults to `App.Vector(0, 0, 0)`.
        It translates each copy by the value of `extra`.
        This is useful to adjust for the difference between shape centre
        and shape reference point.

    subelements: list or tuple of str, optional
        It defaults to `None`.
        It should be a list of names of edges that must exist in `path_object`.
        Then the path array will be created along these edges only,
        and not the entire `path_object`.
        ::
            subelements = ['Edge1', 'Edge2']

        The edges must be contiguous, meaning that it is not allowed to
        input `'Edge1'` and `'Edge3'` if they do not touch each other.

        A single string value is also allowed.
        ::
            subelements = 'Edge1'

    align: bool, optional
        It defaults to `False`.
        If it is `True` it will align `base_object` to tangent, normal,
        or binormal to the `path_object`, depending on the value
        of `tan_vector`.

    align_mode: str, optional
        It defaults to `'Original'` which is the traditional alignment.
        It can also be `'Frenet'` or `'Tangent'`.

        - Original. It does not calculate curve normal.
          `X` is curve tangent, `Y` is normal parameter, Z is the cross
          product `X` x `Y`.
        - Frenet. It defines a local coordinate system along the path.
          `X` is tangent to curve, `Y` is curve normal, `Z` is curve binormal.
          If normal cannot be computed, for example, in a straight path,
          a default is used.
        - Tangent. It is similar to `'Original'` but includes a pre-rotation
          to align the base object's `X` to the value of `tan_vector`,
          then `X` follows curve tangent.

    tan_vector: Base::Vector3, optional
        It defaults to `App.Vector(1, 0, 0)` or the +X axis.
        It aligns the tangent of the path to this local unit vector
        of the object.

    force_vertical: Base::Vector3, optional
        It defaults to `False`.
        If it is `True`, the value of `vertical_vector`
        will be used when `align_mode` is `'Original'` or `'Tangent'`.

    vertical_vector: Base::Vector3, optional
        It defaults to `App.Vector(0, 0, 1)` or the +Z axis.
        It will force this vector to be the vertical direction
        when `force_vertical` is `True`.

    use_link: bool, optional
        It defaults to `True`, in which case the copies are `App::Link`
        elements. Otherwise, the copies are shape copies which makes
        the resulting array heavier.

    Returns
    -------
    Part::FeaturePython
        The scripted object of type `'PathArray'`.
        Its `Shape` is a compound of the copies of the original object.

    None
        If there is a problem it will return `None`.
    """
    _name = "make_path_array"
    utils.print_header(_name, "Path array")

    found, doc = utils.find_doc(App.activeDocument())
    if not found:
        _err(_tr("No active document. Aborting."))
        return None

    if isinstance(base_object, str):
        base_object_str = base_object

    found, base_object = utils.find_object(base_object, doc)
    if not found:
        _msg("base_object: {}".format(base_object_str))
        _err(_tr("Wrong input: object not in document."))
        return None

    _msg("base_object: {}".format(base_object.Label))

    if isinstance(path_object, str):
        path_object_str = path_object

    found, path_object = utils.find_object(path_object, doc)
    if not found:
        _msg("path_object: {}".format(path_object_str))
        _err(_tr("Wrong input: object not in document."))
        return None

    _msg("path_object: {}".format(path_object.Label))

    _msg("count: {}".format(count))
    try:
        utils.type_check([(count, (int, float))], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a number."))
        return None
    count = int(count)

    _msg("extra: {}".format(extra))
    try:
        utils.type_check([(extra, App.Vector)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a vector."))
        return None

    _msg("subelements: {}".format(subelements))
    if subelements:
        try:
            # Make a list
            if isinstance(subelements, str):
                subelements = [subelements]

            utils.type_check([(subelements, (list, tuple, str))], name=_name)
        except TypeError:
            _err(
                _tr("Wrong input: must be a list or tuple of strings. "
                    "Or a single string."))
            return None

        # The subelements list is used to build a special list
        # called a LinkSubList, which includes the path_object.
        # Old style: [(path_object, "Edge1"), (path_object, "Edge2")]
        # New style: [(path_object, ("Edge1", "Edge2"))]
        #
        # If a simple list is given ["a", "b"], this will create an old-style
        # SubList.
        # If a nested list is given [["a", "b"]], this will create a new-style
        # SubList.
        # In any case, the property of the object accepts both styles.
        #
        # If the old style is deprecated then this code should be updated
        # to create new style lists exclusively.
        sub_list = list()
        for sub in subelements:
            sub_list.append((path_object, sub))
    else:
        sub_list = None

    align = bool(align)
    _msg("align: {}".format(align))

    _msg("align_mode: {}".format(align_mode))
    try:
        utils.type_check([(align_mode, str)], name=_name)

        if align_mode not in ("Original", "Frenet", "Tangent"):
            raise TypeError
    except TypeError:
        _err(_tr("Wrong input: must be "
                 "'Original', 'Frenet', or 'Tangent'."))
        return None

    _msg("tan_vector: {}".format(tan_vector))
    try:
        utils.type_check([(tan_vector, App.Vector)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a vector."))
        return None

    force_vertical = bool(force_vertical)
    _msg("force_vertical: {}".format(force_vertical))

    _msg("vertical_vector: {}".format(vertical_vector))
    try:
        utils.type_check([(vertical_vector, App.Vector)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a vector."))
        return None

    use_link = bool(use_link)
    _msg("use_link: {}".format(use_link))

    if use_link:
        # The PathArray class must be called in this special way
        # to make it a PathLinkArray
        new_obj = doc.addObject("Part::FeaturePython", "PathArray",
                                PathArray(None), None, True)
    else:
        new_obj = doc.addObject("Part::FeaturePython", "PathArray")
        PathArray(new_obj)

    new_obj.Base = base_object
    new_obj.PathObject = path_object
    new_obj.Count = count
    new_obj.ExtraTranslation = extra
    new_obj.PathSubelements = sub_list
    new_obj.Align = align
    new_obj.AlignMode = align_mode
    new_obj.TangentVector = tan_vector
    new_obj.ForceVertical = force_vertical
    new_obj.VerticalVector = vertical_vector

    if App.GuiUp:
        if use_link:
            ViewProviderDraftLink(new_obj.ViewObject)
        else:
            ViewProviderDraftArray(new_obj.ViewObject)
            gui_utils.formatObject(new_obj, new_obj.Base)

            if hasattr(new_obj.Base.ViewObject, "DiffuseColor"):
                if len(new_obj.Base.ViewObject.DiffuseColor) > 1:
                    new_obj.ViewObject.Proxy.resetColors(new_obj.ViewObject)

        new_obj.Base.ViewObject.hide()
        gui_utils.select(new_obj)

    return new_obj
예제 #17
0
def make_ortho_array(base_object,
                     v_x=App.Vector(10, 0, 0),
                     v_y=App.Vector(0, 10, 0),
                     v_z=App.Vector(0, 0, 10),
                     n_x=2,
                     n_y=2,
                     n_z=1,
                     use_link=True):
    """Create an orthogonal array from the given object.

    Parameters
    ----------
    base_object: Part::Feature or str
        Any of object that has a `Part::TopoShape` that can be duplicated.
        This means most 2D and 3D objects produced with any workbench.
        If it is a string, it must be the `Label` of that object.
        Since a label is not guaranteed to be unique in a document,
        it will use the first object found with this label.

    v_x, v_y, v_z: Base::Vector3, optional
        The vector indicating the vector displacement between two elements
        in the specified orthogonal direction X, Y, Z.

        By default:
        ::
            v_x = App.Vector(10, 0, 0)
            v_y = App.Vector(0, 10, 0)
            v_z = App.Vector(0, 0, 10)

        Given that this is a vectorial displacement
        the next object can appear displaced in one, two or three axes
        at the same time.

        For example
        ::
            v_x = App.Vector(10, 5, 0)

        means that the next element in the X direction will be displaced
        10 mm in X, 5 mm in Y, and 0 mm in Z.

        A traditional "rectangular" array is obtained when
        the displacement vector only has its corresponding component,
        like in the default case.

        If these values are entered as single numbers instead
        of vectors, the single value is expanded into a vector
        of the corresponding direction, and the other components are assumed
        to be zero.

        For example
        ::
            v_x = 15
            v_y = 10
            v_z = 1
        becomes
        ::
            v_x = App.Vector(15, 0, 0)
            v_y = App.Vector(0, 10, 0)
            v_z = App.Vector(0, 0, 1)

    n_x, n_y, n_z: int, optional
        The number of copies in the specified orthogonal direction X, Y, Z.
        This number includes the original object, therefore, it must be
        at least 1.

        The values of `n_x` and `n_y` default to 2,
        while `n_z` defaults to 1.
        This means the array is a planar array by default.

    use_link: bool, optional
        It defaults to `True`.
        If it is `True` the produced copies are not `Part::TopoShape` copies,
        but rather `App::Link` objects.
        The Links repeat the shape of the original `base_object` exactly,
        and therefore the resulting array is more memory efficient.

        Also, when `use_link` is `True`, the `Fuse` property
        of the resulting array does not work; the array doesn't
        contain separate shapes, it only has the original shape repeated
        many times, so there is nothing to fuse together.

        If `use_link` is `False` the original shape is copied many times.
        In this case the `Fuse` property is able to fuse
        all copies into a single object, if they touch each other.

    Returns
    -------
    Part::FeaturePython
        A scripted object of type `'Array'`.
        Its `Shape` is a compound of the copies of the original object.

    None
        If there is a problem it will return `None`.

    See Also
    --------
    make_ortho_array2d, make_rect_array, make_rect_array2d, make_polar_array,
    make_circular_array, make_path_array, make_point_array
    """
    _name = "make_ortho_array"
    utils.print_header(_name, _tr("Orthogonal array"))

    found, base_object = _find_object_in_doc(base_object,
                                             doc=App.activeDocument())
    if not found:
        return None

    ok, v_x, v_y, v_z = _are_vectors(v_x, v_y, v_z, name=_name)
    if not ok:
        return None

    ok, n_x, n_y, n_z = _are_integers(n_x, n_y, n_z, name=_name)
    if not ok:
        return None

    use_link = bool(use_link)
    _msg("use_link: {}".format(use_link))

    new_obj = _make_ortho_array(base_object,
                                v_x=v_x,
                                v_y=v_y,
                                v_z=v_z,
                                n_x=n_x,
                                n_y=n_y,
                                n_z=n_z,
                                use_link=use_link)
    return new_obj
예제 #18
0
def make_circular_array(base_object,
                        r_distance=100,
                        tan_distance=50,
                        number=3,
                        symmetry=1,
                        axis=App.Vector(0, 0, 1),
                        center=App.Vector(0, 0, 0),
                        use_link=True):
    """Create a circular array from the given object.

    Parameters
    ----------
    base_object: Part::Feature or str
        Any of object that has a `Part::TopoShape` that can be duplicated.
        This means most 2D and 3D objects produced with any workbench.
        If it is a string, it must be the `Label` of that object.
        Since a label is not guaranteed to be unique in a document,
        it will use the first object found with this label.

    r_distance: float, optional
        It defaults to `100`.
        Radial distance to the next ring of circular arrays.

    tan_distance: float, optional
        It defaults to `50`.
        The tangential distance between two elements located
        in the same circular ring.
        The tangential distance together with the radial distance
        determine how many copies are created.

    number: int, optional
        It defaults to 3.
        The number of layers or rings of repeated objects.
        The original object stays at the center, and is counted
        as a layer itself. So, if you want at least one layer of circular
        copies, this number must be at least 2.

    symmetry: int, optional
        It defaults to 1.
        It indicates how many lines of symmetry the entire circular pattern
        has. That is, with 1, the array is symmetric only after a full
        360 degrees rotation.

        When it is 2, the array is symmetric at 0 and 180 degrees.
        When it is 3, the array is symmetric at 0, 120, and 240 degrees.
        When it is 4, the array is symmetric at 0, 90, 180, and 270 degrees.
        Et cetera.

    axis: Base::Vector3, optional
        It defaults to `App.Vector(0, 0, 1)` or the `+Z` axis.
        The unit vector indicating the axis of rotation.

    center: Base::Vector3, optional
        It defaults to `App.Vector(0, 0, 0)` or the global origin.
        The point through which the `axis` passes to define
        the axis of rotation.

    use_link: bool, optional
        It defaults to `True`.
        If it is `True` the produced copies are not `Part::TopoShape` copies,
        but rather `App::Link` objects.
        The Links repeat the shape of the original `base_object` exactly,
        and therefore the resulting array is more memory efficient.

        Also, when `use_link` is `True`, the `Fuse` property
        of the resulting array does not work; the array doesn't
        contain separate shapes, it only has the original shape repeated
        many times, so there is nothing to fuse together.

        If `use_link` is `False` the original shape is copied many times.
        In this case the `Fuse` property is able to fuse
        all copies into a single object, if they touch each other.

    Returns
    -------
    Part::FeaturePython
        A scripted object of type `'Array'`.
        Its `Shape` is a compound of the copies of the original object.

    None
        If there is a problem it will return `None`.

    See Also
    --------
    make_ortho_array, make_polar_array, make_path_array, make_point_array
    """
    _name = "make_circular_array"
    utils.print_header(_name, translate("draft", "Circular array"))

    if isinstance(base_object, str):
        base_object_str = base_object

    found, base_object = utils.find_object(base_object,
                                           doc=App.activeDocument())
    if not found:
        _msg("base_object: {}".format(base_object_str))
        _err(translate("draft", "Wrong input: object not in document."))
        return None

    _msg("base_object: {}".format(base_object.Label))

    _msg("r_distance: {}".format(r_distance))
    _msg("tan_distance: {}".format(tan_distance))

    try:
        utils.type_check([(r_distance, (int, float, App.Units.Quantity)),
                          (tan_distance, (int, float, App.Units.Quantity))],
                         name=_name)
    except TypeError:
        _err(translate("draft", "Wrong input: must be a number or quantity."))
        return None

    _msg("number: {}".format(number))
    _msg("symmetry: {}".format(symmetry))

    try:
        utils.type_check([(number, int), (symmetry, int)], name=_name)
    except TypeError:
        _err(translate("draft", "Wrong input: must be an integer number."))
        return None

    _msg("axis: {}".format(axis))
    _msg("center: {}".format(center))

    try:
        utils.type_check([(axis, App.Vector), (center, App.Vector)],
                         name=_name)
    except TypeError:
        _err(translate("draft", "Wrong input: must be a vector."))
        return None

    use_link = bool(use_link)
    _msg("use_link: {}".format(use_link))

    new_obj = make_array.make_array(base_object,
                                    arg1=r_distance,
                                    arg2=tan_distance,
                                    arg3=axis,
                                    arg4=center,
                                    arg5=number,
                                    arg6=symmetry,
                                    use_link=use_link)
    return new_obj
예제 #19
0
def make_rect_array2d(base_object,
                      d_x=10,
                      d_y=10,
                      n_x=2,
                      n_y=2,
                      use_link=True):
    """Create a 2D rectangular array from the given object.

    This function wraps around `make_ortho_array`,
    to produce strictly rectangular arrays, in which
    the displacement vectors `v_x` and `v_y`
    only have their respective components in X and Y.
    The Z component is ignored.

    Parameters
    ----------
    base_object: Part::Feature or str
        Any of object that has a `Part::TopoShape` that can be duplicated.
        This means most 2D and 3D objects produced with any workbench.
        If it is a string, it must be the `Label` of that object.
        Since a label is not guaranteed to be unique in a document,
        it will use the first object found with this label.

    d_x, d_y: Base::Vector3, optional
        Displacement of elements in the corresponding X and Y directions.

    n_x, n_y: int, optional
        Number of elements in the corresponding X and Y directions.

    use_link: bool, optional
        If it is `True`, create `App::Link` array.
        See `make_ortho_array`.

    Returns
    -------
    Part::FeaturePython
        A scripted object of type `'Array'`.
        Its `Shape` is a compound of the copies of the original object.

    None
        If there is a problem it will return `None`.

    See Also
    --------
    make_ortho_array, make_ortho_array2d, make_rect_array, make_polar_array,
    make_circular_array, make_path_array, make_point_array
    """
    _name = "make_rect_array2d"
    utils.print_header(_name, _tr("Rectangular array 2D"))

    found, base_object = _find_object_in_doc(base_object,
                                             doc=App.activeDocument())
    if not found:
        return None

    ok, d_x, d_y, __ = _are_numbers(d_x, d_y, d_z=None, name=_name)
    if not ok:
        return None

    ok, n_x, n_y, __ = _are_integers(n_x, n_y, n_z=None, name=_name)
    if not ok:
        return None

    use_link = bool(use_link)
    _msg("use_link: {}".format(use_link))

    new_obj = _make_ortho_array(base_object,
                                v_x=App.Vector(d_x, 0, 0),
                                v_y=App.Vector(0, d_y, 0),
                                n_x=n_x,
                                n_y=n_y,
                                use_link=use_link)
    return new_obj
예제 #20
0
def make_polar_array(base_object,
                     number=5,
                     angle=360,
                     center=App.Vector(0, 0, 0),
                     use_link=True):
    """Create a polar array from the given object.

    Parameters
    ----------
    base_object: Part::Feature or str
        Any of object that has a `Part::TopoShape` that can be duplicated.
        This means most 2D and 3D objects produced with any workbench.
        If it is a string, it must be the `Label` of that object.
        Since a label is not guaranteed to be unique in a document,
        it will use the first object found with this label.

    number: int, optional
        It defaults to 5.
        The number of copies produced in the polar pattern.

    angle: float, optional
        It defaults to 360.
        The magnitude in degrees swept by the polar pattern.

    center: Base::Vector3, optional
        It defaults to the origin `App.Vector(0, 0, 0)`.
        The vector indicating the center of rotation of the array.

    use_link: bool, optional
        It defaults to `True`.
        If it is `True` the produced copies are not `Part::TopoShape` copies,
        but rather `App::Link` objects.
        The Links repeat the shape of the original `obj` exactly,
        and therefore the resulting array is more memory efficient.

        Also, when `use_link` is `True`, the `Fuse` property
        of the resulting array does not work; the array doesn't
        contain separate shapes, it only has the original shape repeated
        many times, so there is nothing to fuse together.

        If `use_link` is `False` the original shape is copied many times.
        In this case the `Fuse` property is able to fuse
        all copies into a single object, if they touch each other.

    Returns
    -------
    Part::FeaturePython
        A scripted object of type `'Array'`.
        Its `Shape` is a compound of the copies of the original object.

    None
        If there is a problem it will return `None`.

    See Also
    --------
    make_ortho_array, make_circular_array, make_path_array, make_point_array
    """
    _name = "make_polar_array"
    utils.print_header(_name, _tr("Polar array"))

    if isinstance(base_object, str):
        base_object_str = base_object

    found, base_object = utils.find_object(base_object,
                                           doc=App.activeDocument())
    if not found:
        _msg("base_object: {}".format(base_object_str))
        _err(_tr("Wrong input: object not in document."))
        return None

    _msg("base_object: {}".format(base_object.Label))

    _msg("number: {}".format(number))
    try:
        utils.type_check([(number, int)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be an integer number."))
        return None

    _msg("angle: {}".format(angle))
    try:
        utils.type_check([(angle, (int, float))], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a number."))
        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

    use_link = bool(use_link)
    _msg("use_link: {}".format(use_link))

    new_obj = make_array.make_array(base_object,
                                    arg1=center,
                                    arg2=angle,
                                    arg3=number,
                                    use_link=use_link)
    return new_obj
예제 #21
0
def make_arc_3points(points, placement=None, face=False,
                     support=None, map_mode="Deactivated",
                     primitive=False):
    """Draw a circular arc defined by three points in the circumference.

    Parameters
    ----------
    points: list of Base::Vector3
        A list that must be three points.

    placement: Base::Placement, optional
        It defaults to `None`.
        It is a placement, comprised of a `Base` (`Base::Vector3`),
        and a `Rotation` (`Base::Rotation`).
        If it exists it moves the center of the new object to the point
        indicated by `placement.Base`, while `placement.Rotation`
        is ignored so that the arc keeps the same orientation
        with which it was created.

        If both `support` and `placement` are given,
        `placement.Base` is used for the `AttachmentOffset.Base`,
        and again `placement.Rotation` is ignored.

    face: bool, optional
        It defaults to `False`.
        If it is `True` it will create a face in the closed arc.
        Otherwise only the circumference edge will be shown.

    support: App::PropertyLinkSubList, optional
        It defaults to `None`.
        It is a list containing tuples to define the attachment
        of the new object.

        A tuple in the list needs two elements;
        the first is an external object, and the second is another tuple
        with the names of sub-elements on that external object
        likes vertices or faces.
        ::
            support = [(obj, ("Face1"))]
            support = [(obj, ("Vertex1", "Vertex5", "Vertex8"))]

        This parameter sets the `Support` property but it only really affects
        the position of the new object when the `map_mode`
        is set to other than `'Deactivated'`.

    map_mode: str, optional
        It defaults to `'Deactivated'`.
        It defines the type of `'MapMode'` of the new object.
        This parameter only works when a `support` is also provided.

        Example: place the new object on a face or another object.
        ::
            support = [(obj, ("Face1"))]
            map_mode = 'FlatFace'

        Example: place the new object on a plane created by three vertices
        of an object.
        ::
            support = [(obj, ("Vertex1", "Vertex5", "Vertex8"))]
            map_mode = 'ThreePointsPlane'

    primitive: bool, optional
        It defaults to `False`. If it is `True`, it will create a Part
        primitive instead of a Draft object.
        In this case, `placement`, `face`, `support`, and `map_mode`
        are ignored.

    Returns
    -------
    Part::Part2DObject or Part::Feature
        The new arc object.
        Normally it returns a parametric Draft object (`Part::Part2DObject`).
        If `primitive` is `True`, it returns a basic `Part::Feature`.

    None
        Returns `None` if there is a problem and the object cannot be created.
    """
    _name = "make_arc_3points"
    utils.print_header(_name, "Arc by 3 points")

    try:
        utils.type_check([(points, (list, tuple))], name=_name)
    except TypeError:
        _err(_tr("Points: ") + "{}".format(points))
        _err(_tr("Wrong input: "
                 "must be list or tuple of three points exactly."))
        return None

    if len(points) != 3:
        _err(_tr("Points: ") + "{}".format(points))
        _err(_tr("Wrong input: "
                 "must be list or tuple of three points exactly."))
        return None

    if placement is not None:
        try:
            utils.type_check([(placement, App.Placement)], name=_name)
        except TypeError:
            _err(_tr("Placement: ") + "{}".format(placement))
            _err(_tr("Wrong input: incorrect type of placement."))
            return None

    p1, p2, p3 = points

    _msg("p1: {}".format(p1))
    _msg("p2: {}".format(p2))
    _msg("p3: {}".format(p3))

    try:
        utils.type_check([(p1, App.Vector),
                          (p2, App.Vector),
                          (p3, App.Vector)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: incorrect type of points."))
        return None

    try:
        _edge = Part.Arc(p1, p2, p3)
    except Part.OCCError as error:
        _err(_tr("Cannot generate shape: ") + "{}".format(error))
        return None

    edge = _edge.toShape()
    radius = edge.Curve.Radius
    center = edge.Curve.Center

    _msg(_tr("Radius: ") + "{}".format(radius))
    _msg(_tr("Center: ") + "{}".format(center))

    if primitive:
        _msg(_tr("Create primitive object"))
        obj = App.ActiveDocument.addObject("Part::Feature", "Arc")
        obj.Shape = edge
        return obj

    rot = App.Rotation(edge.Curve.XAxis,
                       edge.Curve.YAxis,
                       edge.Curve.Axis, "ZXY")
    _placement = App.Placement(center, rot)
    start = edge.FirstParameter
    end = math.degrees(edge.LastParameter)
    obj = Draft.makeCircle(radius,
                           placement=_placement, face=face,
                           startangle=start, endangle=end,
                           support=support)

    if App.GuiUp:
        gui_utils.autogroup(obj)

    original_placement = obj.Placement

    if placement and not support:
        obj.Placement.Base = placement.Base
        _msg(_tr("Final placement: ") + "{}".format(obj.Placement))
    if face:
        _msg(_tr("Face: True"))
    if support:
        _msg(_tr("Support: ") + "{}".format(support))
        _msg(_tr("Map mode: " + "{}".format(map_mode)))
        obj.MapMode = map_mode
        if placement:
            obj.AttachmentOffset.Base = placement.Base
            obj.AttachmentOffset.Rotation = original_placement.Rotation
            _msg(_tr("Attachment offset: {}".format(obj.AttachmentOffset)))
        _msg(_tr("Final placement: ") + "{}".format(obj.Placement))

    return obj
예제 #22
0
def make_linear_dimension(p1, p2, dim_line=None):
    """Create a free linear dimension from two main points.

    Parameters
    ----------
    p1: Base::Vector3
        First point of the measurement.

    p2: Base::Vector3
        Second point of the measurement.

    dim_line: Base::Vector3, optional
        It defaults to `None`.
        This is a point through which the extension of the dimension line
        will pass.
        This point controls how close or how far the dimension line is
        positioned from the measured segment that goes from `p1` to `p2`.

        If it is `None`, this point will be calculated from the intermediate
        distance betwwen `p1` and `p2`.

    Returns
    -------
    App::FeaturePython
        A scripted object of type `'LinearDimension'`.
        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_linear_dimension"
    utils.print_header(_name, "Linear dimension")

    found, doc = utils.find_doc(App.activeDocument())
    if not found:
        _err(_tr("No active document. Aborting."))
        return None

    _msg("p1: {}".format(p1))
    try:
        utils.type_check([(p1, App.Vector)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a vector."))
        return None

    _msg("p2: {}".format(p2))
    try:
        utils.type_check([(p2, App.Vector)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a vector."))
        return None

    _msg("dim_line: {}".format(dim_line))
    if dim_line:
        try:
            utils.type_check([(dim_line, App.Vector)], name=_name)
        except TypeError:
            _err(_tr("Wrong input: must be a vector."))
            return None
    else:
        diff = p2.sub(p1)
        diff.multiply(0.5)
        dim_line = p1.add(diff)

    new_obj = make_dimension(p1, p2, dim_line)

    return new_obj
예제 #23
0
def make_text(string, placement=None, screen=False):
    """Create a Text object containing the given list of strings.

    The current color and text height and font specified in preferences
    are used.

    Parameters
    ----------
    string: str, or list of str
        String to display on screen.

        If it is a list, each element in the list should be a string.
        In this case each element will be printed in its own line, that is,
        a newline will be added at the end of each string.

        If an empty string is passed `''` this won't cause an error
        but the text `'Label'` will be displayed in the 3D view.

    placement: Base::Placement, Base::Vector3, or Base::Rotation, optional
        It defaults to `None`.
        If it is provided, it is the placement of the new text.
        The input could be a full placement, just a vector indicating
        the translation, or just a rotation.

    screen: bool, optional
        It defaults to `False`, in which case the text is placed in 3D space
        oriented like any other object, on top of a given plane,
        by the default the XY plane.
        If it is `True`, the text will always face perpendicularly
        to the camera direction, that is, it will be flat on the screen.

    Returns
    -------
    App::FeaturePython
        A scripted object of type `'Text'`.
        This object does not have a `Shape` attribute, as the text is created
        on screen by Coin (pivy).

    None
        If there is a problem it will return `None`.
    """
    _name = "make_text"
    utils.print_header(_name, "Text")

    found, doc = utils.find_doc(App.activeDocument())
    if not found:
        _err(_tr("No active document. Aborting."))
        return None

    _msg("string: {}".format(string))
    try:
        utils.type_check([(string, (str, list))])
    except TypeError:
        _err(
            _tr("Wrong input: must be a list of strings "
                "or a single string."))
        return None

    if not all(isinstance(element, str) for element in string):
        _err(
            _tr("Wrong input: must be a list of strings "
                "or a single string."))
        return None

    if isinstance(string, str):
        string = [string]

    _msg("placement: {}".format(placement))
    if not placement:
        placement = App.Placement()
    try:
        utils.type_check([(placement,
                           (App.Placement, App.Vector, App.Rotation))],
                         name=_name)
    except TypeError:
        _err(
            _tr("Wrong input: must be a placement, a vector, "
                "or a rotation."))
        return None

    # Convert the vector or rotation to a full placement
    if isinstance(placement, App.Vector):
        placement = App.Placement(placement, App.Rotation())
    elif isinstance(placement, App.Rotation):
        placement = App.Placement(App.Vector(), placement)

    new_obj = doc.addObject("App::FeaturePython", "Text")
    Text(new_obj)
    new_obj.Text = string
    new_obj.Placement = placement

    if App.GuiUp:
        ViewProviderText(new_obj.ViewObject)

        h = utils.get_param("textheight", 0.20)

        if screen:
            _msg("screen: {}".format(screen))
            new_obj.ViewObject.DisplayMode = "3D text"
            h = h * 10

        new_obj.ViewObject.FontSize = h
        new_obj.ViewObject.FontName = utils.get_param("textfont", "")
        new_obj.ViewObject.LineSpacing = 1

        gui_utils.format_object(new_obj)
        gui_utils.select(new_obj)

    return new_obj
예제 #24
0
def make_radial_dimension_obj(edge_object,
                              index=1,
                              mode="radius",
                              dim_line=None):
    """Create a radial or diameter dimension from an arc object.

    Parameters
    ----------
    edge_object: Part::Feature
        The object which has a circular edge which will be measured.
        It must have a `Part::TopoShape`, and at least one element
        must be a circular edge in `Shape.Edges` to be able to measure
        its radius.

    index: int, optional
        It defaults to `1`.
        It is the index of the edge in `edge_object` which is going to
        be measured.
        The minimum value should be `1`, which will be interpreted
        as `'Edge1'`. If the value is below `1`, it will be set to `1`.

    mode: str, optional
        It defaults to `'radius'`; the other option is `'diameter'`.
        It determines whether the dimension will be shown as a radius
        or as a diameter.

    dim_line: Base::Vector3, optional
        It defaults to `None`.
        This is a point through which the extension of the dimension line
        will pass. The dimension line will be a radius or diameter
        of the measured arc, extending from the center to the arc itself.

        If it is `None`, this point will be set to one unit to the right
        of the center of the arc, which will create a dimension line that is
        horizontal, that is, parallel to the +X axis.

    Returns
    -------
    App::FeaturePython
        A scripted object of type `'LinearDimension'`.
        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_radial_dimension_obj"
    utils.print_header(_name, "Radial dimension")

    found, doc = utils.find_doc(App.activeDocument())
    if not found:
        _err(_tr("No active document. Aborting."))
        return None

    if isinstance(edge_object, str):
        edge_object_str = edge_object

    found, edge_object = utils.find_object(edge_object, doc)
    if not found:
        _msg("edge_object: {}".format(edge_object_str))
        _err(_tr("Wrong input: object not in document."))
        return None

    _msg("edge_object: {}".format(edge_object.Label))
    if not hasattr(edge_object, "Shape"):
        _err(_tr("Wrong input: object doesn't have a 'Shape' to measure."))
        return None
    if (not hasattr(edge_object.Shape, "Edges")
            or len(edge_object.Shape.Edges) < 1):
        _err(
            _tr("Wrong input: object doesn't have at least one element "
                "in 'Edges' to use for measuring."))
        return None

    _msg("index: {}".format(index))
    try:
        utils.type_check([(index, int)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be an integer."))
        return None

    if index < 1:
        index = 1
        _wrn(_tr("index: values below 1 are not allowed; will be set to 1."))

    edge = edge_object.getSubObject("Edge" + str(index))
    if not edge:
        _err(
            _tr("Wrong input: index doesn't correspond to an edge "
                "in the object."))
        return None

    if not hasattr(edge, "Curve") or edge.Curve.TypeId != 'Part::GeomCircle':
        _err(_tr("Wrong input: index doesn't correspond to a circular edge."))
        return None

    _msg("mode: {}".format(mode))
    try:
        utils.type_check([(mode, str)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a string, 'radius' or 'diameter'."))
        return None

    if mode not in ("radius", "diameter"):
        _err(_tr("Wrong input: must be a string, 'radius' or 'diameter'."))
        return None

    _msg("dim_line: {}".format(dim_line))
    if dim_line:
        try:
            utils.type_check([(dim_line, App.Vector)], name=_name)
        except TypeError:
            _err(_tr("Wrong input: must be a vector."))
            return None
    else:
        center = edge_object.Shape.Edges[index - 1].Curve.Center
        dim_line = center + App.Vector(1, 0, 0)

    # TODO: the internal function expects an index starting with 0
    # so we need to decrease the value here.
    # This should be changed in the future in the internal function.
    index -= 1

    new_obj = make_dimension(edge_object, index, mode, dim_line)

    return new_obj
예제 #25
0
def mirror(objlist, p1, p2):
    """Create a mirror object from the provided list and line.

    It creates a `Part::Mirroring` object from the given `objlist` using
    a plane that is defined by the two given points `p1` and `p2`,
    and either

    - the Draft working plane normal, or
    - the negative normal provided by the camera direction
      if the working plane normal does not exist and the graphical interface
      is available.

    If neither of these two is available, it uses as normal the +Z vector.

    Parameters
    ----------
    objlist: single object or a list of objects
        A single object or a list of objects.

    p1: Base::Vector3
        Point 1 of the mirror plane. It is also used as the `Placement.Base`
        of the resulting object.

    p2: Base::Vector3
        Point 1 of the mirror plane.

    Returns
    -------
    None
        If the operation fails.

    list
        List of `Part::Mirroring` objects, or a single one
        depending on the input `objlist`.

    To Do
    -----
    Implement a mirror tool specific to the workbench that does not
    just use `Part::Mirroring`. It should create a derived object,
    that is, it should work similar to `Draft.offset`.
    """
    utils.print_header('mirror', "Create mirror")

    if not objlist:
        _err(_tr("No object given"))
        return

    if p1 == p2:
        _err(_tr("The two points are coincident"))
        return

    if not isinstance(objlist, list):
        objlist = [objlist]

    if hasattr(App, "DraftWorkingPlane"):
        norm = App.DraftWorkingPlane.getNormal()
    elif App.GuiUp:
        norm = Gui.ActiveDocument.ActiveView.getViewDirection().negative()
    else:
        norm = App.Vector(0, 0, 1)

    pnorm = p2.sub(p1).cross(norm).normalize()

    result = []

    for obj in objlist:
        mir = App.ActiveDocument.addObject("Part::Mirroring", "mirror")
        mir.Label = obj.Label + _tr(" (mirrored)")
        mir.Source = obj
        mir.Base = p1
        mir.Normal = pnorm
        gui_utils.format_object(mir, obj)
        result.append(mir)

    if len(result) == 1:
        result = result[0]
        gui_utils.select(result)

    return result
예제 #26
0
def get_bbox(obj, debug=False):
    """Return a BoundBox from any object that has a Coin RootNode.

    Normally the bounding box of an object can be taken
    from its `Part::TopoShape`.
    ::
        >>> print(obj.Shape.BoundBox)

    However, for objects without a `Shape`, such as those
    derived from `App::FeaturePython` like `Draft Text` and `Draft Dimension`,
    the bounding box can be calculated from the `RootNode` of the viewprovider.

    Parameters
    ----------
    obj: App::DocumentObject
        Any object that has a `ViewObject.RootNode`.

    Returns
    -------
    Base::BoundBox
        It returns a `BoundBox` object which has information like
        minimum and maximum values of X, Y, and Z, as well as bounding box
        center.

    None
        If there is a problem it will return `None`.
    """
    _name = "get_bbox"
    utils.print_header(_name, "Bounding box", debug=debug)

    found, doc = utils.find_doc(App.activeDocument())
    if not found:
        _err(_tr("No active document. Aborting."))
        return None

    if isinstance(obj, str):
        obj_str = obj

    found, obj = utils.find_object(obj, doc)
    if not found:
        _msg("obj: {}".format(obj_str))
        _err(_tr("Wrong input: object not in document."))
        return None

    if debug:
        _msg("obj: {}".format(obj.Label))

    if (not hasattr(obj, "ViewObject")
            or not obj.ViewObject
            or not hasattr(obj.ViewObject, "RootNode")):
        _err(_tr("Does not have 'ViewObject.RootNode'."))

    # For Draft Dimensions
    # node = obj.ViewObject.Proxy.node
    node = obj.ViewObject.RootNode

    view = Gui.ActiveDocument.ActiveView
    region = view.getViewer().getSoRenderManager().getViewportRegion()
    action = coin.SoGetBoundingBoxAction(region)

    node.getBoundingBox(action)
    bb = action.getBoundingBox()

    # xlength, ylength, zlength = bb.getSize().getValue()
    xmin, ymin, zmin = bb.getMin().getValue()
    xmax, ymax, zmax = bb.getMax().getValue()

    return App.BoundBox(xmin, ymin, zmin, xmax, ymax, zmax)
예제 #27
0
def make_ortho_array2d(obj,
                       v_x=App.Vector(10, 0, 0),
                       v_y=App.Vector(0, 10, 0),
                       n_x=2,
                       n_y=2,
                       use_link=True):
    """Create a 2D orthogonal array from the given object.

    This works the same as `make_ortho_array`.
    The Z component is ignored so it only considers vector displacements
    in X and Y directions.

    Parameters
    ----------
    obj: Part::Feature
        Any type of object that has a `Part::TopoShape`
        that can be duplicated.
        This means most 2D and 3D objects produced
        with any workbench.

    v_x, v_y: Base::Vector3, optional
        Vectorial displacement of elements
        in the corresponding X and Y directions.
        See `make_ortho_array`.

    n_x, n_y: int, optional
        Number of elements
        in the corresponding X and Y directions.
        See `make_ortho_array`.

    use_link: bool, optional
        If it is `True`, create `App::Link` array.
        See `make_ortho_array`.

    Returns
    -------
    Part::FeaturePython
        A scripted object with `Proxy.Type='Array'`.
        Its `Shape` is a compound of the copies of the original object.

    See Also
    --------
    make_ortho_array, make_rect_array, make_rect_array2d
    """
    _name = "make_ortho_array2d"
    utils.print_header(_name, _tr("Orthogonal array 2D"))

    _msg("v_x: {}".format(v_x))
    _msg("v_y: {}".format(v_y))

    try:
        utils.type_check([(v_x, (int, float, App.Vector)),
                          (v_y, (int, float, App.Vector))],
                         name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be a number or vector."))
        return None

    _text = "Input: single value expanded to vector."
    if not isinstance(v_x, App.Vector):
        v_x = App.Vector(v_x, 0, 0)
        _wrn(_tr(_text))
    if not isinstance(v_y, App.Vector):
        v_y = App.Vector(0, v_y, 0)
        _wrn(_tr(_text))

    _msg("n_x: {}".format(n_x))
    _msg("n_y: {}".format(n_y))

    try:
        utils.type_check([(n_x, int), (n_y, int)], name=_name)
    except TypeError:
        _err(_tr("Wrong input: must be an integer number."))
        return None

    _text = ("Input: number of elements must be at least 1. "
             "It is set to 1.")
    if n_x < 1:
        _wrn(_tr(_text))
        n_x = 1
    if n_y < 1:
        _wrn(_tr(_text))
        n_y = 1

    _msg("use_link: {}".format(bool(use_link)))

    # new_obj = make_array.make_array()
    new_obj = Draft.makeArray(obj,
                              arg1=v_x,
                              arg2=v_y,
                              arg3=n_x,
                              arg4=n_y,
                              use_link=use_link)
    return new_obj