Draft Bezier Curve: implemented Continuity Property

Draft: allow continuity on closed Bezier curves

Draft: BezCurve bugfixes
    - smoothBezCurve
    - edit mode
    - avoid splitting points into segments as long as degree is zero
    - increase the continuity when closing curve
    - closed berzier curves
    - Catch modifications of continuity of first and last knot on open curves
This commit is contained in:
Sebastian Hoogen 2014-02-02 18:22:27 +01:00 committed by Yorik van Havre
parent 6f8c1c61b7
commit 2dfabfdcb5
6 changed files with 491 additions and 55039 deletions

View File

@ -853,6 +853,7 @@ def makeBezCurve(pointslist,closed=False,placement=None,support=None,Degree=None
Part.BezierCurve().MaxDegree)
obj.Closed = closed
obj.Support = support
obj.Proxy.resetcontinuity(obj)
if placement: obj.Placement = placement
if gui:
_ViewProviderWire(obj.ViewObject)
@ -3971,30 +3972,60 @@ class _BezCurve(_DraftObject):
"The points of the Bezier curve")
obj.addProperty("App::PropertyInteger","Degree","Draft",
"The degree of the Bezier function")
obj.addProperty("App::PropertyIntegerList","Continuity","Draft",
"Continuity")
obj.addProperty("App::PropertyBool","Closed","Draft",
"If the Bezier curve should be closed or not")
obj.Closed = False
obj.Degree = 3
obj.Continuity = []
#obj.setEditorMode("Degree",2)#hide
obj.setEditorMode("Continuity",1)#ro
def execute(self, fp):
self.createGeometry(fp)
def _segpoleslst(self,fp):
"""split the points into segments"""
if not fp.Closed and len(fp.Points) >= 2: #allow lower degree segement
poles=fp.Points[1:]
elif fp.Closed and len(fp.Points) >= fp.Degree: #drawable
#poles=fp.Points[1:(fp.Degree*(len(fp.Points)//fp.Degree))]+fp.Points[0:1]
poles=fp.Points[1:]+fp.Points[0:1]
else:
poles=[]
return [poles[x:x+fp.Degree] for x in \
xrange(0, len(poles), (fp.Degree or 1))]
def resetcontinuity(self,fp):
fp.Continuity = [0]*(len(self._segpoleslst(fp))-1+1*fp.Closed)
#nump= len(fp.Points)-1+fp.Closed*1
#numsegments = (nump // fp.Degree) + 1 * (nump % fp.Degree > 0) -1
#fp.Continuity = [0]*numsegments
def onChanged(self, fp, prop):
if prop in ["Points","Degree", "Closed"]:
if prop == 'Closed': # if remove the last entry when curve gets opened
oldlen = len(fp.Continuity)
newlen = (len(self._segpoleslst(fp))-1+1*fp.Closed)
if oldlen > newlen:
fp.Continuity = fp.Continuity[:newlen]
if oldlen < newlen:
fp.Continuity = fp.Continuity + [0]*(newlen-oldlen)
if hasattr(fp,'Closed') and fp.Closed and prop in ['Points','Degree','Closed'] and\
len(fp.Points) % fp.Degree: # the curve editing tools can't handle extra points
fp.Points=fp.Points[:(fp.Degree*(len(fp.Points)//fp.Degree))] #for closed curves
if prop in ["Degree"] and fp.Degree >= 1: #reset Continuity
self.resetcontinuity(fp)
if prop in ["Points","Degree","Continuity","Closed"]:
self.createGeometry(fp)
def createGeometry(self,fp):
import Part
plm = fp.Placement
if fp.Points:
startpoint=fp.Points[0]
poles=fp.Points[1:]
if fp.Closed and (len(fp.Points) > 2):
poles.append(fp.Points[0])
segpoleslst=[poles[x:x+fp.Degree] for x in \
xrange(0, len(poles), fp.Degree)]
edges = []
for segpoles in segpoleslst:
for segpoles in self._segpoleslst(fp):
# if len(segpoles) == fp.Degree # would skip additional poles
c = Part.BezierCurve() #last segment may have lower degree
c.increase(len(segpoles))

View File

@ -283,6 +283,7 @@ class DraftToolBar:
self.addButton = self._pushbutton("addButton", self.layout, icon="Draft_AddPoint", width=22, checkable=True)
self.delButton = self._pushbutton("delButton", self.layout, icon="Draft_DelPoint", width=22, checkable=True)
self.sharpButton = self._pushbutton("sharpButton", self.layout, icon="Draft_BezSharpNode", width=22, checkable=True)
self.tangentButton = self._pushbutton("tangentButton", self.layout, icon="Draft_BezTanNode", width=22, checkable=True)
self.symmetricButton = self._pushbutton("symmetricButton", self.layout, icon="Draft_BezSymNode", width=22, checkable=True)
@ -373,6 +374,7 @@ class DraftToolBar:
QtCore.QObject.connect(self.offsetValue,QtCore.SIGNAL("returnPressed()"),self.validatePoint)
QtCore.QObject.connect(self.addButton,QtCore.SIGNAL("toggled(bool)"),self.setAddMode)
QtCore.QObject.connect(self.delButton,QtCore.SIGNAL("toggled(bool)"),self.setDelMode)
QtCore.QObject.connect(self.sharpButton,QtCore.SIGNAL("toggled(bool)"),self.setSharpMode)
QtCore.QObject.connect(self.tangentButton,QtCore.SIGNAL("toggled(bool)"),self.setTangentMode)
QtCore.QObject.connect(self.symmetricButton,QtCore.SIGNAL("toggled(bool)"),self.setSymmetricMode)
QtCore.QObject.connect(self.finishButton,QtCore.SIGNAL("pressed()"),self.finish)
@ -450,6 +452,7 @@ class DraftToolBar:
style = "#constrButton:Checked {background-color: "
style += self.getDefaultColor("constr",rgb=True)+" } "
style += "#addButton:Checked, #delButton:checked, "
style += "#sharpButton:Checked, "
style += "#tangentButton:Checked, #symmetricButton:checked {"
style += "background-color: rgb(20,100,250) }"
self.baseWidget.setStyleSheet(style)
@ -482,6 +485,7 @@ class DraftToolBar:
self.occOffset.setText(translate("draft", "&OCC-style offset"))
self.addButton.setToolTip(translate("draft", "Add points to the current object"))
self.delButton.setToolTip(translate("draft", "Remove points from the current object"))
self.sharpButton.setToolTip(translate("draft", "Make Bezier node sharp"))
self.tangentButton.setToolTip(translate("draft", "Make Bezier node tangent"))
self.symmetricButton.setToolTip(translate("draft", "Make Bezier node symmetric"))
self.undoButton.setText(translate("draft", "&Undo"))
@ -679,6 +683,7 @@ class DraftToolBar:
self.finishButton.hide()
self.addButton.hide()
self.delButton.hide()
self.sharpButton.hide()
self.tangentButton.hide()
self.symmetricButton.hide()
self.undoButton.hide()
@ -810,6 +815,7 @@ class DraftToolBar:
self.addButton.show()
self.delButton.show()
if mode == 'BezCurve':
self.sharpButton.show()
self.tangentButton.show()
self.symmetricButton.show()
self.finishButton.show()
@ -817,6 +823,7 @@ class DraftToolBar:
# always start Edit with buttons unchecked
self.addButton.setChecked(False)
self.delButton.setChecked(False)
self.sharpButton.setChecked(False)
self.tangentButton.setChecked(False)
self.symmetricButton.setChecked(False)
@ -848,6 +855,7 @@ class DraftToolBar:
self.delButton.setEnabled(mode)
def setBezEditButtons(self,mode):
self.sharpButton.setEnabled(mode)
self.tangentButton.setEnabled(mode)
self.symmetricButton.setEnabled(mode)
@ -1378,22 +1386,33 @@ class DraftToolBar:
if self.addButton.isChecked():
self.delButton.setChecked(False)
self.symmetricButton.setChecked(False)
self.sharpButton.setChecked(False)
self.tangentButton.setChecked(False)
def setDelMode(self,bool):
if self.delButton.isChecked():
self.addButton.setChecked(False)
self.symmetricButton.setChecked(False)
self.sharpButton.setChecked(False)
self.tangentButton.setChecked(False)
def setSharpMode(self,bool):
if self.sharpButton.isChecked():
self.tangentButton.setChecked(False)
self.symmetricButton.setChecked(False)
self.addButton.setChecked(False)
self.delButton.setChecked(False)
def setTangentMode(self,bool):
if self.tangentButton.isChecked():
self.sharpButton.setChecked(False)
self.symmetricButton.setChecked(False)
self.addButton.setChecked(False)
self.delButton.setChecked(False)
def setSymmetricMode(self,bool):
if self.symmetricButton.isChecked():
self.sharpButton.setChecked(False)
self.tangentButton.setChecked(False)
self.addButton.setChecked(False)
self.delButton.setChecked(False)

View File

@ -3319,6 +3319,10 @@ class Edit(Modifier):
if 'EditNode' in info["Component"]:
self.delPoint(int(info["Component"][8:]))
# don't do tan/sym on DWire/BSpline!
elif ((Draft.getType(self.obj) == "BezCurve") and
(self.ui.sharpButton.isChecked())):
if 'EditNode' in info["Component"]:
self.smoothBezPoint(int(info["Component"][8:]), info, 'Sharp')
elif ((Draft.getType(self.obj) == "BezCurve") and
(self.ui.tangentButton.isChecked())):
if 'EditNode' in info["Component"]:
@ -3354,14 +3358,48 @@ class Edit(Modifier):
self.obj.Closed = True
# DNC: fix error message if edited point coinsides with one of the existing points
if ( editPnt in pts ) == False:
if Draft.getType(self.obj) in ["BezCurve"] and self.obj.Degree >=3 and \
(self.editing % self.obj.Degree) == 0: #it's a knot
if self.editing >= 1: #move left pole
pts[self.editing-1] = pts[self.editing-1] + editPnt - pts[self.editing]
self.trackers[self.editing-1].set(pts[self.editing-1])
if self.editing < len(pts)-1: #move right pole
pts[self.editing+1] = pts[self.editing+1] + editPnt - pts[self.editing]
self.trackers[self.editing+1].set(pts[self.editing+1])
if Draft.getType(self.obj) in ["BezCurve"]:
knot = None
ispole = self.editing % self.obj.Degree #
if ispole == 0: #knot
if self.obj.Degree >=3:
if self.editing >= 1: #move left pole
knotidx = self.editing if self.editing < len(pts) else 0
pts[self.editing-1] = pts[self.editing-1] + \
editPnt - pts[knotidx]
self.trackers[self.editing-1].set(\
pts[self.editing-1])
if self.editing < len(pts)-1: #move right pole
pts[self.editing+1] = pts[self.editing+1] + \
editPnt - pts[self.editing]
self.trackers[self.editing+1].set(\
pts[self.editing+1])
if self.editing == 0 and self.obj.Closed: # move last pole
pts[-1] = pts [-1] + editPnt -pts[self.editing]
self.trackers[-1].set(pts[-1])
elif ispole == 1 and (self.editing >=2 or self.obj.Closed): #right pole
knot = self.editing -1
changep = self.editing -2 # -1 in case of closed curve
elif ispole == self.obj.Degree-1 and \
self.editing <= len(pts)-3: #left pole
knot = self.editing +1
changep = self.editing +2
elif ispole == self.obj.Degree-1 and self.obj.Closed and \
self.editing == len(pts)-1: #last pole
knot = 0
changep = 1
if knot is not None: # we need to modify the oposite pole
segment = knot / self.obj.Degree -1
cont=self.obj.Continuity[segment] if \
len(self.obj.Continuity) > segment else 0
if cont == 1: #tangent
pts[changep] = self.obj.Proxy.modifytangentpole(\
pts[knot],editPnt,pts[changep])
self.trackers[changep].set(pts[changep])
elif cont ==2: #symmetric
pts[changep] = self.obj.Proxy.modifysymmetricpole(\
pts[knot],editPnt)
self.trackers[changep].set(pts[changep])
pts[self.editing] = editPnt
self.obj.Points = pts
self.trackers[self.editing].set(v)
@ -3463,6 +3501,11 @@ class Edit(Modifier):
pts.extend(edge.Curve.getPoles()[1:])
if self.obj.Closed:
pts.pop()
c=self.obj.Continuity
# assume we have a tangent continuity for an arbitrarily split
# segment, unless it's linear
cont = 1 if (self.obj.Degree >= 2) else 0
self.obj.Continuity = c[0:edgeindex]+[cont]+c[edgeindex:]
else:
if ( Draft.getType(self.obj) == "Wire" ):
if (self.obj.Closed == True):
@ -3510,64 +3553,111 @@ class Edit(Modifier):
pts.pop(point)
self.doc.openTransaction("Edit "+self.obj.Name)
self.obj.Points = pts
if Draft.getType(self.obj) =="BezCurve":
self.obj.Proxy.resetcontinuity(self.obj)
self.doc.commitTransaction()
self.resetTrackers()
def smoothBezPoint(self,point, info=None, style='Symmetric'):
if not (Draft.getType(self.obj) == "BezCurve"): return
style2cont = {'Sharp':0,'Tangent':1,'Symmetric':2}
if not (Draft.getType(self.obj) == "BezCurve"):return
if info['Component'].startswith('Edge'):
return # didn't click control point
pts = self.obj.Points
deg = self.obj.Degree
if point % deg != 0:
if deg < 2: return
if point % deg != 0: #point is a pole
if deg >=3: #allow to select poles
if point > 2 and point % deg == 1: #right pole
if (point % deg == 1) and (point > 2 or self.obj.Closed): #right pole
knot = point -1
keepp = point
changep = point -2
elif point -3 < len(pts): #left pole
elif point < len(pts) -3 and point % deg == deg -1: #left pole
knot = point +1
keepp = point
changep = point +2
elif point == len(pts)-1 and self.obj.Closed: #last pole
# if the curve is closed the last pole has the last
# index in the poits lists
knot = 0
keepp = point
changep = 1
else:
msg(translate("draft", "Can't change Knot belonging to that pole\n"),'warning')
msg(translate("draft", "Can't change Knot belonging to pole %d\n"%point)\
,'warning')
return
if knot:
if style == 'Tangent':
pts[changep] = self.obj.Proxy.modifytangentpole(pts[knot],pts[keepp],\
pts[changep])
else:
pts[changep] = self.obj.Proxy.modifysymmetricpole(pts[knot],pts[keepp])
pts[changep] = self.obj.Proxy.modifytangentpole(\
pts[knot],pts[keepp],pts[changep])
elif style == 'Symmetric':
pts[changep] = self.obj.Proxy.modifysymmetricpole(\
pts[knot],pts[keepp])
else: #sharp
pass #
else:
msg(translate("draft", "Selection is not a Pole\n"),'warning')
msg(translate("draft", "Selection is not a Knot\n"),'warning')
return
elif (point == 0) or (point == (len(pts)-1)):
msg(translate("draft", "Endpoint of BezCurve can't be smoothed\n"),'warning')
return
else:
if style == 'Tangent':
else: #point is a knot
if style == 'Sharp':
if self.obj.Closed and point == len(pts)-1:
knot = 0
else:
knot = point
elif style == 'Tangent' and point > 0 and point < len(pts)-1:
prev, next = self.obj.Proxy.tangentpoles(pts[point],pts[point-1],pts[point+1])
else:
pts[point-1] = prev
pts[point+1] = next
knot = point #index for continuity
elif style == 'Symmetric' and point > 0 and point < len(pts)-1:
prev, next = self.obj.Proxy.symmetricpoles(pts[point],pts[point-1],pts[point+1])
pts[point-1] = prev
pts[point+1] = next
self.obj.Points = pts
pts[point-1] = prev
pts[point+1] = next
knot = point #index for continuity
elif self.obj.Closed and (style == 'Symmetric' or style == 'Tangent'):
if style == 'Tangent':
pts[1],pts[-1] = self.obj.Proxy.tangentpoles(pts[0],pts[1],pts[-1])
elif style == 'Symmetric':
pts[1],pts[-1] = self.obj.Proxy.symmetricpoles(pts[0],pts[1],pts[-1])
knot = 0
else:
msg(translate("draft", "Endpoint of BezCurve can't be smoothed\n"),'warning')
return
segment = knot // deg #segment index
newcont=self.obj.Continuity[:] #dont edit a property inplace !!!
if not self.obj.Closed and (len(self.obj.Continuity) == segment -1 or \
segment == 0) : pass # open curve
elif len(self.obj.Continuity) >= segment or \
self.obj.Closed and segment == 0 and \
len(self.obj.Continuity) >1:
newcont[segment-1] = style2cont.get(style)
else: #should not happen
FreeCAD.Console.PrintWarning('Continuity indexing error:'+\
'point:%d deg:%d len(cont):%d' % (knot,deg,\
len(self.obj.Continuity)))
self.doc.openTransaction("Edit "+self.obj.Name)
self.obj.Points = pts
self.obj.Continuity=newcont
self.doc.commitTransaction()
self.resetTrackers()
def resetTrackersBezier(self):
knotmarker = coin.SoMarkerSet.SQUARE_FILLED_9_9
polemarker = coin.SoMarkerSet.CIRCLE_FILLED_9_9
knotmarkers = (coin.SoMarkerSet.DIAMOND_FILLED_9_9,#sharp
coin.SoMarkerSet.SQUARE_FILLED_9_9, #tangent
coin.SoMarkerSet.HOURGLASS_FILLED_9_9) #symmetric
polemarker = coin.SoMarkerSet.CIRCLE_FILLED_9_9 #pole
self.trackers=[]
cont=self.obj.Continuity
firstknotcont = cont[-1] if (self.obj.Closed and cont) else 0
pointswithmarkers=[(self.obj.Shape.Edges[0].Curve.\
getPole(1),knotmarker)]
getPole(1),knotmarkers[firstknotcont])]
for edgeindex, edge in enumerate(self.obj.Shape.Edges):
poles=edge.Curve.getPoles()
pointswithmarkers.extend([(point,polemarker) for \
point in poles[1:-1]])
if not self.obj.Closed or len(self.obj.Shape.Edges) > edgeindex +1:
pointswithmarkers.append((poles[-1],knotmarker))
knotmarkeri=cont[edgeindex] if len(cont) > edgeindex else 0
pointswithmarkers.append((poles[-1],knotmarkers[knotmarkeri]))
for index,pwm in enumerate(pointswithmarkers):
p,marker=pwm
if self.pl: p = self.pl.multVec(p)

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,7 @@
<file>icons/Draft_Arc.svg</file>
<file>icons/Draft_BSpline.svg</file>
<file>icons/Draft_BezCurve.svg</file>
<file>icons/Draft_BezSharpNode.svg</file>
<file>icons/Draft_BezTanNode.svg</file>
<file>icons/Draft_BezSymNode.svg</file>
<file>icons/Draft_Circle.svg</file>

View File

@ -0,0 +1,304 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="64"
height="64"
id="svg3612">
<defs
id="defs3614">
<linearGradient
id="linearGradient3144-6">
<stop
id="stop3146-9"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop3148-2"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3701">
<stop
id="stop3703"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop3705"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<radialGradient
cx="225.26402"
cy="672.79736"
r="34.345188"
fx="225.26402"
fy="672.79736"
id="radialGradient3688"
xlink:href="#linearGradient3144-6"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.6985294,0,202.82863)" />
<linearGradient
id="linearGradient3708">
<stop
id="stop3710"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop3712"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3864-0-0">
<stop
id="stop3866-5-7"
style="stop-color:#0619c0;stop-opacity:1"
offset="0" />
<stop
id="stop3868-7-6"
style="stop-color:#379cfb;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3377">
<stop
id="stop3379"
style="stop-color:#ffaa00;stop-opacity:1"
offset="0" />
<stop
id="stop3381"
style="stop-color:#faff2b;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3864-0">
<stop
id="stop3866-5"
style="stop-color:#0619c0;stop-opacity:1"
offset="0" />
<stop
id="stop3868-7"
style="stop-color:#379cfb;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient5048">
<stop
id="stop5050"
style="stop-color:#000000;stop-opacity:0"
offset="0" />
<stop
id="stop5056"
style="stop-color:#000000;stop-opacity:1"
offset="0.5" />
<stop
id="stop5052"
style="stop-color:#000000;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3841-0-3">
<stop
id="stop3843-1-3"
style="stop-color:#0619c0;stop-opacity:1"
offset="0" />
<stop
id="stop3845-0-8"
style="stop-color:#379cfb;stop-opacity:1"
offset="1" />
</linearGradient>
<radialGradient
cx="20.892099"
cy="114.5684"
r="5.256"
fx="20.892099"
fy="114.5684"
id="aigrd2"
gradientUnits="userSpaceOnUse">
<stop
id="stop15566"
style="stop-color:#f0f0f0;stop-opacity:1"
offset="0" />
<stop
id="stop15568"
style="stop-color:#9a9a9a;stop-opacity:1"
offset="1" />
</radialGradient>
<radialGradient
cx="20.892099"
cy="64.567902"
r="5.257"
fx="20.892099"
fy="64.567902"
id="aigrd3"
gradientUnits="userSpaceOnUse">
<stop
id="stop15573"
style="stop-color:#f0f0f0;stop-opacity:1"
offset="0" />
<stop
id="stop15575"
style="stop-color:#9a9a9a;stop-opacity:1"
offset="1" />
</radialGradient>
<linearGradient
id="linearGradient15662">
<stop
id="stop15664"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop15666"
style="stop-color:#f8f8f8;stop-opacity:1"
offset="1" />
</linearGradient>
<radialGradient
cx="33.966679"
cy="35.736916"
r="86.70845"
fx="33.966679"
fy="35.736916"
id="radialGradient4452"
xlink:href="#linearGradient259"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.96049297,0,0,1.041132,-52.144249,-702.33158)" />
<linearGradient
id="linearGradient259">
<stop
id="stop260"
style="stop-color:#fafafa;stop-opacity:1"
offset="0" />
<stop
id="stop261"
style="stop-color:#bbbbbb;stop-opacity:1"
offset="1" />
</linearGradient>
<radialGradient
cx="8.824419"
cy="3.7561285"
r="37.751713"
fx="8.824419"
fy="3.7561285"
id="radialGradient4454"
xlink:href="#linearGradient269"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.96827297,0,0,1.032767,-48.790699,-701.68513)" />
<linearGradient
id="linearGradient269">
<stop
id="stop270"
style="stop-color:#a3a3a3;stop-opacity:1"
offset="0" />
<stop
id="stop271"
style="stop-color:#4c4c4c;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient4095">
<stop
id="stop4097"
style="stop-color:#005bff;stop-opacity:1"
offset="0" />
<stop
id="stop4099"
style="stop-color:#c1e3f7;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
x1="394.15784"
y1="185.1304"
x2="434.73947"
y2="140.22731"
id="linearGradient4253"
xlink:href="#linearGradient4247"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.94231826,0,0,0.94231826,23.727549,8.8262536)" />
<linearGradient
id="linearGradient4247">
<stop
id="stop4249"
style="stop-color:#2e8207;stop-opacity:1"
offset="0" />
<stop
id="stop4251"
style="stop-color:#52ff00;stop-opacity:1"
offset="1" />
</linearGradient>
<radialGradient
cx="225.26402"
cy="672.79736"
r="34.345188"
fx="225.26402"
fy="672.79736"
id="radialGradient4317"
xlink:href="#linearGradient3144-8"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.6985294,0,202.82863)" />
<linearGradient
id="linearGradient3144-8">
<stop
id="stop3146-96"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop3148-4"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
</defs>
<metadata
id="metadata3617">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1">
<path
d="M 7.090909,5.8181818 C 15.272728,20 -3.7471829,48.338335 31.636364,48.363637 61.272846,26.482175 48.000001,19.757576 56.181819,5.4545454 l 0,0"
id="path3179"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
d="M 6,48 31.560197,48.181818 61.382433,24.261076"
id="path3082"
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<g
transform="matrix(-0.11387155,-0.09751677,0.09751677,-0.11387155,-9.9749576,141.51281)"
id="g4248">
<path
d="m 245.71428,655.2193 a 48.57143,48.57143 0 1 1 -97.14286,0 48.57143,48.57143 0 1 1 97.14286,0 z"
id="path4250"
style="fill:#0048ff;fill-opacity:1;stroke:#000000;stroke-width:5.80000019;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
d="m 259.60921,672.79736 a 34.34519,23.991124 0 1 1 -68.69038,0 34.34519,23.991124 0 1 1 68.69038,0 z"
transform="matrix(0.8513023,-0.5246754,0.5246754,0.8513023,-338.69692,214.19328)"
id="path4252"
style="fill:url(#radialGradient4317);fill-opacity:1;stroke:none" />
</g>
<path
d="m -28.545455,43.545456 a 1.727273,1.727273 0 1 1 -3.454546,0 1.727273,1.727273 0 1 1 3.454546,0 z"
transform="translate(36.727273,4.3636364)"
id="path3084"
style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
<path
d="m 62.574703,24.99591 a 1.727273,1.727273 0 0 1 -3.454546,0 1.727273,1.727273 0 1 1 3.454546,0 z"
id="path3084-5"
style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.5 KiB