diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index 67b61722f..c8d70a85f 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -1054,6 +1054,54 @@ void StdCmdViewAxo::activated(int iMsg) doCommand(Command::Gui,"Gui.activeDocument().activeView().viewAxometric()"); } +//=========================================================================== +// Std_ViewRotateLeft +//=========================================================================== +DEF_3DV_CMD(StdCmdViewRotateLeft); + +StdCmdViewRotateLeft::StdCmdViewRotateLeft() + : Command("Std_ViewRotateLeft") +{ + sGroup = QT_TR_NOOP("Standard-View"); + sMenuText = QT_TR_NOOP("Rotate Left"); + sToolTipText = QT_TR_NOOP("Rotate the view by 90° counter-clockwise"); + sWhatsThis = "Std_ViewXX"; + sStatusTip = QT_TR_NOOP("Rotate the view by 90° counter-clockwise"); + sPixmap = "view-rotate-left"; + //sAccel = "Shift Left"; + eType = Alter3DView; +} + +void StdCmdViewRotateLeft::activated(int iMsg) +{ + doCommand(Command::Gui,"Gui.activeDocument().activeView().viewRotateLeft()"); +} + + +//=========================================================================== +// Std_ViewRotateRight +//=========================================================================== +DEF_3DV_CMD(StdCmdViewRotateRight); + +StdCmdViewRotateRight::StdCmdViewRotateRight() + : Command("Std_ViewRotateRight") +{ + sGroup = QT_TR_NOOP("Standard-View"); + sMenuText = QT_TR_NOOP("Rotate Right"); + sToolTipText = QT_TR_NOOP("Rotate the view by 90° clockwise"); + sWhatsThis = "Std_ViewXX"; + sStatusTip = QT_TR_NOOP("Rotate the view by 90° clockwise"); + sPixmap = "view-rotate-right"; + //sAccel = "Shift Right"; + eType = Alter3DView; +} + +void StdCmdViewRotateRight::activated(int iMsg) +{ + doCommand(Command::Gui,"Gui.activeDocument().activeView().viewRotateRight()"); +} + + //=========================================================================== // Std_ViewFitAll //=========================================================================== @@ -2030,6 +2078,8 @@ void CreateViewStdCommands(void) rcCmdMgr.addCommand(new StdCmdViewAxo()); rcCmdMgr.addCommand(new StdCmdViewFitAll()); rcCmdMgr.addCommand(new StdCmdViewFitSelection()); + rcCmdMgr.addCommand(new StdCmdViewRotateLeft()); + rcCmdMgr.addCommand(new StdCmdViewRotateRight()); rcCmdMgr.addCommand(new StdCmdViewExample1()); rcCmdMgr.addCommand(new StdCmdViewExample2()); diff --git a/src/Gui/Icons/Makefile.am b/src/Gui/Icons/Makefile.am index 9366066f5..70c30f0f4 100644 --- a/src/Gui/Icons/Makefile.am +++ b/src/Gui/Icons/Makefile.am @@ -86,6 +86,8 @@ EXTRA_DIST = \ view-zoom-in.svg \ view-zoom-out.svg \ view-zoom-selection.svg \ + view-rotate-left.svg \ + view-rotate-right.svg \ Tree_Annotation.svg \ Tree_Dimension.svg \ Tree_Python.svg \ diff --git a/src/Gui/Icons/resource.qrc b/src/Gui/Icons/resource.qrc index f64cb89bd..78a314d16 100644 --- a/src/Gui/Icons/resource.qrc +++ b/src/Gui/Icons/resource.qrc @@ -68,6 +68,8 @@ view-zoom-fit.svg view-zoom-in.svg view-zoom-out.svg + view-rotate-left.svg + view-rotate-right.svg view-measurement.svg view-zoom-selection.svg Tree_Annotation.svg diff --git a/src/Gui/Icons/view-rotate-left.svg b/src/Gui/Icons/view-rotate-left.svg new file mode 100644 index 000000000..71f10ca01 --- /dev/null +++ b/src/Gui/Icons/view-rotate-left.svg @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/Gui/Icons/view-rotate-right.svg b/src/Gui/Icons/view-rotate-right.svg new file mode 100644 index 000000000..4cbb4fc6b --- /dev/null +++ b/src/Gui/Icons/view-rotate-right.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/Gui/View3DPy.cpp b/src/Gui/View3DPy.cpp index 9ef5e9ccb..b1e1e51a6 100644 --- a/src/Gui/View3DPy.cpp +++ b/src/Gui/View3DPy.cpp @@ -76,6 +76,8 @@ void View3DInventorPy::init_type() add_varargs_method("viewRight",&View3DInventorPy::viewRight,"viewRight()"); add_varargs_method("viewTop",&View3DInventorPy::viewTop,"viewTop()"); add_varargs_method("viewAxometric",&View3DInventorPy::viewAxometric,"viewAxometric()"); + add_varargs_method("viewRotateLeft",&View3DInventorPy::viewRotateLeft,"viewRotateLeft()"); + add_varargs_method("viewRotateRight",&View3DInventorPy::viewRotateRight,"viewRotateRight()"); add_varargs_method("viewPosition",&View3DInventorPy::viewPosition,"viewPosition()"); add_varargs_method("startAnimating",&View3DInventorPy::startAnimating,"startAnimating()"); add_varargs_method("stopAnimating",&View3DInventorPy::stopAnimating,"stopAnimating()"); @@ -407,6 +409,58 @@ Py::Object View3DInventorPy::viewAxometric(const Py::Tuple& args) return Py::None(); } +Py::Object View3DInventorPy::viewRotateLeft(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + try { + SoCamera* cam = _view->getViewer()->getCamera(); + SbRotation rot = cam->orientation.getValue(); + SbVec3f vdir(0, 0, -1); + rot.multVec(vdir, vdir); + SbRotation nrot(vdir, M_PI/2); + cam->orientation.setValue(rot*nrot); + } + catch (const Base::Exception& e) { + throw Py::Exception(e.what()); + } + catch (const std::exception& e) { + throw Py::Exception(e.what()); + } + catch(...) { + throw Py::Exception("Unknown C++ exception"); + } + + return Py::None(); +} + +Py::Object View3DInventorPy::viewRotateRight(const Py::Tuple& args) +{ + if (!PyArg_ParseTuple(args.ptr(), "")) + throw Py::Exception(); + + try { + SoCamera* cam = _view->getViewer()->getCamera(); + SbRotation rot = cam->orientation.getValue(); + SbVec3f vdir(0, 0, -1); + rot.multVec(vdir, vdir); + SbRotation nrot(vdir, -M_PI/2); + cam->orientation.setValue(rot*nrot); + } + catch (const Base::Exception& e) { + throw Py::Exception(e.what()); + } + catch (const std::exception& e) { + throw Py::Exception(e.what()); + } + catch(...) { + throw Py::Exception("Unknown C++ exception"); + } + + return Py::None(); +} + Py::Object View3DInventorPy::setCameraOrientation(const Py::Tuple& args) { PyObject* o; diff --git a/src/Gui/View3DPy.h b/src/Gui/View3DPy.h index 3e9e1218b..43fd65318 100644 --- a/src/Gui/View3DPy.h +++ b/src/Gui/View3DPy.h @@ -55,6 +55,8 @@ public: Py::Object viewTop(const Py::Tuple&); Py::Object viewAxometric(const Py::Tuple&); Py::Object viewPosition(const Py::Tuple&); + Py::Object viewRotateLeft(const Py::Tuple&); + Py::Object viewRotateRight(const Py::Tuple&); Py::Object startAnimating(const Py::Tuple&); Py::Object stopAnimating(const Py::Tuple&); Py::Object setAnimationEnabled(const Py::Tuple&); diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index e173b22e2..b3e1115d0 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -395,7 +395,8 @@ void StdWorkbench::setupContextMenu(const char* recipient, MenuItem* item) const StdViews->setCommand( "Standard views" ); *StdViews << "Std_ViewAxo" << "Separator" << "Std_ViewFront" << "Std_ViewTop" << "Std_ViewRight" - << "Std_ViewRear" << "Std_ViewBottom" << "Std_ViewLeft"; + << "Std_ViewRear" << "Std_ViewBottom" << "Std_ViewLeft" + << "Separator" << "Std_ViewRotateLeft" << "Std_ViewRotateRight"; *item << "Std_ViewFitAll" << "Std_ViewFitSelection" << StdViews << "Separator" << "Std_ViewDockUndockFullscreen"; @@ -447,7 +448,8 @@ MenuItem* StdWorkbench::setupMenuBar() const *stdviews << "Std_ViewFitAll" << "Std_ViewFitSelection" << "Std_ViewAxo" << "Separator" << "Std_ViewFront" << "Std_ViewRight" << "Std_ViewTop" << "Separator" << "Std_ViewRear" - << "Std_ViewLeft" << "Std_ViewBottom"; + << "Std_ViewLeft" << "Std_ViewBottom" + << "Separator" << "Std_ViewRotateLeft" << "Std_ViewRotateRight"; // stereo MenuItem* view3d = new MenuItem; diff --git a/src/Mod/Arch/ArchAxis.py b/src/Mod/Arch/ArchAxis.py index 42af3160e..7dc9f9f8d 100644 --- a/src/Mod/Arch/ArchAxis.py +++ b/src/Mod/Arch/ArchAxis.py @@ -21,10 +21,10 @@ #* * #*************************************************************************** -import FreeCAD,FreeCADGui,Draft,ArchComponent,math +import FreeCAD,FreeCADGui,Draft,math from draftlibs import fcvec from FreeCAD import Vector -from PyQt4 import QtCore +from PyQt4 import QtCore, QtGui from pivy import coin __title__="FreeCAD Axis System" @@ -61,17 +61,15 @@ class _CommandAxis: makeAxis(5,1) FreeCAD.ActiveDocument.commitTransaction() -class _Axis(ArchComponent.Component): +class _Axis: "The Axis object" def __init__(self,obj): obj.addProperty("App::PropertyFloatList","Distances","Base", "The intervals between axes") obj.addProperty("App::PropertyFloatList","Angles","Base", "The angles of each axis") - obj.addProperty("App::PropertyFloatList","Limits","Base", "The inferior and superior drawing limits") + obj.addProperty("App::PropertyFloat","Length","Base", "The length of the axes") self.Type = "Axis" - - obj.Limits=[0.0,1.0] + obj.Length=1.0 obj.Proxy = self - self.Object = obj def execute(self,obj): self.createGeometry(obj) @@ -90,25 +88,29 @@ class _Axis(ArchComponent.Component): for i in range(len(obj.Distances)): dist += obj.Distances[i] ang = math.radians(obj.Angles[i]) - p1 = Vector(dist,obj.Limits[0],0) - p2 = Vector(dist+(obj.Limits[1]/math.cos(ang))*math.sin(ang),obj.Limits[1],0) + p1 = Vector(dist,0,0) + p2 = Vector(dist+(obj.Length/math.cos(ang))*math.sin(ang),obj.Length,0) geoms.append(Part.Line(p1,p2).toShape()) if geoms: obj.Shape = Part.Compound(geoms) obj.Placement = pl -class _ViewProviderAxis(ArchComponent.ViewProviderComponent): +class _ViewProviderAxis: "A View Provider for the Axis object" def __init__(self,vobj): vobj.addProperty("App::PropertyLength","BubbleSize","Base", "The size of the axis bubbles") vobj.addProperty("App::PropertyEnumeration","NumerationStyle","Base", "The numeration style") - vobj.NumerationStyle = ["1,2,3","01,02,03","A,B,C","a,b,c","I,II,III"] + vobj.addProperty("App::PropertyEnumeration","DrawStyle","Base", "The representation style") + vobj.NumerationStyle = ["1,2,3","01,02,03","001,002,003","A,B,C","a,b,c","I,II,III","L0,L1,L2"] + vobj.DrawStyle = ["solid","dotted","dashed","dashdot"] vobj.Proxy = self self.Object = vobj.Object self.ViewObject = vobj vobj.BubbleSize = .1 vobj.LineWidth = 1 + vobj.LineColor = (0.13,0.15,0.37) + vobj.DrawStyle = "dashdot" def getIcon(self): return ":/icons/Arch_Axis_Tree.svg" @@ -117,10 +119,59 @@ class _ViewProviderAxis(ArchComponent.ViewProviderComponent): return [] def attach(self, vobj): - self.Object = vobj.Object self.ViewObject = vobj self.bubbles = None + def getNumber(self,num): + chars = "abcdefghijklmnopqrstuvwxyz" + roman=(('M',1000),('CM',900),('D',500),('CD',400), + ('C',100),('XC',90),('L',50),('XL',40), + ('X',10),('IX',9),('V',5),('IV',4),('I',1)) + if self.ViewObject.NumerationStyle == "1,2,3": + return str(num+1) + elif self.ViewObject.NumerationStyle == "01,02,03": + return str(num+1).zfill(2) + elif self.ViewObject.NumerationStyle == "001,002,003": + return str(num+1).zfill(3) + elif self.ViewObject.NumerationStyle == "A,B,C": + result = "" + base = num/26 + if base: + result += chars[base].upper() + remainder = num % 26 + result += chars[remainder].upper() + return result + elif self.ViewObject.NumerationStyle == "a,b,c": + result = "" + base = num/26 + if base: + result += chars[base] + remainder = num % 26 + result += chars[remainder] + return result + elif self.ViewObject.NumerationStyle == "I,II,III": + result = "" + num += 1 + for numeral, integer in roman: + while num >= integer: + result += numeral + num -= integer + return result + elif self.ViewObject.NumerationStyle == "L0,L1,L2": + return "L"+str(num) + return "" + + def setStyle(self): + ds = self.ViewObject.RootNode.getChild(2).getChild(0).getChild(0).getChild(1) + if self.ViewObject.DrawStyle == "solid": + ds.linePattern = 0xffff + elif self.ViewObject.DrawStyle == "dotted": + ds.linePattern = 0x0f0f + elif self.ViewObject.DrawStyle == "dashed": + ds.linePattern = 0xf00f + elif self.ViewObject.DrawStyle == "dashdot": + ds.linePattern = 0xff88 + def makeBubbles(self): import Part rn = self.ViewObject.RootNode.getChild(2).getChild(0).getChild(0) @@ -128,9 +179,13 @@ class _ViewProviderAxis(ArchComponent.ViewProviderComponent): rn.removeChild(self.bubbles) self.bubbles = None self.bubbles = coin.SoSeparator() - for i in range(len(self.Object.Distances)): - invpl = self.Object.Placement.inverse() - verts = self.Object.Shape.Edges[i].Vertexes + isep = coin.SoSeparator() + self.bubblestyle = coin.SoDrawStyle() + self.bubblestyle.linePattern = 0xffff + self.bubbles.addChild(self.bubblestyle) + for i in range(len(self.ViewObject.Object.Distances)): + invpl = self.ViewObject.Object.Placement.inverse() + verts = self.ViewObject.Object.Shape.Edges[i].Vertexes p1 = invpl.multVec(verts[0].Point) p2 = invpl.multVec(verts[1].Point) dv = p2.sub(p1) @@ -153,12 +208,12 @@ class _ViewProviderAxis(ArchComponent.ViewProviderComponent): fo.size = rad*100 tx = coin.SoText2() tx.justification = coin.SoText2.CENTER - tx.string = str(i) + tx.string = self.getNumber(i) st.addChild(tr) st.addChild(fo) st.addChild(tx) - self.bubbles.addChild(st) - + isep.addChild(st) + self.bubbles.addChild(isep) rn.addChild(self.bubbles) def updateData(self, obj, prop): @@ -169,7 +224,140 @@ class _ViewProviderAxis(ArchComponent.ViewProviderComponent): def onChanged(self, vobj, prop): if prop in ["NumerationStyle","BubbleSize"]: self.makeBubbles() + elif prop == "DrawStyle": + self.setStyle() + elif prop == "LineWidth": + if self.bubbles: + self.bubblestyle.lineWidth = vobj.LineWidth + return + + def setEdit(self,vobj,mode): + taskd = _AxisTaskPanel() + taskd.obj = vobj.Object + taskd.update() + FreeCADGui.Control.showDialog(taskd) + return True + + def unsetEdit(self,vobj,mode): + FreeCADGui.Control.closeDialog() return + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + + +class _AxisTaskPanel: + '''The editmode TaskPanel for Axis objects''' + def __init__(self): + # the panel has a tree widget that contains categories + # for the subcomponents, such as additions, subtractions. + # the categories are shown only if they are not empty. + + self.obj = None + self.form = QtGui.QWidget() + self.form.setObjectName("TaskPanel") + self.grid = QtGui.QGridLayout(self.form) + self.grid.setObjectName("grid") + self.title = QtGui.QLabel(self.form) + self.grid.addWidget(self.title, 0, 0, 1, 2) + + # tree + self.tree = QtGui.QTreeWidget(self.form) + self.grid.addWidget(self.tree, 1, 0, 1, 2) + self.tree.setColumnCount(3) + self.tree.header().resizeSection(0,50) + self.tree.header().resizeSection(1,80) + self.tree.header().resizeSection(2,60) + + # buttons + self.addButton = QtGui.QPushButton(self.form) + self.addButton.setObjectName("addButton") + self.addButton.setIcon(QtGui.QIcon(":/icons/Arch_Add.svg")) + self.grid.addWidget(self.addButton, 3, 0, 1, 1) + self.addButton.setEnabled(True) + + self.delButton = QtGui.QPushButton(self.form) + self.delButton.setObjectName("delButton") + self.delButton.setIcon(QtGui.QIcon(":/icons/Arch_Remove.svg")) + self.grid.addWidget(self.delButton, 3, 1, 1, 1) + self.delButton.setEnabled(True) + + self.okButton = QtGui.QPushButton(self.form) + self.okButton.setObjectName("okButton") + self.okButton.setIcon(QtGui.QIcon(":/icons/edit_OK.svg")) + self.grid.addWidget(self.okButton, 4, 0, 1, 2) + + QtCore.QObject.connect(self.addButton, QtCore.SIGNAL("clicked()"), self.addElement) + QtCore.QObject.connect(self.delButton, QtCore.SIGNAL("clicked()"), self.removeElement) + QtCore.QObject.connect(self.okButton, QtCore.SIGNAL("clicked()"), self.finish) + self.update() + + def isAllowedAlterSelection(self): + return False + + def isAllowedAlterView(self): + return True + + def getStandardButtons(self): + return 0 + def update(self): + 'fills the treewidget' + self.tree.clear() + if self.obj: + for i in range(len(self.obj.Distances)): + item = QtGui.QTreeWidgetItem(self.tree) + item.setText(0,str(i+1)) + item.setText(1,str(self.obj.Distances[i])) + item.setText(2,str(self.obj.Angles[i])) + item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) + item.setTextAlignment(0,QtCore.Qt.AlignLeft) + self.retranslateUi(self.form) + + def addElement(self): + item = QtGui.QTreeWidgetItem(self.tree) + item.setText(0,str(self.tree.topLevelItemCount())) + item.setText(1,"1.0") + item.setText(2,"0.0") + item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) + self.resetObject() + + def removeElement(self): + it = self.tree.currentItem() + if it: + nr = int(it.text(0))-1 + self.resetObject(remove=nr) + self.update() + + def resetObject(self,remove=None): + d = [] + a = [] + for i in range(self.tree.topLevelItemCount()): + it = self.tree.findItems(str(i+1),QtCore.Qt.MatchExactly,0)[0] + if (remove == None) or (remove != i): + d.append(float(it.text(1))) + a.append(float(it.text(2))) + self.obj.Distances = d + self.obj.Angles = a + FreeCAD.ActiveDocument.recompute() + + def finish(self): + self.resetObject() + if self.obj: + self.obj.ViewObject.finishEditing() + + def retranslateUi(self, TaskPanel): + TaskPanel.setWindowTitle(QtGui.QApplication.translate("Arch", "Axes", None, QtGui.QApplication.UnicodeUTF8)) + self.delButton.setText(QtGui.QApplication.translate("Arch", "Remove", None, QtGui.QApplication.UnicodeUTF8)) + self.addButton.setText(QtGui.QApplication.translate("Arch", "Add", None, QtGui.QApplication.UnicodeUTF8)) + self.okButton.setText(QtGui.QApplication.translate("Arch", "Done", None, QtGui.QApplication.UnicodeUTF8)) + self.title.setText(QtGui.QApplication.translate("Arch", "Distances and angles between axes", None, QtGui.QApplication.UnicodeUTF8)) + self.tree.setHeaderLabels([QtGui.QApplication.translate("Arch", "Axis", None, QtGui.QApplication.UnicodeUTF8), + QtGui.QApplication.translate("Arch", "Distance", None, QtGui.QApplication.UnicodeUTF8), + QtGui.QApplication.translate("Arch", "Angle", None, QtGui.QApplication.UnicodeUTF8)]) + FreeCADGui.addCommand('Arch_Axis',_CommandAxis()) diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index f749ff634..b1607b971 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -34,6 +34,7 @@ def addToComponent(compobject,addobject,mod=None): "Components", the first one that exists in the component. Mod can be set to one of those attributes ("Objects", Base", etc...) to override the default.''' + import Draft if compobject == addobject: return # first check is already there found = False @@ -51,11 +52,17 @@ def addToComponent(compobject,addobject,mod=None): if hasattr(compobject,mod): if mod == "Base": setattr(compobject,mod,addobject) + addobject.ViewObject.hide() + elif mod == "Axes": + if Draft.getType(addobject) == "Axis": + l = getattr(compobject,mod) + l.append(addobject) + setattr(compobject,mod,l) else: l = getattr(compobject,mod) l.append(addobject) setattr(compobject,mod,l) - addobject.ViewObject.hide() + addobject.ViewObject.hide() else: for a in attribs[:3]: if hasattr(compobject,a): @@ -103,7 +110,7 @@ class ComponentTaskPanel: # the categories are shown only if they are not empty. self.obj = None - self.attribs = ["Base","Additions","Subtractions","Objects","Components"] + self.attribs = ["Base","Additions","Subtractions","Objects","Components","Axes"] self.form = QtGui.QWidget() self.form.setObjectName("TaskPanel") self.grid = QtGui.QGridLayout(self.form) @@ -150,9 +157,6 @@ class ComponentTaskPanel: def getStandardButtons(self): return 0 - def removeElement(self): - return - def check(self,wid,col): if not wid.parent(): self.delButton.setEnabled(False) @@ -236,6 +240,7 @@ class ComponentTaskPanel: self.treeAdditions.setText(0,QtGui.QApplication.translate("Arch", "Additions", None, QtGui.QApplication.UnicodeUTF8)) self.treeSubtractions.setText(0,QtGui.QApplication.translate("Arch", "Subtractions", None, QtGui.QApplication.UnicodeUTF8)) self.treeObjects.setText(0,QtGui.QApplication.translate("Arch", "Objects", None, QtGui.QApplication.UnicodeUTF8)) + self.treeAxes.setText(0,QtGui.QApplication.translate("Arch", "Axes", None, QtGui.QApplication.UnicodeUTF8)) self.treeComponents.setText(0,QtGui.QApplication.translate("Arch", "Components", None, QtGui.QApplication.UnicodeUTF8)) class Component: @@ -251,7 +256,6 @@ class Component: "The normal extrusion direction of this wall (keep (0,0,0) for automatic normal)") obj.Proxy = self self.Type = "Component" - self.Object = obj self.Subvolume = None diff --git a/src/Mod/Arch/ArchStructure.py b/src/Mod/Arch/ArchStructure.py index af7674e42..daa7b25e9 100644 --- a/src/Mod/Arch/ArchStructure.py +++ b/src/Mod/Arch/ArchStructure.py @@ -84,15 +84,32 @@ class _Structure(ArchComponent.Component): "The width of this element, if not based on a profile") obj.addProperty("App::PropertyLength","Height","Base", "The height or extrusion depth of this element. Keep 0 for automatic") + obj.addProperty("App::PropertyLinkList","Axes","Base", + "Axes systems this structure is built on") self.Type = "Structure" def execute(self,obj): self.createGeometry(obj) def onChanged(self,obj,prop): - if prop in ["Base","Length","Width","Height","Normal","Additions","Subtractions"]: + if prop in ["Base","Length","Width","Height","Normal","Additions","Subtractions","Axes"]: self.createGeometry(obj) + def getAxisPoints(self,obj): + "returns the gridpoints of linked axes" + from draftlibs import fcgeo + pts = [] + if len(obj.Axes) == 1: + for e in obj.Axes[0].Shape.Edges: + pts.append(e.Vertexes[0].Point) + elif len(obj.Axes) >= 2: + set1 = obj.Axes[0].Shape.Edges + set2 = obj.Axes[1].Shape.Edges + for e1 in set1: + for e2 in set2: + pts.extend(fcgeo.findIntersection(e1,e2)) + return pts + def createGeometry(self,obj): import Part from draftlibs import fcgeo @@ -158,7 +175,16 @@ class _Structure(ArchComponent.Component): base = base.cut(hole.Shape) hole.ViewObject.hide() # to be removed if base: - obj.Shape = base + pts = self.getAxisPoints(obj) + if pts: + fsh = [] + for p in pts: + sh = base.copy() + sh.translate(p) + fsh.append(sh) + obj.Shape = Part.makeCompound(fsh) + else: + obj.Shape = base if not fcgeo.isNull(pl): obj.Placement = pl class _ViewProviderStructure(ArchComponent.ViewProviderComponent): diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index bbca0e2b1..347d25071 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -86,36 +86,37 @@ def readOpenShell(filename): import Mesh getIfcOpenShell() if IfcOpenShell: - IfcOpenShell.Init(filename) - while True: - obj = IfcOpenShell.Get() - print "parsing ",obj.guid,": ",obj.name," of type ",obj.type - meshdata = [] - n = obj.name - if not n: n = "Unnamed" - f = obj.mesh.faces - v = obj.mesh.verts - m = obj.matrix - print "verts: ",len(v)," faces: ",len(f) - mat = FreeCAD.Matrix(m[0], m[1], m[2], 0, - m[3], m[4], m[5], 0, - m[6], m[7], m[8], 0, - m[9], m[10], m[11], 1) - for i in range(0, len(f), 3): - print "face ",f[i],f[i+1],f[i+2] - face = [] - print "i:",i - for j in range(3): - vi = f[i+j]*3 - print "vi:",vi - face.append([v[vi],v[vi+1],v[vi+2]]) - meshdata.append(face) - newmesh = Mesh.Mesh(meshdata) - mobj = FreeCAD.ActiveDocument.addObject("Mesh::Feature",n) - mobj.Mesh = newmesh - mobj.Placement = FreeCAD.Placement(mat) - if not IfcOpenShell.Next(): - break + if IfcOpenShell.Init(filename): + while True: + obj = IfcOpenShell.Get() + print "parsing ",obj.guid,": ",obj.name," of type ",obj.type + meshdata = [] + n = obj.name + if not n: n = "Unnamed" + f = obj.mesh.faces + v = obj.mesh.verts + m = obj.matrix + print "verts: ",len(v)," faces: ",len(f) + mat = FreeCAD.Matrix(m[0], m[3], m[6], m[9], + m[1], m[4], m[7], m[10], + m[2], m[5], m[8], m[11], + 0, 0, 0, 1) + for i in range(0, len(f), 3): + print "face ",f[i],f[i+1],f[i+2] + face = [] + print "i:",i + for j in range(3): + vi = f[i+j]*3 + print "vi:",vi + face.append([v[vi],v[vi+1],v[vi+2]]) + meshdata.append(face) + newmesh = Mesh.Mesh(meshdata) + mobj = FreeCAD.ActiveDocument.addObject("Mesh::Feature",n) + mobj.Mesh = newmesh + mobj.Placement = FreeCAD.Placement(mat) + if not IfcOpenShell.Next(): + break + IfcOpenShell.CleanUp() return None def read(filename): diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 22457a4e2..b6e22cd6e 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -283,21 +283,40 @@ def select(objs=None): FreeCADGui.Selection.addSelection(obj) def makeCircle(radius, placement=None, face=True, startangle=None, endangle=None, support=None): - '''makeCircle(radius,[placement,face,startangle,endangle]): Creates a circle - object with given radius. If placement is given, it is + '''makeCircle(radius,[placement,face,startangle,endangle]) + or makeCircle(edge,[face]): + Creates a circle object with given radius. If placement is given, it is used. If face is False, the circle is shown as a wireframe, otherwise as a face. If startangle AND endangle are given - (in degrees), they are used and the object appears as an arc.''' + (in degrees), they are used and the object appears as an arc. If an edge + is passed, its Curve must be a Part.Circle''' + import Part if placement: typecheck([(placement,FreeCAD.Placement)], "makeCircle") obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","Circle") _Circle(obj) _ViewProviderDraft(obj.ViewObject) - obj.Radius = radius + if isinstance(radius,Part.Edge): + edge = radius + if isinstance(edge.Curve,Part.Circle): + obj.Radius = edge.Curve.Radius + placement = FreeCAD.Placement(edge.Placement) + delta = edge.Curve.Center.sub(placement.Base) + placement.move(delta) + if len(edge.Vertexes) > 1: + ref = placement.multVec(FreeCAD.Vector(1,0,0)) + v1 = (edge.Vertexes[0].Point).sub(edge.Curve.Center) + v2 = (edge.Vertexes[-1].Point).sub(edge.Curve.Center) + a1 = -math.degrees(fcvec.angle(v1,ref)) + a2 = -math.degrees(fcvec.angle(v2,ref)) + obj.FirstAngle = a1 + obj.LastAngle = a2 + else: + obj.Radius = radius + if (startangle != None) and (endangle != None): + if startangle == -0: startangle = 0 + obj.FirstAngle = startangle + obj.LastAngle = endangle if not face: obj.ViewObject.DisplayMode = "Wireframe" - if (startangle != None) and (endangle != None): - if startangle == -0: startangle = 0 - obj.FirstAngle = startangle - obj.LastAngle = endangle obj.Support = support if placement: obj.Placement = placement formatObject(obj) @@ -517,13 +536,13 @@ def makeCopy(obj): _Block(newobj) _ViewProviderDraftPart(newobj.ViewObject) elif getType(obj) == "Structure": - import Structure - Structure._Structure(newobj) - Structure._ViewProviderStructure(newobj.ViewObject) + import Arch + Arch._Structure(newobj) + Arch._ViewProviderStructure(newobj.ViewObject) elif getType(obj) == "Wall": - import Wall - Wall._Wall(newobj) - Wall._ViewProviderWall(newobj.ViewObject) + import Arch + Arch._Wall(newobj) + Arch._ViewProviderWall(newobj.ViewObject) elif obj.isDerivedFrom("Part::Feature"): newobj.Shape = obj.Shape else: @@ -937,6 +956,7 @@ def draftify(objectslist,makeblock=False): (objectslist can also be a single object) into a Draft parametric wire. If makeblock is True, multiple objects will be grouped in a block''' from draftlibs import fcgeo + import Part if not isinstance(objectslist,list): objectslist = [objectslist] newobjlist = [] @@ -944,8 +964,11 @@ def draftify(objectslist,makeblock=False): if obj.isDerivedFrom('Part::Feature'): for w in obj.Shape.Wires: if fcgeo.hasCurves(w): - nobj = FreeCAD.ActiveDocument.addObject("Part::Feature",name) - nobj.Shape = w + if (len(w.Edges) == 1) and isinstance(w.Edges[0].Curve,Part.Circle): + nobj = makeCircle(w.Edges[0]) + else: + nobj = FreeCAD.ActiveDocument.addObject("Part::Feature",obj.Name) + nobj.Shape = w else: nobj = makeWire(w) if obj.Shape.Faces: @@ -1222,6 +1245,7 @@ def makeSketch(objectslist,autoconstraints=False,addTo=None,name="Sketch"): nobj = addTo else: nobj = FreeCAD.ActiveDocument.addObject("Sketcher::SketchObject",name) + nobj.ViewObject.Autoconstraints = False for obj in objectslist: ok = False tp = getType(obj) @@ -1274,7 +1298,9 @@ def makeSketch(objectslist,autoconstraints=False,addTo=None,name="Sketch"): if fcgeo.hasOnlyWires(obj.Shape): for w in obj.Shape.Wires: for edge in fcgeo.sortEdges(w.Edges): - nobj.addGeometry(fcgeo.geom(edge)) + g = fcgeo.geom(edge) + if g: + nobj.addGeometry(g) if autoconstraints: last = nobj.GeometryCount segs = range(last-len(w.Edges),last-1) @@ -1429,7 +1455,7 @@ class _ViewProviderDimension: obj.addProperty("App::PropertyLength","LineWidth","Base","Line width") obj.addProperty("App::PropertyColor","LineColor","Base","Line color") obj.addProperty("App::PropertyLength","ExtLines","Base","Ext lines") - obj.addProperty("App::PropertyVector","Position","Base","The position of the text. Leave (0,0,0) for automatic position") + obj.addProperty("App::PropertyVector","TextPosition","Base","The position of the text. Leave (0,0,0) for automatic position") obj.addProperty("App::PropertyString","Override","Base","Text override. Use 'dim' to insert the dimension length") obj.Proxy = self obj.FontSize=getParam("textheight") @@ -1476,10 +1502,10 @@ class _ViewProviderDimension: if hasattr(obj.ViewObject,"DisplayMode"): if obj.ViewObject.DisplayMode == "3D": offset = fcvec.neg(offset) - if obj.ViewObject.Position == Vector(0,0,0): + if obj.ViewObject.TextPosition == Vector(0,0,0): tbase = midpoint.add(offset) else: - tbase = obj.ViewObject.Position + tbase = obj.ViewObject.TextPosition rot = FreeCAD.Placement(fcvec.getPlaneRotation(u,v,norm)).Rotation.Q return p1,p2,p3,p4,tbase,norm,rot @@ -1719,7 +1745,7 @@ class _ViewProviderAngularDimension: obj.addProperty("App::PropertyString","FontName","Base","Font name") obj.addProperty("App::PropertyLength","LineWidth","Base","Line width") obj.addProperty("App::PropertyColor","LineColor","Base","Line color") - obj.addProperty("App::PropertyVector","Position","Base","The position of the text. Leave (0,0,0) for automatic position") + obj.addProperty("App::PropertyVector","TextPosition","Base","The position of the text. Leave (0,0,0) for automatic position") obj.addProperty("App::PropertyString","Override","Base","Text override. Use 'dim' to insert the dimension length") obj.Proxy = self obj.FontSize=getParam("textheight") diff --git a/src/Mod/Draft/DraftSnap.py b/src/Mod/Draft/DraftSnap.py index 7bd5ce5da..14d409009 100644 --- a/src/Mod/Draft/DraftSnap.py +++ b/src/Mod/Draft/DraftSnap.py @@ -152,7 +152,7 @@ class Snapper: if self.extLine: self.extLine.off() - point = FreeCADGui.ActiveDocument.ActiveView.getPoint(screenpos[0],screenpos[1]) + point = self.getApparentPoint(screenpos[0],screenpos[1]) # check if we snapped to something info = FreeCADGui.ActiveDocument.ActiveView.getObjectInfo((screenpos[0],screenpos[1])) @@ -259,6 +259,12 @@ class Snapper: # return the final point return cstr(winner[2]) + + def getApparentPoint(self,x,y): + "returns a 3D point, projected on the current working plane" + pt = FreeCADGui.ActiveDocument.ActiveView.getPoint(x,y) + dv = FreeCADGui.ActiveDocument.ActiveView.getViewDirection() + return FreeCAD.DraftWorkingPlane.projectPoint(pt,dv) def snapToExtensions(self,point,last,constrain): "returns a point snapped to extension or parallel line to last object, if any" @@ -515,7 +521,8 @@ class Snapper: delta = point.sub(self.basepoint) # setting constraint axis - self.affinity = FreeCAD.DraftWorkingPlane.getClosestAxis(delta) + if not self.affinity: + self.affinity = FreeCAD.DraftWorkingPlane.getClosestAxis(delta) if isinstance(axis,FreeCAD.Vector): self.constraintAxis = axis elif axis == "x": diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 62d7643e6..6e21bb347 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -455,9 +455,9 @@ class Line(Creator): if len(edges) > 1: edges.pop() newshape = Part.Wire(edges) + self.obj.Shape = newshape else: - newshape = Part.Shape() - self.obj.Shape = newshape + self.obj.ViewObject.hide() # DNC: report on removal msg(translate("draft", "Last point has been removed\n")) @@ -2114,7 +2114,8 @@ class Offset(Modifier): v2 = fcgeo.getTangent(self.shape.Edges[dist[1]],point) a = -fcvec.angle(v1,v2) self.dvec = fcvec.rotate(d,a,plane.axis) - self.ghost.update(fcgeo.offsetWire(self.shape,self.dvec,occ=self.ui.occOffset.isChecked())) + occmode = self.ui.occOffset.isChecked() + self.ghost.update(fcgeo.offsetWire(self.shape,self.dvec,occ=occmode),forceclosed=occmode) elif self.mode == "Circle": self.dvec = point.sub(self.center).Length self.ghost.setRadius(self.dvec) @@ -3080,8 +3081,9 @@ class Edit(Modifier): if self.obj: self.obj = self.obj[0] # store selectable state of the object - self.selectstate = self.obj.ViewObject.Selectable - self.obj.ViewObject.Selectable = False + if hasattr(self.obj.ViewObject,"Selectable"): + self.selectstate = self.obj.ViewObject.Selectable + self.obj.ViewObject.Selectable = False if not Draft.getType(self.obj) in ["Wire","BSpline"]: self.ui.setEditButtons(False) else: @@ -3148,7 +3150,8 @@ class Edit(Modifier): t.finalize() if self.constraintrack: self.constraintrack.finalize() - self.obj.ViewObject.Selectable = self.selectstate + if hasattr(self.obj.ViewObject,"Selectable"): + self.obj.ViewObject.Selectable = self.selectstate Modifier.finish(self) plane.restore() self.running = False @@ -3188,13 +3191,15 @@ class Edit(Modifier): self.ui.isRelative.show() self.editing = int(snapped['Component'][8:]) self.trackers[self.editing].off() - self.obj.ViewObject.Selectable = False + if hasattr(self.obj.ViewObject,"Selectable"): + self.obj.ViewObject.Selectable = False if "Points" in self.obj.PropertiesList: self.node.append(self.obj.Points[self.editing]) else: print "finishing edit" self.trackers[self.editing].on() - self.obj.ViewObject.Selectable = True + if hasattr(self.obj.ViewObject,"Selectable"): + self.obj.ViewObject.Selectable = True self.numericInput(self.trackers[self.editing].get()) def update(self,v): @@ -3540,6 +3545,9 @@ class Draft2Sketch(): allDraft = False elif obj.isDerivedFrom("Part::Part2DObjectPython"): allSketches = False + else: + allDraft = False + allSketches = False if not sel: return elif allDraft: @@ -3554,9 +3562,12 @@ class Draft2Sketch(): FreeCAD.ActiveDocument.openTransaction("Convert") for obj in sel: if obj.isDerivedFrom("Sketcher::SketchObject"): - Draft.makeSketch(sel,autoconstraints=True) + Draft.draftify(obj) elif obj.isDerivedFrom("Part::Part2DObjectPython"): - Draft.draftify(sel,makeblock=True) + Draft.makeSketch(obj,autoconstraints=True) + elif obj.isDerivedFrom("Part::Feature"): + if len(obj.Shape.Wires) == 1: + Draft.makeSketch(obj,autoconstraints=False) FreeCAD.ActiveDocument.commitTransaction() diff --git a/src/Mod/Draft/DraftTrackers.py b/src/Mod/Draft/DraftTrackers.py index 7311d23f5..c071f6ef1 100644 --- a/src/Mod/Draft/DraftTrackers.py +++ b/src/Mod/Draft/DraftTrackers.py @@ -505,16 +505,16 @@ class wireTracker(Tracker): self.update(wire) Tracker.__init__(self,children=[self.coords,self.line]) - def update(self,wire): + def update(self,wire,forceclosed=False): if wire: - if self.closed: + if self.closed or forceclosed: self.line.numVertices.setValue(len(wire.Vertexes)+1) else: self.line.numVertices.setValue(len(wire.Vertexes)) for i in range(len(wire.Vertexes)): p=wire.Vertexes[i].Point self.coords.point.set1Value(i,[p.x,p.y,p.z]) - if self.closed: + if self.closed or forceclosed: t = len(wire.Vertexes) p = wire.Vertexes[0].Point self.coords.point.set1Value(t,[p.x,p.y,p.z]) diff --git a/src/Mod/Draft/draftlibs/fcgeo.py b/src/Mod/Draft/draftlibs/fcgeo.py index 2d1355260..0c5c95089 100755 --- a/src/Mod/Draft/draftlibs/fcgeo.py +++ b/src/Mod/Draft/draftlibs/fcgeo.py @@ -361,14 +361,17 @@ def geom(edge): return edge.Curve elif isinstance(edge.Curve,Part.Circle): if len(edge.Vertexes) == 1: - return edge.Curve + return Part.Circle(edge.Curve.Center,edge.Curve.Axis,edge.Curve.Radius) else: + ref = edge.Placement.multVec(Vector(1,0,0)) v1 = edge.Vertexes[0].Point v2 = edge.Vertexes[-1].Point c = edge.Curve.Center - a1 = -fcvec.angle(v1.sub(c)) - a2 = -fcvec.angle(v2.sub(c)) - return Part.ArcOfCircle(edge.Curve,a1,a2) + cu = Part.Circle(edge.Curve.Center,edge.Curve.Axis,edge.Curve.Radius) + a1 = -fcvec.angle(v1.sub(c),ref) + a2 = -fcvec.angle(v2.sub(c),ref) + p= Part.ArcOfCircle(cu,a1,a2) + return p else: return edge.Curve @@ -764,12 +767,20 @@ def connect(edges,closed=False): next = None if prev: # print "debug: fcgeo.connect prev : ",prev.Vertexes[0].Point,prev.Vertexes[-1].Point - v1 = findIntersection(curr,prev,True,True)[0] + i = findIntersection(curr,prev,True,True) + if i: + v1 = i[0] + else: + v1 = curr.Vertexes[0].Point else: v1 = curr.Vertexes[0].Point if next: # print "debug: fcgeo.connect next : ",next.Vertexes[0].Point,next.Vertexes[-1].Point - v2 = findIntersection(curr,next,True,True)[0] + i = findIntersection(curr,next,True,True) + if i: + v2 = i[0] + else: + v2 = curr.Vertexes[-1].Point else: v2 = curr.Vertexes[-1].Point if isinstance(curr.Curve,Part.Line): @@ -778,7 +789,10 @@ def connect(edges,closed=False): elif isinstance(curr.Curve,Part.Circle): if v1 != v2: nedges.append(Part.Arc(v1,findMidPoint(curr),v2)) - return Part.Wire(nedges) + try: + return Part.Wire(nedges) + except: + return None def findDistance(point,edge,strict=False): '''