Merge pull request #10838 from Roy-043/Draft-Update-the-Plane-class-(step-1)

Draft: Update the Plane class (step 1)
This commit is contained in:
Yorik van Havre
2023-10-02 11:36:38 +02:00
committed by GitHub

View File

@@ -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):