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
def make_rect_array(obj, d_x=10, d_y=10, d_z=10, n_x=2, n_y=2, n_z=1, use_link=True): """Create a 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`, `v_y`, and `v_z` only have their respective components in X, Y, and Z. 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, d_z: Base::Vector3, optional Displacement of elements in the corresponding X, Y, and Z directions. n_x, n_y, n_z: int, optional Number of elements in the corresponding X, Y, and Z 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_array2d """ _name = "make_rect_array" utils.print_header(_name, _tr("Rectangular array")) _msg("d_x: {}".format(d_x)) _msg("d_y: {}".format(d_y)) _msg("d_z: {}".format(d_z)) try: utils.type_check([(d_x, (int, float)), (d_y, (int, float)), (d_z, (int, float))], name=_name) except TypeError: _err(_tr("Wrong input: must be a number.")) return None new_obj = make_ortho_array(obj, v_x=App.Vector(d_x, 0, 0), v_y=App.Vector(0, d_y, 0), v_z=App.Vector(0, 0, d_z), n_x=n_x, n_y=n_y, n_z=n_z, use_link=use_link) return new_obj
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
def make_bspline(pointslist, closed=False, placement=None, face=None, support=None): """make_bspline(pointslist, [closed], [placement]) Creates a B-Spline object from the given list of vectors. Parameters ---------- pointlist : [Base.Vector] List of points to create the polyline. Instead of a pointslist, you can also pass a Part Wire. TODO: Change the name so! closed : bool If closed is True or first and last points are identical, the created BSpline will be closed. placement : Base.Placement If a placement is given, it is used. face : Bool If face is False, the rectangle is shown as a wireframe, otherwise as a face. support : TODO: Describe """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return if not isinstance(pointslist,list): nlist = [] for v in pointslist.Vertexes: nlist.append(v.Point) pointslist = nlist if len(pointslist) < 2: _err = "Draft.makeBSpline: not enough points" App.Console.PrintError(translate("draft", _err)+"\n") return if (pointslist[0] == pointslist[-1]): if len(pointslist) > 2: closed = True pointslist.pop() _err = "Draft.makeBSpline: Equal endpoints forced Closed" App.Console.PrintWarning(translate("Draft", _err) + _err + "\n") else: # len == 2 and first == last GIGO _err = "Draft.makeBSpline: Invalid pointslist" App.Console.PrintError(translate("Draft", _err)+"\n") return # should have sensible parms from here on if placement: utils.type_check([(placement,App.Placement)], "make_bspline") if len(pointslist) == 2: fname = "Line" else: fname = "BSpline" obj = App.ActiveDocument.addObject("Part::Part2DObjectPython",fname) BSpline(obj) obj.Closed = closed obj.Points = pointslist obj.Support = support if face != None: obj.MakeFace = face if placement: obj.Placement = placement if App.GuiUp: ViewProviderBSpline(obj.ViewObject) gui_utils.format_object(obj) gui_utils.select(obj) return obj
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, or list of str, optional It defaults to `'Label'`. If it is a list, each element in the list represents a new text line. 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(translate("draft", "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(translate("draft", "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( translate( "draft", "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(translate("draft", "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(translate("draft", "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( translate( "draft", "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( translate("draft", "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( translate( "draft", "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( translate( "draft", "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, list))], name=_name) except TypeError: _err( translate( "draft", "Wrong input: must be a list of strings or a single string.")) return None if (type(custom_text) is list and not all(isinstance(element, str) for element in custom_text)): _err( translate( "draft", "Wrong input: must be a list of strings or a single string.")) return None _msg("direction: {}".format(direction)) if not direction: direction = "Horizontal" try: utils.type_check([(direction, str)], name=_name) except TypeError: _err( translate( "draft", "Wrong input: must be a string, 'Horizontal', 'Vertical', or 'Custom'." )) return None if direction not in ("Horizontal", "Vertical", "Custom"): _err( translate( "draft", "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(translate("draft", "Wrong input: must be a number.")) return None if points: _msg("points: {}".format(points)) _err_msg = translate( "draft", "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( translate("draft", "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
def make_point_array(base_object, point_object, extra=None, use_link=True): """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) if use_link: # The PointArray class must be called in this special way # to make it a LinkArray new_obj = doc.addObject("Part::FeaturePython", "PointArray", PointArray(None), None, True) else: 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: if use_link: ViewProviderDraftLink(new_obj.ViewObject) else: ViewProviderDraftArray(new_obj.ViewObject) gui_utils.format_object(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
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
def make_bezcurve(pointslist, closed=False, placement=None, face=None, support=None, degree=None): """make_bezcurve(pointslist, [closed], [placement]) Creates a Bezier Curve object from the given list of vectors. Parameters ---------- pointlist : [Base.Vector] List of points to create the polyline. Instead of a pointslist, you can also pass a Part Wire. TODO: Change the name so! closed : bool If closed is True or first and last points are identical, the created BSpline will be closed. placement : Base.Placement If a placement is given, it is used. face : Bool If face is False, the rectangle is shown as a wireframe, otherwise as a face. support : TODO: Describe degree : int Degree of the BezCurve """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return if not isinstance(pointslist, list): nlist = [] for v in pointslist.Vertexes: nlist.append(v.Point) pointslist = nlist if placement: type_check([(placement, App.Placement)], "make_bezcurve") if len(pointslist) == 2: fname = "Line" else: fname = "BezCurve" obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", fname) BezCurve(obj) obj.Points = pointslist if degree: obj.Degree = degree else: import Part obj.Degree = min((len(pointslist) - (1 * (not closed))), Part.BezierCurve().MaxDegree) obj.Closed = closed obj.Support = support if face != None: obj.MakeFace = face obj.Proxy.resetcontinuity(obj) if placement: obj.Placement = placement if App.GuiUp: ViewProviderBezCurve(obj.ViewObject) # if not face: obj.ViewObject.DisplayMode = "Wireframe" # obj.ViewObject.DisplayMode = "Wireframe" format_object(obj) select(obj) return obj
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
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
def make_wire(pointslist, closed=False, placement=None, face=None, support=None, bs2wire=False): """makeWire(pointslist,[closed],[placement]) Creates a Wire object from the given list of vectors. If face is true (and wire is closed), the wire will appear filled. Instead of a pointslist, you can also pass a Part Wire. Parameters ---------- pointlist : [Base.Vector] List of points to create the polyline closed : bool If closed is True or first and last points are identical, the created polyline will be closed. placement : Base.Placement If a placement is given, it is used. face : Bool If face is False, the rectangle is shown as a wireframe, otherwise as a face. support : TODO: Describe bs2wire : bool TODO: Describe """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return import Part if not isinstance(pointslist, list): e = pointslist.Wires[0].Edges pointslist = Part.Wire(Part.__sortEdges__(e)) nlist = [] for v in pointslist.Vertexes: nlist.append(v.Point) if DraftGeomUtils.isReallyClosed(pointslist): closed = True pointslist = nlist if len(pointslist) == 0: print("Invalid input points: ", pointslist) #print(pointslist) #print(closed) if placement: utils.type_check([(placement, App.Placement)], "make_wire") ipl = placement.inverse() if not bs2wire: pointslist = [ipl.multVec(p) for p in pointslist] if len(pointslist) == 2: fname = "Line" else: fname = "Wire" obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", fname) Wire(obj) obj.Points = pointslist obj.Closed = closed obj.Support = support if face != None: obj.MakeFace = face if placement: obj.Placement = placement if App.GuiUp: ViewProviderWire(obj.ViewObject) gui_utils.format_object(obj) gui_utils.select(obj) return obj
def make_circle(radius, placement=None, face=None, startangle=None, endangle=None, support=None): """make_circle(radius, [placement, face, startangle, endangle]) or make_circle(edge,[face]): Creates a circle object with given parameters. Parameters ---------- radius : the radius of the circle. placement : If placement is given, it is used. face : Bool If face is False, the circle is shown as a wireframe, otherwise as a face. startangle : start angle of the arc (in degrees) endangle : end angle of the arc (in degrees) if startangle and endangle are equal, a circle is created, if they are different an arc is created edge : edge.Curve must be a 'Part.Circle' the circle is created from the given edge support : TODO: Describe """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return if placement: utils.type_check([(placement, App.Placement)], "make_circle") if startangle != endangle: _name = "Arc" else: _name = "Circle" obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", _name) Circle(obj) if face is not None: obj.MakeFace = face if isinstance(radius, Part.Edge): edge = radius if DraftGeomUtils.geomType(edge) == "Circle": obj.Radius = edge.Curve.Radius placement = App.Placement(edge.Placement) delta = edge.Curve.Center.sub(placement.Base) placement.move(delta) # Rotation of the edge rotOk = App.Rotation(edge.Curve.XAxis, edge.Curve.YAxis, edge.Curve.Axis, "ZXY") placement.Rotation = rotOk if len(edge.Vertexes) > 1: v0 = edge.Curve.XAxis v1 = (edge.Vertexes[0].Point).sub(edge.Curve.Center) v2 = (edge.Vertexes[-1].Point).sub(edge.Curve.Center) # Angle between edge.Curve.XAxis and the vector from center to start of arc a0 = math.degrees(App.Vector.getAngle(v0, v1)) # Angle between edge.Curve.XAxis and the vector from center to end of arc a1 = math.degrees(App.Vector.getAngle(v0, v2)) obj.FirstAngle = a0 obj.LastAngle = a1 else: obj.Radius = radius if (startangle is not None) and (endangle is not None): if startangle == -0: startangle = 0 obj.FirstAngle = startangle obj.LastAngle = endangle obj.Support = support if placement: obj.Placement = placement if App.GuiUp: ViewProviderDraft(obj.ViewObject) gui_utils.format_object(obj) gui_utils.select(obj) return obj
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
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
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 apperture of the dimension line, that is, of the circular arc. It is drawn counter-clockwise. :: angles = [0 90] angles = [330 60] # the arc crosses the X axis angles = [-30 60] # same angle dim_line: Base::Vector3, optional It defaults to `Vector(10, 10, 0)`. This is a point through which the extension of the dimension line will pass. This defines the radius of the dimension line, the circular arc. normal: Base::Vector3, optional It defaults to `None`, in which case the `normal` is taken from the currently active `App.DraftWorkingPlane.axis`. If the working plane is not available, then the `normal` defaults to +Z or `Vector(0, 0, 1)`. Returns ------- App::FeaturePython A scripted object of type `'AngularDimension'`. This object does not have a `Shape` attribute, as the text and lines are created on screen by Coin (pivy). None If there is a problem it will return `None`. """ _name = "make_angular_dimension" utils.print_header(_name, "Angular dimension") found, doc = utils.find_doc(App.activeDocument()) if not found: _err(_tr("No active document. Aborting.")) return None _msg("center: {}".format(center)) try: utils.type_check([(center, App.Vector)], name=_name) except TypeError: _err(_tr("Wrong input: must be a vector.")) return None _msg("angles: {}".format(angles)) try: utils.type_check([(angles, (tuple, list))], name=_name) if len(angles) != 2: _err(_tr("Wrong input: must be a list with two angles.")) return None ang1, ang2 = angles utils.type_check([(ang1, (int, float)), (ang2, (int, float))], name=_name) except TypeError: _err(_tr("Wrong input: must be a list with two angles.")) return None # If the angle is larger than 360 degrees, make sure # it is smaller than 360 for n in range(len(angles)): if angles[n] > 360: angles[n] = angles[n] - 360 _msg("dim_line: {}".format(dim_line)) try: utils.type_check([(dim_line, App.Vector)], name=_name) except TypeError: _err(_tr("Wrong input: must be a vector.")) return None _msg("normal: {}".format(normal)) if normal: try: utils.type_check([(dim_line, App.Vector)], name=_name) except TypeError: _err(_tr("Wrong input: must be a vector.")) return None if not normal: if hasattr(App, "DraftWorkingPlane"): normal = App.DraftWorkingPlane.axis else: normal = App.Vector(0, 0, 1) new_obj = App.ActiveDocument.addObject("App::FeaturePython", "Dimension") AngularDimension(new_obj) new_obj.Center = center new_obj.FirstAngle = angles[0] new_obj.LastAngle = angles[1] new_obj.Dimline = dim_line if App.GuiUp: ViewProviderAngularDimension(new_obj.ViewObject) # Invert the normal if we are viewing it from the back. # This is determined by the angle between the current # 3D view and the provided normal being below 90 degrees vnorm = gui_utils.get3DView().getViewDirection() if vnorm.getAngle(normal) < math.pi/2: normal = normal.negative() new_obj.Normal = normal if App.GuiUp: gui_utils.format_object(new_obj) gui_utils.select(new_obj) return new_obj
def make_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 tanget 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
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 represents a new text line. 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(translate("draft", "No active document. Aborting.")) return None _msg("string: {}".format(string)) try: utils.type_check([(string, (str, list))], name=_name) except TypeError: _err( translate( "draft", "Wrong input: must be a list of strings or a single string.")) return None if (type(string) is list and not all(isinstance(element, str) for element in string)): _err( translate( "draft", "Wrong input: must be a list of strings or a single string.")) 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( translate( "draft", "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", 2) new_obj.ViewObject.DisplayMode = "3D text" if screen: _msg("screen: {}".format(screen)) new_obj.ViewObject.DisplayMode = "2D 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
def make_wire(pointslist, closed=False, placement=None, face=None, support=None, bs2wire=False): """make_wire(pointslist, [closed], [placement]) Creates a Wire object from the given list of vectors. If face is true (and wire is closed), the wire will appear filled. Instead of a pointslist, you can also pass a Part Wire. Parameters ---------- pointslist : [Base.Vector] List of points to create the polyline closed : bool If closed is True or first and last points are identical, the created polyline will be closed. placement : Base.Placement If a placement is given, it is used. face : Bool If face is False, the rectangle is shown as a wireframe, otherwise as a face. support : TODO: Describe bs2wire : bool TODO: Describe """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return None import Part if isinstance(pointslist, (list, tuple)): for pnt in pointslist: if not isinstance(pnt, App.Vector): App.Console.PrintError( "Items must be Base.Vector objects, not {}\n".format( type(pnt))) return None elif isinstance(pointslist, Part.Wire): for edge in pointslist.Edges: if not DraftGeomUtils.is_straight_line(edge): App.Console.PrintError("All edges must be straight lines\n") return None closed = pointslist.isClosed() pointslist = [v.Point for v in pointslist.OrderedVertexes] else: App.Console.PrintError("Can't make Draft Wire from {}\n".format( type(pointslist))) return None if len(pointslist) == 0: App.Console.PrintWarning("Draft Wire created with empty point list\n") if placement: utils.type_check([(placement, App.Placement)], "make_wire") ipl = placement.inverse() if not bs2wire: pointslist = [ipl.multVec(p) for p in pointslist] if len(pointslist) == 2: fname = "Line" else: fname = "Wire" obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", fname) Wire(obj) obj.Points = pointslist obj.Closed = closed obj.Support = support if face != None: obj.MakeFace = face if placement: obj.Placement = placement if App.GuiUp: ViewProviderWire(obj.ViewObject) gui_utils.format_object(obj) gui_utils.select(obj) return obj
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, _tr("Layer")) found, doc = utils.find_doc(App.activeDocument()) if not found: _err(_tr("No active document. Aborting.")) return None if name: _msg("name: {}".format(name)) try: utils.type_check([(name, str)], name=_name) except TypeError: _err(_tr("Wrong input: it must be a string.")) return None else: name = translate("draft", "Layer") _info_color = ("Wrong input: " "must be a tuple of three floats 0.0 to 1.0.") if line_color: _msg("line_color: {}".format(line_color)) try: utils.type_check([(line_color, tuple)], name=_name) except TypeError: _err(_tr(_info_color)) return None if not all(isinstance(color, (int, float)) for color in line_color): _err(_tr(_info_color)) 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(_tr(_info_color)) return None if not all(isinstance(color, (int, float)) for color in shape_color): _err(_tr(_info_color)) 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(_tr("Wrong input: must be a number.")) return None _info_style = ("Wrong input: " "must be 'Solid', 'Dashed', 'Dotted', or 'Dashdot'.") _msg("draw_style: {}".format(draw_style)) try: utils.type_check([(draw_style, str)], name=_name) except TypeError: _err(_tr(_info_style)) return None if draw_style not in ('Solid', 'Dashed', 'Dotted', 'Dashdot'): _err(_tr(_info_style)) return None _msg("transparency: {}".format(transparency)) try: utils.type_check([(transparency, (int, float))], name=_name) transparency = int(abs(transparency)) except TypeError: _err(_tr("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
def move(objectslist, vector, copy=False): """move(objects,vector,[copy]) Move the objects contained in objects (that can be an object or a list of objects) in the direction and distance indicated by the given vector. Parameters ---------- objectslist : list vector : Base.Vector Delta Vector to move the clone from the original position. copy : bool If copy is True, the actual objects are not moved, but copies are created instead. Return ---------- The objects (or their copies) are returned. """ utils.type_check([(vector, App.Vector), (copy, bool)], "move") if not isinstance(objectslist, list): objectslist = [objectslist] objectslist.extend(groups.get_movable_children(objectslist)) newobjlist = [] newgroups = {} objectslist = utils.filter_objects_for_modifiers(objectslist, copy) for obj in objectslist: newobj = None # real_vector have been introduced to take into account # the possibility that object is inside an App::Part # TODO: Make Move work also with App::Link if hasattr(obj, "getGlobalPlacement"): v_minus_global = obj.getGlobalPlacement().inverse( ).Rotation.multVec(vector) real_vector = obj.Placement.Rotation.multVec(v_minus_global) else: real_vector = vector if utils.get_type(obj) == "Point": if copy: newobj = make_copy.make_copy(obj) else: newobj = obj newobj.X = obj.X.Value + real_vector.x newobj.Y = obj.Y.Value + real_vector.y newobj.Z = obj.Z.Value + real_vector.z elif obj.isDerivedFrom("App::DocumentObjectGroup"): pass elif hasattr(obj, 'Shape'): if copy: newobj = make_copy.make_copy(obj) else: newobj = obj pla = newobj.Placement pla.move(real_vector) elif utils.get_type(obj) == "Annotation": if copy: newobj = make_copy.make_copy(obj) else: newobj = obj newobj.Position = obj.Position.add(real_vector) elif utils.get_type(obj) in ("Text", "DraftText"): if copy: newobj = make_copy.make_copy(obj) else: newobj = obj newobj.Placement.Base = obj.Placement.Base.add(real_vector) elif utils.get_type(obj) in ["Dimension", "LinearDimension"]: if copy: newobj = make_copy.make_copy(obj) else: newobj = obj newobj.Start = obj.Start.add(real_vector) newobj.End = obj.End.add(real_vector) newobj.Dimline = obj.Dimline.add(real_vector) elif utils.get_type(obj) in ["AngularDimension"]: if copy: newobj = make_copy.make_copy(obj) else: newobj = obj newobj.Center = obj.Start.add(real_vector) elif "Placement" in obj.PropertiesList: if copy: newobj = make_copy.make_copy(obj) else: newobj = obj pla = obj.Placement pla.move(real_vector) if newobj is not None: newobjlist.append(newobj) if copy: for p in obj.InList: if p.isDerivedFrom("App::DocumentObjectGroup") and ( p in objectslist): g = newgroups.setdefault( p.Name, App.ActiveDocument.addObject(p.TypeId, p.Name)) g.addObject(newobj) break if utils.get_type(p) == "Layer": p.Proxy.addObject(p, newobj) if copy and utils.get_param("selectBaseObjects", False): gui_utils.select(objectslist) else: gui_utils.select(newobjlist) if len(newobjlist) == 1: return newobjlist[0] return newobjlist
def rotate(objectslist, angle, center=App.Vector(0, 0, 0), axis=App.Vector(0, 0, 1), copy=False): """rotate(objects,angle,[center,axis,copy]) Rotates the objects contained in objects (that can be a list of objects or an object) of the given angle (in degrees) around the center, using axis as a rotation axis. Parameters ---------- objectlist : list angle : list center : Base.Vector axis : Base.Vector If axis is omitted, the rotation will be around the vertical Z axis. copy : bool If copy is True, the actual objects are not moved, but copies are created instead. Return ---------- The objects (or their copies) are returned. """ import Part utils.type_check([(copy, bool)], "rotate") if not isinstance(objectslist, list): objectslist = [objectslist] objectslist.extend(groups.get_movable_children(objectslist)) newobjlist = [] newgroups = {} objectslist = utils.filter_objects_for_modifiers(objectslist, copy) for obj in objectslist: newobj = None # real_center and real_axis are introduced to take into account # the possibility that object is inside an App::Part if hasattr(obj, "getGlobalPlacement"): ci = obj.getGlobalPlacement().inverse().multVec(center) real_center = obj.Placement.multVec(ci) ai = obj.getGlobalPlacement().inverse().Rotation.multVec(axis) real_axis = obj.Placement.Rotation.multVec(ai) else: real_center = center real_axis = axis if copy: newobj = make_copy.make_copy(obj) else: newobj = obj if obj.isDerivedFrom("App::Annotation"): # TODO: this is very different from how move handle annotations # maybe we can uniform the two methods if axis.normalize() == App.Vector(1, 0, 0): newobj.ViewObject.RotationAxis = "X" newobj.ViewObject.Rotation = angle elif axis.normalize() == App.Vector(0, 1, 0): newobj.ViewObject.RotationAxis = "Y" newobj.ViewObject.Rotation = angle elif axis.normalize() == App.Vector(0, -1, 0): newobj.ViewObject.RotationAxis = "Y" newobj.ViewObject.Rotation = -angle elif axis.normalize() == App.Vector(0, 0, 1): newobj.ViewObject.RotationAxis = "Z" newobj.ViewObject.Rotation = angle elif axis.normalize() == App.Vector(0, 0, -1): newobj.ViewObject.RotationAxis = "Z" newobj.ViewObject.Rotation = -angle elif utils.get_type(obj) == "Point": v = App.Vector(obj.X, obj.Y, obj.Z) rv = v.sub(real_center) rv = DraftVecUtils.rotate(rv, math.radians(angle), real_axis) v = real_center.add(rv) newobj.X = v.x newobj.Y = v.y newobj.Z = v.z elif obj.isDerivedFrom("App::DocumentObjectGroup"): pass elif hasattr(obj, "Placement"): #FreeCAD.Console.PrintMessage("placement rotation\n") shape = Part.Shape() shape.Placement = obj.Placement shape.rotate(DraftVecUtils.tup(real_center), DraftVecUtils.tup(real_axis), angle) newobj.Placement = shape.Placement elif hasattr(obj, 'Shape') and (utils.get_type(obj) not in [ "WorkingPlaneProxy", "BuildingPart" ]): #think it make more sense to try first to rotate placement and later to try with shape. no? shape = obj.Shape.copy() shape.rotate(DraftVecUtils.tup(real_center), DraftVecUtils.tup(real_axis), angle) newobj.Shape = shape if copy: gui_utils.formatObject(newobj, obj) if newobj is not None: newobjlist.append(newobj) if copy: for p in obj.InList: if p.isDerivedFrom("App::DocumentObjectGroup") and ( p in objectslist): g = newgroups.setdefault( p.Name, App.ActiveDocument.addObject(p.TypeId, p.Name)) g.addObject(newobj) break gui_utils.select(newobjlist) if len(newobjlist) == 1: return newobjlist[0] return newobjlist