Files
create/src/Mod/Draft/DraftEdit.py
2020-03-06 14:50:35 +01:00

1849 lines
75 KiB
Python

# -*- coding: utf8 -*-
#***************************************************************************
#* Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> *
#* Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> *
#* Copyright (c) 2019, 2020 Carlo Pavan <carlopav@gmail.com> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
__title__= "FreeCAD Draft Edit Tool"
__author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, \
Dmitry Chigrin, Carlo Pavan"
__url__ = "https://www.freecadweb.org"
import FreeCAD as App
import math
import Draft
if App.GuiUp:
# Do not import GUI-related modules if GUI is not there
import FreeCADGui as Gui
import DraftTools
from DraftTrackers import editTracker, wireTracker, arcTracker, bsplineTracker, bezcurveTracker
from pivy import coin
from PySide import QtCore, QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
from DraftTools import translate
COLORS = {
"default": Gui.draftToolBar.getDefaultColor("snap"),
"black": (0., 0., 0.),
"white": (1., 1., 1.),
"grey": (.5, .5, .5),
"red": (1., 0., 0.),
"green": (0., 1., 0.),
"blue": (0., 0., 1.),
"yellow": (1., 1., 0.),
"cyan": (0., 1., 1.),
"magenta":(1., 0., 1.)
}
class Edit():
"""
The Draft_Edit FreeCAD command definition.
A tool to graphically edit FreeCAD objects.
Current implementation use many parts of pivy graphics code by user "looo".
The tool collect editpoints from objects and display Trackers on them to allow
editing their Shape and their parameters.
Callbacks
----------
selection_callback
registered when tool is launched, identify
selected objects.
editing_callbacks
self._keyPressedCB -> self.keyPressed
self._mouseMovedCB -> self._mouseMovedCB
if self._mousePressedCB -> self.mousePressed
when trackers are displayed for selected objects,
these callbacks capture user events and forward
them to related functions
Task panel (Draft Toolbar)
----------
self.ui = Gui.draftToolBar
TODO: since we introduced context menu for interacting
with editTrackers, point 2 should become obsolete,
because not consistent with multi-object editing.
Draft_Edit uses taskpanel in 3 ways:
1 - when waiting for user to select an object
calling self.ui.selectUi()
2 - when Trackers are displayed and user must click one, a
custom task panel is displayed depending on edited
object:
self.ui.editUi() -> the default one
self.ui.editUi("Wire") -> line and wire editing
self.ui.editUi("BezCurve") -> BezCurve editing
self.ui.editUi("Circle") -> circle editing
self.ui.editUi("Arc") -> arc editing
When Draft_Edit evaluate mouse click, depending if some
ui button have been pressed (.isChecked()), decide if
the action is a startEditing or AddPoint or DelPoint or
change BezCurve Continuity, ecc.
3 - when in editing, lineUi support clicking destination point
by self.startEditing
self.ui.lineUi()
self.ui.isRelative.show()
Tracker selection
----------
If the tool recognize mouse click as an attempt to startEditing,
using soRayPickAction, it identifies the selected editTracker and
start editing it. Here is where "looo" code was very useful.
Editing preview
----------
When object editing begins, self.ghost is initiated with the
corresponding DraftTracker of the object type. The object Tracker
is deleted when user clicks again and endEditing.
Context Menu
----------
Activated with Alt+LeftClick or pressing key "e"
It's a custom context menu, that depends on clicked tracker
or on clicked object.
display_tracker_menu
populates the menu with custom actions
evaluate_menu_action
evaluate user chosen action and launch corresponding
function.
Preferences
----------
maxObjects : Int
set by "DraftEditMaxObjects" in user preferences
The max number of FreeCAD objects the tool is
allowed to edit at the same time.
pick_radius : Int
set by "DraftEditPickRadius" in user preferences
The pick radius during editing operation.
Increase if you experience problems in clicking
on a editTracker because of screen resolution.
Attributes
----------
obj : Edited object
I'm planning to discard this attribute.
In old implementation every function was supposed to
act on self.obj, self.editpoints, self.trackers,
self.pl, self.invpl.
Due to multiple object editing, i'm planning to keep
just self.trackers. Any other object will be identified
and processed starting from editTracker information.
editing : Int
Index of the editTracker that has been clicked by the
user. Tracker selection mechanism is based on it.
if self.editing == None :
the user didn't click any node, and next click will
be processed as an attempt to start editing operation
if self.editing == o or 1 or 2 or 3 etc :
the user is editing corresponding node, so next click
will be processed as an attempt to end editing operation
editpoints : List [FreeCAD::App.Vector]
List of editpoints collected from the edited object,
on whick editTrackers will be placed.
trackers : Dictionary {object.Name : [editTrackers]}
It records the list of DraftTrackers.editTracker.
{object.Name as String : [editTrackers for the object]}
Each tracker is created with (position,obj.Name,idx),
so it's possible to recall it
self.trackers[str(node.objectName.getValue())][ep]
overNode : DraftTrackers.editTracker
It represent the editTracker under the cursor position.
It is used to preview the tracker selection action.
ghost : DraftTrackers.*
Handles the tracker to preview editing operations.
it is initialized when user clicks on a editTracker
by self.startEditing() function.
alt_edit_mode : Int
Allows alternative editing modes for objects.
ATM supported for:
- arcs: if 0 edit by 3 points, if 1 edit by center,
radius, angles
supportedObjs : List
List of supported Draft Objects.
The tool use Draft.getType(obj) to compare object type
to the list.
supportedPartObjs : List
List of supported Part Objects.
The tool use Draft.getType(obj) and obj.TypeId to compare
object type to the list.
"""
def __init__(self):
"""
Initialize Draft_Edit Command.
"""
self.running = False
self.trackers = {'object':[]}
self.overNode = None # preselected node with mouseover
self.obj = None
self.editing = None
# event callbacks
self.selection_callback = None
self._keyPressedCB = None
self._mouseMovedCB = None
self._mousePressedCB = None
# this are used to edit structure objects, it's a bit buggy i think
self.selectstate = None
self.originalDisplayMode = None
self.originalPoints = None
self.originalNodes = None
# settings
param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
self.maxObjects = param.GetInt("DraftEditMaxObjects", 5)
self.pick_radius = param.GetInt("DraftEditPickRadius", 20)
self.alt_edit_mode = 0 # alternative edit mode for objects
# preview
self.ghost = None
#list of supported Draft and Arch objects
self.supportedObjs = ["BezCurve","Wire","BSpline","Circle","Rectangle",
"Polygon","Dimension","Space","Structure","PanelCut",
"PanelSheet","Wall", "Window"]
#list of supported Part objects (they don't have a proxy)
#TODO: Add support for "Part::Circle" "Part::RegularPolygon" "Part::Plane" "Part::Ellipse" "Part::Vertex" "Part::Spiral"
self.supportedPartObjs = ["Sketch", "Sketcher::SketchObject",
"Part", "Part::Line", "Part::Box"]
def GetResources(self):
return {'Pixmap': 'Draft_Edit',
'Accel': "D, E",
'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_Edit", "Edit"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_Edit", "Edits the active object")}
#---------------------------------------------------------------------------
# MAIN FUNCTIONS
#---------------------------------------------------------------------------
def Activated(self):
"""
Activated is run when user launch Edit command.
If something is selected -> call self.proceed()
If nothing is selected -> self.register_selection_callback()
"""
if self.running:
self.finish()
DraftTools.Modifier.Activated(self,"Edit")
if not App.ActiveDocument:
self.finish()
self.ui = Gui.draftToolBar
self.view = Draft.get3DView()
if Gui.Selection.getSelection():
self.proceed()
else:
self.ui.selectUi()
App.Console.PrintMessage(translate("draft",
"Select a Draft object to edit")
+ "\n")
self.register_selection_callback()
def proceed(self):
"this method defines editpoints and set the editTrackers"
self.unregister_selection_callback()
self.edited_objects = self.getObjsFromSelection()
if not self.edited_objects:
return self.finish()
# Save selectstate and turn selectable false.
# Object can remain selectable commenting following lines:
# self.saveSelectState(self.obj)
# self.setSelectState(self.obj, False)
# start object editing
Gui.Selection.clearSelection()
Gui.Snapper.setSelectMode(True)
self.ui.editUi()
for obj in self.edited_objects:
self.setEditPoints(obj)
self.register_editing_callbacks()
# TODO: align working plane when editing starts
# App.DraftWorkingPlane.save()
# self.alignWorkingPlane()
def finish(self,closed=False):
"""
terminates Edit Tool
"""
self.unregister_selection_callback()
self.unregister_editing_callbacks()
self.editing = None
self.finalizeGhost()
Gui.Snapper.setSelectMode(False)
if self.obj and closed:
if "Closed" in self.obj.PropertiesList:
if not self.obj.Closed:
self.obj.Closed = True
if self.ui:
self.removeTrackers()
self.restoreSelectState(self.obj)
if Draft.getType(self.obj) == "Structure":
if self.originalDisplayMode is not None:
self.obj.ViewObject.DisplayMode = self.originalDisplayMode
if self.originalPoints is not None:
self.obj.ViewObject.NodeSize = self.originalPoints
if self.originalNodes is not None:
self.obj.ViewObject.ShowNodes = self.originalNodes
self.selectstate = None
self.originalDisplayMode = None
self.originalPoints = None
self.originalNodes = None
DraftTools.Modifier.finish(self)
App.DraftWorkingPlane.restore()
if Gui.Snapper.grid:
Gui.Snapper.grid.set()
self.running = False
# delay resetting edit mode otherwise it doesn't happen
from PySide import QtCore
QtCore.QTimer.singleShot(0,Gui.ActiveDocument.resetEdit)
#---------------------------------------------------------------------------
# SCENE EVENTS CALLBACKS
#---------------------------------------------------------------------------
def register_selection_callback(self):
"""
register callback for selection when command is launched
"""
self.unregister_selection_callback()
self.selection_callback = self.view.addEventCallback("SoEvent",DraftTools.selectObject)
def unregister_selection_callback(self):
"""
remove selection callback if it exists
"""
if self.selection_callback:
self.view.removeEventCallback("SoEvent",self.selection_callback)
self.selection_callback = None
def register_editing_callbacks(self):
"""
register editing callbacks (former action function)
"""
viewer = Gui.ActiveDocument.ActiveView.getViewer()
self.render_manager = viewer.getSoRenderManager()
view = Gui.ActiveDocument.ActiveView
if self._keyPressedCB is None:
self._keyPressedCB = view.addEventCallbackPivy(
coin.SoKeyboardEvent.getClassTypeId(), self.keyPressed)
if self._mouseMovedCB is None:
self._mouseMovedCB = view.addEventCallbackPivy(
coin.SoLocation2Event.getClassTypeId(), self.mouseMoved)
if self._mousePressedCB is None:
self._mousePressedCB = view.addEventCallbackPivy(
coin.SoMouseButtonEvent.getClassTypeId(), self.mousePressed)
#App.Console.PrintMessage("Draft edit callbacks registered \n")
def unregister_editing_callbacks(self):
"""
remove callbacks used during editing if they exist
"""
view = Gui.ActiveDocument.ActiveView
if self._keyPressedCB:
view.removeEventCallbackSWIG(coin.SoKeyboardEvent.getClassTypeId(), self._keyPressedCB)
self._keyPressedCB = None
#App.Console.PrintMessage("Draft edit keyboard callback unregistered \n")
if self._mouseMovedCB:
view.removeEventCallbackSWIG(coin.SoLocation2Event.getClassTypeId(), self._mouseMovedCB)
self._mouseMovedCB = None
#App.Console.PrintMessage("Draft edit location callback unregistered \n")
if self._mousePressedCB:
view.removeEventCallbackSWIG(coin.SoMouseButtonEvent.getClassTypeId(), self._mousePressedCB)
self._mousePressedCB = None
#App.Console.PrintMessage("Draft edit mouse button callback unregistered \n")
#---------------------------------------------------------------------------
# SCENE EVENT HANDLERS
#---------------------------------------------------------------------------
def keyPressed(self, event_callback):
"""
keyboard event handler
"""
#TODO: Get the keys from preferences
event = event_callback.getEvent()
if event.getState() == coin.SoKeyboardEvent.DOWN:
key = event.getKey()
#App.Console.PrintMessage("pressed key : "+str(key)+"\n")
if key == 65307: # ESC
self.finish()
if key == 97: # "a"
self.finish()
if key == 111: # "o"
self.finish(closed=True)
if key == 101: # "e"
self.display_tracker_menu(event)
if key == 105: # "i"
if Draft.getType(self.obj) == "Circle":
self.arcInvert(self.obj)
def mousePressed(self, event_callback):
"""
mouse button event handler, calls: startEditing, endEditing, addPoint, delPoint
"""
event = event_callback.getEvent()
if (event.getState() == coin.SoMouseButtonEvent.DOWN and
event.getButton() == event.BUTTON1
):#left click
if not event.wasAltDown():
if self.ui.addButton.isChecked():
self.addPoint(event)
return
if self.ui.delButton.isChecked():
self.delPoint(event)
return
if Draft.getType(self.obj) == "BezCurve" and (self.ui.sharpButton.isChecked()
or self.ui.tangentButton.isChecked() or
self.ui.symmetricButton.isChecked()):
pos = event.getPosition()
node = self.getEditNode(pos)
ep = self.getEditNodeIndex(node)
if ep is None:
return
doc = App.getDocument(str(node.documentName.getValue()))
self.obj = doc.getObject(str(node.objectName.getValue()))
if self.obj is None:
return
if self.ui.sharpButton.isChecked():
return self.smoothBezPoint(self.obj, ep, 'Sharp')
elif self.ui.tangentButton.isChecked():
return self.smoothBezPoint(self.obj, ep, 'Tangent')
elif self.ui.symmetricButton.isChecked():
return self.smoothBezPoint(self.obj, ep, 'Symmetric')
if self.editing is None:
self.startEditing(event)
else:
self.endEditing(self.obj,self.editing)
elif event.wasAltDown(): #left click with ctrl down
self.display_tracker_menu(event)
def mouseMoved(self, event_callback):
"mouse moved event handler, update tracker position and update preview ghost"
event = event_callback.getEvent()
if self.editing != None:
self.updateTrackerAndGhost(event)
else:
# look for a node in mouse position and highlight it
pos = event.getPosition()
node = self.getEditNode(pos)
ep = self.getEditNodeIndex(node)
if ep is not None:
if self.overNode is not None:
self.overNode.setColor(COLORS["default"])
self.trackers[str(node.objectName.getValue())][ep].setColor(COLORS["red"])
self.overNode = self.trackers[str(node.objectName.getValue())][ep]
else:
if self.overNode is not None:
self.overNode.setColor(COLORS["default"])
self.overNode = None
def startEditing(self, event):
"start editing selected EditNode"
pos = event.getPosition()
node = self.getEditNode(pos)
ep = self.getEditNodeIndex(node)
if ep is None:
return
doc = App.getDocument(str(node.documentName.getValue()))
self.obj = doc.getObject(str(node.objectName.getValue()))
if self.obj is None:
return
self.setPlacement(self.obj)
App.Console.PrintMessage(self.obj.Name
+ ": editing node number "
+ str(ep) + "\n")
self.ui.lineUi()
self.ui.isRelative.show()
self.editing = ep
self.trackers[self.obj.Name][self.editing].off()
self.finalizeGhost()
self.ghost = self.initGhost(self.obj)
self.node.append(self.trackers[self.obj.Name][self.editing].get())
Gui.Snapper.setSelectMode(False)
self.hideTrackers()
def updateTrackerAndGhost(self, event):
"updates tracker position when editing and update ghost"
pos = event.getPosition().getValue()
orthoConstrain = False
if event.wasShiftDown() == 1: orthoConstrain = True
snappedPos = Gui.Snapper.snap((pos[0],pos[1]),self.node[-1], constrain=orthoConstrain)
self.trackers[self.obj.Name][self.editing].set(snappedPos)
self.ui.displayPoint(snappedPos,self.node[-1])
if self.ghost:
self.updateGhost(obj=self.obj,idx=self.editing,pt=snappedPos)
def endEditing(self, obj, nodeIndex, v = None):
"terminate editing and start object updating process"
self.finalizeGhost()
self.trackers[obj.Name][nodeIndex].on()
Gui.Snapper.setSelectMode(True)
if v is None:
# endEditing is called by mousePressed
v = self.trackers[obj.Name][nodeIndex].get()
else:
# endEditing is called by numericInput, so tracker
# position should be updated manually
self.trackers[obj.Name][nodeIndex].set(v)
self.update(obj, nodeIndex, v)
self.alt_edit_mode = 0
self.ui.editUi(self.ui.lastMode)
self.node = []
self.editing = None
self.showTrackers()
DraftTools.redraw3DView()
#---------------------------------------------------------------------------
# UTILS
#---------------------------------------------------------------------------
def getObjsFromSelection(self):
"evaluate selection and returns a valid object to edit"
selection = Gui.Selection.getSelection()
self.edited_objects = []
if len(selection) > self.maxObjects:
App.Console.PrintMessage(translate("draft",
"Too many objects selected, max number set to: ")
+ str(self.maxObjects) + "\n")
return None
for obj in selection:
if Draft.getType(obj) in self.supportedObjs:
self.edited_objects.append(obj)
continue
elif Draft.getType(obj) in self.supportedPartObjs:
if obj.TypeId in self.supportedPartObjs:
self.edited_objects.append(obj)
continue
App.Console.PrintWarning(obj.Name
+ translate("draft",
": this object is not editable")
+ "\n")
return self.edited_objects
def get_selected_obj_at_position(self, pos):
"""return object at given position
if object is one of the edited objects (self.edited_objects)
"""
selobjs = Gui.ActiveDocument.ActiveView.getObjectsInfo((pos[0],pos[1]))
if not selobjs:
return
for info in selobjs:
if not info:
return
for obj in self.edited_objects:
if obj.Name == info["Object"]:
return obj
def numericInput(self, v, numy=None, numz=None):
"""this function gets called by the toolbar
or by the mouse click and activate the update function"""
if (numy is not None):
v = App.Vector(v,numy,numz)
self.endEditing(self.obj, self.editing, v)
App.ActiveDocument.recompute()
def setSelectState(self, obj, selState = False):
if hasattr(obj.ViewObject,"Selectable"):
obj.ViewObject.Selectable = selState
def saveSelectState(self, obj):
if hasattr(obj.ViewObject,"Selectable"):
self.selectstate = obj.ViewObject.Selectable
def restoreSelectState(self,obj):
if obj:
if hasattr(obj.ViewObject,"Selectable") and (self.selectstate is not None):
obj.ViewObject.Selectable = self.selectstate
def setPlacement(self,obj):
"set self.pl and self.invpl to self.obj placement and inverse placement"
if not obj:
return
if "Placement" in obj.PropertiesList:
self.pl = obj.getGlobalPlacement()
self.invpl = self.pl.inverse()
def alignWorkingPlane(self):
"align working plane to self.obj"
if "Shape" in self.obj.PropertiesList:
if DraftTools.plane.weak:
DraftTools.plane.alignToFace(self.obj.Shape)
if self.planetrack:
self.planetrack.set(self.editpoints[0])
def getEditNode(self, pos):
"get edit node from given screen position"
node = self.sendRay(pos)
return node
def sendRay(self, mouse_pos):
"sends a ray through the scene and return the nearest entity"
ray_pick = coin.SoRayPickAction(self.render_manager.getViewportRegion())
ray_pick.setPoint(coin.SbVec2s(*mouse_pos))
ray_pick.setRadius(self.pick_radius)
ray_pick.setPickAll(True)
ray_pick.apply(self.render_manager.getSceneGraph())
picked_point = ray_pick.getPickedPointList()
return self.searchEditNode(picked_point)
def searchEditNode(self, picked_point):
"search edit node inside picked point list and retrurn node number"
for point in picked_point:
path = point.getPath()
length = path.getLength()
point = path.getNode(length - 2)
#import DraftTrackers
if hasattr(point,"subElementName") and 'EditNode' in str(point.subElementName.getValue()):
return point
return None
def getEditNodeIndex(self, point):
"get edit node index from given screen position"
if point:
subElement = str(point.subElementName.getValue())
ep = int(subElement[8:])
return ep
else:
return None
#---------------------------------------------------------------------------
# EDIT TRACKERS functions
#---------------------------------------------------------------------------
def setTrackers(self, obj, points=None):
"set Edit Trackers for editpoints collected from self.obj"
if points is None or len(points) == 0:
App.Console.PrintWarning(translate("draft",
"No edit point found for selected object")
+ "\n")
# do not finish if some trackers are still present
if self.trackers == {'object':[]}:
self.finish()
return
self.trackers[obj.Name] = []
if Draft.getType(obj) == "BezCurve":
self.resetTrackersBezier(obj)
else:
if obj.Name in self.trackers:
self.removeTrackers(obj)
for ep in range(len(points)):
self.trackers[obj.Name].append(editTracker(pos=points[ep],name=obj.Name,idx=ep))
def resetTrackers(self, obj):
"reset Edit Trackers and set them again"
self.removeTrackers(obj)
self.setTrackers(obj, self.getEditPoints(obj))
def removeTrackers(self, obj = None):
"reset Edit Trackers and set them again"
if obj:
if obj.Name in self.trackers:
for t in self.trackers[obj.Name]:
t.finalize()
self.trackers[obj.Name] = []
else:
for key in self.trackers.keys():
for t in self.trackers[key]:
t.finalize()
self.trackers = {'object':[]}
def hideTrackers(self, obj=None):
"""hide Edit Trackers
Attributes
----------
obj : FreeCAD object
hides trackers only for given object,
if obj is None, hides all trackers
"""
if obj is None:
for key in self.trackers:
for t in self.trackers[key]:
t.off()
else:
for t in self.trackers[obj.Name]:
t.off()
def showTrackers(self, obj=None):
"""show Edit Trackers
Attributes
----------
obj : FreeCAD object
shows trackers only for given object,
if obj is None, shows all trackers
"""
if obj is None:
for key in self.trackers:
for t in self.trackers[key]:
t.on()
else:
for t in self.trackers[obj.Name]:
t.on()
#---------------------------------------------------------------------------
# PREVIEW
#---------------------------------------------------------------------------
def initGhost(self,obj):
"initialize preview ghost"
if Draft.getType(obj) == "Wire":
return wireTracker(obj.Shape)
elif Draft.getType(obj) == "BSpline":
return bsplineTracker()
elif Draft.getType(obj) == "BezCurve":
return bezcurveTracker()
elif Draft.getType(obj) == "Circle":
return arcTracker()
def updateGhost(self,obj,idx,pt):
if Draft.getType(obj) in ["Wire"]:
self.ghost.on()
pointList = self.applyPlacement(obj.Points)
pointList[idx] = pt
if obj.Closed:
pointList.append(pointList[0])
self.ghost.updateFromPointlist(pointList)
elif Draft.getType(obj) == "BSpline":
self.ghost.on()
pointList = self.applyPlacement(obj.Points)
pointList[idx] = pt
if obj.Closed:
pointList.append(pointList[0])
self.ghost.update(pointList)
elif Draft.getType(obj) == "BezCurve":
self.ghost.on()
plist = self.applyPlacement(obj.Points)
pointList = self.recomputePointsBezier(obj,plist,idx,pt,obj.Degree,moveTrackers=True)
self.ghost.update(pointList,obj.Degree)
elif Draft.getType(obj) == "Circle":
self.ghost.on()
self.ghost.setCenter(obj.getGlobalPlacement().Base)
self.ghost.setRadius(obj.Radius)
if self.obj.FirstAngle == self.obj.LastAngle:
# self.obj is a circle
self.ghost.circle = True
if self.editing == 0:
self.ghost.setCenter(pt)
elif self.editing == 1:
radius = pt.sub(obj.getGlobalPlacement().Base).Length
self.ghost.setRadius(radius)
else:
if self.alt_edit_mode == 0:
# edit by 3 points
if self.editing == 0:
# center point
import DraftVecUtils
p1 = self.invpl.multVec(self.obj.Shape.Vertexes[0].Point)
p2 = self.invpl.multVec(self.obj.Shape.Vertexes[1].Point)
p0 = DraftVecUtils.project(self.invpl.multVec(pt),self.invpl.multVec(self.getArcMid(obj, global_placement=True)))
self.ghost.autoinvert=False
self.ghost.setRadius(p1.sub(p0).Length)
self.ghost.setStartPoint(self.obj.Shape.Vertexes[1].Point)
self.ghost.setEndPoint(self.obj.Shape.Vertexes[0].Point)
self.ghost.setCenter(self.pl.multVec(p0))
return
else:
p1=self.getArcStart(obj,global_placement=True)
p2=self.getArcMid(obj,global_placement=True)
p3=self.getArcEnd(obj,global_placement=True)
if self.editing == 1:
p1=pt
elif self.editing == 3:
p2=pt
elif self.editing == 2:
p3=pt
self.ghost.setBy3Points(p1,p2,p3)
elif self.alt_edit_mode == 1:
# edit by center radius angles
self.ghost.setStartAngle(math.radians(obj.FirstAngle))
self.ghost.setEndAngle(math.radians(obj.LastAngle))
if self.editing == 0:
self.ghost.setCenter(pt)
elif self.editing == 1:
self.ghost.setStartPoint(pt)
elif self.editing == 2:
self.ghost.setEndPoint(pt)
elif self.editing == 3:
self.ghost.setRadius(self.invpl.multVec(pt).Length)
DraftTools.redraw3DView()
def applyPlacement(self,pointList):
if self.pl:
plist = []
for p in pointList:
point = self.pl.multVec(p)
plist.append(point)
return plist
else:
return pointList
def finalizeGhost(self):
try:
self.ghost.finalize()
self.ghost = None
except:
return
#---------------------------------------------------------------------------
# EDIT OBJECT TOOLS : Add/Delete Vertexes
#---------------------------------------------------------------------------
def addPoint(self,event):
"called by action, add point to obj and reset trackers"
pos = event.getPosition()
#self.setSelectState(self.obj, True)
selobjs = Gui.ActiveDocument.ActiveView.getObjectsInfo((pos[0],pos[1]))
if not selobjs:
return
for info in selobjs:
if not info:
return
for o in self.edited_objects:
if o.Name != info["Object"]:
continue
self.obj = o
break
self.setPlacement(self.obj)
if Draft.getType(self.obj) == "Wire" and 'Edge' in info["Component"]:
pt = App.Vector(info["x"], info["y"], info["z"])
self.addPointToWire(self.obj, pt, int(info["Component"][4:]))
elif Draft.getType(self.obj) in ["BSpline", "BezCurve"]: #to fix double vertex created
#pt = self.point
if "x" in info:# prefer "real" 3D location over working-plane-driven one if possible
pt = App.Vector(info["x"], info["y"], info["z"])
else:
continue
self.addPointToCurve(pt,info)
self.obj.recompute()
self.removeTrackers(self.obj)
self.setEditPoints(self.obj)
#self.setSelectState(self.obj, False)
return
def addPointToWire(self, obj, newPoint, edgeIndex):
newPoints = []
hasAddedPoint = False
if hasattr(obj, "ChamferSize") and hasattr(obj, "FilletRadius"):
if obj.ChamferSize > 0 and obj.FilletRadius > 0:
edgeIndex = (edgeIndex +3) / 4
elif obj.ChamferSize > 0 or obj.FilletRadius > 0:
edgeIndex = (edgeIndex +1) / 2
for index, point in enumerate(self.obj.Points):
if index == edgeIndex:
hasAddedPoint = True
newPoints.append(self.invpl.multVec(newPoint))
newPoints.append(point)
if obj.Closed and edgeIndex == len(obj.Points):
# last segment when object is closed
newPoints.append(self.invpl.multVec(newPoint))
obj.Points = newPoints
def addPointToCurve(self,point,info=None):
import Part
if not (Draft.getType(self.obj) in ["BSpline","BezCurve"]):
return
pts = self.obj.Points
if Draft.getType(self.obj) == "BezCurve":
if not info['Component'].startswith('Edge'):
return # clicked control point
edgeindex = int(info['Component'].lstrip('Edge')) -1
wire = self.obj.Shape.Wires[0]
bz = wire.Edges[edgeindex].Curve
param = bz.parameter(point)
seg1 = wire.Edges[edgeindex].copy().Curve
seg2 = wire.Edges[edgeindex].copy().Curve
seg1.segment(seg1.FirstParameter,param)
seg2.segment(param,seg2.LastParameter)
if edgeindex == len(wire.Edges):
#we hit the last segment, we need to fix the degree
degree=wire.Edges[0].Curve.Degree
seg1.increase(degree)
seg2.increase(degree)
edges = wire.Edges[0:edgeindex] + [Part.Edge(seg1),Part.Edge(seg2)] \
+ wire.Edges[edgeindex + 1:]
pts = edges[0].Curve.getPoles()[0:1]
for edge in edges:
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) in ["BSpline"]):
if (self.obj.Closed == True):
curve = self.obj.Shape.Edges[0].Curve
else:
curve = self.obj.Shape.Curve
uNewPoint = curve.parameter(point)
uPoints = []
for p in self.obj.Points:
uPoints.append(curve.parameter(p))
for i in range(len(uPoints) -1):
if ( uNewPoint > uPoints[i] ) and ( uNewPoint < uPoints[i+1] ):
pts.insert(i + 1, self.invpl.multVec(point))
break
# DNC: fix: add points to last segment if curve is closed
if ( self.obj.Closed ) and ( uNewPoint > uPoints[-1] ) :
pts.append(self.invpl.multVec(point))
self.obj.Points = pts
def delPoint(self,event):
pos = event.getPosition()
node = self.getEditNode(pos)
ep = self.getEditNodeIndex(node)
if ep is None:
return App.Console.PrintWarning(translate("draft",
"Node not found")
+ "\n")
doc = App.getDocument(str(node.documentName.getValue()))
self.obj = doc.getObject(str(node.objectName.getValue()))
if self.obj is None:
return
if not (Draft.getType(self.obj) in ["Wire","BSpline","BezCurve"]):
return
if len(self.obj.Points) <= 2:
App.Console.PrintWarning(translate("draft",
"Active object must have more than two points/nodes")
+ "\n")
return
pts = self.obj.Points
pts.pop(ep)
self.obj.Points = pts
if Draft.getType(self.obj) =="BezCurve":
self.obj.Proxy.resetcontinuity(self.obj)
self.obj.recompute()
# don't do tan/sym on DWire/BSpline!
self.removeTrackers(self.obj)
self.setEditPoints(self.obj)
#---------------------------------------------------------------------------
# EDIT OBJECT TOOLS : GENERAL
#---------------------------------------------------------------------------
def setEditPoints(self,obj):
"append given object's editpoints to self.edipoints and set EditTrackers"
self.setPlacement(obj)
self.editpoints = self.getEditPoints(obj)
if self.editpoints: # set trackers and align plane
self.setTrackers(obj, self.editpoints)
self.editpoints = []
def getEditPoints(self, obj):
"""
(object) return a list of App.Vectors relative to object edit nodes
"""
objectType = Draft.getType(obj)
if objectType in ["Wire","BSpline"]:
self.ui.editUi("Wire")
return self.getWirePts(obj)
elif objectType == "BezCurve":
self.ui.editUi("BezCurve")
self.resetTrackersBezier(obj)
self.editpoints = []
return
elif objectType == "Circle":
return self.getCirclePts(obj)
elif objectType == "Rectangle":
return self.getRectanglePts(obj)
elif objectType == "Polygon":
return self.getPolygonPts(obj)
elif objectType == "Dimension":
return self.getDimensionPts(obj)
elif objectType == "Wall":
return self.getWallPts(obj)
elif objectType == "Window":
return self.getWindowPts(obj)
elif objectType == "Space":
return self.getSpacePts(obj)
elif objectType == "Structure":
return self.getStructurePts(obj)
elif objectType == "PanelCut":
return self.getPanelCutPts(obj)
elif objectType == "PanelSheet":
return self.getPanelSheetPts(obj)
elif objectType == "Part" and obj.TypeId == "Part::Box":
return self.getPartBoxPts(obj)
elif objectType == "Part::Line" and obj.TypeId == "Part::Line":
return self.getPartLinePts(obj)
elif objectType == "Sketch":
return self.getSketchPts(obj)
else:
return None
def update(self,obj, nodeIndex, v):
"apply the App.Vector to the modified point and update self.obj"
objectType = Draft.getType(obj)
App.ActiveDocument.openTransaction("Edit")
if objectType in ["Wire","BSpline"]:
self.updateWire(obj, nodeIndex, v)
elif objectType == "BezCurve":
self.updateWire(obj, nodeIndex, v)
elif objectType == "Circle":
self.updateCircle(obj, nodeIndex, v)
elif objectType == "Rectangle":
self.updateRectangle(obj, nodeIndex, v)
elif objectType == "Polygon":
self.updatePolygon(obj, nodeIndex, v)
elif objectType == "Dimension":
self.updateDimension(obj, nodeIndex, v)
elif objectType == "Sketch":
self.updateSketch(obj, nodeIndex, v)
elif objectType == "Wall":
self.updateWall(obj, nodeIndex, v)
elif objectType == "Window":
self.updateWindow(obj, nodeIndex, v)
elif objectType == "Space":
self.updateSpace(obj, nodeIndex, v)
elif objectType == "Structure":
self.updateStructure(obj, nodeIndex, v)
elif objectType == "PanelCut":
self.updatePanelCut(obj, nodeIndex, v)
elif objectType == "PanelSheet":
self.updatePanelSheet(obj, nodeIndex, v)
elif objectType == "Part::Line" and self.obj.TypeId == "Part::Line":
self.updatePartLine(obj, nodeIndex, v)
elif objectType == "Part" and self.obj.TypeId == "Part::Box":
self.updatePartBox(obj, nodeIndex, v)
obj.recompute()
App.ActiveDocument.commitTransaction()
try:
Gui.ActiveDocument.ActiveView.redraw()
except AttributeError as err:
pass
#---------------------------------------------------------------------------
# EDIT OBJECT TOOLS : Line/Wire/Bspline/Bezcurve
#---------------------------------------------------------------------------
def getWirePts(self,obj):
editpoints = []
for p in obj.Points:
p = obj.getGlobalPlacement().multVec(p)
editpoints.append(p)
return editpoints
def updateWire(self, obj, nodeIndex, v):
pts = obj.Points
editPnt = obj.getGlobalPlacement().inverse().multVec(v)
# DNC: allows to close the curve by placing ends close to each other
tol = 0.001
if ( ( nodeIndex == 0 ) and ( (editPnt - pts[-1]).Length < tol) ) or (
nodeIndex == len(pts) - 1 ) and ( (editPnt - pts[0]).Length < tol):
obj.Closed = True
# DNC: fix error message if edited point coincides with one of the existing points
if ( editPnt in pts ) == True: # checks if point enter is equal to other, this could cause a OCC problem
App.Console.PrintMessage(translate("draft",
"This object does not support possible "
"coincident points, please try again.")
+ "\n")
if Draft.getType(obj) in ["BezCurve"]:
self.resetTrackers(obj)
else:
self.trackers[obj.Name][nodeIndex].set(obj.getGlobalPlacement().
multVec(obj.Points[nodeIndex]))
return
if Draft.getType(obj) in ["BezCurve"]:
pts = self.recomputePointsBezier(obj,pts,nodeIndex,v,obj.Degree,moveTrackers=False)
if obj.Closed:
# check that the new point lies on the plane of the wire
if hasattr(obj.Shape,"normalAt"):
normal = obj.Shape.normalAt(0,0)
point_on_plane = obj.Shape.Vertexes[0].Point
print(v)
v.projectToPlane(point_on_plane, normal)
print(v)
editPnt = obj.getGlobalPlacement().inverse().multVec(v)
pts[nodeIndex] = editPnt
obj.Points = pts
self.trackers[obj.Name][nodeIndex].set(v)
def recomputePointsBezier(self,obj,pts,idx,v,degree,moveTrackers=True):
"""
(object, Points as list, nodeIndex as Int, App.Vector of new point, moveTrackers as Bool)
return the new point list, applying the App.Vector to the given index point
"""
editPnt = v
# DNC: allows to close the curve by placing ends close to each other
tol = 0.001
if ( ( idx == 0 ) and ( (editPnt - pts[-1]).Length < tol) ) or (
idx == len(pts) - 1 ) and ( (editPnt - pts[0]).Length < tol):
obj.Closed = True
# DNC: fix error message if edited point coincides with one of the existing points
#if ( editPnt in pts ) == False:
knot = None
ispole = idx % degree
if ispole == 0: #knot
if degree >= 3:
if idx >= 1: #move left pole
knotidx = idx if idx < len(pts) else 0
pts[idx-1] = pts[idx-1] + editPnt - pts[knotidx]
if moveTrackers:
self.trackers[obj.Name][idx-1].set(pts[idx-1])
if idx < len(pts)-1: #move right pole
pts[idx+1] = pts[idx+1] + editPnt - pts[idx]
if moveTrackers:
self.trackers[obj.Name][idx+1].set(pts[idx+1])
if idx == 0 and obj.Closed: # move last pole
pts[-1] = pts [-1] + editPnt -pts[idx]
if moveTrackers:
self.trackers[obj.Name][-1].set(pts[-1])
elif ispole == 1 and (idx >=2 or obj.Closed): #right pole
knot = idx -1
changep = idx -2 # -1 in case of closed curve
elif ispole == degree-1 and idx <= len(pts)-3: #left pole
knot = idx +1
changep = idx +2
elif ispole == degree-1 and obj.Closed and idx == len(pts)-1: #last pole
knot = 0
changep = 1
if knot is not None: # we need to modify the opposite pole
segment = int(knot / degree) -1
cont = obj.Continuity[segment] if len(obj.Continuity) > segment else 0
if cont == 1: #tangent
pts[changep] = obj.Proxy.modifytangentpole(pts[knot],
editPnt,pts[changep])
if moveTrackers:
self.trackers[obj.Name][changep].set(pts[changep])
elif cont == 2: #symmetric
pts[changep] = obj.Proxy.modifysymmetricpole(pts[knot],editPnt)
if moveTrackers:
self.trackers[obj.Name][changep].set(pts[changep])
pts[idx]=v
return pts #returns the list of new points, taking into account knot continuity
def resetTrackersBezier(self, obj):
#in future move tracker definition to DraftTrackers
from pivy import coin
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[obj.Name] = []
cont = obj.Continuity
firstknotcont = cont[-1] if (obj.Closed and cont) else 0
pointswithmarkers = [(obj.Shape.Edges[0].Curve.
getPole(1),knotmarkers[firstknotcont])]
for edgeindex, edge in enumerate(obj.Shape.Edges):
poles = edge.Curve.getPoles()
pointswithmarkers.extend([(point,polemarker) for point in poles[1:-1]])
if not obj.Closed or len(obj.Shape.Edges) > edgeindex +1:
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)
self.trackers[obj.Name].append(editTracker(p,obj.Name,
index,obj.ViewObject.LineColor,marker=marker))
def smoothBezPoint(self, obj, point, style='Symmetric'):
"called when changing the continuity of a knot"
style2cont = {'Sharp':0,'Tangent':1,'Symmetric':2}
if point is None:
return
if not (Draft.getType(obj) == "BezCurve"):
return
pts = obj.Points
deg = obj.Degree
if deg < 2:
return
if point % deg != 0: #point is a pole
if deg >=3: #allow to select poles
if (point % deg == 1) and (point > 2 or obj.Closed): #right pole
knot = point -1
keepp = point
changep = point -2
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 obj.Closed: #last pole
# if the curve is closed the last pole has the last
# index in the points lists
knot = 0
keepp = point
changep = 1
else:
App.Console.PrintWarning(translate("draft",
"Can't change Knot belonging to pole %d"%point)
+ "\n")
return
if knot:
if style == 'Tangent':
pts[changep] = obj.Proxy.modifytangentpole(pts[knot],
pts[keepp],pts[changep])
elif style == 'Symmetric':
pts[changep] = obj.Proxy.modifysymmetricpole(pts[knot],
pts[keepp])
else: #sharp
pass #
else:
App.Console.PrintWarning(translate("draft",
"Selection is not a Knot")
+ "\n")
return
else: #point is a knot
if style == 'Sharp':
if 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 = obj.Proxy.tangentpoles(pts[point], pts[point-1], pts[point+1])
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 = obj.Proxy.symmetricpoles(pts[point], pts[point-1], pts[point+1])
pts[point-1] = prev
pts[point+1] = next
knot = point #index for continuity
elif obj.Closed and (style == 'Symmetric' or style == 'Tangent'):
if style == 'Tangent':
pts[1], pts[-1] = obj.Proxy.tangentpoles(pts[0], pts[1], pts[-1])
elif style == 'Symmetric':
pts[1], pts[-1] = obj.Proxy.symmetricpoles(pts[0], pts[1], pts[-1])
knot = 0
else:
App.Console.PrintWarning(translate("draft",
"Endpoint of BezCurve can't be smoothed")
+ "\n")
return
segment = knot // deg #segment index
newcont = obj.Continuity[:] #don't edit a property inplace !!!
if not obj.Closed and (len(obj.Continuity) == segment -1 or
segment == 0) :
pass # open curve
elif (len(obj.Continuity) >= segment or obj.Closed and segment == 0 and
len(obj.Continuity) >1):
newcont[segment-1] = style2cont.get(style)
else: #should not happen
App.Console.PrintWarning('Continuity indexing error:'
+ 'point:%d deg:%d len(cont):%d' % (knot,deg,
len(obj.Continuity)))
obj.Points = pts
obj.Continuity = newcont
self.resetTrackers(obj)
#---------------------------------------------------------------------------
# EDIT OBJECT TOOLS : Rectangle
#---------------------------------------------------------------------------
def getRectanglePts(self, obj):
"""
returns the list of edipoints for the given Draft Rectangle
0 : Placement.Base
1 : Length
2 : Height
"""
editpoints = []
editpoints.append(obj.getGlobalPlacement().Base)
editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(obj.Length,0,0)))
editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(0,obj.Height,0)))
return editpoints
def updateRectangleTrackers(self, obj):
self.trackers[obj.Name][0].set(obj.getGlobalPlacement().Base)
self.trackers[obj.Name][1].set(obj.getGlobalPlacement().multVec(App.Vector(obj.Length,0,0)))
self.trackers[obj.Name][2].set(obj.getGlobalPlacement().multVec(App.Vector(0,obj.Height,0)))
def updateRectangle(self, obj, nodeIndex, v):
import DraftVecUtils
delta = obj.getGlobalPlacement().inverse().multVec(v)
if nodeIndex == 0:
#p = obj.getGlobalPlacement()
#p.move(delta)
obj.Placement.move(delta)
elif self.editing == 1:
obj.Length = DraftVecUtils.project(delta,App.Vector(1,0,0)).Length
elif self.editing == 2:
obj.Height = DraftVecUtils.project(delta,App.Vector(0,1,0)).Length
self.updateRectangleTrackers(obj)
#---------------------------------------------------------------------------
# EDIT OBJECT TOOLS : Ellipse (# TODO: yet to be implemented)
#---------------------------------------------------------------------------
def setEllipsePts(self):
return
def updateEllipse(self,v):
return
#---------------------------------------------------------------------------
# EDIT OBJECT TOOLS : Circle/Arc
#---------------------------------------------------------------------------
def getCirclePts(self, obj):
"""
returns the list of edipoints for the given Draft Arc or Circle
circle:
0 : Placement.Base or center
1 : radius
arc:
0 : Placement.Base or center
1 : first endpoint
2 : second endpoint
3 : midpoint
"""
editpoints = []
editpoints.append(obj.getGlobalPlacement().Base)
if obj.FirstAngle == obj.LastAngle:
# obj is a circle
self.ui.editUi("Circle")
editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(obj.Radius,0,0)))
else:
# obj is an arc
self.ui.editUi("Arc")
editpoints.append(self.getArcStart(obj, global_placement=True))#First endpoint
editpoints.append(self.getArcEnd(obj, global_placement=True))#Second endpoint
editpoints.append(self.getArcMid(obj, global_placement=True))#Midpoint
return editpoints
def updateCircleTrackers(self, obj):
self.trackers[obj.Name][0].set(obj.getGlobalPlacement().Base)
self.trackers[obj.Name][1].set(self.getArcStart(obj, global_placement=True))
if len(self.trackers[obj.Name]) > 2:
# object is an arc
self.trackers[obj.Name][2].set(self.getArcEnd(obj, global_placement=True))
self.trackers[obj.Name][3].set(self.getArcMid(obj, global_placement=True))
def updateCircle(self, obj, nodeIndex, v):
delta = obj.getGlobalPlacement().inverse().multVec(v)
local_v = obj.Placement.multVec(delta)
if obj.FirstAngle == obj.LastAngle:
# object is a circle
if nodeIndex == 0:
obj.Placement.Base = local_v
elif nodeIndex == 1:
obj.Radius = delta.Length
else:
# obj is an arc
if self.alt_edit_mode == 0:
# edit arc by 3 points
import Part
if nodeIndex == 0:
#center point
import DraftVecUtils
p1 = self.getArcStart(obj)
p2 = self.getArcEnd(obj)
p0 = DraftVecUtils.project(delta,self.getArcMid(obj))
obj.Radius = p1.sub(p0).Length
obj.FirstAngle = -math.degrees(DraftVecUtils.angle(p1.sub(p0)))
obj.LastAngle = -math.degrees(DraftVecUtils.angle(p2.sub(p0)))
obj.Placement.Base = obj.Placement.multVec(p0)
self.setPlacement(obj)
else:
if nodeIndex == 1:#first point
p1=v
p2=self.getArcMid(obj,global_placement=True)
p3=self.getArcEnd(obj,global_placement=True)
elif nodeIndex == 3:#midpoint
p1=self.getArcStart(obj,global_placement=True)
p2=v
p3=self.getArcEnd(obj,global_placement=True)
elif nodeIndex == 2:#second point
p1=self.getArcStart(obj,global_placement=True)
p2=self.getArcMid(obj,global_placement=True)
p3=v
arc=Part.ArcOfCircle(p1,p2,p3)
obj.Placement.Base = obj.Placement.multVec(obj.getGlobalPlacement().inverse().multVec(arc.Location))
self.setPlacement(obj)
obj.Radius = arc.Radius
delta = self.invpl.multVec(p1)
obj.FirstAngle = math.degrees(math.atan2(delta[1],delta[0]))
delta = self.invpl.multVec(p3)
obj.LastAngle = math.degrees(math.atan2(delta[1],delta[0]))
elif self.alt_edit_mode == 1:
# edit arc by center radius FirstAngle LastAngle
if nodeIndex == 0:
obj.Placement.Base = local_v
self.setPlacement(obj)
else:
dangle = math.degrees(math.atan2(delta[1],delta[0]))
if nodeIndex == 1:
obj.FirstAngle = dangle
elif nodeIndex == 2:
obj.LastAngle = dangle
elif nodeIndex == 3:
obj.Radius = delta.Length
obj.recompute()
self.updateCircleTrackers(obj)
def getArcStart(self, obj, global_placement=False):#Returns object midpoint
if Draft.getType(obj) == "Circle":
return self.pointOnCircle(obj, obj.FirstAngle, global_placement)
def getArcEnd(self, obj, global_placement=False):#Returns object midpoint
if Draft.getType(obj) == "Circle":
return self.pointOnCircle(obj, obj.LastAngle, global_placement)
def getArcMid(self, obj, global_placement=False):#Returns object midpoint
if Draft.getType(obj) == "Circle":
if obj.LastAngle > obj.FirstAngle:
midAngle = obj.FirstAngle + (obj.LastAngle - obj.FirstAngle) / 2.0
else:
midAngle = obj.FirstAngle + (obj.LastAngle - obj.FirstAngle) / 2.0
midAngle += App.Units.Quantity(180,App.Units.Angle)
return self.pointOnCircle(obj, midAngle, global_placement)
def pointOnCircle(self, obj, angle, global_placement=False):
if Draft.getType(obj) == "Circle":
px = obj.Radius * math.cos(math.radians(angle))
py = obj.Radius * math.sin(math.radians(angle))
p = App.Vector(px, py, 0.0)
if global_placement == True:
p = obj.getGlobalPlacement().multVec(p)
return p
return None
def arcInvert(self, obj):
obj.FirstAngle, obj.LastAngle = obj.LastAngle, obj.FirstAngle
obj.recompute()
self.updateCircleTrackers(obj)
#---------------------------------------------------------------------------
# EDIT OBJECT TOOLS : Polygon (maybe could also rotate the polygon)
#---------------------------------------------------------------------------
def getPolygonPts(self, obj):
editpoints = []
editpoints.append(obj.Placement.Base)
editpoints.append(obj.Shape.Vertexes[0].Point)
return editpoints
def updatePolygon(self, obj, nodeIndex, v):
delta = v.sub(self.obj.Placement.Base)
if self.editing == 0:
p = self.obj.Placement
p.move(delta)
self.obj.Placement = p
self.trackers[self.obj.Name][0].set(self.obj.Placement.Base)
elif self.editing == 1:
if self.obj.DrawMode == 'inscribed':
self.obj.Radius = delta.Length
else:
halfangle = ((math.pi*2)/self.obj.FacesNumber)/2
rad = math.cos(halfangle)*delta.Length
self.obj.Radius = rad
self.obj.recompute()
self.trackers[self.obj.Name][1].set(self.obj.Shape.Vertexes[0].Point)
#---------------------------------------------------------------------------
# EDIT OBJECT TOOLS : Dimension (point on dimension line is not clickable)
#---------------------------------------------------------------------------
def getDimensionPts(self, obj):
editpoints = []
p = obj.ViewObject.Proxy.textpos.translation.getValue()
editpoints.append(obj.Start)
editpoints.append(obj.End)
editpoints.append(obj.Dimline)
editpoints.append(App.Vector(p[0],p[1],p[2]))
return editpoints
def updateDimension(self, obj, nodeIndex, v):
if self.editing == 0:
self.obj.Start = v
elif self.editing == 1:
self.obj.End = v
elif self.editing == 2:
self.obj.Dimline = v
elif self.editing == 3:
self.obj.ViewObject.TextPosition = v
#---------------------------------------------------------------------------
# EDIT OBJECT TOOLS : ARCH Wall, Windows, Structure, Panel, etc.
#---------------------------------------------------------------------------
# SKETCH: just if it's composed by a single segment-------------------------
def getSketchPts(self, obj):
"""
returns the list of edipoints for the given single line sketch (WallTrace)
0 : startpoint
1 : endpoint
"""
editpoints = []
if obj.GeometryCount == 1:
editpoints.append(obj.getGlobalPlacement().multVec(obj.getPoint(0,1)))
editpoints.append(obj.getGlobalPlacement().multVec(obj.getPoint(0,2)))
return editpoints
else:
App.Console.PrintWarning(translate("draft",
"Sketch is too complex to edit: "
"it is suggested to use sketcher default editor")
+ "\n")
return None
def updateSketch(self, obj, nodeIndex, v):
"""
(single segment sketch object, node index as Int, App.Vector)
move a single line sketch (WallTrace) vertex according to a given App.Vector
0 : startpoint
1 : endpoint
"""
if nodeIndex == 0:
obj.movePoint(0,1,obj.getGlobalPlacement().inverse().multVec(v))
elif nodeIndex == 1:
obj.movePoint(0,2,obj.getGlobalPlacement().inverse().multVec(v))
obj.recompute()
#WALL-----------------------------------------------------------------------
def getWallPts(self, obj):
"""
returns the list of edipoints for the given Arch Wall object
0 : height of the wall
1-to end : base object editpoints, in place with the wall
"""
editpoints = []
#height of the wall
editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(0,0,obj.Height)))
# try to add here an editpoint based on wall height (maybe should be good to associate it with a circular tracker)
if obj.Base:
# base points are added to self.trackers under wall-name key
basepoints = []
if Draft.getType(obj.Base) in ["Wire","Circle","Rectangle",
"Polygon", "Sketch"]:
basepoints = self.getEditPoints(obj.Base)
for point in basepoints:
editpoints.append(obj.Placement.multVec(point)) #works ok except if App::Part is rotated... why?
return editpoints
def updateWallTrackers(self, obj):
"""
update self.trackers[obj.Name][0] to match with given object
"""
pass
def updateWall(self, obj, nodeIndex, v):
import DraftVecUtils
if nodeIndex == 0:
delta= obj.getGlobalPlacement().inverse().multVec(v)
vz=DraftVecUtils.project(delta,App.Vector(0,0,1))
if vz.Length > 0:
obj.Height = vz.Length
elif nodeIndex > 0:
if obj.Base:
if Draft.getType(obj.Base) in ["Wire","Circle","Rectangle",
"Polygon", "Sketch"]:
self.update(obj.Base, nodeIndex - 1,
obj.Placement.inverse().multVec(v))
obj.recompute()
#WINDOW---------------------------------------------------------------------
def getWindowPts(self, obj):
import DraftGeomUtils
editpoints = []
pos = obj.Base.Placement.Base
h = float(obj.Height) + pos.z
normal = obj.Normal
angle = normal.getAngle(App.Vector(1, 0, 0))
editpoints.append(pos)
editpoints.append(App.Vector(pos.x + float(obj.Width) * math.cos(angle-math.pi / 2.0),
pos.y + float(obj.Width) * math.sin(angle-math.pi / 2.0),
pos.z))
editpoints.append(App.Vector(pos.x, pos.y, h))
return editpoints
def updateWindow(self, obj, nodeIndex, v):
pos=self.obj.Base.Placement.Base
if self.editing == 0:
self.obj.Base.Placement.Base=v
self.obj.Base.recompute()
if self.editing == 1:
self.obj.Width = pos.sub(v).Length
self.obj.Base.recompute()
if self.editing == 2:
self.obj.Height = pos.sub(v).Length
self.obj.Base.recompute()
for obj in self.obj.Hosts:
obj.recompute()
self.obj.recompute()
#STRUCTURE-------------------------------------------------------------------
def getStructurePts(self, obj):
if obj.Nodes:
editpoints = []
self.originalDisplayMode = obj.ViewObject.DisplayMode
self.originalPoints = obj.ViewObject.NodeSize
self.originalNodes = obj.ViewObject.ShowNodes
self.obj.ViewObject.DisplayMode = "Wireframe"
self.obj.ViewObject.NodeSize = 1
# self.obj.ViewObject.ShowNodes = True
for p in obj.Nodes:
if self.pl:
p = self.pl.multVec(p)
editpoints.append(p)
return editpoints
else:
return None
def updateStructure(self, obj, nodeIndex, v):
nodes = self.obj.Nodes
nodes[self.editing] = self.invpl.multVec(v)
self.obj.Nodes = nodes
#SPACE----------------------------------------------------------------------
def getSpacePts(self, obj):
try:
editpoints = []
self.editpoints.append(obj.ViewObject.Proxy.getTextPosition(obj.ViewObject))
return editpoints
except:
pass
def updateSpace(self, obj, nodeIndex, v):
if self.editing == 0:
self.obj.ViewObject.TextPosition = v
#PANELS---------------------------------------------------------------------
def getPanelCutPts(self, obj):
editpoints = []
if self.obj.TagPosition.Length == 0:
pos = obj.Shape.BoundBox.Center
else:
pos = self.pl.multVec(obj.TagPosition)
editpoints.append(pos)
return editpoints
def updatePanelCut(self, obj, nodeIndex, v):
if self.editing == 0:
self.obj.TagPosition = self.invpl.multVec(v)
def getPanelSheetPts(self, obj):
editpoints = []
editpoints.append(self.pl.multVec(obj.TagPosition))
for o in obj.Group:
editpoints.append(self.pl.multVec(o.Placement.Base))
return editpoints
def updatePanelSheet(self, obj, nodeIndex, v):
if self.editing == 0:
self.obj.TagPosition = self.invpl.multVec(v)
else:
self.obj.Group[self.editing-1].Placement.Base = self.invpl.multVec(v)
# PART::LINE----------------------------------------------------------------
def getPartLinePts(self, obj):
editpoints = []
editpoints.append(self.pl.multVec(App.Vector(obj.X1,obj.Y1,obj.Z1)))
editpoints.append(self.pl.multVec(App.Vector(obj.X2,obj.Y2,obj.Z2)))
return editpoints
def updatePartLine(self, obj, nodeIndex, v):
pt=self.invpl.multVec(v)
if self.editing == 0:
self.obj.X1 = pt.x
self.obj.Y1 = pt.y
self.obj.Z1 = pt.z
elif self.editing == 1:
self.obj.X2 = pt.x
self.obj.Y2 = pt.y
self.obj.Z2 = pt.z
# PART::BOX-----------------------------------------------------------------
def getPartBoxPts(self, obj):
editpoints = []
editpoints.append(obj.Placement.Base)
editpoints.append(self.pl.multVec(App.Vector(obj.Length,0,0)))
editpoints.append(self.pl.multVec(App.Vector(0,obj.Width,0)))
editpoints.append(self.pl.multVec(App.Vector(0,0,obj.Height)))
return editpoints
def updatePartBox(self, obj, nodeIndex, v):
import DraftVecUtils
delta = self.invpl.multVec(v)
if self.editing == 0:
self.obj.Placement.Base = v
self.setPlacement(self.obj)
elif self.editing == 1:
xApp.Vector = DraftVecUtils.project(delta,App.Vector(1,0,0))
self.obj.Length = xApp.Vector.Length
elif self.editing == 2:
xApp.Vector = DraftVecUtils.project(delta,App.Vector(0,1,0))
self.obj.Width = xApp.Vector.Length
elif self.editing == 3:
xApp.Vector = DraftVecUtils.project(delta,App.Vector(0,0,1))
self.obj.Height = xApp.Vector.Length
self.trackers[self.obj.Name][0].set(self.obj.Placement.Base)
self.trackers[self.obj.Name][1].set(self.pl.multVec(App.Vector(self.obj.Length,0,0)))
self.trackers[self.obj.Name][2].set(self.pl.multVec(App.Vector(0,self.obj.Width,0)))
self.trackers[self.obj.Name][3].set(self.pl.multVec(App.Vector(0,0,self.obj.Height)))
#---------------------------------------------------------------------------
# Context menu
#---------------------------------------------------------------------------
def display_tracker_menu(self, event):
self.tracker_menu = QtGui.QMenu()
self.event = event
actions = None
if self.overNode:
# if user is over a node
doc = self.overNode.get_doc_name()
obj = App.getDocument(doc).getObject(self.overNode.get_obj_name())
ep = self.overNode.get_subelement_index()
if Draft.getType(obj) in ["Line", "Wire"]:
actions = ["delete point"]
elif Draft.getType(obj) in ["Circle"]:
if obj.FirstAngle != obj.LastAngle:
if ep == 0: # user is over arc start point
actions = ["move arc"]
elif ep == 1: # user is over arc start point
actions = ["set first angle"]
elif ep == 2: # user is over arc end point
actions = ["set last angle"]
elif ep == 3: # user is over arc mid point
actions = ["set radius"]
elif Draft.getType(obj) in ["BezCurve"]:
actions = ["make sharp", "make tangent", "make symmetric", "delete point"]
else:
return
else:
# if user is over an edited object
pos = self.event.getPosition()
obj = self.get_selected_obj_at_position(pos)
if Draft.getType(obj) in ["Line", "Wire","BSpline", "BezCurve"]:
actions = ["add point"]
elif Draft.getType(obj) in ["Circle"] and obj.FirstAngle != obj.LastAngle:
actions = ["invert arc"]
if actions is None:
return
for a in actions:
self.tracker_menu.addAction(a)
self.tracker_menu.popup(Gui.getMainWindow().cursor().pos())
QtCore.QObject.connect(self.tracker_menu,QtCore.SIGNAL("triggered(QAction *)"),self.evaluate_menu_action)
def evaluate_menu_action(self,labelname):
action_label = str(labelname.text())
# Bezier curve menu
if action_label in ["make sharp", "make tangent", "make symmetric"]:
doc = self.overNode.get_doc_name()
obj = App.getDocument(doc).getObject(self.overNode.get_obj_name())
idx = self.overNode.get_subelement_index()
if action_label == "make sharp":
self.smoothBezPoint(obj, idx, 'Sharp')
elif action_label == "make tangent":
self.smoothBezPoint(obj, idx, 'Tangent')
elif action_label == "make symmetric":
self.smoothBezPoint(obj, idx, 'Symmetric')
# addPoint and deletePoint menu
elif action_label == "delete point":
self.delPoint(self.event)
elif action_label == "add point":
self.addPoint(self.event)
# arc tools
elif action_label in ["move arc","set radius", "set first angle", "set last angle"]:
self.alt_edit_mode = 1
self.startEditing(self.event)
elif action_label == "invert arc":
pos = self.event.getPosition()
obj = self.get_selected_obj_at_position(pos)
self.arcInvert(obj)
del self.event
if App.GuiUp:
# setup command
Gui.addCommand('Draft_Edit', Edit())