from fontParts.base.errors import FontPartsError
from fontParts.base.base import (
BaseObject,
TransformationMixin,
InterpolationMixin,
SelectionMixin,
IdentifierMixin,
dynamicProperty,
reference
)
from fontParts.base import normalizers
from fontParts.base.compatibility import ContourCompatibilityReporter
from fontParts.base.bPoint import absoluteBCPIn, absoluteBCPOut
from fontParts.base.deprecated import DeprecatedContour, RemovedContour
[docs]class BaseContour(
BaseObject,
TransformationMixin,
InterpolationMixin,
SelectionMixin,
IdentifierMixin,
DeprecatedContour,
RemovedContour
):
segmentClass = None
bPointClass = None
def _reprContents(self):
contents = []
if self.identifier is not None:
contents.append("identifier='%r'" % self.identifier)
if self.glyph is not None:
contents.append("in glyph")
contents += self.glyph._reprContents()
return contents
def copyData(self, source):
super(BaseContour, self).copyData(source)
for sourcePoint in source.points:
self.appendPoint((0, 0))
selfPoint = self.points[-1]
selfPoint.copyData(sourcePoint)
# -------
# Parents
# -------
# Glyph
_glyph = None
glyph = dynamicProperty("glyph",
"The contour's parent :class:`BaseGlyph`.")
def _get_glyph(self):
if self._glyph is None:
return None
return self._glyph()
def _set_glyph(self, glyph):
if self._glyph is not None:
raise AssertionError("glyph for contour already set")
if glyph is not None:
glyph = reference(glyph)
self._glyph = glyph
# Font
font = dynamicProperty("font", "The contour's parent font.")
def _get_font(self):
if self._glyph is None:
return None
return self.glyph.font
# Layer
layer = dynamicProperty("layer", "The contour's parent layer.")
def _get_layer(self):
if self._glyph is None:
return None
return self.glyph.layer
# --------------
# Identification
# --------------
# index
index = dynamicProperty(
"base_index",
"""
The index of the contour within the parent glyph's contours.
>>> contour.index
1
>>> contour.index = 0
The value will always be a :ref:`type-int`.
"""
)
def _get_base_index(self):
glyph = self.glyph
if glyph is None:
return None
value = self._get_index()
value = normalizers.normalizeIndex(value)
return value
def _set_base_index(self, value):
glyph = self.glyph
if glyph is None:
raise FontPartsError("The contour does not belong to a glyph.")
value = normalizers.normalizeIndex(value)
contourCount = len(glyph.contours)
if value < 0:
value = -(value % contourCount)
if value >= contourCount:
value = contourCount
self._set_index(value)
[docs] def _get_index(self):
"""
Subclasses may override this method.
"""
glyph = self.glyph
return glyph.contours.index(self)
[docs] def _set_index(self, value):
"""
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# identifier
def getIdentifierForPoint(self, point):
"""
Create a unique identifier for and assign it to ``point``.
If the point already has an identifier, the existing
identifier will be returned.
>>> contour.getIdentifierForPoint(point)
'ILHGJlygfds'
``point`` must be a :class:`BasePoint`. The returned value
will be a :ref:`type-identifier`.
"""
point = normalizers.normalizePoint(point)
return self._getIdentifierforPoint(point)
def _getIdentifierforPoint(self, point):
"""
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# ----
# Pens
# ----
[docs] def draw(self, pen):
"""
Draw the contour's outline data to the given :ref:`type-pen`.
>>> contour.draw(pen)
"""
self._draw(pen)
[docs] def _draw(self, pen, **kwargs):
"""
Subclasses may override this method.
"""
from fontTools.ufoLib.pointPen import PointToSegmentPen
adapter = PointToSegmentPen(pen)
self.drawPoints(adapter)
[docs] def drawPoints(self, pen):
"""
Draw the contour's outline data to the given :ref:`type-point-pen`.
>>> contour.drawPoints(pointPen)
"""
self._drawPoints(pen)
[docs] def _drawPoints(self, pen, **kwargs):
"""
Subclasses may override this method.
"""
# The try: ... except TypeError: ...
# handles backwards compatibility with
# point pens that have not been upgraded
# to point pen protocol 2.
try:
pen.beginPath(self.identifier)
except TypeError:
pen.beginPath()
for point in self.points:
typ = point.type
if typ == "offcurve":
typ = None
try:
pen.addPoint(pt=(point.x, point.y), segmentType=typ,
smooth=point.smooth, name=point.name,
identifier=point.identifier)
except TypeError:
pen.addPoint(pt=(point.x, point.y), segmentType=typ,
smooth=point.smooth, name=point.name)
pen.endPath()
# ------------------
# Data normalization
# ------------------
[docs] def autoStartSegment(self):
"""
Automatically calculate and set the first segment
in this contour.
The behavior of this may vary accross environments.
"""
self._autoStartSegment()
[docs] def _autoStartSegment(self, **kwargs):
"""
Subclasses may override this method.
XXX port this from robofab
"""
self.raiseNotImplementedError()
[docs] def round(self):
"""
Round coordinates in all points to integers.
"""
self._round()
[docs] def _round(self, **kwargs):
"""
Subclasses may override this method.
"""
for point in self.points:
point.round()
# --------------
# Transformation
# --------------
# -------------
# Interpolation
# -------------
compatibilityReporterClass = ContourCompatibilityReporter
def isCompatible(self, other):
"""
Evaluate interpolation compatibility with **other**. ::
>>> compatible, report = self.isCompatible(otherContour)
>>> compatible
False
>>> compatible
[Fatal] Contour: [0] + [0]
[Fatal] Contour: [0] contains 4 segments | [0] contains 3 segments
[Fatal] Contour: [0] is closed | [0] is open
This will return a ``bool`` indicating if the contour is
compatible for interpolation with **other** and a
:ref:`type-string` of compatibility notes.
"""
return super(BaseContour, self).isCompatible(other, BaseContour)
def _isCompatible(self, other, reporter):
"""
This is the environment implementation of
:meth:`BaseContour.isCompatible`.
Subclasses may override this method.
"""
contour1 = self
contour2 = other
# open/closed
if contour1.open != contour2.open:
reporter.openDifference = True
# direction
if contour1.clockwise != contour2.clockwise:
reporter.directionDifference = True
# segment count
if len(contour1) != len(contour2.segments):
reporter.segmentCountDifference = True
reporter.fatal = True
# segment pairs
for i in range(min(len(contour1), len(contour2))):
segment1 = contour1[i]
segment2 = contour2[i]
segmentCompatibility = segment1.isCompatible(segment2)[1]
if segmentCompatibility.fatal or segmentCompatibility.warning:
if segmentCompatibility.fatal:
reporter.fatal = True
if segmentCompatibility.warning:
reporter.warning = True
reporter.segments.append(segmentCompatibility)
# ----
# Open
# ----
open = dynamicProperty("base_open",
"Boolean indicating if the contour is open.")
def _get_base_open(self):
value = self._get_open()
value = normalizers.normalizeBoolean(value)
return value
def _get_open(self):
"""
Subclasses must override this method.
"""
self.raiseNotImplementedError()
# ---------
# Direction
# ---------
clockwise = dynamicProperty("base_clockwise",
("Boolean indicating if the contour's "
"winding direction is clockwise."))
def _get_base_clockwise(self):
value = self._get_clockwise()
value = normalizers.normalizeBoolean(value)
return value
def _set_base_clockwise(self, value):
value = normalizers.normalizeBoolean(value)
self._set_clockwise(value)
[docs] def _get_clockwise(self):
"""
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs] def _set_clockwise(self, value):
"""
Subclasses may override this method.
"""
if self.clockwise != value:
self.reverse()
[docs] def reverse(self):
"""
Reverse the direction of the contour.
"""
self._reverseContour()
[docs] def _reverse(self, **kwargs):
"""
Subclasses may override this method.
"""
self.raiseNotImplementedError()
# ------------------------
# Point and Contour Inside
# ------------------------
[docs] def pointInside(self, point):
"""
Determine if ``point`` is in the black or white of the contour.
>>> contour.pointInside((40, 65))
True
``point`` must be a :ref:`type-coordinate`.
"""
point = normalizers.normalizeCoordinateTuple(point)
return self._pointInside(point)
[docs] def _pointInside(self, point):
"""
Subclasses may override this method.
"""
from fontTools.pens.pointInsidePen import PointInsidePen
pen = PointInsidePen(glyphSet=None, testPoint=point, evenOdd=False)
self.draw(pen)
return pen.getResult()
def contourInside(self, otherContour):
"""
Determine if ``otherContour`` is in the black or white of this contour.
>>> contour.contourInside(otherContour)
True
``contour`` must be a :class:`BaseContour`.
"""
otherContour = normalizers.normalizeContour(otherContour)
return self._contourInside(otherContour)
def _contourInside(self, otherContour):
"""
Subclasses may override this method.
"""
self.raiseNotImplementedError()
# ---------------
# Bounds and Area
# ---------------
bounds = dynamicProperty("bounds",
("The bounds of the contour: "
"(xMin, yMin, xMax, yMax) or None."))
def _get_base_bounds(self):
value = self._get_bounds()
if value is not None:
value = normalizers.normalizeBoundingBox(value)
return value
[docs] def _get_bounds(self):
"""
Subclasses may override this method.
"""
from fontTools.pens.boundsPen import BoundsPen
pen = BoundsPen(self.layer)
self.draw(pen)
return pen.bounds
area = dynamicProperty("area",
("The area of the contour: "
"A positive number or None."))
def _get_base_area(self):
value = self._get_area()
if value is not None:
value = normalizers.normalizeArea(value)
return value
def _get_area(self):
"""
Subclasses may override this method.
"""
from fontTools.pens.areaPen import AreaPen
pen = AreaPen(self.layer)
self.draw(pen)
return abs(pen.value)
# --------
# Segments
# --------
# The base class implements the full segment interaction API.
# Subclasses do not need to override anything within the contour
# other than registering segmentClass. Subclasses may choose to
# implement this API independently if desired.
def _setContourInSegment(self, segment):
if segment.contour is None:
segment.contour = self
segments = dynamicProperty("segments")
[docs] def _get_segments(self):
"""
Subclasses may override this method.
"""
points = list(self.points)
if not points:
return []
segments = [[]]
lastWasOffCurve = False
firstIsMove = points[0].type == "move"
for point in points:
segments[-1].append(point)
if point.type != "offcurve":
segments.append([])
lastWasOffCurve = point.type == "offcurve"
if len(segments[-1]) == 0:
del segments[-1]
if lastWasOffCurve and firstIsMove:
# ignore trailing off curves
del segments[-1]
if lastWasOffCurve and not firstIsMove and len(segments) > 1:
segment = segments.pop(-1)
segment.extend(segments[0])
del segments[0]
segments.append(segment)
if not lastWasOffCurve and not firstIsMove:
segment = segments.pop(0)
segments.append(segment)
# wrap into segments
wrapped = []
for points in segments:
s = self.segmentClass()
s._setPoints(points)
self._setContourInSegment(s)
wrapped.append(s)
return wrapped
[docs] def __getitem__(self, index):
return self.segments[index]
[docs] def __iter__(self):
return self._iterSegments()
def _iterSegments(self):
segments = self.segments
count = len(segments)
index = 0
while count:
yield segments[index]
count -= 1
index += 1
[docs] def __len__(self):
return self._len__segments()
[docs] def _len__segments(self, **kwargs):
"""
Subclasses may override this method.
"""
return len(self.segments)
[docs] def appendSegment(self, type=None, points=None, smooth=False, segment=None):
"""
Append a segment to the contour.
"""
if segment is not None:
if type is not None:
type = segment.type
if points is None:
points = [(point.x, point.y) for point in segment.points]
smooth = segment.smooth
type = normalizers.normalizeSegmentType(type)
pts = []
for pt in points:
pt = normalizers.normalizeCoordinateTuple(pt)
pts.append(pt)
points = pts
smooth = normalizers.normalizeBoolean(smooth)
self._appendSegment(type=type, points=points, smooth=smooth)
[docs] def _appendSegment(self, type=None, points=None, smooth=False, **kwargs):
"""
Subclasses may override this method.
"""
self._insertSegment(len(self), type=type, points=points,
smooth=smooth, **kwargs)
[docs] def insertSegment(self, index, type=None, points=None, smooth=False, segment=None):
"""
Insert a segment into the contour.
"""
if segment is not None:
if type is not None:
type = segment.type
if points is None:
points = [(point.x, point.y) for point in segment.points]
smooth = segment.smooth
index = normalizers.normalizeIndex(index)
type = normalizers.normalizeSegmentType(type)
pts = []
for pt in points:
pt = normalizers.normalizeCoordinateTuple(pt)
pts.append(pt)
points = pts
smooth = normalizers.normalizeBoolean(smooth)
self._insertSegment(index=index, type=type,
points=points, smooth=smooth)
[docs] def _insertSegment(self, index=None, type=None, points=None,
smooth=False, **kwargs):
"""
Subclasses may override this method.
"""
onCurve = points[-1]
offCurve = points[:-1]
segments = self.segments
addPointCount = 1
if self.open:
index += 1
addPointCount = 0
ptCount = sum([len(segments[s].points) for s in range(index)]) + addPointCount
self.insertPoint(ptCount, onCurve, type=type, smooth=smooth)
for offCurvePoint in reversed(offCurve):
self.insertPoint(ptCount, offCurvePoint, type="offcurve")
[docs] def removeSegment(self, segment, preserveCurve=False):
"""
Remove segment from the contour.
If ``preserveCurve`` is set to ``True`` an attempt
will be made to preserve the shape of the curve
if the environment supports that functionality.
"""
if not isinstance(segment, int):
segment = self.segments.index(segment)
segment = normalizers.normalizeIndex(segment)
if segment >= self._len__segments():
raise ValueError("No segment located at index %d." % segment)
preserveCurve = normalizers.normalizeBoolean(preserveCurve)
self._removeSegment(segment, preserveCurve)
[docs] def _removeSegment(self, segment, preserveCurve, **kwargs):
"""
segment will be a valid segment index.
preserveCurve will be a boolean.
Subclasses may override this method.
"""
segment = self.segments[segment]
for point in segment.points:
self.removePoint(point, preserveCurve)
[docs] def setStartSegment(self, segment):
"""
Set the first segment on the contour.
segment can be a segment object or an index.
"""
segments = self.segments
if not isinstance(segment, int):
segmentIndex = segments.index(segment)
else:
segmentIndex = segment
if len(self.segments) < 2:
return
if segmentIndex == 0:
return
if segmentIndex >= len(segments):
raise ValueError(("The contour does not contain a segment "
"at index %d" % segmentIndex))
self._setStartSegment(segmentIndex)
[docs] def _setStartSegment(self, segmentIndex, **kwargs):
"""
Subclasses may override this method.
"""
segments = self.segments
oldStart = segments[-1]
oldLast = segments[0]
# If the contour ends with a curve on top of a move,
# delete the move.
if oldLast.type == "curve" or oldLast.type == "qcurve":
startOn = oldStart.onCurve
lastOn = oldLast.onCurve
if startOn.x == lastOn.x and startOn.y == lastOn.y:
self.removeSegment(0)
# Shift new the start index.
segmentIndex = segmentIndex - 1
segments = self.segments
# If the first point is a move, convert it to a line.
if segments[0].type == "move":
segments[0].type = "line"
# Reorder the points internally.
segments = segments[segmentIndex - 1:] + segments[:segmentIndex - 1]
points = []
for segment in segments:
for point in segment:
points.append(((point.x, point.y), point.type,
point.smooth, point.name, point.identifier))
# Clear the points.
for point in self.points:
self.removePoint(point)
# Add the points.
for point in points:
position, type, smooth, name, identifier = point
self.appendPoint(
position,
type=type,
smooth=smooth,
name=name,
identifier=identifier
)
# -------
# bPoints
# -------
bPoints = dynamicProperty("bPoints")
def _get_bPoints(self):
bPoints = []
for point in self.points:
if point.type not in ("move", "line", "curve"):
continue
bPoint = self.bPointClass()
bPoint.contour = self
bPoint._setPoint(point)
bPoints.append(bPoint)
return tuple(bPoints)
[docs] def appendBPoint(self, type=None, anchor=None, bcpIn=None, bcpOut=None, bPoint=None):
"""
Append a bPoint to the contour.
"""
if bPoint is not None:
if type is None:
type = bPoint.type
if anchor is None:
anchor = bPoint.anchor
if bcpIn is None:
bcpIn = bPoint.bcpIn
if bcpOut is None:
bcpOut = bPoint.bcpOut
type = normalizers.normalizeBPointType(type)
anchor = normalizers.normalizeCoordinateTuple(anchor)
if bcpIn is None:
bcpIn = (0, 0)
bcpIn = normalizers.normalizeCoordinateTuple(bcpIn)
if bcpOut is None:
bcpOut = (0, 0)
bcpOut = normalizers.normalizeCoordinateTuple(bcpOut)
self._appendBPoint(type, anchor, bcpIn=bcpIn, bcpOut=bcpOut)
[docs] def _appendBPoint(self, type, anchor, bcpIn=None, bcpOut=None, **kwargs):
"""
Subclasses may override this method.
"""
self.insertBPoint(
len(self.bPoints),
type,
anchor,
bcpIn=bcpIn,
bcpOut=bcpOut
)
[docs] def insertBPoint(self, index, type=None, anchor=None, bcpIn=None, bcpOut=None, bPoint=None):
"""
Insert a bPoint at index in the contour.
"""
if bPoint is not None:
if type is None:
type = bPoint.type
if anchor is None:
anchor = bPoint.anchor
if bcpIn is None:
bcpIn = bPoint.bcpIn
if bcpOut is None:
bcpOut = bPoint.bcpOut
index = normalizers.normalizeIndex(index)
type = normalizers.normalizeBPointType(type)
anchor = normalizers.normalizeCoordinateTuple(anchor)
if bcpIn is None:
bcpIn = (0, 0)
bcpIn = normalizers.normalizeCoordinateTuple(bcpIn)
if bcpOut is None:
bcpOut = (0, 0)
bcpOut = normalizers.normalizeCoordinateTuple(bcpOut)
self._insertBPoint(index=index, type=type, anchor=anchor,
bcpIn=bcpIn, bcpOut=bcpOut)
[docs] def _insertBPoint(self, index, type, anchor, bcpIn, bcpOut, **kwargs):
"""
Subclasses may override this method.
"""
# insert a simple line segment at the given anchor
# look it up as a bPoint and change the bcpIn and bcpOut there
# this avoids code duplication
self._insertSegment(index=index, type="line",
points=[anchor], smooth=False)
bPoints = self.bPoints
index += 1
if index >= len(bPoints):
# its an append instead of an insert
# so take the last bPoint
index = -1
bPoint = bPoints[index]
bPoint.bcpIn = bcpIn
bPoint.bcpOut = bcpOut
bPoint.type = type
def removeBPoint(self, bPoint):
"""
Remove the bpoint from the contour.
bpoint can be a point object or an index.
"""
if not isinstance(bPoint, int):
bPoint = bPoint.index
bPoint = normalizers.normalizeIndex(bPoint)
if bPoint >= self._len__points():
raise ValueError("No bPoint located at index %d." % bPoint)
self._removeBPoint(bPoint)
def _removeBPoint(self, index, **kwargs):
"""
index will be a valid index.
Subclasses may override this method.
"""
bPoint = self.bPoints[index]
nextSegment = bPoint._nextSegment
offCurves = nextSegment.offCurve
if offCurves:
offCurve = offCurves[0]
self.removePoint(offCurve)
segment = bPoint._segment
offCurves = segment.offCurve
if offCurves:
offCurve = offCurves[-1]
self.removePoint(offCurve)
self.removePoint(bPoint._point)
# ------
# Points
# ------
def _setContourInPoint(self, point):
if point.contour is None:
point.contour = self
points = dynamicProperty("points")
[docs] def _get_points(self):
"""
Subclasses may override this method.
"""
return tuple([self._getitem__points(i)
for i in range(self._len__points())])
def _len__points(self):
return self._lenPoints()
[docs] def _lenPoints(self, **kwargs):
"""
This must return an integer indicating
the number of points in the contour.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
def _getitem__points(self, index):
index = normalizers.normalizeIndex(index)
if index >= self._len__points():
raise ValueError("No point located at index %d." % index)
point = self._getPoint(index)
self._setContourInPoint(point)
return point
[docs] def _getPoint(self, index, **kwargs):
"""
This must return a wrapped point.
index will be a valid index.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
def _getPointIndex(self, point):
for i, other in enumerate(self.points):
if point == other:
return i
raise FontPartsError("The point could not be found.")
[docs] def appendPoint(self, position=None, type="line", smooth=False, name=None, identifier=None, point=None):
"""
Append a point to the contour.
"""
if point is not None:
if position is None:
position = point.position
type = point.type
smooth = point.smooth
if name is None:
name = point.name
if identifier is not None:
identifier = point.identifier
self.insertPoint(
len(self.points),
position=position,
type=type,
smooth=smooth,
name=name,
identifier=identifier
)
[docs] def insertPoint(self, index, position=None, type="line", smooth=False, name=None, identifier=None, point=None):
"""
Insert a point into the contour.
"""
if point is not None:
if position is None:
position = point.position
type = point.type
smooth = point.smooth
if name is None:
name = point.name
if identifier is not None:
identifier = point.identifier
index = normalizers.normalizeIndex(index)
position = normalizers.normalizeCoordinateTuple(position)
type = normalizers.normalizePointType(type)
smooth = normalizers.normalizeBoolean(smooth)
if name is not None:
name = normalizers.normalizePointName(name)
if identifier is not None:
identifier = normalizers.normalizeIdentifier(identifier)
self._insertPoint(
index,
position=position,
type=type,
smooth=smooth,
name=name,
identifier=identifier
)
[docs] def _insertPoint(self, index, position, type="line",
smooth=False, name=None, identifier=None, **kwargs):
"""
position will be a valid position (x, y).
type will be a valid type.
smooth will be a valid boolean.
name will be a valid name or None.
identifier will be a valid identifier or None.
The identifier will not have been tested for uniqueness.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
[docs] def removePoint(self, point, preserveCurve=False):
"""
Remove the point from the contour.
point can be a point object or an index.
If ``preserveCurve`` is set to ``True`` an attempt
will be made to preserve the shape of the curve
if the environment supports that functionality.
"""
if not isinstance(point, int):
point = self.points.index(point)
point = normalizers.normalizeIndex(point)
if point >= self._len__points():
raise ValueError("No point located at index %d." % point)
preserveCurve = normalizers.normalizeBoolean(preserveCurve)
self._removePoint(point, preserveCurve)
[docs] def _removePoint(self, index, preserveCurve, **kwargs):
"""
index will be a valid index. preserveCurve will be a boolean.
Subclasses must override this method.
"""
self.raiseNotImplementedError()
def setStartPoint(self, point):
"""
Set the first point on the contour.
point can be a segment object or an index.
"""
if self.open:
raise FontPartsError("An open contour can not change the starting point.")
points = self.points
if not isinstance(point, int):
pointIndex = points.index(point)
else:
pointIndex = point
if pointIndex == 0:
return
if pointIndex >= len(points):
raise ValueError(("The contour does not contain a point "
"at index %d" % pointIndex))
self._setStartPoint(pointIndex)
def _setStartPoint(self, pointIndex, **kwargs):
"""
Subclasses may override this method.
"""
points = self.points
points = points[pointIndex:] + points[:pointIndex]
# Clear the points.
for point in self.points:
self.removePoint(point)
# Add the points.
for point in points:
self.appendPoint(
(point.x, point.y),
type=point.type,
smooth=point.smooth,
name=point.name,
identifier=point.identifier
)
# ---------
# Selection
# ---------
# segments
selectedSegments = dynamicProperty(
"base_selectedSegments",
"""
A list of segments selected in the contour.
Getting selected segment objects:
>>> for segment in contour.selectedSegments:
... segment.move((10, 20))
Setting selected segment objects:
>>> contour.selectedSegments = someSegments
Setting also supports segment indexes:
>>> contour.selectedSegments = [0, 2]
"""
)
def _get_base_selectedSegments(self):
selected = tuple([normalizers.normalizeSegment(segment)
for segment in self._get_selectedSegments()])
return selected
def _get_selectedSegments(self):
"""
Subclasses may override this method.
"""
return self._getSelectedSubObjects(self.segments)
def _set_base_selectedSegments(self, value):
normalized = []
for i in value:
if isinstance(i, int):
i = normalizers.normalizeSegmentIndex(i)
else:
i = normalizers.normalizeSegment(i)
normalized.append(i)
self._set_selectedSegments(normalized)
def _set_selectedSegments(self, value):
"""
Subclasses may override this method.
"""
return self._setSelectedSubObjects(self.segments, value)
# points
selectedPoints = dynamicProperty(
"base_selectedPoints",
"""
A list of points selected in the contour.
Getting selected point objects:
>>> for point in contour.selectedPoints:
... point.move((10, 20))
Setting selected point objects:
>>> contour.selectedPoints = somePoints
Setting also supports point indexes:
>>> contour.selectedPoints = [0, 2]
"""
)
def _get_base_selectedPoints(self):
selected = tuple([normalizers.normalizePoint(point)
for point in self._get_selectedPoints()])
return selected
def _get_selectedPoints(self):
"""
Subclasses may override this method.
"""
return self._getSelectedSubObjects(self.points)
def _set_base_selectedPoints(self, value):
normalized = []
for i in value:
if isinstance(i, int):
i = normalizers.normalizePointIndex(i)
else:
i = normalizers.normalizePoint(i)
normalized.append(i)
self._set_selectedPoints(normalized)
def _set_selectedPoints(self, value):
"""
Subclasses may override this method.
"""
return self._setSelectedSubObjects(self.points, value)
# bPoints
selectedBPoints = dynamicProperty(
"base_selectedBPoints",
"""
A list of bPoints selected in the contour.
Getting selected bPoint objects:
>>> for bPoint in contour.selectedBPoints:
... bPoint.move((10, 20))
Setting selected bPoint objects:
>>> contour.selectedBPoints = someBPoints
Setting also supports bPoint indexes:
>>> contour.selectedBPoints = [0, 2]
"""
)
def _get_base_selectedBPoints(self):
selected = tuple([normalizers.normalizeBPoint(bPoint)
for bPoint in self._get_selectedBPoints()])
return selected
def _get_selectedBPoints(self):
"""
Subclasses may override this method.
"""
return self._getSelectedSubObjects(self.bPoints)
def _set_base_selectedBPoints(self, value):
normalized = []
for i in value:
if isinstance(i, int):
i = normalizers.normalizeBPointIndex(i)
else:
i = normalizers.normalizeBPoint(i)
normalized.append(i)
self._set_selectedBPoints(normalized)
def _set_selectedBPoints(self, value):
"""
Subclasses may override this method.
"""
return self._setSelectedSubObjects(self.bPoints, value)