diff --git a/src/Mod/Draft/WorkingPlane.py b/src/Mod/Draft/WorkingPlane.py index 44c5a1d739..4dda9406ce 100644 --- a/src/Mod/Draft/WorkingPlane.py +++ b/src/Mod/Draft/WorkingPlane.py @@ -35,13 +35,13 @@ YZ, and XZ planes. # in FreeCAD and a couple of utility functions. import math -from sys import float_info import lazy_loader.lazy_loader as lz import FreeCAD import DraftVecUtils from FreeCAD import Vector from draftutils import utils +from draftutils.messages import _wrn from draftutils.translate import translate DraftGeomUtils = lz.LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") @@ -534,7 +534,7 @@ class PlaneBase: direction: Base.Vector, optional Defaults to `None` in which case the WP `axis` is used. Direction of projection. - force_projection: Bool, optional + force_projection: bool, optional Defaults to `True`. See DraftGeomUtils.project_point_on_plane @@ -591,617 +591,205 @@ class PlaneBase: "position"] -class Plane: - """A WorkPlane object. +class Plane(PlaneBase): + """The old Plane class. Parameters ---------- - u: Base::Vector3, optional - An axis (vector) that helps define the working plane. - It defaults to `(1, 0, 0)`, or the +X axis. + u: Base.Vector or WorkingPlane.Plane, optional + Defaults to Vector(1, 0, 0). + If a WP is provided: + A copy of the WP is created, all other parameters are then ignored. + If a vector is provided: + Unit vector for the `u` attribute (+X axis). - v: Base::Vector3, optional - An axis (vector) that helps define the working plane. - It defaults to `(0, 1, 0)`, or the +Y axis. + v: Base.Vector, optional + Defaults to Vector(0, 1, 0). + Unit vector for the `v` attribute (+Y axis). - w: Base::Vector3, optional - An axis that is supposed to be perpendicular to `u` and `v`; - it is redundant. - It defaults to `(0, 0, 1)`, or the +Z axis. + w: Base.Vector, optional + Defaults to Vector(0, 0, 1). + Unit vector for the `axis` attribute (+Z axis). - pos: Base::Vector3, optional - A point through which the plane goes through. - It defaults to the origin `(0, 0, 0)`. + pos: Base.Vector, optional + Defaults to Vector(0, 0, 0). + Vector for the `position` attribute (origin). - Attributes - ---------- - doc: App::Document - The active document. Reset view when `doc` changes. + Note that the u, v and w vectors are not checked for validity. + Other attributes + ---------------- weak: bool - It is `True` if the plane has been defined by `setup()` - or has been reset. A weak plane can be changed - (it is the "auto" mode), while a strong plane will keep - its position until weakened (it is "locked") + A weak WP is in "Auto" mode and will adapt to the current view. - u: Base::Vector3 - An axis (vector) that helps define the working plane. - - v: Base::Vector3 - An axis (vector) that helps define the working plane. - - axis: Base::Vector3 - A vector that is supposed to be perpendicular to `u` and `v`; - it is helpful although redundant. - - position: Base::Vector3 - A point, which the plane goes through, - that helps define the working plane. - - stored: bool + stored: None/list A placeholder for a stored state. """ def __init__(self, u=Vector(1, 0, 0), v=Vector(0, 1, 0), w=Vector(0, 0, 1), - pos=Vector(0, 0, 0)): + pos=Vector(0, 0, 0), + weak=True): - # keep track of active document. Reset view when doc changes. - self.doc = None - self.weak = True - self.u = u - self.v = v - self.axis = w - self.position = pos + if isinstance(u, Plane): + self.match(u) + return + super().__init__(u, v, w, pos) + self.weak = weak # a placeholder for a stored state self.stored = None - def __repr__(self): - """Show the string representation of the object.""" - text = "Workplane" - text += " x=" + str(DraftVecUtils.rounded(self.u)) - text += " y=" + str(DraftVecUtils.rounded(self.v)) - text += " z=" + str(DraftVecUtils.rounded(self.axis)) - return text - def copy(self): - """Return a new plane that is a copy of the present object.""" - p = plane(u=self.u, v=self.v, w=self.axis, pos=self.position) - p.weak = self.weak - return p + """See PlaneBase.copy.""" + wp = Plane() + self.match(source=self, target=wp) + return wp - def offsetToPoint(self, p, direction=None): - """Return the signed distance from a point to the plane. + def offsetToPoint(self, point, direction=None): + """Return the signed distance from a point to the plane. The direction + argument is ignored. - Parameters - ---------- - p : Base::Vector3 - The external point to consider. - - direction : Base::Vector3, optional - The unit vector that indicates the direction of the distance. - - It defaults to `None`, which then uses the `plane.axis` (normal) - value, meaning that the measured distance is perpendicular - to the plane. - - Returns - ------- - float - The distance from the point to the plane. - - Notes - ----- - The signed distance `d`, from `p` to the plane, is such that - :: - x = p + d*direction, - - where `x` is a point that lies on the plane. - - The `direction` is a unit vector that specifies the direction - in which the distance is measured. - It defaults to `plane.axis`, - meaning that it is the perpendicular distance. - - A picture will help explain the computation - :: - p - //| - / / | - d / / | axis - / / | - / / | - -------- plane -----x-----c-----a-------- - - The points are as follows - - * `p` is an arbitrary point outside the plane. - * `c` is a known point on the plane, - for example, `plane.position`. - * `x` is the intercept on the plane from `p` in - the desired `direction`. - * `a` is the perpendicular intercept on the plane, - i.e. along `plane.axis`. - - The distance is calculated through the dot product - of the vector `pc` (going from point `p` to point `c`, - both of which are known) with the unit vector `direction` - (which is provided or defaults to `plane.axis`). - :: - d = pc . direction - d = (c - p) . direction - - **Warning:** this implementation doesn't calculate the entire - distance `|xp|`, only the distance `|pc|` projected onto `|xp|`. - - Trigonometric relationships - --------------------------- - In 2D the distances can be calculated by trigonometric relationships - :: - |ap| = |cp| cos(apc) = |xp| cos(apx) - - Then the desired distance is `d = |xp|` - :: - |xp| = |cp| cos(apc) / cos(apx) - - The cosines can be obtained from the definition of the dot product - :: - A . B = |A||B| cos(angleAB) - - If one vector is a unit vector - :: - A . uB = |A| cos(angleAB) - cp . axis = |cp| cos(apc) - - and if both vectors are unit vectors - :: - uA . uB = cos(angleAB). - direction . axis = cos(apx) - - Then - :: - d = (cp . axis) / (direction . axis) - - **Note:** for 2D these trigonometric operations - produce the full `|xp|` distance. + The returned value is the negative of the local Z coordinate of the point. """ - if direction is None: - direction = self.axis - return direction.dot(self.position.sub(p)) + return -DraftGeomUtils.distance_to_plane(point, self.position, self.axis) - def projectPoint(self, p, direction=None, force_projection=True): - """Project a point onto the plane, by default orthogonally. - - Parameters - ---------- - p : Base::Vector3 - The point to project. - direction : Base::Vector3, optional - The unit vector that indicates the direction of projection. - - It defaults to `None`, which then uses the `plane.axis` (normal) - value, meaning that the point is projected perpendicularly - to the plane. - force_projection: Bool, optional - Forces the projection if the deviation between the direction and - the normal is less than float epsilon from the orthogonality. - The direction of projection is modified to a float epsilon - deviation between the direction and the orthogonal. - It defaults to True. - - Returns - ------- - Base::Vector3 - The projected vector, scaled to the appropriate distance. - """ - - axis = Vector(self.axis).normalize() - if direction is None: - dir = axis - else: - dir = Vector(direction).normalize() - - cos = dir.dot(axis) - delta_ax_proj = (p - self.position).dot(axis) - # check the only conflicting case: direction orthogonal to axis - if abs(cos) <= float_info.epsilon: - if force_projection: - cos = math.copysign(float_info.epsilon, delta_ax_proj) - dir = axis.cross(dir).cross(axis) - cos*axis - else: - return None - - proj = p - delta_ax_proj/cos*dir - - return proj - - def projectPointOld(self, p, direction=None): - """Project a point onto the plane. OBSOLETE. - - Parameters - ---------- - p : Base::Vector3 - The point to project. - direction : Base::Vector3, optional - The unit vector that indicates the direction of projection. - - It defaults to `None`, which then uses the `plane.axis` (normal) - value, meaning that the point is projected perpendicularly - to the plane. - - Returns - ------- - Base::Vector3 - The projected point, - or the original point if the angle between the `direction` - and the `plane.axis` is 90 degrees. - """ - if not direction: - direction = self.axis - t = Vector(direction) - # t.normalize() - a = round(t.getAngle(self.axis), DraftVecUtils.precision()) - pp = round((math.pi)/2, DraftVecUtils.precision()) - if a == pp: - return p - t.multiply(self.offsetToPoint(p, direction)) - return p.add(t) + def projectPoint(self, point, direction=None, force_projection=True): + """See PlaneBase.project_point.""" + return super().project_point(point, direction, force_projection) def alignToPointAndAxis(self, point, axis, offset=0, upvec=None): - """Align the working plane to a point and an axis (vector). - - Set `v` as the cross product of `axis` with `(1, 0, 0)` or `+X`, - and `u` as `v` rotated -90 degrees around the `axis`. - Also set `weak` to `False`. - - Parameters - ---------- - point : Base::Vector3 - The new `position` of the plane, adjusted by - the `offset`. - axis : Base::Vector3 - A vector whose unit vector will be used as the new `axis` - of the plane. - If it is very close to the `X` or `-X` axes, - it will use this axis exactly, and will adjust `u` and `v` - to `+Y` and `+Z`, or `-Y` and `+Z`, respectively. - offset : float, optional - Defaults to zero. A value which will be used to offset - the plane in the direction of its `axis`. - upvec : Base::Vector3, optional - Defaults to `None`. - If it exists, its unit vector will be used as `v`, - and will set `u` as the cross product of `v` with `axis`. - """ - self.doc = FreeCAD.ActiveDocument - self.axis = axis - self.axis.normalize() - if axis.getAngle(Vector(1, 0, 0)) < 0.00001: - self.axis = Vector(1, 0, 0) - self.u = Vector(0, 1, 0) - self.v = Vector(0, 0, 1) - elif axis.getAngle(Vector(-1, 0, 0)) < 0.00001: - self.axis = Vector(-1, 0, 0) - self.u = Vector(0, -1, 0) - self.v = Vector(0, 0, 1) - elif upvec: - self.u = upvec.cross(self.axis) - self.u.normalize() - self.v = self.axis.cross(self.u) - self.v.normalize() - else: - self.v = axis.cross(Vector(1, 0, 0)) - self.v.normalize() - self.u = DraftVecUtils.rotate(self.v, -math.pi/2, self.axis) - self.u.normalize() - offsetVector = Vector(axis) - offsetVector.multiply(offset) - self.position = point.add(offsetVector) + """See PlaneBase.align_to_point_and_axis.""" + if upvec is None: + upvec = Vector(1, 0, 0) + super().align_to_point_and_axis(point, axis, offset, upvec) self.weak = False - # Console.PrintMessage("(position = " + str(self.position) + ")\n") - # Console.PrintMessage(self.__repr__() + "\n") + return True def alignToPointAndAxis_SVG(self, point, axis, offset=0): - """Align the working plane to a point and an axis (vector). - - It aligns `u` and `v` based on the magnitude of the components - of `axis`. - Also set `weak` to `False`. - - Parameters - ---------- - point : Base::Vector3 - The new `position` of the plane, adjusted by - the `offset`. - axis : Base::Vector3 - A vector whose unit vector will be used as the new `axis` - of the plane. - The magnitudes of the `x`, `y`, `z` components of the axis - determine the orientation of `u` and `v` of the plane. - offset : float, optional - Defaults to zero. A value which will be used to offset - the plane in the direction of its `axis`. - - Cases - ----- - The `u` and `v` are always calculated the same - - * `u` is the cross product of the positive or negative of `axis` - with a `reference vector`. - :: - u = [+1|-1] axis.cross(ref_vec) - * `v` is `u` rotated 90 degrees around `axis`. - - Whether the `axis` is positive or negative, and which reference - vector is used, depends on the absolute values of the `x`, `y`, `z` - components of the `axis` unit vector. - - #. If `x > y`, and `y > z` - The reference vector is +Z - :: - u = -1 axis.cross(+Z) - #. If `y > z`, and `z >= x` - The reference vector is +X. - :: - u = -1 axis.cross(+X) - #. If `y >= x`, and `x > z` - The reference vector is +Z. - :: - u = +1 axis.cross(+Z) - #. If `x > z`, and `z >= y` - The reference vector is +Y. - :: - u = +1 axis.cross(+Y) - #. If `z >= y`, and `y > x` - The reference vector is +X. - :: - u = +1 axis.cross(+X) - #. otherwise - The reference vector is +Y. - :: - u = -1 axis.cross(+Y) - """ - self.doc = FreeCAD.ActiveDocument - self.axis = axis - self.axis.normalize() - ref_vec = Vector(0.0, 1.0, 0.0) - - if ((abs(axis.x) > abs(axis.y)) and (abs(axis.y) > abs(axis.z))): - ref_vec = Vector(0.0, 0., 1.0) - self.u = axis.negative().cross(ref_vec) - self.u.normalize() - self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis) - # projcase = "Case new" - - elif ((abs(axis.y) > abs(axis.z)) and (abs(axis.z) >= abs(axis.x))): - ref_vec = Vector(1.0, 0.0, 0.0) - self.u = axis.negative().cross(ref_vec) - self.u.normalize() - self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis) - # projcase = "Y>Z, View Y" - - elif ((abs(axis.y) >= abs(axis.x)) and (abs(axis.x) > abs(axis.z))): - ref_vec = Vector(0.0, 0., 1.0) - self.u = axis.cross(ref_vec) - self.u.normalize() - self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis) - # projcase = "ehem. XY, Case XY" - - elif ((abs(axis.x) > abs(axis.z)) and (abs(axis.z) >= abs(axis.y))): - self.u = axis.cross(ref_vec) - self.u.normalize() - self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis) - # projcase = "X>Z, View X" - - elif ((abs(axis.z) >= abs(axis.y)) and (abs(axis.y) > abs(axis.x))): - ref_vec = Vector(1.0, 0., 0.0) - self.u = axis.cross(ref_vec) - self.u.normalize() - self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis) - # projcase = "Y>X, Case YZ" - - else: - self.u = axis.negative().cross(ref_vec) - self.u.normalize() - self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis) - # projcase = "else" - - # spat_vec = self.u.cross(self.v) - # spat_res = spat_vec.dot(axis) - # Console.PrintMessage(projcase + " spat Prod = " + str(spat_res) + "\n") - - offsetVector = Vector(axis) - offsetVector.multiply(offset) - self.position = point.add(offsetVector) + """See PlaneBase.align_to_point_and_axis_svg.""" + super().align_to_point_and_axis_svg(point, axis, offset) self.weak = False - # Console.PrintMessage("(position = " + str(self.position) + ")\n") - # Console.PrintMessage(self.__repr__() + "\n") + return True def alignToCurve(self, shape, offset=0): - """Align plane to curve. NOT YET IMPLEMENTED. + """Align the WP to a curve. NOT IMPLEMENTED. Parameters ---------- - shape : Part.Shape - A curve that will serve to align the plane. - It can be an `'Edge'` or `'Wire'`. - offset : float - Defaults to zero. A value which will be used to offset - the plane in the direction of its `axis`. + shape: Part.Shape + Edge or Wire. + offset: float, optional + Defaults to zero. + Offset along the WP `axis`. Returns ------- - False - Returns `False` if the shape is null. - Currently it always returns `False`. + `False` """ - if shape.isNull(): - return False - elif shape.ShapeType == 'Edge': - # ??? TODO: process curve here. look at shape.edges[0].Curve - return False - elif shape.ShapeType == 'Wire': - # ??? TODO: determine if edges define a plane - return False - else: - return False + return False - def alignToEdges(self, edges): - """Align plane to two edges. + def alignToEdges(self, shapes, offset=0): + """Align the WP to edges with an optional offset. - Uses the two points of the first edge to define the direction - of the unit vector `u`, the other two points of the other edge - to define the other unit vector `v`, and then the cross product - of `u` with `v` to define the `axis`. + The eges must define a plane. + + The endpoints of the first edge defines the WP `position` and `u` axis. Parameters ---------- - edges : list - A list of two edges. + shapes: iterable + Two edges. + offset: float, optional + Defaults to zero. + Offset along the WP `axis`. Returns ------- - False - Return `False` if `edges` is a list of more than 2 elements. + `True`/`False` + `True` if successful. """ - # use a list of edges to find a plane position - if len(edges) > 2: + if super().align_to_edges_vertexes(shapes, offset) is False: return False - # for axes systems, we suppose the 2 first edges are parallel - # ??? TODO: exclude other cases first - v1 = edges[0].Vertexes[-1].Point.sub(edges[0].Vertexes[0].Point) - v2 = edges[1].Vertexes[0].Point.sub(edges[0].Vertexes[0].Point) - v3 = v1.cross(v2) - v1.normalize() - v2.normalize() - v3.normalize() - # print(v1,v2,v3) - self.u = v1 - self.v = v2 - self.axis = v3 + self.weak = False + return True def alignToFace(self, shape, offset=0, parent=None): - """Align the plane to a face. + """Align the WP to a face with an optional offset. - It uses the center of mass of the face as `position`, - and its normal in the center of the face as `axis`, - then calls `alignToPointAndAxis(position, axis, offset)`. + The face must be planar. - If the face is a quadrilateral, then it adjusts the position - of the plane according to its reported X direction and Y direction. + The center of gravity of the face defines the WP `position` and the + normal of the face the WP `axis`. The WP `u` and `v` vectors are + determined by the DraftGeomUtils.uv_vectors_from_face function. + See there. - Also set `weak` to `False`. - - Parameter - -------- - shape : Part.Face - A shape of type `'Face'`. - - offset : float - Defaults to zero. A value which will be used to offset - the plane in the direction of its `axis`. - - parent : object - Defaults to None. The ParentGeoFeatureGroup of the object - the face belongs to. + Parameters + ---------- + shape: Part.Face + Face. + offset: float, optional + Defaults to zero. + Offset along the WP `axis`. + parent: object + Defaults to `None`. + The ParentGeoFeatureGroup of the object the face belongs to. Returns ------- - bool - `True` if the operation was successful, and `False` if the shape - is not a `'Face'`. - - See Also - -------- - alignToPointAndAxis + `True`/`False` + `True` if successful. """ - # Set face to the unique selected face, if found - if shape.ShapeType == 'Face': + if shape.ShapeType == "Face" and shape.Surface.isPlanar(): + place = DraftGeomUtils.placement_from_face(shape) if parent: - place = parent.getGlobalPlacement() - else: - place = FreeCAD.Placement() - rot = place.Rotation - - cen = place.multVec(shape.CenterOfMass) - nor = rot.multVec(shape.normalAt(0, 0)) - self.alignToPointAndAxis(cen, nor, offset) - - pmr = shape.ParameterRange # (uMin, uMax, vMin, vMax) - u = shape.valueAt(pmr[1], 0).sub(shape.valueAt(pmr[0], 0)) - v = shape.valueAt(0, pmr[3]).sub(shape.valueAt(0, pmr[2])) - self.u = rot.multVec(u).normalize() - self.v = rot.multVec(v).normalize() - - if shape.Orientation == "Reversed": - self.u, self.v = self.v, self.u - - # If self.u or self.v matches a wrong global axis, rotate them: - if DraftVecUtils.equals(self.v, Vector(0, 0, -1)): - self.u, self.v = self.u.negative(), self.v.negative() - elif DraftVecUtils.equals(self.u, Vector(0, 0, 1)): - self.u, self.v = self.v.negative(), self.u - elif DraftVecUtils.equals(self.u, Vector(0, 0, -1)): - self.u, self.v = self.v, self.u.negative() - + place = parent.getGlobalPlacement() * place + super().align_to_placement(place, offset) self.weak = False return True else: return False def alignTo3Points(self, p1, p2, p3, offset=0): - """Align the plane to three points. + """Align the plane to a temporary face created from three points. - It makes a closed quadrilateral face with the three points, - and then calls `alignToFace(shape, offset)`. - - Parameter - --------- - p1 : Base::Vector3 - The first point. - p2 : Base::Vector3 - The second point. - p3 : Base::Vector3 - The third point. - - offset : float - Defaults to zero. A value which will be used to offset - the plane in the direction of its `axis`. + Parameters + ---------- + p1: Base.Vector + First point. + p2: Base.Vector + Second point. + p3: Base.Vector + Third point. + offset: float, optional + Defaults to zero. + Offset along the WP `axis` Returns ------- - bool - `True` if the operation was successful, and `False` otherwise. + `True`/`False` + `True` if successful. """ - import Part w = Part.makePolygon([p1, p2, p3, p1]) f = Part.Face(w) return self.alignToFace(f, offset) def alignToSelection(self, offset=0): - """Align the plane to a selection if it defines a plane. + """Align the plane to a selection with an optional offset. - If the selection uniquely defines a plane it will be used. + The selection must define a plane. Parameter --------- - offset : float - Defaults to zero. A value which will be used to offset - the plane in the direction of its `axis`. + offset: float, optional + Defaults to zero. + Offset along the WP `axis` Returns ------- - bool - `True` if the operation was successful, and `False` otherwise. - It returns `False` if the selection has no elements, - or if the object is not derived from `'Part::Feature'` - or if the object doesn't have a `Shape`. - - See Also - -------- - alignToFace, alignToCurve + `True`/`False` + `True` if successful. """ - sel_ex = FreeCADGui.Selection.getSelectionEx(FreeCAD.ActiveDocument.Name) + sel_ex = FreeCADGui.Selection.getSelectionEx() if not sel_ex: return False @@ -1215,13 +803,13 @@ class Plane: if isinstance(geom, Part.Shape): geom_is_shape = True if not geom_is_shape: - FreeCAD.Console.PrintError(translate( + _wrn(translate( "draft", "Object without Part.Shape geometry:'{}'".format( obj.ObjectName)) + "\n") return False if geom.isNull(): - FreeCAD.Console.PrintError(translate( + _wrn(translate( "draft", "Object with null Part.Shape geometry:'{}'".format( obj.ObjectName)) + "\n") @@ -1236,7 +824,7 @@ class Plane: normal = None for n in range(len(shapes)): if not DraftGeomUtils.is_planar(shapes[n]): - FreeCAD.Console.PrintError(translate( + _wrn(translate( "draft", "'{}' object is not planar".format(names[n])) + "\n") return False if not normal: @@ -1247,7 +835,7 @@ class Plane: if normal: for n in range(len(shapes)): if not DraftGeomUtils.are_coplanar(shapes[shape_ref], shapes[n]): - FreeCAD.Console.PrintError(translate( + _wrn(translate( "draft", "{} and {} aren't coplanar".format( names[shape_ref],names[n])) + "\n") return False @@ -1257,7 +845,7 @@ class Plane: if len(points) >= 3: poly = Part.makePolygon(points) if not DraftGeomUtils.is_planar(poly): - FreeCAD.Console.PrintError(translate( + _wrn(translate( "draft", "All Shapes must be coplanar") + "\n") return False normal = DraftGeomUtils.get_normal(poly) @@ -1265,13 +853,13 @@ class Plane: normal = None if not normal: - FreeCAD.Console.PrintError(translate( + _wrn(translate( "draft", "Selected Shapes must define a plane") + "\n") return False # set center of mass - ctr_mass = FreeCAD.Vector(0,0,0) - ctr_pts = FreeCAD.Vector(0,0,0) + ctr_mass = Vector(0,0,0) + ctr_pts = Vector(0,0,0) mass = 0 for shape in shapes: if hasattr(shape, "CenterOfMass"): @@ -1285,8 +873,8 @@ class Plane: else: ctr_mass = ctr_pts/len(shapes) - self.alignToPointAndAxis(ctr_mass, normal, offset) - + super().align_to_point_and_axis(ctr_mass, normal, offset) + self.weak = False return True def setup(self, direction=None, point=None, upvec=None, force=False): @@ -1320,7 +908,7 @@ class Plane: To do ----- When the interface is not loaded it should fail and print - a message, `FreeCAD.Console.PrintError()`. + a message, `_wrn()`. """ if self.weak or force: if direction and point: @@ -1352,9 +940,8 @@ class Plane: def reset(self): """Reset the plane. - Set the `doc` attribute to `None`, and `weak` to `True`. + Set `weak` to `True`. """ - self.doc = None self.weak = True def setTop(self):