Allow tabbing to cycle through objects to snap

To do this, the object snapping code has been refactored into a snapToObject function. This allows it to be called on different objects easily. A new base class which handles the tab event has been added.
This commit is contained in:
Dion Moult
2019-02-02 20:48:39 +11:00
committed by Yorik van Havre
parent 18b5f238ae
commit 334a2a31d0
2 changed files with 217 additions and 199 deletions

View File

@@ -270,7 +270,17 @@ def displayExternal(internValue,decimals=None,dim='Length',showUnit=True,unit=No
# Customized widgets
#---------------------------------------------------------------------------
class DraftDockWidget(QtGui.QWidget):
class DraftBaseWidget(QtGui.QWidget):
def __init__(self,parent = None):
QtGui.QWidget.__init__(self,parent)
def eventFilter(self, widget, event):
if event.type() == QtCore.QEvent.KeyPress and event.key()==QtCore.Qt.Key_Tab:
if hasattr(FreeCADGui,"Snapper"):
FreeCADGui.Snapper.cycleSnapObject()
return True
return QtGui.QWidget.eventFilter(self, widget, event)
class DraftDockWidget(DraftBaseWidget):
"custom Widget that emits a resized() signal when resized"
def __init__(self,parent = None):
QtGui.QWidget.__init__(self,parent)
@@ -359,7 +369,7 @@ class DraftToolBar:
if self.taskmode:
# add only a dummy widget, since widgets are created on demand
self.baseWidget = QtGui.QWidget()
self.baseWidget = DraftBaseWidget()
self.tray = QtGui.QToolBar(None)
self.tray.setObjectName("Draft tray")
self.tray.setWindowTitle("Draft tray")
@@ -505,6 +515,7 @@ class DraftToolBar:
self.layout.addLayout(bl)
self.labelx = self._label("labelx", xl)
self.xValue = self._inputfield("xValue", xl) #width=60
self.xValue.installEventFilter(self.baseWidget)
self.xValue.setText(FreeCAD.Units.Quantity(0,FreeCAD.Units.Length).UserString)
self.labely = self._label("labely", yl)
self.yValue = self._inputfield("yValue", yl)
@@ -843,7 +854,7 @@ class DraftToolBar:
if self.taskmode:
self.isTaskOn = True
todo.delay(FreeCADGui.Control.closeDialog,None)
self.baseWidget = QtGui.QWidget()
self.baseWidget = DraftBaseWidget()
self.layout = QtGui.QVBoxLayout(self.baseWidget)
self.setupToolBar(task=True)
self.retranslateUi(self.baseWidget)

View File

@@ -97,6 +97,7 @@ class Snapper:
self.running = False
self.callbackClick = None
self.callbackMove = None
self.snapObjectIndex = 0
# the snapmarker has "dot","circle" and "square" available styles
if self.snapStyle:
@@ -138,7 +139,18 @@ class Snapper:
('ortho', ':/icons/Snap_Ortho.svg'),
('intersection', ':/icons/Snap_Intersection.svg'),
('special', ':/icons/Snap_Special.svg')])
def cstr(self, lastpoint, constrain, point):
"constrains if needed"
if constrain or self.mask:
fpt = self.constrain(point,lastpoint)
else:
self.unconstrain()
fpt = point
if self.radiusTracker:
self.radiusTracker.update(fpt)
return fpt
def snap(self,screenpos,lastpoint=None,active=True,constrain=False,noTracker=False):
"""snap(screenpos,lastpoint=None,active=True,constrain=False,noTracker=False): returns a snapped
point from the given (x,y) screenpos (the position of the mouse cursor), active is to
@@ -168,18 +180,6 @@ class Snapper:
if Draft.getParam("showSnapBar",True):
bt.show()
def cstr(point):
"constrains if needed"
if constrain or self.mask:
fpt = self.constrain(point,lastpoint)
else:
self.unconstrain()
fpt = point
if self.radiusTracker:
self.radiusTracker.update(fpt)
return fpt
snaps = []
self.snapInfo = None
# type conversion if needed
@@ -230,202 +230,209 @@ class Snapper:
if self.trackLine:
self.trackLine.p1(lastpoint)
# check if we snapped to something
self.snapInfo = Draft.get3DView().getObjectInfo((screenpos[0],screenpos[1]))
# checking if parallel to one of the edges of the last objects or to a polar direction
if active:
eline = None
point,eline = self.snapToPolar(point,lastpoint)
point,eline = self.snapToExtensions(point,lastpoint,constrain,eline)
if not self.snapInfo or "Component" not in self.snapInfo:
# nothing has been snapped
objectsUnderCursor = Draft.get3DView().getObjectsInfo((screenpos[0],screenpos[1]))
if objectsUnderCursor:
if self.snapObjectIndex >= len(objectsUnderCursor):
self.snapObjectIndex = 0
self.snapInfo = objectsUnderCursor[self.snapObjectIndex]
if self.snapInfo and "Component" in self.snapInfo:
return self.snapToObject(lastpoint, active, constrain, eline, point, oldActive)
# nothing has been snapped
# check for grid snap and ext crossings
if active:
epoint = self.snapToCrossExtensions(point)
if epoint:
point = epoint
else:
point = self.snapToGrid(point)
fp = self.cstr(lastpoint, constrain, point)
if self.trackLine and lastpoint and (not noTracker):
self.trackLine.p2(fp)
self.trackLine.on()
# set the arch point tracking
if self.lastArchPoint:
self.setArchDims(self.lastArchPoint,fp)
self.spoint = fp
self.running = False
return fp
def cycleSnapObject(self):
self.snapObjectIndex = self.snapObjectIndex + 1
def snapToObject(self, lastpoint, active, constrain, eline, point, oldActive):
# we have an object to snap to
snaps = []
obj = FreeCAD.ActiveDocument.getObject(self.snapInfo['Object'])
if not obj:
self.spoint = self.cstr(lastpoint, constrain, point)
self.running = False
return self.spoint
self.lastSnappedObject = obj
# check for grid snap and ext crossings
if active:
epoint = self.snapToCrossExtensions(point)
if epoint:
point = epoint
else:
point = self.snapToGrid(point)
fp = cstr(point)
if self.trackLine and lastpoint and (not noTracker):
if hasattr(obj.ViewObject,"Selectable"):
if not obj.ViewObject.Selectable:
self.spoint = self.cstr(lastpoint, constrain, point)
self.running = False
return self.spoint
if not active:
# passive snapping
snaps = [self.snapToVertex(self.snapInfo)]
else:
# first stick to the snapped object
s = self.snapToVertex(self.snapInfo)
if s:
point = s[0]
snaps = [s]
# active snapping
comp = self.snapInfo['Component']
if obj.isDerivedFrom("Part::Feature"):
# applying global placements
shape = obj.Shape.copy()
shape.Placement = obj.getGlobalPlacement()
snaps.extend(self.snapToSpecials(obj,lastpoint,eline))
if Draft.getType(obj) == "Polygon":
# special snapping for polygons: add the center
snaps.extend(self.snapToPolygon(obj))
if (not self.maxEdges) or (len(shape.Edges) <= self.maxEdges):
if "Edge" in comp:
# we are snapping to an edge
en = int(comp[4:])-1
if len(shape.Edges) > en:
edge = shape.Edges[en]
snaps.extend(self.snapToEndpoints(edge))
snaps.extend(self.snapToMidpoint(edge))
snaps.extend(self.snapToPerpendicular(edge,lastpoint))
snaps.extend(self.snapToIntersection(edge))
snaps.extend(self.snapToElines(edge,eline))
et = DraftGeomUtils.geomType(edge)
if et == "Circle":
# the edge is an arc, we have extra options
snaps.extend(self.snapToAngles(edge))
snaps.extend(self.snapToCenter(edge))
elif et == "Ellipse":
# extra ellipse options
snaps.extend(self.snapToCenter(edge))
elif "Face" in comp:
en = int(comp[4:])-1
if len(shape.Faces) > en:
face = shape.Faces[en]
snaps.extend(self.snapToFace(face))
elif "Vertex" in comp:
# directly snapped to a vertex
snaps.append(self.snapToVertex(self.snapInfo,active=True))
elif comp == '':
# workaround for the new view provider
snaps.append(self.snapToVertex(self.snapInfo,active=True))
else:
# all other cases (face, etc...) default to passive snap
snapArray = [self.snapToVertex(self.snapInfo)]
elif Draft.getType(obj) == "Dimension":
# for dimensions we snap to their 2 points:
snaps.extend(self.snapToDim(obj))
elif Draft.getType(obj) == "Axis":
for edge in obj.Shape.Edges:
snaps.extend(self.snapToEndpoints(edge))
snaps.extend(self.snapToIntersection(edge))
elif Draft.getType(obj) == "Mesh":
# for meshes we only snap to vertices
snaps.extend(self.snapToEndpoints(obj.Mesh))
elif Draft.getType(obj) == "Points":
# for points we only snap to points
snaps.extend(self.snapToEndpoints(obj.Points))
elif Draft.getType(obj) in ["WorkingPlaneProxy","BuildingPart"]:
# snap to the center of WPProxies and BuildingParts
snaps.append([obj.Placement.Base,'endpoint',self.toWP(obj.Placement.Base)])
elif Draft.getType(obj) == "SectionPlane":
# snap to corners of section planes
snaps.extend(self.snapToEndpoints(obj.Shape))
# updating last objects list
if not self.lastObj[1]:
self.lastObj[1] = obj.Name
elif self.lastObj[1] != obj.Name:
self.lastObj[0] = self.lastObj[1]
self.lastObj[1] = obj.Name
if not snaps:
self.spoint = self.cstr(lastpoint, constrain, point)
self.running = False
return self.spoint
# calculating the nearest snap point
shortest = 1000000000000000000
origin = Vector(self.snapInfo['x'],self.snapInfo['y'],self.snapInfo['z'])
winner = None
fp = point
for snap in snaps:
if (not snap) or (snap[0] == None):
pass
#print("debug: Snapper: invalid snap point: ",snaps)
else:
delta = snap[0].sub(origin)
if delta.Length < shortest:
shortest = delta.Length
winner = snap
if winner:
# see if we are out of the max radius, if any
if self.radius:
dv = point.sub(winner[2])
if (dv.Length > self.radius):
if (not oldActive) and self.isEnabled("passive"):
winner = self.snapToVertex(self.snapInfo)
# setting the cursors
if self.tracker and not self.selectMode:
self.tracker.setCoords(winner[2])
self.tracker.setMarker(self.mk[winner[1]])
self.tracker.on()
# setting the trackline
fp = self.cstr(lastpoint, constrain, winner[2])
if self.trackLine and lastpoint:
self.trackLine.p2(fp)
self.trackLine.on()
# set the cursor
self.setCursor(winner[1])
# set the arch point tracking
if self.lastArchPoint:
self.setArchDims(self.lastArchPoint,fp)
self.spoint = fp
self.running = False
return fp
else:
# we have an object to snap to
obj = FreeCAD.ActiveDocument.getObject(self.snapInfo['Object'])
if not obj:
self.spoint = cstr(point)
self.running = False
return self.spoint
self.lastSnappedObject = obj
if hasattr(obj.ViewObject,"Selectable"):
if not obj.ViewObject.Selectable:
self.spoint = cstr(point)
self.running = False
return self.spoint
if not active:
# passive snapping
snaps = [self.snapToVertex(self.snapInfo)]
if Draft.getType(obj) in ["Wall","Structure"]:
self.lastArchPoint = winner[2]
else:
# first stick to the snapped object
s = self.snapToVertex(self.snapInfo)
if s:
point = s[0]
snaps = [s]
# active snapping
comp = self.snapInfo['Component']
if obj.isDerivedFrom("Part::Feature"):
# applying global placements
shape = obj.Shape.copy()
shape.Placement = obj.getGlobalPlacement()
snaps.extend(self.snapToSpecials(obj,lastpoint,eline))
if Draft.getType(obj) == "Polygon":
# special snapping for polygons: add the center
snaps.extend(self.snapToPolygon(obj))
if (not self.maxEdges) or (len(shape.Edges) <= self.maxEdges):
if "Edge" in comp:
# we are snapping to an edge
en = int(comp[4:])-1
if len(shape.Edges) > en:
edge = shape.Edges[en]
snaps.extend(self.snapToEndpoints(edge))
snaps.extend(self.snapToMidpoint(edge))
snaps.extend(self.snapToPerpendicular(edge,lastpoint))
snaps.extend(self.snapToIntersection(edge))
snaps.extend(self.snapToElines(edge,eline))
et = DraftGeomUtils.geomType(edge)
if et == "Circle":
# the edge is an arc, we have extra options
snaps.extend(self.snapToAngles(edge))
snaps.extend(self.snapToCenter(edge))
elif et == "Ellipse":
# extra ellipse options
snaps.extend(self.snapToCenter(edge))
elif "Face" in comp:
en = int(comp[4:])-1
if len(shape.Faces) > en:
face = shape.Faces[en]
snaps.extend(self.snapToFace(face))
elif "Vertex" in comp:
# directly snapped to a vertex
snaps.append(self.snapToVertex(self.snapInfo,active=True))
elif comp == '':
# workaround for the new view provider
snaps.append(self.snapToVertex(self.snapInfo,active=True))
else:
# all other cases (face, etc...) default to passive snap
snapArray = [self.snapToVertex(self.snapInfo)]
elif Draft.getType(obj) == "Dimension":
# for dimensions we snap to their 2 points:
snaps.extend(self.snapToDim(obj))
elif Draft.getType(obj) == "Axis":
for edge in obj.Shape.Edges:
snaps.extend(self.snapToEndpoints(edge))
snaps.extend(self.snapToIntersection(edge))
elif Draft.getType(obj) == "Mesh":
# for meshes we only snap to vertices
snaps.extend(self.snapToEndpoints(obj.Mesh))
elif Draft.getType(obj) == "Points":
# for points we only snap to points
snaps.extend(self.snapToEndpoints(obj.Points))
elif Draft.getType(obj) in ["WorkingPlaneProxy","BuildingPart"]:
# snap to the center of WPProxies and BuildingParts
snaps.append([obj.Placement.Base,'endpoint',self.toWP(obj.Placement.Base)])
elif Draft.getType(obj) == "SectionPlane":
# snap to corners of section planes
snaps.extend(self.snapToEndpoints(obj.Shape))
# updating last objects list
if not self.lastObj[1]:
self.lastObj[1] = obj.Name
elif self.lastObj[1] != obj.Name:
self.lastObj[0] = self.lastObj[1]
self.lastObj[1] = obj.Name
if not snaps:
self.spoint = cstr(point)
self.running = False
return self.spoint
# calculating the nearest snap point
shortest = 1000000000000000000
origin = Vector(self.snapInfo['x'],self.snapInfo['y'],self.snapInfo['z'])
winner = None
fp = point
for snap in snaps:
if (not snap) or (snap[0] == None):
pass
#print("debug: Snapper: invalid snap point: ",snaps)
else:
delta = snap[0].sub(origin)
if delta.Length < shortest:
shortest = delta.Length
winner = snap
if winner:
# see if we are out of the max radius, if any
if self.radius:
dv = point.sub(winner[2])
if (dv.Length > self.radius):
if (not oldActive) and self.isEnabled("passive"):
winner = self.snapToVertex(self.snapInfo)
# setting the cursors
if self.tracker and not self.selectMode:
self.tracker.setCoords(winner[2])
self.tracker.setMarker(self.mk[winner[1]])
self.tracker.on()
# setting the trackline
fp = cstr(winner[2])
if self.trackLine and lastpoint:
self.trackLine.p2(fp)
self.trackLine.on()
# set the cursor
self.setCursor(winner[1])
# set the arch point tracking
if self.lastArchPoint:
self.setArchDims(self.lastArchPoint,fp)
if Draft.getType(obj) in ["Wall","Structure"]:
self.lastArchPoint = winner[2]
else:
self.lastArchPoint = None
# return the final point
self.spoint = fp
self.running = False
return self.spoint
self.lastArchPoint = None
# return the final point
self.spoint = fp
self.running = False
return self.spoint
def toWP(self,point):
"projects the given point on the working plane, if needed"