├── .gitignore ├── CompoundFilter2.py ├── ExampleProjects ├── Lattice2WorkbenchBasicTutorial.pdf ├── LinearArray-Alignment-Demo.FCStd ├── bicycle-cogset-tutorial.FCStd ├── bike-chain-example.FCStd ├── circular-text-example.FCStd ├── pdpattern-body-tutorial.FCStd └── pdpattern-item-removal.FCStd ├── FuseCompound2.py ├── InitGui.py ├── Lattice2.py ├── Lattice2ArrayFeatures.py ├── Lattice2CodeModules.py ├── Lattice2CompoundFeatures.py ├── Lattice2GuiTools.py ├── Lattice2PartDesignFeatures.py ├── PyResources ├── icons.qrc ├── icons │ ├── Lattice2.svg │ ├── Lattice2_ArrayFilter.svg │ ├── Lattice2_ArrayFromShape.svg │ ├── Lattice2_AttachablePlacement.svg │ ├── Lattice2_AttachedPlacementSubsequence.svg │ ├── Lattice2_BoundBox.svg │ ├── Lattice2_BoundBox_Compound.svg │ ├── Lattice2_CompoundFilter.svg │ ├── Lattice2_Downgrade.svg │ ├── Lattice2_Explode.svg │ ├── Lattice2_ExplodeArray.svg │ ├── Lattice2_FuseCompound.svg │ ├── Lattice2_Inspect.svg │ ├── Lattice2_Invert.svg │ ├── Lattice2_JoinArrays.svg │ ├── Lattice2_LinearArray.svg │ ├── Lattice2_LinearArray_New.svg │ ├── Lattice2_MakeCompound.svg │ ├── Lattice2_Mirror.svg │ ├── Lattice2_Mirror_PlmPlms.svg │ ├── Lattice2_Mirror_PlmSh.svg │ ├── Lattice2_Mirror_PlmsPlms.svg │ ├── Lattice2_Mirror_ShPlms.svg │ ├── Lattice2_Mirror_ShSh.svg │ ├── Lattice2_Mirror_ShsPlms.svg │ ├── Lattice2_PDPattern.svg │ ├── Lattice2_ParaSeries.svg │ ├── Lattice2_Placement.svg │ ├── Lattice2_PlacementFromShape.svg │ ├── Lattice2_PlacementShooter.svg │ ├── Lattice2_Placement_New.svg │ ├── Lattice2_PolarArray.svg │ ├── Lattice2_PolarArray_New.svg │ ├── Lattice2_PopulateChildren_Array.svg │ ├── Lattice2_PopulateChildren_Move.svg │ ├── Lattice2_PopulateChildren_Normal.svg │ ├── Lattice2_PopulateChildren_Plms_Array.svg │ ├── Lattice2_PopulateChildren_Plms_Move.svg │ ├── Lattice2_PopulateChildren_Plms_Normal.svg │ ├── Lattice2_PopulateCopies_Array.svg │ ├── Lattice2_PopulateCopies_Move.svg │ ├── Lattice2_PopulateCopies_Normal.svg │ ├── Lattice2_PopulateCopies_Plms_Array.svg │ ├── Lattice2_PopulateCopies_Plms_Move.svg │ ├── Lattice2_PopulateCopies_Plms_Normal.svg │ ├── Lattice2_ProjectArray.svg │ ├── Lattice2_RecomputeLocker_ForceRecompute.svg │ ├── Lattice2_RecomputeLocker_LockRecomputes.svg │ ├── Lattice2_RecomputeLocker_Locked.svg │ ├── Lattice2_RecomputeLocker_MakeFeature.svg │ ├── Lattice2_RecomputeLocker_RecomputeDocument.svg │ ├── Lattice2_RecomputeLocker_RecomputeFeature.svg │ ├── Lattice2_RecomputeLocker_Touch.svg │ ├── Lattice2_RecomputeLocker_UnlockRecomputes.svg │ ├── Lattice2_RecomputeLocker_Unlocked.svg │ ├── Lattice2_Resample.svg │ ├── Lattice2_ScLERP.svg │ ├── Lattice2_ShapeInfoFeature.svg │ ├── Lattice2_ShapeString.svg │ ├── Lattice2_Slice.svg │ ├── Lattice2_SubLink.svg │ ├── Lattice2_SubLinkSubsequence.svg │ ├── Lattice2_SubLinkSubsequence_Edge.svg │ ├── Lattice2_SubLinkSubsequence_Face.svg │ ├── Lattice2_SubLinkSubsequence_Vertex.svg │ ├── Lattice2_SubLink_Edge.svg │ ├── Lattice2_SubLink_Face.svg │ ├── Lattice2_SubLink_Vertex.svg │ ├── Lattice2_SubstituteObject.svg │ ├── Lattice2_TopoSeries.svg │ └── Lattice2_ViewFromPlacement.svg ├── recompile_rc.bat └── wb-icon.qrc ├── README.md ├── copying.lib ├── fonts ├── FreeUniversal-Bold.ttf ├── FreeUniversal-BoldItalic.ttf ├── FreeUniversal-Italic.ttf ├── FreeUniversal-Regular.ttf └── license.txt ├── lattice2ArrayFilter.py ├── lattice2ArrayFromShape.py ├── lattice2AttachablePlacement.py ├── lattice2Base ├── Autosize.py ├── Containers.py ├── Rounder.py └── __init__.py ├── lattice2BaseFeature.py ├── lattice2BoundBox.py ├── lattice2Common.py ├── lattice2Compatibility.py ├── lattice2CompoundExplorer.py ├── lattice2Downgrade.py ├── lattice2Dummy.py ├── lattice2Executer.py ├── lattice2ExposeLinkSub.py ├── lattice2GeomUtils.py ├── lattice2HelpCommands.py ├── lattice2InjectedToolbars.py ├── lattice2Inspect.py ├── lattice2InterpolateGroup.py ├── lattice2InterpolatorUtil.py ├── lattice2Invert.py ├── lattice2JoinArrays.py ├── lattice2LinearArray.py ├── lattice2MakeCompound.py ├── lattice2Markers.py ├── lattice2Mirror.py ├── lattice2PDPattern.py ├── lattice2PDPatternCommand.py ├── lattice2ParaSeries.py ├── lattice2Placement.py ├── lattice2PlacementShooter.py ├── lattice2PolarArray.py ├── lattice2PolarArray2.py ├── lattice2PopulateChildren.py ├── lattice2PopulateCopies.py ├── lattice2Preferences.py ├── lattice2ProjectArray.py ├── lattice2RecomputeLocker.py ├── lattice2Resample.py ├── lattice2ScLERP.py ├── lattice2SeriesGroup.py ├── lattice2ShapeCopy.py ├── lattice2ShapeInfoFeature.py ├── lattice2ShapeString.py ├── lattice2Slice.py ├── lattice2SubLink.py ├── lattice2Subsequencer.py ├── lattice2SubstituteObject.py ├── lattice2TopoSeries.py ├── lattice2Utils.py ├── lattice2ValueSeriesGenerator.py ├── lattice2ViewFromPlacement.py ├── lattice2_rc.py ├── lattice2_rc_minimal.py ├── package.xml ├── shapes ├── empty-shape.FCStd ├── empty-shape.brep ├── paperplane-orimarker.FCStd ├── paperplane-orimarker.brep ├── tetra-orimarker.FCStd └── tetra-orimarker.brep └── ui ├── TaskPlacementShooter.ui └── pref ├── lattice2-pref-general.ui ├── pref.qrc └── recompile-rc.bat /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | *.FCStd1 59 | -------------------------------------------------------------------------------- /ExampleProjects/Lattice2WorkbenchBasicTutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/ExampleProjects/Lattice2WorkbenchBasicTutorial.pdf -------------------------------------------------------------------------------- /ExampleProjects/LinearArray-Alignment-Demo.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/ExampleProjects/LinearArray-Alignment-Demo.FCStd -------------------------------------------------------------------------------- /ExampleProjects/bicycle-cogset-tutorial.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/ExampleProjects/bicycle-cogset-tutorial.FCStd -------------------------------------------------------------------------------- /ExampleProjects/bike-chain-example.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/ExampleProjects/bike-chain-example.FCStd -------------------------------------------------------------------------------- /ExampleProjects/circular-text-example.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/ExampleProjects/circular-text-example.FCStd -------------------------------------------------------------------------------- /ExampleProjects/pdpattern-body-tutorial.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/ExampleProjects/pdpattern-body-tutorial.FCStd -------------------------------------------------------------------------------- /ExampleProjects/pdpattern-item-removal.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/ExampleProjects/pdpattern-item-removal.FCStd -------------------------------------------------------------------------------- /FuseCompound2.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | from lattice2Common import * 25 | 26 | 27 | __title__="FuseCompound module for FreeCAD" 28 | __author__ = "DeepSOIC" 29 | __url__ = "" 30 | 31 | # -------------------------- document object -------------------------------------------------- 32 | 33 | def makeFuseCompound(name): 34 | '''makeFuseCompound(name): makes a FuseCompound object.''' 35 | obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name) 36 | _FuseCompound(obj) 37 | obj.Refine = getParamRefine() 38 | if FreeCAD.GuiUp: 39 | _ViewProviderFuseCompound(obj.ViewObject) 40 | return obj 41 | 42 | class _FuseCompound: 43 | "The FuseCompound object" 44 | def __init__(self,obj): 45 | self.Type = "FuseCompound" 46 | obj.addProperty("App::PropertyLink","Base","FuseCompound","Compound with self-intersections to be fused") 47 | obj.addProperty("App::PropertyBool","Refine","FuseCompound","True = refine resulting shape. False = output as is.") 48 | obj.addProperty("App::PropertyInteger","recomputeQuota","FuseCompound","recompute limiter. Will decrease by one each time it recomputes. Setting to zero disables recomputes. Setting negative makes recomputes unlimited.") 49 | obj.recomputeQuota = -1 50 | obj.Proxy = self 51 | 52 | 53 | def execute(self,obj): 54 | rst = None 55 | shps = screen(obj.Base).Shape.childShapes() 56 | if len(shps) > 1: 57 | rst = shps[0].multiFuse(shps[1:]) 58 | if obj.Refine: 59 | rst = rst.removeSplitter() 60 | obj.Shape = rst 61 | else: 62 | obj.Shape = shps[0] 63 | return 64 | 65 | 66 | class _ViewProviderFuseCompound: 67 | "A View Provider for the FuseCompound object" 68 | 69 | def __init__(self,vobj): 70 | vobj.Proxy = self 71 | 72 | def getIcon(self): 73 | return getIconPath("Lattice2_FuseCompound.svg") 74 | 75 | def attach(self, vobj): 76 | self.ViewObject = vobj 77 | self.Object = vobj.Object 78 | 79 | def __getstate__(self): 80 | return None 81 | 82 | def __setstate__(self,state): 83 | return None 84 | 85 | def dumps(self): 86 | return None 87 | 88 | def loads(self,state): 89 | return None 90 | 91 | def claimChildren(self): 92 | return [screen(self.Object.Base)] 93 | 94 | def onDelete(self, feature, subelements): # subelements is a tuple of strings 95 | try: 96 | screen(self.Object.Base).ViewObject.show() 97 | except Exception as err: 98 | FreeCAD.Console.PrintError("Error in onDelete: " + str(err)) 99 | return True 100 | 101 | # -------------------------- /document object -------------------------------------------------- 102 | 103 | # -------------------------- Gui command -------------------------------------------------- 104 | 105 | def CreateFuseCompound(name): 106 | FreeCAD.ActiveDocument.openTransaction("Create FuseCompound") 107 | FreeCADGui.addModule("FuseCompound2") 108 | FreeCADGui.addModule("lattice2Executer") 109 | FreeCADGui.doCommand("f = FuseCompound2.makeFuseCompound(name = '"+name+"')") 110 | FreeCADGui.doCommand("f.Base = FreeCADGui.Selection.getSelection()[0]") 111 | FreeCADGui.doCommand("lattice2Executer.executeFeature(f)") 112 | FreeCADGui.doCommand("f.Base.ViewObject.hide()") 113 | FreeCADGui.doCommand("f = None") 114 | FreeCAD.ActiveDocument.commitTransaction() 115 | 116 | class _CommandFuseCompound: 117 | "Command to create FuseCompound feature" 118 | def GetResources(self): 119 | return {'Pixmap' : getIconPath("Lattice2_FuseCompound.svg"), 120 | 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_FuseCompound","Fuse compound"), 121 | 'Accel': "", 122 | 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_FuseCompound","Fuse objects contained in a compound")} 123 | 124 | def Activated(self): 125 | if len(FreeCADGui.Selection.getSelection()) == 1 : 126 | CreateFuseCompound(name = "FuseCompound") 127 | else: 128 | mb = QtGui.QMessageBox() 129 | mb.setIcon(mb.Icon.Warning) 130 | mb.setText(translate("Lattice2_FuseCompound", "Select a shape that is a compound whose children intersect, first!", None)) 131 | mb.setWindowTitle(translate("Lattice2_FuseCompound","Bad selection", None)) 132 | mb.exec_() 133 | 134 | def IsActive(self): 135 | if FreeCAD.ActiveDocument: 136 | return activeBody() is None 137 | else: 138 | return False 139 | 140 | if FreeCAD.GuiUp: 141 | FreeCADGui.addCommand('Lattice2_FuseCompound', _CommandFuseCompound()) 142 | 143 | exportedCommands = ['Lattice2_FuseCompound'] 144 | 145 | # -------------------------- /Gui command -------------------------------------------------- 146 | -------------------------------------------------------------------------------- /InitGui.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* copyright (c) 2015 - victor titov (deepsoic) * 4 | #* * 5 | #* * 6 | #* this program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the gnu lesser general public license (lgpl) * 8 | #* as published by the free software foundation; either version 2 of * 9 | #* the license, or (at your option) any later version. * 10 | #* for detail see the licence text file. * 11 | #* * 12 | #* this program is distributed in the hope that it will be useful, * 13 | #* but without any warranty; without even the implied warranty of * 14 | #* merchantability or fitness for a particular purpose. see the * 15 | #* gnu library general public license for more details. * 16 | #* * 17 | #* you should have received a copy of the gnu library general public * 18 | #* license along with this program; if not, write to the free software * 19 | #* foundation, inc., 59 temple place, suite 330, boston, ma 02111-1307 * 20 | #* usa * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __Comment__ = 'Advanced array tools and parametric compounding tools' 25 | __Web__ = 'http://forum.freecadweb.org/viewtopic.php?f=22&t=12464' 26 | __Wiki__ = '' 27 | __Icon__ = '' 28 | __Help__ = 'Install as a workbench - copy everything to path/to/FreeCAD/Mod/Lattice2' 29 | __Author__ = 'DeepSOIC' 30 | __Version__ = '2' 31 | __Status__ = 'alpha' 32 | __Requires__ = 'freecad 0.16.5155' 33 | __Communication__ = 'vv.titov@gmail.com; DeepSOIC on FreeCAD forum' 34 | 35 | import lattice2_rc_minimal 36 | import lattice2Preferences 37 | 38 | import lattice2InjectedToolbars as TB 39 | TB.registerPDToolbar() 40 | 41 | class Lattice2Workbench (Workbench): 42 | MenuText = 'Lattice2' 43 | ToolTip = "Lattice2 workbench" 44 | 45 | def __init__(self): 46 | # Hack: obtain path to Lattice by loading a dummy Py module 47 | import os 48 | import lattice2Dummy 49 | self.__class__.Icon = os.path.dirname(lattice2Dummy.__file__) + u"/PyResources/icons/Lattice2.svg".replace("/", os.path.sep) 50 | 51 | def Initialize(self): 52 | 53 | import lattice2HelpCommands 54 | 55 | cmdsHelp = lattice2HelpCommands.exportedCommands 56 | self.appendMenu('Lattice2', cmdsHelp + ['Separator']) 57 | 58 | import Lattice2 59 | cmdsArrayTools = ([] 60 | + Lattice2.ArrayFeatures.Placement.exportedCommands 61 | + Lattice2.ArrayFeatures.AttachablePlacement.exportedCommands 62 | + Lattice2.ArrayFeatures.LinearArray.exportedCommands 63 | + Lattice2.ArrayFeatures.PolarArray2.exportedCommands 64 | + Lattice2.ArrayFeatures.PlacementShooter.exportedCommands 65 | + Lattice2.ArrayFeatures.ArrayFromShape.exportedCommands 66 | + Lattice2.ArrayFeatures.Invert.exportedCommands 67 | + Lattice2.ArrayFeatures.JoinArrays.exportedCommands 68 | + Lattice2.ArrayFeatures.ArrayFilter.exportedCommands 69 | + Lattice2.ArrayFeatures.ProjectArray.exportedCommands 70 | + Lattice2.ArrayFeatures.InterpolateGroup.exportedCommands 71 | + Lattice2.ArrayFeatures.PopulateCopies.exportedCommands 72 | + Lattice2.ArrayFeatures.PopulateChildren.exportedCommands 73 | + Lattice2.ArrayFeatures.Mirror.exportedCommands 74 | ) 75 | self.appendToolbar('Lattice2ArrayFeatres', cmdsArrayTools) 76 | self.appendMenu('Lattice2', cmdsArrayTools) 77 | 78 | cmdsCompoundTools = ([] 79 | + Lattice2.CompoundFeatures.Downgrade.exportedCommands 80 | + Lattice2.CompoundFeatures.SubLink.exportedCommands 81 | + Lattice2.CompoundFeatures.MakeCompound.exportedCommands 82 | + Lattice2.CompoundFeatures.CompoundFilter.exportedCommands 83 | + Lattice2.CompoundFeatures.FuseCompound.exportedCommands 84 | + Lattice2.CompoundFeatures.Slice.exportedCommands 85 | + Lattice2.CompoundFeatures.BoundBox.exportedCommands 86 | + Lattice2.CompoundFeatures.ShapeString.exportedCommands 87 | + Lattice2.CompoundFeatures.SeriesGroup.exportedCommands 88 | ) 89 | self.appendToolbar('Lattice2CompoundFeatures', cmdsCompoundTools) 90 | self.appendMenu('Lattice2', cmdsCompoundTools) 91 | 92 | cmdsPDTools = ([] 93 | + Lattice2.PartDesignFeatures.PDPatternCommand.exportedCommands 94 | ) 95 | self.appendToolbar('Lattice2PartDesignFeatres', cmdsPDTools) 96 | self.appendMenu('Lattice2', cmdsPDTools) 97 | 98 | cmdsGuiTools = ([] 99 | + Lattice2.GuiTools.Inspect.exportedCommands 100 | + Lattice2.GuiTools.SubstituteObject.exportedCommands 101 | + Lattice2.GuiTools.ViewFromPlacement.exportedCommands 102 | ) 103 | self.appendToolbar('Lattice2GuiTools', cmdsGuiTools) 104 | self.appendMenu('Lattice2', cmdsGuiTools + Lattice2.GuiTools.ExposeLinkSub.exportedCommands) 105 | 106 | 107 | cmdsRecomputeLocker = ([] 108 | + Lattice2.GuiTools.RecomputeLocker.exportedCommands 109 | ) 110 | self.appendToolbar('Lattice2RecomputeLocker', cmdsRecomputeLocker) 111 | self.appendMenu('Recomputes', cmdsRecomputeLocker) 112 | 113 | def Activated(self): 114 | pass 115 | 116 | 117 | 118 | Gui.addWorkbench(Lattice2Workbench()) 119 | 120 | -------------------------------------------------------------------------------- /Lattice2.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__="Lattice module for FreeCAD. The file that imports all Lattice components" 25 | __author__ = "DeepSOIC" 26 | __url__ = "https://github.com/DeepSOIC/Lattice2" 27 | 28 | import Lattice2CodeModules as Code 29 | 30 | import Lattice2CompoundFeatures as CompoundFeatures 31 | 32 | import Lattice2ArrayFeatures as ArrayFeatures 33 | 34 | import Lattice2PartDesignFeatures as PartDesignFeatures 35 | 36 | import Lattice2GuiTools as GuiTools 37 | 38 | import lattice2_rc as resource_module 39 | 40 | import lattice2Compatibility as Compatibility 41 | -------------------------------------------------------------------------------- /Lattice2ArrayFeatures.py: -------------------------------------------------------------------------------- 1 | import lattice2ArrayFilter as ArrayFilter 2 | import lattice2ArrayFromShape as ArrayFromShape 3 | import lattice2AttachablePlacement as AttachablePlacement 4 | import lattice2BaseFeature as BaseFeature 5 | import lattice2InterpolateGroup as InterpolateGroup 6 | import lattice2Invert as Invert 7 | import lattice2JoinArrays as JoinArrays 8 | import lattice2LinearArray as LinearArray 9 | import lattice2Placement as Placement 10 | import lattice2PlacementShooter as PlacementShooter 11 | import lattice2PolarArray as PolarArray 12 | import lattice2PolarArray2 as PolarArray2 13 | import lattice2PopulateChildren as PopulateChildren 14 | import lattice2PopulateCopies as PopulateCopies 15 | import lattice2ProjectArray as ProjectArray 16 | import lattice2Resample as Resample 17 | import lattice2ScLERP as ScLERP 18 | import lattice2Mirror as Mirror -------------------------------------------------------------------------------- /Lattice2CodeModules.py: -------------------------------------------------------------------------------- 1 | import lattice2Common as _common_code 2 | import lattice2CompoundExplorer as CompoundExplorer 3 | import lattice2Dummy as Dummy 4 | import lattice2Executer as Executer 5 | import lattice2GeomUtils as GeomUtils 6 | import lattice2InterpolatorUtil as InterpolatorUtil 7 | import lattice2Markers as Markers 8 | import lattice2ValueSeriesGenerator as ValueSeriesGenerator 9 | import lattice2ShapeCopy as ShapeCopy 10 | import lattice2Subsequencer as Subsequencer 11 | import lattice2Utils as Utils -------------------------------------------------------------------------------- /Lattice2CompoundFeatures.py: -------------------------------------------------------------------------------- 1 | import lattice2SubLink as SubLink 2 | import lattice2ShapeInfoFeature as ShapeInfoFeature 3 | import CompoundFilter2 as CompoundFilter 4 | import FuseCompound2 as FuseCompound 5 | import lattice2Downgrade as Downgrade 6 | import lattice2BoundBox as BoundBox 7 | import lattice2ShapeString as ShapeString 8 | import lattice2MakeCompound as MakeCompound 9 | import lattice2ParaSeries as ParaSeries 10 | import lattice2TopoSeries as TopoSeries 11 | import lattice2SeriesGroup as SeriesGroup 12 | import lattice2Slice as Slice -------------------------------------------------------------------------------- /Lattice2GuiTools.py: -------------------------------------------------------------------------------- 1 | import lattice2Inspect as Inspect 2 | import lattice2RecomputeLocker as RecomputeLocker 3 | import lattice2SubstituteObject as SubstituteObject 4 | import lattice2ExposeLinkSub as ExposeLinkSub 5 | import lattice2ViewFromPlacement as ViewFromPlacement -------------------------------------------------------------------------------- /Lattice2PartDesignFeatures.py: -------------------------------------------------------------------------------- 1 | import lattice2PDPattern as PDPattern 2 | import lattice2PDPatternCommand as PDPatternCommand 3 | -------------------------------------------------------------------------------- /PyResources/icons.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/Lattice2_ArrayFilter.svg 4 | icons/Lattice2_ArrayFromShape.svg 5 | icons/Lattice2_AttachablePlacement.svg 6 | icons/Lattice2_AttachedPlacementSubsequence.svg 7 | icons/Lattice2_BoundBox.svg 8 | icons/Lattice2_BoundBox_Compound.svg 9 | icons/Lattice2_CompoundFilter.svg 10 | icons/Lattice2_Downgrade.svg 11 | icons/Lattice2_Explode.svg 12 | icons/Lattice2_ExplodeArray.svg 13 | icons/Lattice2_FuseCompound.svg 14 | icons/Lattice2_Inspect.svg 15 | icons/Lattice2_Invert.svg 16 | icons/Lattice2_JoinArrays.svg 17 | icons/Lattice2_LinearArray.svg 18 | icons/Lattice2_LinearArray_New.svg 19 | icons/Lattice2_MakeCompound.svg 20 | icons/Lattice2_Mirror.svg 21 | icons/Lattice2_Mirror_PlmSh.svg 22 | icons/Lattice2_Mirror_PlmPlms.svg 23 | icons/Lattice2_Mirror_PlmsPlms.svg 24 | icons/Lattice2_Mirror_ShsPlms.svg 25 | icons/Lattice2_Mirror_ShPlms.svg 26 | icons/Lattice2_Mirror_ShSh.svg 27 | icons/Lattice2_ParaSeries.svg 28 | icons/Lattice2_PDPattern.svg 29 | icons/Lattice2_Placement.svg 30 | icons/Lattice2_Placement_New.svg 31 | icons/Lattice2_PlacementShooter.svg 32 | icons/Lattice2_PlacementFromShape.svg 33 | icons/Lattice2_PolarArray.svg 34 | icons/Lattice2_PolarArray_New.svg 35 | icons/Lattice2_PopulateChildren_Array.svg 36 | icons/Lattice2_PopulateChildren_Move.svg 37 | icons/Lattice2_PopulateChildren_Normal.svg 38 | icons/Lattice2_PopulateChildren_Plms_Array.svg 39 | icons/Lattice2_PopulateChildren_Plms_Normal.svg 40 | icons/Lattice2_PopulateChildren_Plms_Move.svg 41 | icons/Lattice2_PopulateCopies_Array.svg 42 | icons/Lattice2_PopulateCopies_Move.svg 43 | icons/Lattice2_PopulateCopies_Normal.svg 44 | icons/Lattice2_PopulateCopies_Plms_Array.svg 45 | icons/Lattice2_PopulateCopies_Plms_Normal.svg 46 | icons/Lattice2_PopulateCopies_Plms_Move.svg 47 | icons/Lattice2_ProjectArray.svg 48 | icons/Lattice2_RecomputeLocker_ForceRecompute.svg 49 | icons/Lattice2_RecomputeLocker_Locked.svg 50 | icons/Lattice2_RecomputeLocker_LockRecomputes.svg 51 | icons/Lattice2_RecomputeLocker_MakeFeature.svg 52 | icons/Lattice2_RecomputeLocker_RecomputeDocument.svg 53 | icons/Lattice2_RecomputeLocker_RecomputeFeature.svg 54 | icons/Lattice2_RecomputeLocker_Touch.svg 55 | icons/Lattice2_RecomputeLocker_Unlocked.svg 56 | icons/Lattice2_RecomputeLocker_UnlockRecomputes.svg 57 | icons/Lattice2_Resample.svg 58 | icons/Lattice2_ScLERP.svg 59 | icons/Lattice2_ShapeInfoFeature.svg 60 | icons/Lattice2_ShapeString.svg 61 | icons/Lattice2_Slice.svg 62 | icons/Lattice2_SubLink.svg 63 | icons/Lattice2_SubLink_Edge.svg 64 | icons/Lattice2_SubLink_Face.svg 65 | icons/Lattice2_SubLink_Vertex.svg 66 | icons/Lattice2_SubLinkSubsequence.svg 67 | icons/Lattice2_SubLinkSubsequence_Edge.svg 68 | icons/Lattice2_SubLinkSubsequence_Face.svg 69 | icons/Lattice2_SubLinkSubsequence_Vertex.svg 70 | icons/Lattice2_SubstituteObject.svg 71 | icons/Lattice2_TopoSeries.svg 72 | icons/Lattice2_ViewFromPlacement.svg 73 | 74 | 75 | -------------------------------------------------------------------------------- /PyResources/icons/Lattice2_ArrayFilter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 18 | 20 | 24 | 28 | 29 | 31 | 35 | 39 | 40 | 42 | 46 | 50 | 51 | 53 | 57 | 58 | 60 | 64 | 65 | 67 | 71 | 75 | 76 | 78 | 82 | 86 | 87 | 95 | 103 | 105 | 109 | 113 | 114 | 122 | 123 | 125 | 126 | 128 | image/svg+xml 129 | 131 | 132 | 133 | [maxwxyz] 134 | 135 | 136 | https://www.freecad.org/wiki/index.php?title=Artwork 137 | 138 | 139 | FreeCAD 140 | 141 | 142 | FreeCAD/src/ 143 | 144 | 145 | FreeCAD LGPL2+ 146 | 147 | 148 | 2024 149 | 150 | 151 | 152 | 154 | 158 | 162 | 166 | 170 | 174 | 175 | 184 | 185 | 189 | 193 | 197 | 201 | 205 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /PyResources/icons/Lattice2_CompoundFilter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 18 | 20 | 24 | 28 | 29 | 31 | 35 | 36 | 38 | 42 | 43 | 45 | 49 | 53 | 54 | 56 | 60 | 64 | 65 | 74 | 76 | 80 | 84 | 85 | 94 | 96 | 100 | 104 | 105 | 106 | 108 | 109 | 111 | image/svg+xml 112 | 114 | 115 | 116 | [maxwxyz] 117 | 118 | 119 | https://www.freecad.org/wiki/index.php?title=Artwork 120 | 121 | 122 | FreeCAD 123 | 124 | 125 | FreeCAD/src/ 126 | 127 | 128 | FreeCAD LGPL2+ 129 | 130 | 131 | 2024 132 | 133 | 134 | 135 | 137 | 141 | 145 | 149 | 153 | 157 | 161 | 165 | 169 | 170 | 174 | 178 | 182 | 183 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /PyResources/icons/Lattice2_Downgrade.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 18 | 20 | 24 | 25 | 27 | 31 | 35 | 36 | 38 | 42 | 43 | 45 | 49 | 50 | 52 | 56 | 60 | 61 | 63 | 67 | 71 | 72 | 81 | 83 | 87 | 91 | 92 | 93 | 95 | 96 | 98 | image/svg+xml 99 | 101 | 102 | 103 | [maxwxyz] 104 | 105 | 106 | https://www.freecad.org/wiki/index.php?title=Artwork 107 | 108 | 109 | FreeCAD 110 | 111 | 112 | FreeCAD/src/ 113 | 114 | 115 | FreeCAD LGPL2+ 116 | 117 | 118 | 2024 119 | 120 | 121 | 122 | 124 | 128 | 132 | 136 | 140 | 144 | 148 | 149 | 153 | 157 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /PyResources/icons/Lattice2_FuseCompound.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 18 | 20 | 24 | 25 | 27 | 31 | 35 | 36 | 38 | 42 | 43 | 45 | 49 | 50 | 52 | 56 | 60 | 61 | 63 | 67 | 71 | 72 | 82 | 84 | 88 | 92 | 93 | 103 | 105 | 109 | 113 | 114 | 115 | 117 | 118 | 120 | image/svg+xml 121 | 123 | 124 | 125 | [maxwxyz] 126 | 127 | 128 | https://www.freecad.org/wiki/index.php?title=Artwork 129 | 130 | 131 | FreeCAD 132 | 133 | 134 | FreeCAD/src/ 135 | 136 | 137 | FreeCAD LGPL2+ 138 | 139 | 140 | 2024 141 | 142 | 143 | 144 | 146 | 150 | 154 | 158 | 162 | 163 | 167 | 169 | 173 | 177 | 178 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /PyResources/icons/Lattice2_Placement.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 18 | 20 | 24 | 25 | 27 | 31 | 35 | 36 | 38 | 42 | 46 | 47 | 49 | 53 | 54 | 56 | 60 | 61 | 63 | 67 | 71 | 72 | 74 | 78 | 82 | 83 | 92 | 94 | 98 | 102 | 103 | 112 | 114 | 118 | 122 | 123 | 125 | 129 | 133 | 134 | 142 | 150 | 158 | 160 | 164 | 168 | 169 | 170 | 172 | 173 | 175 | image/svg+xml 176 | 178 | 179 | 180 | [maxwxyz] 181 | 182 | 183 | https://www.freecad.org/wiki/index.php?title=Artwork 184 | 185 | 186 | FreeCAD 187 | 188 | 189 | FreeCAD/src/ 190 | 191 | 192 | FreeCAD LGPL2+ 193 | 194 | 195 | 2024 196 | 197 | 198 | 199 | 203 | 207 | 211 | 215 | 219 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /PyResources/icons/Lattice2_ShapeInfoFeature.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 18 | 20 | 24 | 28 | 29 | 31 | 35 | 39 | 40 | 42 | 46 | 50 | 51 | 53 | 57 | 61 | 62 | 64 | 68 | 72 | 73 | 75 | 79 | 83 | 84 | 86 | 90 | 91 | 93 | 97 | 98 | 100 | 104 | 108 | 109 | 111 | 115 | 119 | 120 | 128 | 138 | 139 | 141 | 142 | 144 | image/svg+xml 145 | 147 | 148 | 149 | [maxwxyz] 150 | 151 | 152 | https://www.freecad.org/wiki/index.php?title=Artwork 153 | 154 | 155 | FreeCAD 156 | 157 | 158 | FreeCAD/src/ 159 | 160 | 161 | FreeCAD LGPL2+ 162 | 163 | 164 | 2024 165 | 166 | 167 | 168 | 171 | 175 | 179 | 183 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /PyResources/icons/Lattice2_Slice.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | image/svg+xml 37 | 38 | 39 | 40 | 41 | [DeepSOIC] 42 | 43 | 44 | Part_Slice 45 | 2016-07-29 46 | https://www.freecad.org/wiki/index.php?title=Artwork 47 | 48 | 49 | FreeCAD 50 | 51 | 52 | FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Slice.svg 53 | 54 | 55 | FreeCAD LGPL2+ 56 | 57 | 58 | https://www.gnu.org/copyleft/lesser.html 59 | 60 | 61 | [agryson] Alexander Gryson 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /PyResources/icons/Lattice2_TopoSeries.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 18 | 20 | 24 | 28 | 29 | 31 | 35 | 39 | 40 | 42 | 46 | 50 | 51 | 53 | 57 | 61 | 62 | 64 | 68 | 69 | 71 | 75 | 76 | 78 | 82 | 86 | 87 | 89 | 93 | 97 | 98 | 107 | 116 | 125 | 126 | 128 | 129 | 131 | image/svg+xml 132 | 134 | 135 | 136 | [maxwxyz] 137 | 138 | 139 | https://www.freecad.org/wiki/index.php?title=Artwork 140 | 141 | 142 | FreeCAD 143 | 144 | 145 | FreeCAD/src/ 146 | 147 | 148 | FreeCAD LGPL2+ 149 | 150 | 151 | 2024 152 | 153 | 154 | 155 | 158 | 162 | 166 | 170 | 174 | 177 | 184 | 189 | 190 | 198 | 206 | 207 | 208 | -------------------------------------------------------------------------------- /PyResources/recompile_rc.bat: -------------------------------------------------------------------------------- 1 | pyside-rcc icons.qrc -py3 -o ..\lattice2_rc.py 2 | pyside-rcc wb-icon.qrc -py3 -o ..\lattice2_rc_minimal.py 3 | echo Should be compiled now =) 4 | pause -------------------------------------------------------------------------------- /PyResources/wb-icon.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/Lattice2.svg 4 | icons/Lattice2.svg 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lattice2 Workbench 2 | [![Total alerts](https://img.shields.io/lgtm/alerts/g/DeepSOIC/Lattice2.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/DeepSOIC/Lattice2/alerts/) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/DeepSOIC/Lattice2.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/DeepSOIC/Lattice2/context:python) 3 | 4 | Lattice Workbench is a plug-in module/addon (workbench) for FreeCAD. 5 | 6 | It's purpose is working with placements and arrays of placements. It functions similar to what an Assembly workbench does, but with emphasis on arrays. There are **no** constraints and relations, there are just arrays of placements that can be generated, combined, transformed, superimposed, and populated with shapes. 7 | 8 | Ever wondered how to create a protractor with FreeCAD? That's the aim of this workbench (including tick labeling). Also, exploded assemblies can be made with this workbench. 9 | 10 | Additionally, the workbench features a few general-purpose tools, such as parametric downgrade, bounding boxes, shape info tool, and tools for working with collections of shapes (compounds). 11 | 12 | One of the big design goals of the workbench is being as parametric as possible. 13 | 14 | ## Getting started 15 | 16 | Follow through the [Basic Tutorial](https://github.com/DeepSOIC/Lattice2/wiki/Basic-Tutorial) to get the basic concept of Lattice2. 17 | 18 | ## Highlights 19 | ![Lattice2-FreeCAD-wormcutter](https://raw.githubusercontent.com/wiki/DeepSOIC/Lattice2/gallery/worm-cutter-done.png) 20 | 21 | ![Lattice2-FreeCAD-placement-interpolator](https://raw.githubusercontent.com/wiki/DeepSOIC/Lattice2/gallery/placement_interpolator_fixed.png) 22 | 23 | Take a look at other examples in the [Gallery of screenshots](https://github.com/DeepSOIC/Lattice2/wiki/Gallery). 24 | 25 | ## Features 26 | Let's have a glance over the most important capabilities that the workbench adds to FreeCAD: 27 | 28 | * Re-use arrays as many times as you need. Unlike Draft array, which directly generates the array of shapes, lattice array tools generate arrays of placements. These can later be populated with shapes, as many times as necessary, without having to set up another array. 29 | * Extends PartDesign workflow, offering a way to reuse a sequence of features in arbitrary bodies and places. 30 | * Elements of array can be different. Unlike Draft Arrays, which always generate a set of equal shapes, Lattice arrays can be used to arrange a set of different shapes. Pack the shapes to be arranged into a Compound, and use Lattice [Populate with children](https://github.com/DeepSOIC/Lattice2/wiki/Feature-PopulateChildren) feature to arrange them. 31 | * Arrays of placements can be combined, inverted, generated from existing shape arrangements, made from individual placements, projected onto shapes, filtered, etc. This allows to produce complex arrangements without scripting. 32 | * Single placements can be used for primitive assembling of parts. 33 | * linear arrays and polar arrays can have their axes linked to edges of shapes 34 | * [ParaSeries](https://github.com/DeepSOIC/Lattice2/wiki/Feature-ParaSeries) feature allows to generate a series of parts with some parameter varied over a list of values. 35 | 36 | ## Why Lattice2, not just Lattice? 37 | Lattice2 was created at the moment when breaking changes needed to be made to Lattice, but there were a few things made with Lattice. So, it was decided to keep the workbench in that time's state indefinitely as version 1.0, and start development of a new version. 38 | 39 | The goal was to allow editing old projects made with Lattice v1, by having both versions installed at the same time. So a new repository was started, and all the files were renamed to start with 'lattice2' or otherwise differ from those of Lattice v1. 40 | 41 | Lattice3 (if ever) will be a standalone repository, for the same reasons. 42 | 43 | ## Installation 44 | 45 | ### Prerequisites 46 | 47 | * FreeCAD >= `v0.16.5155` 48 | * PartDesign tools require `v0.17+` 49 | * Both Py2/Qt4 and Py3/Qt5 builds are supported. 50 | 51 | The workbench is OS independent, it should work on any system FreeCAD can be run on. If you find that it doesn't - that is a bug. Please open an ticket in the [issue queue](https://github.com/DeepSOIC/Lattice2/issues). 52 | 53 | **Note:** Lattice2 is written in FreeCAD's Python, and **must be run from within FreeCAD**. It requires no compilation, and can be installed by copying the repository to a special location. 54 | 55 | ### Automated install 56 | 57 | There are several options to automate installation of this workbench. 58 | * The **most recommended method** is to use FreeCAD's built-in [Addon Manager](https://github.com/FreeCAD/FreeCAD-addons#1-builtin-addon-manager) (Tools > Addon manager). 59 | * Another method; Lattice2 workbench is packaged to Launchpad in the Ubuntu FreeCAD Community PPA (thanks to @abdullahtahiriyo). 60 | * Lattice2 can be installed via @microelly's [Plugin Loader](https://github.com/microelly2/freecad-pluginloader) (this option is deprecated). 61 | 62 | **Note:** Any of the above options will require a restart of FreeCAD in order for the workbench to function. 63 | 64 | ### Manual install 65 | 66 |
67 | Expand this section if you prefer to manually install Lattice2 68 | 69 | 1. Download the workbench. There are several ways to do this, you can choose either: 70 | * Scroll to the top of the page, and click 'clone or download' -> 'download zip' button 71 | * `git clone https://github.com/DeepSOIC/Lattice2` 72 | 2. If you downloaded the .zip, unpack the archive and rename it to `Lattice2`. If you used `git clone` then ignore this step. 73 | 3. Move the newly created `Lattice2` directory to where your default FreeCAD install directory is located: 74 | * Windows: (sytem-wide install) `%AppData%\FreeCAD\Mod\Lattice2` 75 | * Windows: (for individual installs) 76 | `C:\Program Files\FreeCAD\Mod\Lattice2` 77 | * Linux: `~/.FreeCAD/Mod/Lattice2` 78 | * MacOS: `~/.FreeCAD/Mod/Lattice2` 79 | 3. Restart FreeCAD 80 | 81 | **Important Note:** Make sure that `InitGui.py` (and the rest of `.py` files) end up directly under `Mod\Lattice2` directory (**not** under nested directory like `Mod\Lattice2\Lattice2`). 82 | 83 |
84 | 85 | ## Usage 86 | 87 | After installing the workbench and restarting FC, Lattice2 should now appear in the workbench dropdown menu. It will be listed down towards the bottom of list. Now, you can familiarize yourself with Lattice2 through the [Basic Tutorial](https://github.com/DeepSOIC/Lattice2/wiki/Basic-Tutorial). 88 | 89 | Side Note: If you want to install the workbench for development, `git clone` the repository wherever you like, and make a symlink in where FreeCAD can pick it up as an add-on. 90 | 91 | ## Status 92 | 93 | The workbench is stable. I will take care to not make breaking changes, and some new functionality may keep coming. 94 | 95 | If you make your FreeCAD project using Lattice2, all further changes to the project must be done with Lattice2 installed, even if you don't touch the relevant features. Otherwise, the parametric features in the project will lose their bound functionality, and will not recompute, even if you install Lattice2 later. This is the case for all add-ons in FreeCAD, not just Lattice2. 96 | 97 | ## Getting Help 98 | 99 | For Documentation see the [Lattice2 wiki](https://github.com/DeepSOIC/Lattice2/wiki) on Github. As the word "wiki" suggests, you can help by editing the documentation. 100 | 101 | If you need help on something specific, you can ask a question on [FreeCAD forum](http://forum.freecadweb.org/) (there is no Lattice forum yet...). You can also ask me directly. **Note:** If you post to the forum, please add this to your post so that I get a notification: 102 | `[quote=DeepSOIC user_id=3888]Ding![/quote]` 103 | 104 | NEW!: [Github Discussions for Lattice2](https://github.com/DeepSOIC/Lattice2/discussions) are enabled, you are welcome to ask questions and post random thoughts there! 105 | 106 | ## Contributing 107 | 108 | If you have found a bug or are requesting a new feature, please first check to see if it has been previously reported already on the [Lattice2 Github repo issue tracker](https://github.com/DeepSOIC/Lattice2/issues). If not, feel free to open a ticket. 109 | 110 | If you have fixed a bug or implemented a new feature you think suits the workbench, feel free to make a pull-request on Github. 111 | 112 | ## License 113 | 114 | Lattice workbench is licensed under LGPL V2, just like FreeCAD. For more info, see [copying.lib](copying.lib) file in this repository. 115 | -------------------------------------------------------------------------------- /fonts/FreeUniversal-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/fonts/FreeUniversal-Bold.ttf -------------------------------------------------------------------------------- /fonts/FreeUniversal-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/fonts/FreeUniversal-BoldItalic.ttf -------------------------------------------------------------------------------- /fonts/FreeUniversal-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/fonts/FreeUniversal-Italic.ttf -------------------------------------------------------------------------------- /fonts/FreeUniversal-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/fonts/FreeUniversal-Regular.ttf -------------------------------------------------------------------------------- /fonts/license.txt: -------------------------------------------------------------------------------- 1 | FreeUniversal-Bold.ttf 2 | FreeUniversal-BoldItalic.ttf 3 | FreeUniversal-Italic.ttf 4 | FreeUniversal-Regular.ttf 5 | The fonts listed above are under SIL Open Font License (OFL) ( http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL ) 6 | 7 | They were downloaded from: 8 | https://fontlibrary.org/en/font/freeuniversal 9 | 10 | 11 | The text of the license follows: 12 | 13 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 14 | This license is copied below, and is also available with a FAQ at: 15 | http://scripts.sil.org/OFL 16 | 17 | 18 | ----------------------------------------------------------- 19 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 20 | ----------------------------------------------------------- 21 | 22 | PREAMBLE 23 | The goals of the Open Font License (OFL) are to stimulate worldwide 24 | development of collaborative font projects, to support the font creation 25 | efforts of academic and linguistic communities, and to provide a free and 26 | open framework in which fonts may be shared and improved in partnership 27 | with others. 28 | 29 | The OFL allows the licensed fonts to be used, studied, modified and 30 | redistributed freely as long as they are not sold by themselves. The 31 | fonts, including any derivative works, can be bundled, embedded, 32 | redistributed and/or sold with any software provided that any reserved 33 | names are not used by derivative works. The fonts and derivatives, 34 | however, cannot be released under any other type of license. The 35 | requirement for fonts to remain under this license does not apply 36 | to any document created using the fonts or their derivatives. 37 | 38 | DEFINITIONS 39 | "Font Software" refers to the set of files released by the Copyright 40 | Holder(s) under this license and clearly marked as such. This may 41 | include source files, build scripts and documentation. 42 | 43 | "Reserved Font Name" refers to any names specified as such after the 44 | copyright statement(s). 45 | 46 | "Original Version" refers to the collection of Font Software components as 47 | distributed by the Copyright Holder(s). 48 | 49 | "Modified Version" refers to any derivative made by adding to, deleting, 50 | or substituting -- in part or in whole -- any of the components of the 51 | Original Version, by changing formats or by porting the Font Software to a 52 | new environment. 53 | 54 | "Author" refers to any designer, engineer, programmer, technical 55 | writer or other person who contributed to the Font Software. 56 | 57 | PERMISSION & CONDITIONS 58 | Permission is hereby granted, free of charge, to any person obtaining 59 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 60 | redistribute, and sell modified and unmodified copies of the Font 61 | Software, subject to the following conditions: 62 | 63 | 1) Neither the Font Software nor any of its individual components, 64 | in Original or Modified Versions, may be sold by itself. 65 | 66 | 2) Original or Modified Versions of the Font Software may be bundled, 67 | redistributed and/or sold with any software, provided that each copy 68 | contains the above copyright notice and this license. These can be 69 | included either as stand-alone text files, human-readable headers or 70 | in the appropriate machine-readable metadata fields within text or 71 | binary files as long as those fields can be easily viewed by the user. 72 | 73 | 3) No Modified Version of the Font Software may use the Reserved Font 74 | Name(s) unless explicit written permission is granted by the corresponding 75 | Copyright Holder. This restriction only applies to the primary font name as 76 | presented to the users. 77 | 78 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 79 | Software shall not be used to promote, endorse or advertise any 80 | Modified Version, except to acknowledge the contribution(s) of the 81 | Copyright Holder(s) and the Author(s) or with their explicit written 82 | permission. 83 | 84 | 5) The Font Software, modified or unmodified, in part or in whole, 85 | must be distributed entirely under this license, and must not be 86 | distributed under any other license. The requirement for fonts to 87 | remain under this license does not apply to any document created 88 | using the Font Software. 89 | 90 | TERMINATION 91 | This license becomes null and void if any of the above conditions are 92 | not met. 93 | 94 | DISCLAIMER 95 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 96 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 97 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 98 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 99 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 100 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 101 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 102 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 103 | OTHER DEALINGS IN THE FONT SOFTWARE. 104 | -------------------------------------------------------------------------------- /lattice2Base/Rounder.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2018 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__= "Lattice2 rounder module" 25 | __author__ = "DeepSOIC" 26 | __url__ = "" 27 | __doc__ = "helper module for Lattice add-on workbench for FreeCAD. Provides special rounding routines." 28 | 29 | import math 30 | from math import log 31 | 32 | # copied from FreeCAD /src/Base/UnitsSchema.h, enum UnitSystem 33 | class UnitSystem(object): 34 | SI1 = 0 #, /** internal (mm,kg,s) SI system (http://en.wikipedia.org/wiki/International_System_of_Units) */ 35 | SI2 = 1 #, /** MKS (m,kg,s) SI system */ 36 | Imperial1 = 2 #, /** the Imperial system (http://en.wikipedia.org/wiki/Imperial_units) */ 37 | ImperialDecimal = 3 #, /** Imperial with length in inch only */ 38 | Centimeters = 4 #, /** All lengths in centimeters, areas and volumes in square/cubic meters */ 39 | ImperialBuilding = 5 #, /** All lengths in feet + inches + fractions */ 40 | MmMin = 6 #, /** Lengths in mm, Speed in mm/min. Angle in degrees. Useful for small parts & CNC */ 41 | 42 | @staticmethod 43 | def getActiveSchema(): 44 | import FreeCAD as App 45 | return App.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt("UserSchema", 0) 46 | 47 | def getNiceLengths(unitschema = None): 48 | if unitschema is None: 49 | unitschema = UnitSystem.getActiveSchema() 50 | if unitschema == UnitSystem.SI1 or unitschema == UnitSystem.SI2 or unitschema == UnitSystem.Centimeters or unitschema == UnitSystem.MmMin: 51 | nice_numbers = [1.0, 2.0, 5.0] 52 | nice_magnitudes = [] 53 | for degree in range(-3,6): 54 | order = 10.0 ** degree 55 | nice_magnitudes.extend([order * val for val in nice_numbers]) 56 | return nice_magnitudes 57 | elif unitschema == UnitSystem.Imperial1 or unitschema == UnitSystem.ImperialBuilding or unitschema == UnitSystem.ImperialDecimal: 58 | inch = 25.4 59 | foot = 304.8 60 | yard = 914.4 61 | mile = 1609344.0 62 | #https://forum.freecadweb.org/viewtopic.php?f=8&t=32565#p271923 63 | #.005" .010", .025", .050", .100", .250", 1", 6", 1', 8', 50', 100', 500', 1000', 2500', 1 mile, 10 miles, 100 miles 64 | return [ 65 | 0.005*inch, 66 | 0.010*inch, 0.025*inch, 0.050*inch, 67 | 0.10*inch, 0.25*inch, 0.50*inch, 68 | 1*inch, 2*inch, 6*inch, 69 | 70 | 1*foot, 2*foot, 4*foot, 8*foot, 71 | 16*foot, 32*foot, 50*foot, 72 | 100*foot, 250*foot, 500*foot, 73 | 1000*foot, 2500*foot, 74 | 75 | 1*mile, 2*mile, 5*mile, 76 | 10*mile, 25*mile, 50*mile, 77 | 100*mile 78 | ] 79 | else: 80 | #unit unsupported? fall back to metric 81 | import FreeCAD as App 82 | App.PrintWarning("Lattice Autosize: Unit schema {n} is not yet supported.\n".format(n= unitschema)) 83 | return getNiceLengths(UnitSystem.SI1) 84 | 85 | def roundToNiceValue(value, nice_value_list = None): 86 | if value == 0.0: 87 | return 0.0 88 | 89 | if nice_value_list is None: 90 | nice_value_list = getNiceLengths() 91 | 92 | bestmatch_logdist = log(1e10) 93 | bestmatch = None 94 | 95 | for nice_val in nice_value_list: 96 | logdist = abs(log(abs(value)) - log(nice_val)) 97 | if logdist < bestmatch_logdist: 98 | bestmatch_logdist = logdist 99 | bestmatch = nice_val 100 | 101 | return math.copysign(bestmatch, value) 102 | 103 | def roundToPrecision(value, precision): 104 | if precision < 1e-12: 105 | return value 106 | return round(value/precision)*precision -------------------------------------------------------------------------------- /lattice2Base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/lattice2Base/__init__.py -------------------------------------------------------------------------------- /lattice2Common.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | import FreeCAD, Part 25 | from lattice2Executer import CancelError 26 | if FreeCAD.GuiUp: 27 | import FreeCADGui 28 | from PySide import QtCore, QtGui 29 | 30 | def translate(context, text, disambig): 31 | #Lattice2 is not translatable, sorry... 32 | return text 33 | 34 | 35 | def getParamRefine(): 36 | return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Part/Boolean").GetBool("RefineModel") 37 | def getParamPDRefine(): 38 | return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/PartDesign").GetBool("RefineModel") 39 | 40 | def getIconPath(icon_dot_svg): 41 | return ":/icons/" + icon_dot_svg 42 | 43 | class SelectionError(FreeCAD.Base.FreeCADError): 44 | '''Error that isused inside Gui command code''' 45 | def __init__(self, title, message): 46 | self.message = message 47 | self.args = (message,) 48 | self.title = title 49 | 50 | def msgError(err = None, message = u'{errmsg}'): 51 | import sys 52 | if err is None: 53 | err = sys.exc_info()[1] 54 | if type(err) is CancelError: return # doesn't work! Why! 55 | if hasattr(err, "isCancelError") and err.isCancelError: return #workaround 56 | 57 | # can we get a traceback? 58 | b_tb = err is sys.exc_info()[1] 59 | if b_tb: 60 | import traceback 61 | tb = traceback.format_exc() 62 | import FreeCAD as App 63 | App.Console.PrintError(tb+'\n') 64 | 65 | #make messagebox object 66 | from PySide import QtGui 67 | mb = QtGui.QMessageBox() 68 | mb.setIcon(mb.Icon.Warning) 69 | 70 | #fill in message 71 | errmsg = '' 72 | if hasattr(err,'message'): 73 | if isinstance(err.message, dict): 74 | errmsg = err.message['swhat'] 75 | elif len(err.message) > 0: 76 | errmsg = err.message 77 | else: 78 | errmsg = str(err) 79 | else: 80 | errmsg = str(err) 81 | mb.setText(message.format(errmsg= errmsg, err= err)) 82 | 83 | # fill in title 84 | if hasattr(err, "title"): 85 | mb.setWindowTitle(err.title) 86 | else: 87 | mb.setWindowTitle("Error") 88 | 89 | #add traceback button 90 | if b_tb: 91 | btnClose = mb.addButton(QtGui.QMessageBox.StandardButton.Close) 92 | btnCopy = mb.addButton("Copy traceback", QtGui.QMessageBox.ButtonRole.ActionRole) 93 | mb.setDefaultButton(btnClose) 94 | 95 | mb.exec_() 96 | if b_tb: 97 | if mb.clickedButton() is btnCopy: 98 | cb = QtGui.QClipboard() 99 | cb.setText(tb) 100 | 101 | 102 | def infoMessage(title, message): 103 | mb = QtGui.QMessageBox() 104 | mb.setIcon(mb.Icon.Information) 105 | mb.setText(message) 106 | mb.setWindowTitle(title) 107 | mb.exec_() 108 | 109 | def deselect(sel): 110 | '''deselect(sel): remove objects in sel from selection''' 111 | for selobj in sel: 112 | FreeCADGui.Selection.removeSelection(selobj.Object) 113 | 114 | # OCC's Precision::Confusion; should have taken this from FreeCAD but haven't found; unlikely to ever change. 115 | DistConfusion = 1e-7 116 | ParaConfusion = 1e-8 117 | 118 | if FreeCAD.GuiUp: 119 | import lattice2_rc 120 | 121 | def screen(feature): 122 | """screen(feature): protects link properties from being overwritten. 123 | This is to be used as workaround for a bug where modifying an object accessed through 124 | a link property of another object results in the latter being touched. 125 | 126 | returns: feature""" 127 | if not hasattr(feature,"isDerivedFrom"): 128 | return feature 129 | if not feature.isDerivedFrom("App::DocumentObject"): 130 | return feature 131 | if feature.Document is None: 132 | return feature 133 | feature = getattr(feature.Document, feature.Name) 134 | return feature 135 | 136 | def activeBody(): 137 | if FreeCAD.ActiveDocument is None: return None 138 | if not hasattr(FreeCADGui.ActiveDocument.ActiveView, 'getActiveObject'): #prevent errors in 0.16 139 | return None 140 | return FreeCADGui.ActiveDocument.ActiveView.getActiveObject("pdbody") 141 | 142 | def bodyOf(feature): 143 | body = feature.getParentGeoFeatureGroup() 144 | if body.isDerivedFrom('PartDesign::Body'): 145 | return body 146 | else: 147 | return None 148 | 149 | 150 | # Older FreeCAD has Support, newer has AttachmentSupport: https://github.com/FreeCAD/FreeCAD/issues/12894 151 | 152 | def getAttachmentSupport(documentObject): 153 | if hasattr(documentObject, 'AttachmentSupport'): 154 | return documentObject.AttachmentSupport 155 | elif hasattr(documentObject, 'Support'): 156 | return documentObject.Support 157 | raise AttributeError('No support property found') 158 | -------------------------------------------------------------------------------- /lattice2Compatibility.py: -------------------------------------------------------------------------------- 1 | import FreeCAD as App 2 | 3 | def get_fc_version(): 4 | """returns tuple like (0,18,4,16154) for 0.18.4 release, and (0,19,0,18234) for pre builds""" 5 | # ['0', '18', '4 (GitTag)', 'git://github.com/FreeCAD/FreeCAD.git releases/FreeCAD-0-18', '2019/10/22 16:53:35', 'releases/FreeCAD-0-18', '980bf9060e28555fecd9e3462f68ca74007b70f8'] 6 | # ['0', '19', '18234 (Git)', 'git://github.com/FreeCAD/FreeCAD.git master', '2019/09/15 20:43:17', 'master', '3af5d97e9b2a60823815f662aba25422c4bc45bb'] 7 | # ['0', '21', '0', '32457 (Git)', 'https://github.com/FreeCAD/FreeCAD master', '2023/03/23 00:09:35', 'master', '85216bd12730bbc4c3cbf8f0bc50416ab1556cbb'] 8 | if len(App.Version()) <= 7: 9 | strmaj, strmi, strrev = App.Version()[0:3] 10 | maj, mi = int(strmaj), int(strmi) 11 | submi, rev = 0, 0 12 | elif len(App.Version()) >= 8: 13 | strmaj, strmi, strsubmi, strrev = App.Version()[0:4] 14 | maj, mi, submi = int(strmaj), int(strmi), int(strsubmi) 15 | rev = 0 16 | if '(GitTag)' in strrev: 17 | submi = int(strrev.split(" ")[0]) 18 | elif '(Git)' in strrev: 19 | try: 20 | rev = int(strrev.split(" ")[0]) 21 | except Exception as err: 22 | App.Console.PrintWarning(u"Lattice2 failed to detect FC version number.\n" 23 | " {err}\n".format(err= str(err))) 24 | rev = 39109 #assume fairly modern 25 | if rev < 100: 26 | rev_map = { 27 | (0, 17) : 13544, 28 | (0, 18) : 16154, 29 | (0, 19) : 24276, 30 | (0, 20) : 29177, 31 | (0, 21) : 33771, 32 | (0, 22): 33771, 33 | (1, 0) : 39109, 34 | } 35 | if (maj, mi) in rev_map: 36 | rev = rev_map[(maj, mi)] 37 | else: 38 | rev = 39109 #assume fairly modern 39 | App.Console.PrintWarning( 40 | u"Lattice2 failed to detect FC version number: revision is zero / too low, minor version is unexpected.({ver})." 41 | .format(ver= str((maj, mi, submi, rev))) 42 | ) 43 | return (maj, mi, submi, rev) 44 | 45 | 46 | try: 47 | rev_number = get_fc_version()[3] 48 | except Exception as err: 49 | App.Console.PrintError(str(err)) 50 | rev_number = 10000000 51 | 52 | attach_extension_era = rev_number >= 9177 53 | no_extension_proxy_era = rev_number >= 23869 54 | attachment_support_name = 'AttachmentSupport' 55 | if rev_number < 36274: 56 | # Before 0.22.0dev.36274 https://github.com/FreeCAD/FreeCAD/issues/12894 57 | attachment_support_name = 'Support' 58 | -------------------------------------------------------------------------------- /lattice2CompoundExplorer.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | import Part 25 | 26 | class CompoundExplorer: 27 | """ 28 | CompoundExplorer: Iterator class to traverse compound hierarchy. 29 | Usage: 30 | for (child, msg, it) in lattice2CompoundExplorer.CompoundExplorer(App.ActiveDocument.Compound.Shape): 31 | #child is a shape. 32 | #msg is int equal to one of three constants: 33 | # CompoundExplorer.MSG_LEAF - child is a leaf (non-compound) 34 | # CompoundExplorer.MSG_DIVEDOWN - child is a compound that is about to be traversed 35 | # CompoundExplorer.MSG_BUBBLEUP - child is a compound that was just finished traversing 36 | #it is reference to iterator class (can be useful to extract current depth, or index stack) 37 | print (' ' * it.curDepth(), (child, msg)) 38 | 39 | Output: 40 | (, 2) 41 | (, 1) 42 | (, 2) 43 | (, 1) 44 | (, 1) 45 | (, 3) 46 | (, 3) 47 | """ 48 | 49 | #constants 50 | MSG_LEAF = 1 #the iterator has output a leaf of the compound structure tree (a shape that is not a compound) 51 | MSG_DIVEDOWN = 2 #the iterator is entering a subcompound. Shape is the subcompound about to be iterated 52 | MSG_BUBBLEUP = 3 #the iterator has finished traversing a subcompound and leaves it. Shape is the compound that was just iterated through. 53 | 54 | def __init__(self, compound): 55 | self.compound = compound 56 | 57 | self.indexStack = [-1] 58 | 59 | #childStructure: list of lists of shapes. 60 | # It is for storing the stack of childShapes. The last in the 61 | # list is the list of children being traversed at the moment. 62 | # When just starting, the list contains the base shape - the compound itself. 63 | # After first iteration, childShapes of compound are added as a second item. 64 | # If among those childshapes, a subcompound is encountered, its childShapes are added as the next item. 65 | # When finishing the iteration of a subcompound, the last item is removed from this list. 66 | # The length of this list should always be equal to the length of indexStack 67 | self.childStructure = [[compound]] 68 | 69 | self.lastMsg = self.MSG_BUBBLEUP 70 | 71 | def __iter__(self): 72 | return self 73 | 74 | def nxtDepth(self): 75 | ''' 76 | nxtDepth(): Returns depth inside compound hierarchy that is being entered (applicable when depth is changing, i.e. msg != MSG_LEAF). 77 | For reference: depth of the base shape (compound that was supplied when initiating the loop) is zero. 78 | ''' 79 | assert(len(self.indexStack) == len(self.childStructure)) 80 | return len(self.indexStack) - 1 81 | 82 | def prevDepth(self): 83 | ''' 84 | prevDepth(): Returns depth inside compound hierarchy that is being left (applicable when depth is changing, i.e. msg != MSG_LEAF). 85 | For reference: depth of the base shape (compound that was supplied when initiating the loop) is zero. 86 | ''' 87 | if self.lastMsg == self.MSG_BUBBLEUP: 88 | return self.nxtDepth() + 1 89 | elif self.lastMsg == self.MSG_DIVEDOWN: 90 | return self.nxtDepth() - 1 91 | else: 92 | return self.nxtDepth() 93 | 94 | def curDepth(self): 95 | ''' 96 | curDepth(): Returns depth inside compound hierarchy of the item that was just returned. 97 | For reference: depth of the base shape (compound that was supplied when initiating the loop) is zero. 98 | ''' 99 | if self.lastMsg == self.MSG_BUBBLEUP: 100 | return self.nxtDepth() 101 | elif self.lastMsg == self.MSG_DIVEDOWN: 102 | return self.nxtDepth() - 1 103 | else: 104 | return self.nxtDepth() 105 | 106 | def next(self): 107 | """Returns a tuple: (child,message,self). """ 108 | d = self.nxtDepth() 109 | if d == -1: 110 | raise StopIteration() 111 | 112 | self.indexStack[d] += 1 113 | i = self.indexStack[d] 114 | if i > len(self.childStructure[d])-1: #index gone out of range - finished traversing a subcompound 115 | msg = self.MSG_BUBBLEUP 116 | self.indexStack.pop() 117 | self.childStructure.pop() 118 | if len(self.indexStack) == 0: 119 | raise StopIteration() 120 | i = self.indexStack[d-1] 121 | sh = self.childStructure[d-1][i] 122 | else: 123 | sh = self.childStructure[d][i] 124 | if sh.ShapeType == 'Compound': 125 | msg = self.MSG_DIVEDOWN 126 | self.indexStack.append(-1) 127 | self.childStructure.append(sh.childShapes()) 128 | else: 129 | msg = self.MSG_LEAF 130 | assert(msg != 0) 131 | self.lastMsg = msg 132 | return (sh, msg, self) 133 | __next__ = next 134 | 135 | 136 | def CalculateNumberOfLeaves(compound): 137 | '''CalculateNumberOfLeaves(compound): calculates the number of non-compound shapes (leaves) in the compound tree. Slow; good candidate for transferring into C++.''' 138 | if compound.ShapeType != 'Compound': 139 | return 1 140 | else: 141 | children = compound.childShapes(False,False) 142 | cnt = 0 143 | for ch in children: 144 | cnt += CalculateNumberOfLeaves(ch) 145 | return cnt 146 | 147 | def AllLeaves(compound): 148 | 'AllLeaves(compound): Traverses the compound and collects all the leaves into a single list. Returns list of shapes.' 149 | output = [] 150 | for (child, msg, it) in CompoundExplorer(compound): 151 | if msg == it.MSG_LEAF: 152 | output.append(child) 153 | return output 154 | -------------------------------------------------------------------------------- /lattice2Dummy.py: -------------------------------------------------------------------------------- 1 | #just an empty module, that is used for determining path -------------------------------------------------------------------------------- /lattice2Executer.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__="A helper module for execution of features of Lattice workbench" 25 | __author__ = "DeepSOIC" 26 | __url__ = "" 27 | 28 | import FreeCAD 29 | if FreeCAD.GuiUp: 30 | from PySide import QtGui 31 | 32 | globalIsCreatingLatticeFeature = False 33 | 34 | def executeFeature(obj): 35 | global globalIsCreatingLatticeFeature 36 | globalIsCreatingLatticeFeature = True 37 | try: 38 | obj.Proxy.execute(obj) 39 | obj.purgeTouched() 40 | except CancelError: 41 | obj.Document.abortTransaction() 42 | raise 43 | except Exception as err: 44 | try: 45 | error(obj,str(err)) 46 | except CancelError: 47 | obj.Document.abortTransaction() 48 | raise 49 | finally: 50 | globalIsCreatingLatticeFeature = False 51 | 52 | 53 | def warning(obj,message,forceMessage = None): 54 | ''' 55 | warning(obj,message, forceMessage = None): smart warning message function. If feature is being 56 | created, a warning message pops up. If otherwise, the warning is printed 57 | to the console/report view. 58 | If forceMessage is True, the message is shown regardless; if False, the message is suppressed). 59 | ''' 60 | _showMsg(obj, message, forceMessage, _type= u'Warning') 61 | 62 | def error(obj,message, forceMessage = None): 63 | ''' 64 | error(obj, message, forceMessage = None): smart error message function. If feature is being 65 | created, an error message pops up. If otherwise, the error is printed 66 | to the console/report view. 67 | If forceMessage is True, the message is shown regardless; if False, the message is suppressed). 68 | ''' 69 | _showMsg(obj, message, forceMessage, _type= u'Error') 70 | 71 | def _showMsg(obj, message, forceMessage, _type): 72 | '''showMsg(obj, message, _type): convenience function, contains the shared code of error() and warning()''' 73 | global globalIsCreatingLatticeFeature 74 | if (globalIsCreatingLatticeFeature or forceMessage == True) and not forceMessage == False: 75 | mb = QtGui.QMessageBox() 76 | mb.setIcon(mb.Icon.Warning) 77 | mb.setText(_type + u": \n" + message) 78 | mb.setWindowTitle(_type) 79 | btnAbort = mb.addButton(QtGui.QMessageBox.StandardButton.Abort) 80 | btnOK = mb.addButton("Continue",QtGui.QMessageBox.ButtonRole.ActionRole) 81 | mb.setDefaultButton(btnOK) 82 | mb.exec_() 83 | if mb.clickedButton() is btnAbort: 84 | raise CancelError() 85 | 86 | else: 87 | if _type == 'Warning': 88 | printfunc = FreeCAD.Console.PrintWarning 89 | else: 90 | printfunc = FreeCAD.Console.PrintError 91 | 92 | if obj is not None: 93 | printfunc(obj.Name + ": " + message+"\n") 94 | else: 95 | printfunc(message+"\n") 96 | 97 | 98 | class CancelError(Exception): 99 | def __init__(self): 100 | self.message = "Canceled by user" 101 | self.args = (self.message,) 102 | self.isCancelError = True -------------------------------------------------------------------------------- /lattice2ExposeLinkSub.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2016 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__= "Lattice ExposeLinkSub tool for FreeCAD" 25 | __author__ = "DeepSOIC" 26 | __doc__ = "Tool to expose LinkSub properties of objects, by spawning a SubLink object for each sublinked element, and redirecting the links" 27 | 28 | from lattice2Common import * 29 | import lattice2SubLink as LSL 30 | import lattice2Executer as Executer 31 | from lattice2Subsequencer import linkSubList_convertToOldStyle 32 | import FreeCAD as App 33 | 34 | def ExposeLinkSub(feature, propname): 35 | # read links out into reflist variable 36 | if feature.getTypeIdOfProperty(propname) == 'App::PropertyLinkSubList': 37 | reflist = linkSubList_convertToOldStyle(getattr(feature,propname)) 38 | elif feature.getTypeIdOfProperty(propname) == 'App::PropertyLinkSub': 39 | (obj, subnames) = getattr(feature,propname) 40 | reflist = [(obj,subname) for subname in subnames] 41 | if len(reflist)>1: 42 | raise ValueError("Link of type 'App::PropertyLinkSub' points to more than one subelement. Not supported yet.") 43 | del (obj) 44 | else: 45 | raise ValueError("Link type not supported: "+feature.getTypeIdOfProperty(propname)) 46 | if reflist is None: 47 | raise ValueError("Nothing is linked, nothing to do") 48 | #return 49 | 50 | # make SubLinks objects, and redirect the links in reflist 51 | for i in range(len(reflist)): 52 | (obj, subname) = reflist[i] 53 | if "Face" in subname: 54 | newsubname = "Face1" 55 | elif "Edge" in subname: 56 | newsubname = "Edge1" 57 | elif "Vertex" in subname: 58 | newsubname = "Vertex1" 59 | else: 60 | App.Console.PrintLog("Link %prop of feature %feat links either to whole object, or to something unknown. Subname is %sub. Skipped.\n" 61 | .replace("%prop",propname) 62 | .replace("%feat",feature.Name) 63 | .replace("%sub",repr(subname)) ) 64 | continue 65 | sublink = LSL.CreateSubLink(obj, [subname]) 66 | reflist[i] = (sublink,newsubname) 67 | del obj 68 | 69 | # write back 70 | if feature.getTypeIdOfProperty(propname) == 'App::PropertyLinkSubList': 71 | setattr(feature,propname,reflist) 72 | elif feature.getTypeIdOfProperty(propname) == 'App::PropertyLinkSub': 73 | setattr(feature,propname,reflist[0]) 74 | 75 | def cmdExposeLinkSubs(): 76 | sel = FreeCADGui.Selection.getSelectionEx() 77 | if len(sel) != 1: 78 | raise SelectionError( "Bad selection", "Select one object, first! You have selected %i objects".replace("%i",str(len(sel))) ) 79 | App.ActiveDocument.openTransaction("Expose LinkSub of "+sel[0].Object.Name) 80 | obj = sel[0].Object 81 | cnt = 0 82 | try: 83 | for propname in obj.PropertiesList: 84 | if 'App::PropertyLinkSub' in obj.getTypeIdOfProperty(propname): 85 | if getattr(obj,propname) is None: continue 86 | try: 87 | if obj.isDerivedFrom("Part::Part2DObject") and propname == "Support": 88 | if False == askYesNo("Support", "ExposeLinkSub is about to expose Support link of %feat. This will cause PartDesign additive operations to start new objects instead of adding to support solid, and subtractive PartDesign operations to fail. Expose the support link?" 89 | .replace("%feat",obj.Label)): 90 | continue 91 | ExposeLinkSub(obj, propname) 92 | cnt += 1 93 | except Exception as err: 94 | Executer.warning(None,"Attempting to expose sublink property {prop} of {feat} caused an error:\n{err}" 95 | .format(prop= propname, feat= obj.Name, err= str(err)) ) 96 | if cnt == 0: 97 | raise ValueError("No links to expose were found.") 98 | except Exception: 99 | App.ActiveDocument.abortTransaction() 100 | raise 101 | 102 | 103 | # candidate for transferring 104 | def askYesNo(title, message): 105 | '''Displays messagebox with three buttons: yes, no, abort. yes -> returns True. no -> returns False. abort -> raises CancelError''' 106 | from PySide import QtGui 107 | mb = QtGui.QMessageBox() 108 | mb.setIcon(mb.Icon.Question) 109 | mb.setText(message) 110 | mb.setWindowTitle(title) 111 | btnAbort = mb.addButton(QtGui.QMessageBox.StandardButton.Abort) 112 | btnYes = mb.addButton(QtGui.QMessageBox.StandardButton.Yes) 113 | btnNo = mb.addButton(QtGui.QMessageBox.StandardButton.No) 114 | mb.setDefaultButton(btnYes) 115 | mb.exec_() 116 | if mb.clickedButton() is btnAbort: 117 | raise CancelError() 118 | return mb.clickedButton() is btnYes 119 | 120 | 121 | 122 | class _CommandExposeLinkSub: 123 | "Command to expose ..LinkSub.. properties of other features" 124 | def GetResources(self): 125 | return { 126 | 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_ExposeLinkSub","Expose links to subelements"), 127 | 'Accel': "", 128 | 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_ExposeLinkSub","Expose links to subelements: create SubLink features as proxies for subelement dependencies of selected object")} 129 | 130 | def Activated(self): 131 | try: 132 | if len(FreeCADGui.Selection.getSelection())==0: 133 | infoMessage("ExposeLinkSub", 134 | "'Expose links to subelements' command. Exposes subelement links of an object by creating SubLink features as proxies for subelement dependencies.\n\n"+ 135 | "Please select one object, then invoke the command.") 136 | return 137 | cmdExposeLinkSubs() 138 | except Exception as err: 139 | msgError(err) 140 | 141 | def IsActive(self): 142 | if App.ActiveDocument: 143 | return True 144 | else: 145 | return False 146 | 147 | if FreeCAD.GuiUp: 148 | FreeCADGui.addCommand('Lattice2_ExposeLinkSub', _CommandExposeLinkSub()) 149 | 150 | exportedCommands = ['Lattice2_ExposeLinkSub'] 151 | 152 | -------------------------------------------------------------------------------- /lattice2HelpCommands.py: -------------------------------------------------------------------------------- 1 | import FreeCAD 2 | if FreeCAD.GuiUp: 3 | import FreeCADGui 4 | from lattice2Common import * 5 | 6 | class CommandBasicTutorial: 7 | "opens basic tutorial" 8 | def GetResources(self): 9 | return {'Pixmap' : getIconPath("Lattice2.svg"), 10 | 'MenuText': "Help! Basic tutorial", 11 | 'ToolTip': "Open basic tutorial (available offline)"} 12 | 13 | def Activated(self): 14 | try: 15 | import os 16 | import lattice2Dummy 17 | tutorial_pdf = os.path.dirname(lattice2Dummy.__file__) + "/ExampleProjects/Lattice2WorkbenchBasicTutorial.pdf".replace("/", os.path.sep) 18 | 19 | import webbrowser 20 | webbrowser.open(tutorial_pdf) 21 | except Exception as err: 22 | msgError(err) 23 | 24 | def IsActive(self): 25 | return True 26 | 27 | if FreeCAD.GuiUp: 28 | FreeCADGui.addCommand('Lattice2_Help_BasicTutorial', CommandBasicTutorial()) 29 | 30 | 31 | class CommandOpenManual: 32 | "opens wiki" 33 | def GetResources(self): 34 | return {'Pixmap' : getIconPath("Lattice2.svg"), 35 | 'MenuText': "Help! Open Wiki", 36 | 'ToolTip': "Open Lattice2 documentation (on the web)"} 37 | 38 | def Activated(self): 39 | try: 40 | import webbrowser 41 | webbrowser.open('https://github.com/DeepSOIC/Lattice2/wiki') 42 | except Exception as err: 43 | msgError(err) 44 | 45 | def IsActive(self): 46 | return True 47 | 48 | if FreeCAD.GuiUp: 49 | FreeCADGui.addCommand('Lattice2_Help_OpenManual', CommandOpenManual()) 50 | 51 | exportedCommands = ['Lattice2_Help_BasicTutorial', 'Lattice2_Help_OpenManual'] 52 | -------------------------------------------------------------------------------- /lattice2InjectedToolbars.py: -------------------------------------------------------------------------------- 1 | import FreeCAD as App 2 | 3 | def findToolbar(name, label, workbench, create = False): 4 | """findToolbar(name, label, workbench, create= False): returns tuple "User parameter:BaseApp/Workbench/Global/Toolbar", "toolbar_group_name".""" 5 | tb_root = "User parameter:BaseApp/Workbench/{workbench}/Toolbar".format(workbench= workbench) 6 | pp = App.ParamGet(tb_root) 7 | if pp.HasGroup(name): 8 | return [tb_root, name] 9 | 10 | for i in range(10): 11 | g = 'Custom_'+str(i) 12 | if pp.HasGroup(g) and pp.GetGroup(g).GetString('Name') == label: 13 | return [tb_root, g] 14 | if create: 15 | return [tb_root, name] 16 | return None 17 | 18 | def findGlobalToolbar(name, label, create = False): 19 | return findToolbar(name, label, 'Global', create) 20 | 21 | def findPDToolbar(name, label, create = False): 22 | return findToolbar(name, label, 'PartDesignWorkbench', create) 23 | 24 | def registerPDToolbar(): 25 | creating_anew = not isPDRegistered() 26 | p = App.ParamGet('/'.join(findPDToolbar('Lattice2',"Lattice2 PartDesign", create= True))) 27 | p.SetString("Name", "Lattice2 PartDesign") 28 | import lattice2PDPatternCommand as PDPattern 29 | for cmd in PDPattern.exportedCommands: 30 | p.SetString(cmd, "FreeCAD") 31 | if creating_anew: 32 | p.SetBool("Active", 1) 33 | 34 | def isPDRegistered(): 35 | return findPDToolbar('Lattice2',"Lattice2 PartDesign") 36 | -------------------------------------------------------------------------------- /lattice2Inspect.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__="Command to inspect selected object compounding structure" 25 | __author__ = "DeepSOIC" 26 | __url__ = "" 27 | 28 | import FreeCAD as App 29 | import Part 30 | 31 | from lattice2Common import * 32 | import lattice2CompoundExplorer as LCE 33 | import lattice2BaseFeature 34 | 35 | def shapeInfoString(shape): 36 | strMsg = shape.ShapeType 37 | if shape.ShapeType == 'Vertex': 38 | strMsg += " ("+repr(list(shape.Point))+")" 39 | elif shape.ShapeType == 'Edge': 40 | strMsg += " ("+repr(shape.Curve)+")" 41 | elif shape.ShapeType == 'Wire': 42 | strMsg += " ("+str(len(shape.childShapes()))+" segments)" 43 | elif shape.ShapeType == 'Face': 44 | strMsg += " ("+repr(shape.Surface)+")" 45 | elif shape.ShapeType == 'Shell': 46 | strMsg += " ("+str(len(shape.childShapes()))+" faces)" 47 | elif shape.ShapeType == 'Solid': 48 | strMsg += " ("+str(len(shape.childShapes()))+" shells)" 49 | elif shape.ShapeType == 'CompSolid': 50 | strMsg += " ("+str(len(shape.childShapes()))+" solids)" 51 | elif shape.ShapeType == 'Compound': 52 | strMsg += " ("+str(len(shape.childShapes()))+" objects)" 53 | return strMsg 54 | 55 | class _CommandInspect: 56 | "Command to inspect compounding structure" 57 | 58 | def __init__(self): 59 | pass 60 | 61 | def GetResources(self): 62 | return {'Pixmap' : getIconPath("Lattice2_Inspect.svg"), 63 | 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_Inspect","Inspect selection") , # FIXME: not translation-friendly! 64 | 'Accel': "", 65 | 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_Inspect","Lattice Inspect: display info on compounding structure of selected object.")} 66 | 67 | def Activated(self): 68 | sel = FreeCADGui.Selection.getSelectionEx()[0] 69 | isLattice = lattice2BaseFeature.isObjectLattice(sel.Object) 70 | 71 | strStructure = [] 72 | if not hasattr(sel.Object,"Shape"): 73 | strStructure = [""] 74 | else: 75 | if sel.Object.Shape.isNull(): 76 | strStructure.append("") 77 | else: 78 | for (child, msg, it) in LCE.CompoundExplorer(sel.Object.Shape): 79 | #child is a shape. 80 | #msg is int equal to one of three constants: 81 | # CompoundExplorer.MSG_LEAF - child is a leaf (non-compound) 82 | # CompoundExplorer.MSG_DIVEDOWN - child is a compound that is about to be traversed 83 | # CompoundExplorer.MSG_BUBBLEUP - child is a compound that was just finished traversing 84 | #it is reference to iterator class (can be useful to extract current depth, or index stack) 85 | if msg == LCE.CompoundExplorer.MSG_LEAF or msg == LCE.CompoundExplorer.MSG_DIVEDOWN: 86 | try: 87 | strMsg = ' ' * it.curDepth() + shapeInfoString(child) 88 | if msg == LCE.CompoundExplorer.MSG_DIVEDOWN: 89 | strMsg += ":" 90 | except Exception as err: 91 | strMsg = "ERROR: " + str(err) 92 | strStructure.append(strMsg) 93 | 94 | strSubInfo = [] 95 | if sel.HasSubObjects: 96 | subNames = sel.SubElementNames 97 | subObjects = sel.SubObjects 98 | for i in range(0,len(subNames)): 99 | strMsg = subNames[i] + ": " 100 | child = subObjects[i] 101 | 102 | try: 103 | strMsg += shapeInfoString(child) 104 | except Exception as err: 105 | strMsg += "ERROR: " + str(err) 106 | strSubInfo.append(strMsg) 107 | 108 | allText = u'' 109 | if sel.HasSubObjects: 110 | allText += u"Selected " + str(len(sel.SubElementNames)) + u" subelements:\n" 111 | allText += u'\n'.join(strSubInfo) + u'\n\n' 112 | 113 | allText += u'Selected document object:\n' 114 | allText += u' Name = ' + sel.Object.Name + u'\n' 115 | allText += u' Label = ' + sel.Object.Label + u'\n' 116 | allText += u' Is placement/array = ' + repr(isLattice) + u'\n' 117 | allText += u'Structure: \n' 118 | allText += u'\n'.join(strStructure) 119 | mb = QtGui.QMessageBox() 120 | mb.setIcon(mb.Icon.Information) 121 | lines = allText.split(u"\n") 122 | if len(lines)>30: 123 | lines = lines[0:30] 124 | lines.append(u"...") 125 | mb.setText(u"\n".join(lines)) 126 | mb.setWindowTitle(translate("Lattice2_Inspect","Selection info", None)) 127 | 128 | btnClose = mb.addButton(QtGui.QMessageBox.StandardButton.Close) 129 | btnCopy = mb.addButton("Copy to clipboard",QtGui.QMessageBox.ButtonRole.ActionRole) 130 | mb.setDefaultButton(btnClose) 131 | mb.exec_() 132 | 133 | if mb.clickedButton() is btnCopy: 134 | cb = QtGui.QClipboard() 135 | cb.setText(allText) 136 | 137 | def IsActive(self): 138 | if len(FreeCADGui.Selection.getSelectionEx()) == 1: 139 | return True 140 | else: 141 | return False 142 | 143 | if FreeCAD.GuiUp: 144 | FreeCADGui.addCommand('Lattice2_Inspect',_CommandInspect()) 145 | 146 | import lattice2ShapeInfoFeature 147 | 148 | class GroupCommandInspect: 149 | def GetCommands(self): 150 | return ('Lattice2_Inspect', lattice2ShapeInfoFeature.exportedCommands[0]) # a tuple of command names that you want to group 151 | 152 | def GetDefaultCommand(self): # return the index of the tuple of the default command. This method is optional and when not implemented '0' is used 153 | return 0 154 | 155 | def GetResources(self): 156 | return { 'MenuText': 'Inspect:', 'ToolTip': 'Inspect: tools to analyze shape structure.'} 157 | 158 | def IsActive(self): # optional 159 | return True 160 | 161 | if FreeCAD.GuiUp: 162 | FreeCADGui.addCommand('Lattice2_Inspect_GroupCommand',GroupCommandInspect()) 163 | 164 | exportedCommands = ['Lattice2_Inspect_GroupCommand'] 165 | 166 | -------------------------------------------------------------------------------- /lattice2InterpolateGroup.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2016 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__="Group command for interpolation features" 25 | __author__ = "DeepSOIC" 26 | 27 | import lattice2Resample as Resample 28 | import lattice2ScLERP as Sclerp 29 | 30 | import FreeCAD as App 31 | if App.GuiUp: 32 | import FreeCADGui 33 | 34 | class CommandInterpolateGroup: 35 | def GetCommands(self): 36 | ret = Resample.exportedCommands 37 | return tuple(Sclerp.exportedCommands + Resample.exportedCommands) 38 | 39 | def GetDefaultCommand(self): # return the index of the tuple of the default command. 40 | return 0 41 | 42 | def GetResources(self): 43 | return { 'MenuText': 'Interpolation features:', 44 | 'ToolTip': 'Interpolation features (group): creating placements between occurrences.'} 45 | 46 | def IsActive(self): # optional 47 | return App.ActiveDocument is not None 48 | 49 | if App.GuiUp: 50 | FreeCADGui.addCommand('Lattice2_Interpolate_GroupCommand',CommandInterpolateGroup()) 51 | 52 | exportedCommands = ['Lattice2_Interpolate_GroupCommand'] 53 | 54 | -------------------------------------------------------------------------------- /lattice2InterpolatorUtil.py: -------------------------------------------------------------------------------- 1 | import FreeCAD as App 2 | import Part 3 | 4 | 5 | class InterpolateF: 6 | '''InterpolateF class interpolates an F(x) function from a set of points using BSpline interpolation''' 7 | 8 | def __init__(self, XPoints = None, YPoints = None): 9 | self.XPoints = XPoints 10 | self.YPoints = YPoints 11 | if XPoints is not None: 12 | self.recompute() 13 | 14 | def recompute(self): 15 | '''call before using value(), if changing sample values via attributes''' 16 | 17 | #compute min-max 18 | XPoints = self.XPoints 19 | YPoints = self.YPoints 20 | x_max = max(XPoints) 21 | x_min = min(XPoints) 22 | self._x_max = x_max 23 | self._x_min = x_min 24 | if x_max - x_min <= (x_max + x_min)*1e-9: 25 | raise ValueError('X range too small') 26 | min_x_step = x_max - x_min #initialize 27 | for i in range(0,len(self.XPoints)-1): 28 | step = abs( XPoints[i+1] - XPoints[i] ) 29 | if step <= (x_max + x_min)*1e-9: 30 | raise ValueError("X points "+str(i)+"-"+str(i+1)+" are too close.") 31 | if step < min_x_step: 32 | min_x_step = step 33 | 34 | y_min = min(YPoints) 35 | y_max = max(YPoints) 36 | 37 | # we want to make sure the smallest X step is way larger than possible 38 | # Y step, so only X points affect knotting. This is what we are using 39 | # _y_multiplicator for - it is the scaling applied to Y coordinates of 40 | # the interpolation points. Doing this will make u parameter of the 41 | # spline equivalent to X coordinate. 42 | if y_max - y_min < 1e-40: 43 | self._y_multiplicator = 1.0 44 | else: 45 | self._y_multiplicator = 1e-20*min_x_step/(y_max - y_min) 46 | 47 | self._y_demultiplicator = 1.0/self._y_multiplicator 48 | 49 | # create the spline 50 | if not hasattr(self,"_spline"): 51 | self._spline = Part.BSplineCurve() 52 | spline = self._spline 53 | 54 | points_for_spline = [App.Vector(XPoints[i], YPoints[i]*self._y_multiplicator, 0.0) for i in range(0,len(XPoints))] 55 | spline.interpolate(points_for_spline) 56 | 57 | #precache some scaling values for faster calculation of value() 58 | self._u1 = spline.FirstParameter 59 | self._u2 = spline.LastParameter 60 | self._x_to_u_scale = (self._u2 - self._u1) / (self._x_max - self._x_min) 61 | 62 | def value(self, x): 63 | return self._spline.value( self._u1 + x*self._x_to_u_scale ).y * self._y_demultiplicator 64 | -------------------------------------------------------------------------------- /lattice2Invert.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__="Lattice Invert object: creates an array of placements from a compound." 25 | __author__ = "DeepSOIC" 26 | __url__ = "" 27 | 28 | import math 29 | 30 | import FreeCAD as App 31 | import Part 32 | 33 | from lattice2Common import * 34 | import lattice2BaseFeature 35 | import lattice2CompoundExplorer as LCE 36 | import lattice2GeomUtils as Utils 37 | import lattice2Executer 38 | 39 | # -------------------------- document object -------------------------------------------------- 40 | 41 | def makeLatticeInvert(name): 42 | '''makeLatticeInvert(name): makes a LatticeInvert object.''' 43 | return lattice2BaseFeature.makeLatticeFeature(name, LatticeInvert, ViewProviderInvert) 44 | 45 | class LatticeInvert(lattice2BaseFeature.LatticeFeature): 46 | "The Lattice Invert object" 47 | 48 | def derivedInit(self,obj): 49 | self.Type = "LatticeInvert" 50 | 51 | obj.addProperty("App::PropertyLink","Base","Lattice Invert","Lattice, all the placements of which are to be inverted.") 52 | 53 | obj.addProperty("App::PropertyEnumeration","TranslateMode","Lattice Invert","What to do with translation part of placements") 54 | obj.TranslateMode = ['invert', 'keep', 'reset'] 55 | obj.TranslateMode = 'invert' 56 | 57 | obj.addProperty("App::PropertyEnumeration","OrientMode","Lattice Invert","what to do with orientation part of placements") 58 | obj.OrientMode = ['invert', 'keep', 'reset'] 59 | obj.OrientMode = 'invert' 60 | 61 | def derivedExecute(self,obj): 62 | # cache stuff 63 | base = screen(obj.Base).Shape 64 | if not lattice2BaseFeature.isObjectLattice(screen(obj.Base)): 65 | lattice2Executer.warning(obj, "Base is not a lattice, but lattice is expected. Results may be unexpected.\n") 66 | baseChildren = LCE.AllLeaves(base) 67 | 68 | #cache mode comparisons, for speed 69 | posIsInvert = obj.TranslateMode == 'invert' 70 | posIsKeep = obj.TranslateMode == 'keep' 71 | posIsReset = obj.TranslateMode == 'reset' 72 | 73 | oriIsInvert = obj.OrientMode == 'invert' 74 | oriIsKeep = obj.OrientMode == 'keep' 75 | oriIsReset = obj.OrientMode == 'reset' 76 | 77 | # initialize output containers and loop variables 78 | outputPlms = [] #list of placements 79 | 80 | # the essence 81 | for child in baseChildren: 82 | pos = App.Vector() 83 | ori = App.Rotation() 84 | inverted = child.Placement.inverse() 85 | if posIsInvert: 86 | pos = inverted.Base 87 | elif posIsKeep: 88 | pos = child.Placement.Base 89 | elif posIsReset: 90 | pass 91 | 92 | if oriIsInvert: 93 | ori = inverted.Rotation 94 | elif oriIsKeep: 95 | ori = child.Placement.Rotation 96 | elif oriIsReset: 97 | pass 98 | 99 | plm = App.Placement(pos, ori) 100 | outputPlms.append(plm) 101 | return outputPlms 102 | 103 | 104 | class ViewProviderInvert(lattice2BaseFeature.ViewProviderLatticeFeature): 105 | 106 | def getIcon(self): 107 | return getIconPath('Lattice2_Invert.svg') 108 | 109 | def claimChildren(self): 110 | weakparenting = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Lattice2").GetBool("WeakParenting", True) 111 | if weakparenting: 112 | return [] 113 | return [screen(self.Object.Base)] 114 | 115 | 116 | # -------------------------- /document object -------------------------------------------------- 117 | 118 | # -------------------------- Gui command -------------------------------------------------- 119 | 120 | def CreateLatticeInvert(name): 121 | sel = FreeCADGui.Selection.getSelectionEx() 122 | FreeCAD.ActiveDocument.openTransaction("Create LatticeInvert") 123 | FreeCADGui.addModule("lattice2Invert") 124 | FreeCADGui.addModule("lattice2Executer") 125 | FreeCADGui.doCommand("f = lattice2Invert.makeLatticeInvert(name='"+name+"')") 126 | FreeCADGui.doCommand("f.Base = App.ActiveDocument."+sel[0].ObjectName) 127 | FreeCADGui.doCommand("for child in f.ViewObject.Proxy.claimChildren():\n"+ 128 | " child.ViewObject.hide()") 129 | FreeCADGui.doCommand("lattice2Executer.executeFeature(f)") 130 | FreeCADGui.doCommand("f = None") 131 | FreeCAD.ActiveDocument.commitTransaction() 132 | 133 | 134 | class _CommandLatticeInvert: 135 | "Command to create LatticeInvert feature" 136 | def GetResources(self): 137 | return {'Pixmap' : getIconPath("Lattice2_Invert.svg"), 138 | 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_Invert","Invert lattice"), 139 | 'Accel': "", 140 | 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_Invert","Lattice Invert: invert all placements in a lattice object.")} 141 | 142 | def Activated(self): 143 | if len(FreeCADGui.Selection.getSelection()) == 1 : 144 | CreateLatticeInvert(name = "Invert") 145 | else: 146 | mb = QtGui.QMessageBox() 147 | mb.setIcon(mb.Icon.Warning) 148 | mb.setText(translate("Lattice2_Invert", "Please select one object, first. The object must be a lattice object (array of placements).", None)) 149 | mb.setWindowTitle(translate("Lattice2_Invert","Bad selection", None)) 150 | mb.exec_() 151 | 152 | def IsActive(self): 153 | if FreeCAD.ActiveDocument: 154 | return True 155 | else: 156 | return False 157 | 158 | if FreeCAD.GuiUp: 159 | FreeCADGui.addCommand('Lattice2_Invert', _CommandLatticeInvert()) 160 | 161 | exportedCommands = ['Lattice2_Invert'] 162 | 163 | # -------------------------- /Gui command -------------------------------------------------- 164 | 165 | -------------------------------------------------------------------------------- /lattice2JoinArrays.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__="Lattice JoinArrays object: combine elements of two lattices" 25 | __author__ = "DeepSOIC" 26 | __url__ = "" 27 | 28 | import FreeCAD as App 29 | import Part 30 | 31 | from lattice2Common import * 32 | import lattice2BaseFeature 33 | import lattice2CompoundExplorer as LCE 34 | import lattice2Executer 35 | 36 | # -------------------------- document object -------------------------------------------------- 37 | 38 | def makeJoinArrays(name): 39 | '''makeJoinArrays(name): makes a JoinArrays object.''' 40 | return lattice2BaseFeature.makeLatticeFeature(name, JoinArrays, ViewProviderJoinArrays) 41 | 42 | class JoinArrays(lattice2BaseFeature.LatticeFeature): 43 | "The Lattice JoinArrays object" 44 | 45 | def derivedInit(self,obj): 46 | self.Type = "LatticeJoinArrays" 47 | 48 | obj.addProperty("App::PropertyLinkList","Links","Lattice JoinArrays","Links to arrays to be joined") 49 | 50 | obj.addProperty("App::PropertyBool","Interleave","Lattice JoinArrays","If false, first go all elements of array 1, nect go all elements of array 2, so on. If true, first go all first elements from each array, then all second elements, and so on.") 51 | 52 | 53 | 54 | def derivedExecute(self,obj): 55 | #validity check 56 | nonLattices = [] 57 | for iArr in range(0, len(obj.Links)): 58 | link = obj.Links[iArr] 59 | if not lattice2BaseFeature.isObjectLattice(link): 60 | nonLattices.append(link.Label) 61 | if len(nonLattices) > 0: 62 | lattice2Executer.warning(obj, "Only lattice objects are expected to be linked as arrays in JoinArrays. There are " 63 | +len(nonLattices)+" objects which are not lattice objects. Results may me unexpected.") 64 | 65 | #extract placements 66 | listlistPlms = [] 67 | lengths = [] 68 | for link in obj.Links: 69 | leaves = LCE.AllLeaves(link.Shape) 70 | listlistPlms.append([child.Placement for child in leaves]) 71 | lengths.append(len(leaves)) 72 | 73 | #processing 74 | output = [] #list of placements 75 | if obj.Interleave: 76 | for l in lengths[1:]: 77 | if l != lengths[0]: 78 | lattice2Executer.warning(obj,"Array lengths are unequal: "+repr(lengths)+". Interleaving will be inconsistent.") 79 | break 80 | 81 | for iItem in range(0,max(lengths)): 82 | for list in listlistPlms: 83 | if iItem < len(list): 84 | output.append(list[iItem]) 85 | else: 86 | for list in listlistPlms: 87 | output.extend(list) 88 | return output 89 | 90 | class ViewProviderJoinArrays(lattice2BaseFeature.ViewProviderLatticeFeature): 91 | 92 | def getIcon(self): 93 | return getIconPath('Lattice2_JoinArrays.svg') 94 | 95 | def claimChildren(self): 96 | return self.Object.Links 97 | 98 | # -------------------------- /document object -------------------------------------------------- 99 | 100 | # -------------------------- Gui command -------------------------------------------------- 101 | 102 | def CreateJoinArrays(name): 103 | sel = FreeCADGui.Selection.getSelection() 104 | FreeCAD.ActiveDocument.openTransaction("Create JoinArrays") 105 | FreeCADGui.addModule("lattice2JoinArrays") 106 | FreeCADGui.addModule("lattice2Executer") 107 | FreeCADGui.doCommand("f = lattice2JoinArrays.makeJoinArrays(name='"+name+"')") 108 | FreeCADGui.doCommand("f.Links = []") 109 | for s in sel: 110 | FreeCADGui.doCommand("f.Links = f.Links + [App.ActiveDocument."+s.Name+"]") 111 | 112 | FreeCADGui.doCommand("for child in f.ViewObject.Proxy.claimChildren():\n"+ 113 | " child.ViewObject.hide()") 114 | FreeCADGui.doCommand("lattice2Executer.executeFeature(f)") 115 | FreeCADGui.doCommand("f = None") 116 | FreeCAD.ActiveDocument.commitTransaction() 117 | 118 | 119 | class _CommandJoinArrays: 120 | "Command to create JoinArrays feature" 121 | def GetResources(self): 122 | return {'Pixmap' : getIconPath("Lattice2_JoinArrays.svg"), 123 | 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_JoinArrays","Join arrays"), 124 | 'Accel': "", 125 | 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_JoinArrays","Lattice JoinArrays: concatenate or interleave two or more arrays.")} 126 | 127 | def Activated(self): 128 | if len(FreeCADGui.Selection.getSelection()) > 1 : 129 | CreateJoinArrays(name = "Join") 130 | else: 131 | mb = QtGui.QMessageBox() 132 | mb.setIcon(mb.Icon.Warning) 133 | mb.setText(translate("Lattice2_JoinArrays", "Please select at least two lattice objects. Selected lattice objects will be concatenated or interleaved into one array.", None)) 134 | mb.setWindowTitle(translate("Lattice2_JoinArrays","Bad selection", None)) 135 | mb.exec_() 136 | 137 | def IsActive(self): 138 | if FreeCAD.ActiveDocument: 139 | return True 140 | else: 141 | return False 142 | 143 | if FreeCAD.GuiUp: 144 | FreeCADGui.addCommand('Lattice2_JoinArrays', _CommandJoinArrays()) 145 | 146 | exportedCommands = ['Lattice2_JoinArrays'] 147 | 148 | # -------------------------- /Gui command -------------------------------------------------- 149 | 150 | -------------------------------------------------------------------------------- /lattice2MakeCompound.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__="Mirror of Part MakeCompound command" 25 | __author__ = "DeepSOIC" 26 | __url__ = "" 27 | __doc__ = "Mirror of Part MakeCompound command" 28 | 29 | from lattice2Common import * 30 | from lattice2BaseFeature import isObjectLattice 31 | import lattice2Executer 32 | 33 | class _CommandLatticeMakeCompound: 34 | "Mirror of Part MakeCompound command" 35 | def GetResources(self): 36 | return {'Pixmap' : getIconPath("Lattice2_MakeCompound.svg"), 37 | 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_MakeCompound","Make compound"), 38 | 'Accel': "", 39 | 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_MakeCompound","Make compound: combine several objects into one, without fusing them.")} 40 | 41 | def Activated(self): 42 | try: 43 | if len(FreeCADGui.Selection.getSelection())==0: 44 | infoMessage("Make compound", 45 | "Make compound command. Combines several shapes into one. The shapes are kept as-is. They are not fused together, and can be extracted unchanged.\n\n"+ 46 | "Compounds can contain combination of shapes of any topology: one can compound some edges with some solids. Compound is effectively another kind of group. But unlike normal FreeCAD group, compound only accepts OCC geometry. Compound cannot include meshes, dimensions, labels, or other objects that provide no Shape property.\n\n"+ 47 | "Note that compounds that have objects that touch or intersect are considered invalid by Part CheckGeometry. Such invalid compounds cannot be used for Part Cut/Common/Fuse.") 48 | return 49 | 50 | oldVal = lattice2Executer.globalIsCreatingLatticeFeature 51 | lattice2Executer.globalIsCreatingLatticeFeature = True 52 | 53 | sel = FreeCADGui.Selection.getSelectionEx() 54 | for s in sel: 55 | if isObjectLattice(s.Object): 56 | lattice2Executer.warning(None,"For making a compound, generic shapes are expected, but some of the selected objects are placements/arrays of placements. These will be treated as generic shapes; results may be unexpected.") 57 | break 58 | FreeCADGui.runCommand("Part_Compound") 59 | except Exception as err: 60 | msgError(err) 61 | finally: 62 | lattice2Executer.globalIsCreatingLatticeFeature = oldVal 63 | 64 | def IsActive(self): 65 | if FreeCAD.ActiveDocument: 66 | return activeBody() is None 67 | else: 68 | return False 69 | 70 | if FreeCAD.GuiUp: 71 | FreeCADGui.addCommand('Lattice2_Compound', _CommandLatticeMakeCompound()) 72 | 73 | exportedCommands = ["Lattice2_Compound"] -------------------------------------------------------------------------------- /lattice2Markers.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | import Part, os 25 | 26 | __title__="latticeMarkers module for FreeCAD" 27 | __author__ = "DeepSOIC" 28 | __url__ = "" 29 | __doc__ = "Module for loading marker shapes for Lattice workbench" 30 | 31 | _nullShapeShape = 0 32 | _ShapeDict = {} 33 | 34 | def getShapePath(shapeName): 35 | """ 36 | getShapePath(shapeName) converts marker file name without path 37 | to a full path to a file. shapeName should be a file name with 38 | extension, for example "empty-shape.brep" 39 | """ 40 | return os.path.dirname(__file__) + os.path.sep + "shapes" + os.path.sep + shapeName 41 | 42 | def getNullShapeShape(scale = 1.0): 43 | """obtains a shape intended ad a placeholder in case null shape was produced by an operation""" 44 | 45 | #read shape from file, if not done this before 46 | global _nullShapeShape 47 | if not _nullShapeShape: 48 | _nullShapeShape = Part.Shape() 49 | f = open(getShapePath("empty-shape.brep")) 50 | _nullShapeShape.importBrep(f) 51 | f.close() 52 | 53 | #scale the shape 54 | ret = _nullShapeShape 55 | if scale != 1.0: 56 | ret = _nullShapeShape.copy() 57 | ret.scale(scale) 58 | 59 | return ret 60 | 61 | def loadShape(shapeID): 62 | global _ShapeDict 63 | sh = _ShapeDict.get(shapeID) 64 | if sh is None: 65 | try: 66 | sh = Part.Shape() 67 | f = open(getShapePath(shapeID + '.brep')) 68 | sh.importBrep(f) 69 | if sh.ShapeType == "Compound": 70 | sh = sh.childShapes()[0] 71 | f.close() 72 | except Exception as err: 73 | FreeCAD.Console.PrintError('Failed to load standard shape "'+shapeID+'". \n' + str(err) + '\n') 74 | sh = Part.Point() #Create at least something! 75 | _ShapeDict[shapeID] = sh 76 | return sh 77 | 78 | 79 | def getPlacementMarker(scale = 1.0, markerID = None): 80 | '''getPlacementMarker(scale = 1.0, markerID = None): returns a placement marker shape. 81 | The shape is scaled according to "scale" argument. 82 | markerID sets the marker file name. If omitted, default placement marker is returned.''' 83 | if markerID is None: 84 | markerID = 'paperplane-orimarker' 85 | sh = loadShape(markerID) 86 | if scale != 1.0: 87 | sh = sh.copy() 88 | sh.scale(scale) 89 | return sh 90 | -------------------------------------------------------------------------------- /lattice2PDPatternCommand.py: -------------------------------------------------------------------------------- 1 | # all imports are in command's activated(), for to speed up FC startup 2 | import FreeCAD 3 | if FreeCAD.GuiUp: 4 | import FreeCADGui 5 | 6 | class CommandLatticePDPattern: 7 | "Command to create Lattice PartDesign Pattern feature" 8 | def GetResources(self): 9 | import lattice2_rc 10 | return {'Pixmap' : ":/icons/Lattice2_PDPattern.svg", 11 | 'MenuText': "Lattice PartDesign Pattern", 12 | 'Accel': "", 13 | 'ToolTip': "Lattice PartDesign Pattern command. Replicates partdesign features at every placement in array."} 14 | 15 | def Activated(self): 16 | from lattice2Common import infoMessage, msgError, activeBody 17 | from lattice2PDPattern import cmdPDPattern 18 | try: 19 | if len(FreeCADGui.Selection.getSelection())==0: 20 | infoMessage("Lattice PartDesign Pattern", 21 | "Lattice PartDesign Pattern command. Replicates partdesign features at every placement in array.\n\n" 22 | "Please select features to repeat, reference placement (optional), and target placement/array. \n\n" 23 | "You can use features from another body. Then, reference placement is required. You can also select a body (a \"template body\"), then all features from that body will be replicated.\n\n" 24 | "Please observe scope restrictions. Reference placement must be in same body the original features are in; target placement/array must be in active body. You can create Lattice Arrays " 25 | "right in PartDesign bodies, but you can't drag them in after the fact. You can import arrays of placements from elsewhere using a Shapebinder, or Part-o-Magic Ghost.") 26 | return 27 | if activeBody() is None: 28 | infoMessage("Lattice PartDesign Pattern", "No active body. Please, activate a body, first.") 29 | cmdPDPattern() 30 | except Exception as err: 31 | msgError(err) 32 | 33 | def IsActive(self): 34 | if FreeCAD.ActiveDocument: 35 | return True 36 | else: 37 | return False 38 | 39 | if FreeCAD.GuiUp: 40 | FreeCADGui.addCommand('Lattice2_PDPattern', CommandLatticePDPattern()) 41 | 42 | exportedCommands = ['Lattice2_PDPattern'] 43 | -------------------------------------------------------------------------------- /lattice2Preferences.py: -------------------------------------------------------------------------------- 1 | __doc__ = 'module that registers preference page' 2 | 3 | allSettings = """ 4 | Mod/Lattice2 5 | WeakParenting True 6 | MarkerColor #ffb300 (255,179,0) 7 | 8 | Mod/Lattice2/Autosize 9 | MarkerAdj 1.0 10 | FeatureAdj 1.0 11 | ModelAdj 1.0 12 | AutoPosition True 13 | 14 | Mod/Lattice2/Warnings 15 | PopUpWarn True 16 | PopUpErr True 17 | LengthMismatch True 18 | ParaSeriesRecompute True 19 | """ 20 | 21 | def addPreferences(): 22 | import os 23 | import FreeCADGui as Gui 24 | 25 | # ## this works 26 | # import PySide.QtCore as qtc 27 | # 28 | # ret = \ 29 | # qtc.QResource.registerResource(os.path.dirname(__file__) + '/ui/pref/pref.rcc'.replace('/', os.path.sep)) 30 | # 31 | # if not ret: 32 | # raise RuntimeError('Loading Lattice2/ui/pref/pref.rcc returned False, not loaded?') 33 | # 34 | # Gui.addPreferencePage(':/ui/lattice2-pref-general.ui','Lattice2') 35 | 36 | ## but this is better for now 37 | Gui.addPreferencePage(os.path.dirname(__file__) + '/ui/pref/lattice2-pref-general.ui'.replace('/', os.path.sep),"Lattice2") 38 | 39 | # ## interactive! 40 | # Gui.addPreferencePage(MyPrefPage,'Lattice2') 41 | 42 | 43 | # # interactive version - not now, as it requires manual parameter read/write 44 | # from PySide import QtGui 45 | # 46 | # class MyPrefPage: 47 | # form = None # the widget 48 | # 49 | # def __init__(self, parent=None): 50 | # import FreeCADGui as Gui 51 | # self.form = Gui.PySideUic.loadUi(':/ui/lattice2-pref-general.ui') 52 | # self.form.setWindowTitle("window title") 53 | # 54 | # #debug: 55 | # global prefpage 56 | # prefpage = self.form 57 | # 58 | # def saveSettings(self): 59 | # print ("saveSettings") 60 | # def loadSettings(self): 61 | # print ("loadSettings") 62 | # 63 | 64 | addPreferences() 65 | -------------------------------------------------------------------------------- /lattice2SeriesGroup.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2016 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__="Group command for Lattice Series features" 25 | __author__ = "DeepSOIC" 26 | __url__ = "" 27 | __doc__ = "Group command for Lattice Series features" 28 | 29 | import lattice2ParaSeries as ParaSeries 30 | import lattice2TopoSeries as TopoSeries 31 | from lattice2Common import activeBody 32 | 33 | import FreeCAD as App 34 | if App.GuiUp: 35 | import FreeCADGui 36 | 37 | class CommandSeriesGroup: 38 | def GetCommands(self): 39 | return tuple(ParaSeries.exportedCommands + TopoSeries.exportedCommands) 40 | 41 | def GetDefaultCommand(self): # return the index of the tuple of the default command. 42 | return 0 43 | 44 | def GetResources(self): 45 | return { 'MenuText': 'Series features:', 46 | 'ToolTip': 'Series features (group): features that collect permutations of an object by changing dependent objects.'} 47 | 48 | def IsActive(self): # optional 49 | return App.ActiveDocument is not None and activeBody() is None 50 | 51 | if App.GuiUp: 52 | FreeCADGui.addCommand('Lattice2_Series_GroupCommand',CommandSeriesGroup()) 53 | 54 | exportedCommands = ['Lattice2_Series_GroupCommand'] 55 | 56 | -------------------------------------------------------------------------------- /lattice2ShapeCopy.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2016 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__="ShapeCopy module for Lattice2" 25 | __author__ = "DeepSOIC" 26 | __url__ = "" 27 | __doc__ = "Utility methods to copy shapes" 28 | 29 | import FreeCAD 30 | import Part 31 | from lattice2GeomUtils import PlacementsFuzzyCompare 32 | 33 | def shallowCopy(shape, extra_placement = None): 34 | """shallowCopy(shape, extra_placement = None): creates a shallow copy of a shape. The 35 | copy will match by isSame/isEqual/isPartner tests, but will have an independent placement. 36 | Supports matrix, but the matrix should be pure placement (not be mirroring).""" 37 | 38 | copiers = { 39 | "Vertex": lambda sh: sh.Vertexes[0], 40 | "Edge": lambda sh: sh.Edges[0], 41 | "Wire": lambda sh: sh.Wires[0], 42 | "Face": lambda sh: sh.Faces[0], 43 | "Shell": lambda sh: sh.Shells[0], 44 | "Solid": lambda sh: sh.Solids[0], 45 | "CompSolid": lambda sh: sh.CompSolids[0], 46 | "Compound": lambda sh: sh.Compounds[0], 47 | } 48 | copier = copiers.get(shape.ShapeType) 49 | if copier is None: 50 | copier = lambda sh: sh.copy() 51 | FreeCAD.Console.PrintWarning("Lattice2: shallowCopy: unexpected shape type '{typ}'. Using deep copy instead.\n".format(typ= shape.ShapeType)) 52 | ret = copier(shape) 53 | if extra_placement is not None: 54 | if hasattr(extra_placement, 'toMatrix'): 55 | ret.Placement = extra_placement.multiply(ret.Placement) 56 | elif extra_placement.determinant() - 1.0 < 1e-7: 57 | ret.transformShape(extra_placement) 58 | else: 59 | raise NonPlacementMatrixError("Matrix supplied to shallowCopy must be unitary.") 60 | return ret 61 | 62 | def deepCopy(shape, extra_placement = None): 63 | """deepCopy(shape, extra_placement = None): Copies all subshapes. The copy will not match by isSame/isEqual/ 64 | isPartner tests. If matrix is provided, redirects the call to transformCopy.""" 65 | 66 | if extra_placement is not None: 67 | if hasattr(extra_placement, 'toMatrix'): 68 | ret = shape.copy() 69 | ret.Placement = extra_placement.multiply(ret.Placement) 70 | else: 71 | ret = shallowCopy(shape) 72 | ret.transformShape(extra_placement, True) 73 | return ret 74 | 75 | def transformCopy(shape, extra_placement = None): 76 | """transformCopy(shape, extra_placement = None): creates a deep copy shape with shape's placement applied to 77 | the subelements (the placement of returned shape is zero). Supports matrices, including mirroring matrices.""" 78 | 79 | if extra_placement is None: 80 | extra_placement = FreeCAD.Placement() 81 | if hasattr(extra_placement, 'toMatrix'): 82 | extra_placement = extra_placement.toMatrix() 83 | ret = shape.copy() 84 | if ret.ShapeType == "Vertex": 85 | # oddly, on Vertex, transformShape behaves strangely. So we'll create a new vertex instead. 86 | ret = Part.Vertex(extra_placement.multiply(ret.Point)) 87 | else: 88 | splm = ret.Matrix 89 | ret.Matrix = FreeCAD.Base.Matrix() 90 | ret.transformShape(extra_placement.multiply(splm), True) 91 | return ret 92 | 93 | def transformCopy_Smart(shape, feature_placement): 94 | """transformCopy_Smart(shape, feature_placement): gets rid of shape's internal placement 95 | (by applying transform to all its elements), and assigns feature_placement to the placement. 96 | I.e. feature_placement is the additional transform to apply. Unlike transformCopy, creates 97 | a shallow copy if possible. Does not support matrices.""" 98 | 99 | if shape.isNull(): 100 | return shape 101 | if PlacementsFuzzyCompare(shape.Placement, FreeCAD.Placement()): 102 | sh = shallowCopy(shape) 103 | else: 104 | sh = transformCopy(shape) 105 | sh.Placement = feature_placement 106 | return sh 107 | 108 | 109 | copy_types = ["Shallow copy", "Deep copy", "Transformed deep copy"] 110 | copy_functions = [shallowCopy, deepCopy, transformCopy] 111 | 112 | def getCopyTypeIndex(copy_type_string): 113 | return copy_types.index(str(copy_type_string)) 114 | 115 | def copyShape(shape, copy_type_index, extra_placement = None): 116 | """copyShape(shape, copy_type_index, extra_placement = None): copies a shape (or creates 117 | a moved copy of shape, if extra_placement is given). copy_type_index should be obtained 118 | from string by getCopyTypeIndex() function.""" 119 | 120 | global copy_functions 121 | return copy_functions[copy_type_index](shape, extra_placement) 122 | 123 | def transformShape(shape, extra_placement): 124 | """transformShape(shape, extra_placement): returns shape with extra_placement applied to it. 125 | extra_placement must be either a Placement, or a Matrix. Matrix can be mirroring. 126 | shallowCopy is done if Placement or a placement matrix. transformCopy is done if the matrix features mirroring.""" 127 | 128 | if hasattr(extra_placement, 'toMatrix'): 129 | # extra_placement is a Placement 130 | return shallowCopy(shape, extra_placement) 131 | else: 132 | # extra_placement is a Matrix 133 | return transformCopy(shape, extra_placement) 134 | 135 | 136 | class NonPlacementMatrixError(ValueError): 137 | pass 138 | -------------------------------------------------------------------------------- /lattice2SubstituteObject.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2015 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__="Lattice SubstituteObject command module" 25 | __author__ = "DeepSOIC" 26 | 27 | import FreeCAD as App 28 | 29 | from replaceobj import replaceobj #from OpenSCAD wb, the code that drives replaceChild 30 | from lattice2Common import * 31 | 32 | def getAllDependencies(feat): 33 | '''getAllDependencies(feat): gets all features feat depends on, directly or indirectly. Returns a list, with deepest dependencies last.''' 34 | list_traversing_now = [feat] 35 | set_of_deps = set() 36 | list_of_deps = [] 37 | 38 | while len(list_traversing_now) > 0: 39 | list_to_be_traversed_next = [] 40 | for feat in list_traversing_now: 41 | for dep in feat.OutList: 42 | if not (dep in set_of_deps): 43 | set_of_deps.add(dep) 44 | list_of_deps.append(dep) 45 | list_to_be_traversed_next.append(dep) 46 | 47 | list_traversing_now = list_to_be_traversed_next 48 | 49 | return list_of_deps 50 | 51 | 52 | def substituteobj(oldobj, newobj): 53 | '''Replaces all links to oldobj in the document with links to newobj. 54 | Returns a tuple (list_replaced, list_not_replaced)''' 55 | deps_of_new = getAllDependencies(newobj) + [newobj] 56 | list_not_replaced = [] 57 | list_replaced = [] 58 | for dep in oldobj.InList: 59 | if dep in deps_of_new: 60 | #we are about to make an object newobj depends on, to depend on newobj. 61 | #This will create a circular graph, so we must skip this. 62 | print ("not replacing "+oldobj.Name+" with " + newobj.Name +" in " + dep.Name) 63 | list_not_replaced.append(dep) 64 | else: 65 | print ("replacing "+oldobj.Name+" with " + newobj.Name +" in " + dep.Name) 66 | list_replaced.append(dep) 67 | replaceobj(dep, oldobj, newobj) 68 | return (list_replaced, list_not_replaced) 69 | 70 | class CommandSubstituteObject: 71 | "Command to substitute object" 72 | def GetResources(self): 73 | return {'Pixmap' : getIconPath("Lattice2_SubstituteObject.svg"), 74 | 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice2_SubstituteObject","Substitute object"), 75 | 'Accel': "", 76 | 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice2_SubstituteObject","Substitute Object: find all links to one of the selected objects, and rediret them all to another object")} 77 | 78 | def Activated(self): 79 | sel = FreeCADGui.Selection.getSelectionEx() 80 | if len(sel) == 2 : 81 | App.ActiveDocument.openTransaction("Substitute "+sel[0].ObjectName+" with "+sel[1].ObjectName) 82 | try: 83 | #do it 84 | if len(sel[0].Object.InList) == 0: 85 | raise ValueError("First selected object isn't referenced by anything; nothing to do.") 86 | dummy, list_not_replaced = substituteobj(sel[0].Object, sel[1].Object) 87 | App.ActiveDocument.commitTransaction() 88 | 89 | #verify results: oldobject should not be referenced by anything anymore. 90 | if len(sel[0].Object.InList) != 0: 91 | set_failed = set(sel[0].Object.InList).difference(set(list_not_replaced)) 92 | mb = QtGui.QMessageBox() 93 | if len(set_failed) > 0: 94 | mb.setIcon(mb.Icon.Warning) 95 | msg = translate("Lattice2_SubstituteObject", "Some of the links couldn't be redirected, because they are not supported by the tool. Link redirection failed for: \n%1\nTo redirect these links, the objects have to be edited manually. Sorry!", None) 96 | rem_links = [lnk.Label for lnk in set_failed] 97 | else: 98 | mb.setIcon(mb.Icon.Information) 99 | msg = translate("Lattice2_SubstituteObject", "The following objects still link to old object: \n%1\nReplacing those links would have caused loops in dependency graph, so they were skipped.", None) 100 | rem_links = [lnk.Label for lnk in sel[0].Object.InList] 101 | mb.setText(msg.replace(u"%1", u"\n".join(rem_links))) 102 | mb.setWindowTitle(translate("Lattice2_SubstituteObject","Error", None)) 103 | mb.exec_() 104 | 105 | except Exception as err: 106 | mb = QtGui.QMessageBox() 107 | mb.setIcon(mb.Icon.Warning) 108 | mb.setText(translate("Lattice2_SubstituteObject", "An error occurred while substituting object:", None)+ u"\n" 109 | + str(err)) 110 | mb.setWindowTitle(translate("Lattice2_SubstituteObject","Error", None)) 111 | mb.exec_() 112 | App.ActiveDocument.abortTransaction() 113 | return 114 | 115 | #hide/unhide 116 | try: 117 | old_was_visible = sel[0].Object.ViewObject.Visibility 118 | new_was_visible = sel[1].Object.ViewObject.Visibility 119 | sel[0].Object.ViewObject.Visibility = True 120 | sel[1].Object.ViewObject.Visibility = old_was_visible 121 | except Exception as err: 122 | App.Console.PrintError("SubstituteFeature: error when changing visibilities: "+str(err)+"\n") 123 | else: 124 | mb = QtGui.QMessageBox() 125 | mb.setIcon(mb.Icon.Warning) 126 | mb.setText(translate("Lattice2_SubstituteObject", "Select two objects, first! The first one is the one to be substituted, and the second one is the object to redirect all links to.", None)) 127 | mb.setWindowTitle(translate("Lattice2_SubstituteObject","Bad selection", None)) 128 | mb.exec_() 129 | 130 | def IsActive(self): 131 | if FreeCAD.ActiveDocument: 132 | return True 133 | else: 134 | return False 135 | 136 | if FreeCAD.GuiUp: 137 | FreeCADGui.addCommand('Lattice2_SubstituteObject', CommandSubstituteObject()) 138 | 139 | exportedCommands = ['Lattice2_SubstituteObject'] 140 | -------------------------------------------------------------------------------- /lattice2Utils.py: -------------------------------------------------------------------------------- 1 | #*************************************************************************** 2 | #* * 3 | #* Copyright (c) 2017 - Victor Titov (DeepSOIC) * 4 | #* * 5 | #* * 6 | #* This program is free software; you can redistribute it and/or modify * 7 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 8 | #* as published by the Free Software Foundation; either version 2 of * 9 | #* the License, or (at your option) any later version. * 10 | #* for detail see the LICENCE text file. * 11 | #* * 12 | #* This program is distributed in the hope that it will be useful, * 13 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 14 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 15 | #* GNU Library General Public License for more details. * 16 | #* * 17 | #* You should have received a copy of the GNU Library General Public * 18 | #* License along with this program; if not, write to the Free Software * 19 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 20 | #* USA * 21 | #* * 22 | #*************************************************************************** 23 | 24 | __title__="Utility functions for Lattice2" 25 | __author__ = "DeepSOIC" 26 | __url__ = "" 27 | 28 | def sublinkFromApart(object, subnames): 29 | if type(subnames) is not list and type(subnames) is not tuple: 30 | subnames = [subnames] 31 | return (object, [str(sub) for sub in subnames]) if object is not None else None 32 | 33 | def syncSublinkApart(selfobj, prop, sublink_prop_name, obj_property_name, subnames_prop_name): 34 | """syncSublinkApart(selfobj, prop, sublink_prop_name, obj_property_name, subnames_prop_name): 35 | Function that synchronizes a sublink mirror property with split apart property pair. 36 | 37 | selfobj, prop: as in onChanged 38 | 39 | sublink_prop_name: name of property of type "App::PropertyLinkSub" 40 | 41 | obj_property_name, subnames_prop_name: names of apart properties. First must be 42 | App::PropertyLink. Second can be PropertyString or PropertyStringList. 43 | """ 44 | 45 | # check if changed property is relevant 46 | if not prop in (sublink_prop_name, obj_property_name, subnames_prop_name): 47 | return 48 | 49 | #if restoring, some of the properties may not exist yet. If they don't, skip, and wait for the last relevant onChanged. 50 | if not ( hasattr(selfobj, sublink_prop_name) and hasattr(selfobj, obj_property_name) and hasattr(selfobj, subnames_prop_name) ): 51 | return 52 | 53 | sl = sublinkFromApart(getattr(selfobj,obj_property_name), getattr(selfobj,subnames_prop_name)) 54 | if getattr(selfobj,sublink_prop_name) != sl: #assign only if actually changed, to prevent recursive onChanged calls 55 | if prop == sublink_prop_name: 56 | # update apart properties 57 | tup_apart = (None, []) if getattr(selfobj,sublink_prop_name) is None else getattr(selfobj,sublink_prop_name) 58 | if type(getattr(selfobj, subnames_prop_name)) is not list: 59 | tup_apart = (tup_apart[0], tup_apart[1][0] if len(tup_apart[1]) == 1 else "") 60 | setattr(selfobj, obj_property_name, tup_apart[0]) 61 | if tup_apart[0] is not None: 62 | setattr(selfobj, subnames_prop_name, tup_apart[1]) 63 | else: 64 | setattr(selfobj, sublink_prop_name, sl) 65 | 66 | def getSelectionAsPropertyLinkSubList(): 67 | import FreeCADGui as Gui 68 | sel = Gui.Selection.getSelectionEx() 69 | ret = [] 70 | for s in sel: 71 | subnames = s.SubElementNames 72 | if len(subnames)==0: 73 | subnames = [""] 74 | for sub in subnames: 75 | ret.append((s.Object, sub)) 76 | return ret 77 | 78 | def getSelectionAsListOfLinkSub(): 79 | return [(obj, [sub,]) for (obj,sub) in getSelectionAsPropertyLinkSubList()] 80 | 81 | def linkSubList_convertToOldStyle(references): 82 | ("input: [(obj1, (sub1, sub2)), (obj2, (sub1, sub2))]\n" 83 | "output: [(obj1, sub1), (obj1, sub2), (obj2, sub1), (obj2, sub2)]") 84 | result = [] 85 | for tup in references: 86 | if type(tup[1]) is tuple or type(tup[1]) is list: 87 | for subname in tup[1]: 88 | result.append((tup[0], subname)) 89 | if len(tup[1]) == 0: 90 | result.append((tup[0], '')) 91 | elif isinstance(tup[1],basestring): 92 | # old style references, no conversion required 93 | result.append(tup) 94 | return result 95 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Lattice2 Workbench 4 | Tools and arrays of all sorts and kinds, and local coordinate systems 5 | 1.0 6 | 2022-03-12 7 | DeepSOIC 8 | LGPL-2.0-or-later 9 | https://github.com/DeepSOIC/Lattice2 10 | https://github.com/DeepSOIC/Lattice2/issues 11 | https://github.com/DeepSOIC/Lattice2/wiki 12 | PyResources/icons/Lattice2.svg 13 | 14 | 15 | 16 | Lattice2Workbench 17 | ./ 18 | array 19 | assembly 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /shapes/empty-shape.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/shapes/empty-shape.FCStd -------------------------------------------------------------------------------- /shapes/paperplane-orimarker.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/shapes/paperplane-orimarker.FCStd -------------------------------------------------------------------------------- /shapes/paperplane-orimarker.brep: -------------------------------------------------------------------------------- 1 | DBRep_DrawableShape 2 | 3 | CASCADE Topology V1, (c) Matra-Datavision 4 | Locations 11 5 | 1 6 | 1 0 0 0 7 | 0 1 0 0 8 | 0 0 1 0 9 | 1 10 | 1 0 0 0 11 | 0 1 0 0 12 | 0 0 1 0 13 | 1 14 | 1 0 -0 0 15 | -0 2.22044604925031e-016 -1 0 16 | 0 1 2.22044604925031e-016 0 17 | 1 18 | 1 0 0 0 19 | 0 1 0 0 20 | 0 0 1 0 21 | 2 3 1 4 1 0 22 | 2 4 -1 3 -1 0 23 | 1 24 | 1 0 0 0 25 | 0 1 0 0 26 | 0 0 1 0 27 | 2 7 1 2 1 0 28 | 2 2 -1 7 -1 0 29 | 2 7 -1 0 30 | 2 3 -1 0 31 | Curve2ds 0 32 | Curves 7 33 | 1 0 0 0 1 0 0 34 | 1 0.99999999999999989 0 0 -0.93465529130606351 -0.35555518057479879 0 35 | 1 -0.28144611926053531 -0.48747897815233698 0 0.49999999999999978 0.86602540378443871 0 36 | 1 4.163336342344337e-017 3.9478354044454804e-017 0 -0.49999999999999989 0.86602540378443882 0 37 | 1 -0.2814461192605352 0.48747897815233687 0 0.93465529130606351 -0.35555518057479868 0 38 | 1 0 -0.28560000000000002 0 0 1 0 39 | 1 0.99999999999999967 0 0 -0.96155297028741438 -0.27461952831408565 0 40 | Polygon3D 0 41 | PolygonOnTriangulations 9 42 | 2 1 2 43 | p 0.00423667345927535 1 0 1 44 | 2 1 3 45 | p 0.00423667345927535 1 0 1 46 | 2 1 2 47 | p 0.00423667345927535 1 0 1 48 | 2 2 3 49 | p 0.00423667345927535 1 0 1.37103607199385 50 | 2 3 1 51 | p 0.00423667345927535 1 0 0.562892238521071 52 | 2 1 2 53 | p 0.00423667345927535 1 0 0.562892238521071 54 | 2 2 3 55 | p 0.00423667345927535 1 0 1.37103607199385 56 | 2 3 1 57 | p 0.00423667345927535 1 0 0.2856 58 | 2 2 3 59 | p 0.00423667345927535 1 0 1.03998430757392 60 | Surfaces 2 61 | 1 4.163336342344337e-017 3.9478354044454804e-017 0 -0 -0 1 1 0 0 -0 1 0 62 | 1 0 0 0 0 -1 2.2204460492503136e-016 0 -2.2204460492503136e-016 -1 1 0 0 63 | Triangulations 3 64 | 3 1 1 0 65 | 2.42861274828864e-017 2.37473749547807e-017 0 1 0 0 -0.281446119260535 -0.487478978152337 0 0 0 1 -5.55111512312578e-017 -0.281446119260535 -0.487478978152337 2 1 3 66 | 3 1 1 0 67 | 2.42861274828864e-017 2.37473749547807e-017 0 -0.281446119260535 0.487478978152337 0 1 0 0 0 -1.57309790896741e-017 -0.281446119260535 0.487478978152337 1 -3.94783540444548e-017 3 2 1 68 | 3 1 1 7.91579165159356e-018 69 | 2.42861274828864e-017 2.37473749547807e-017 0 1 0 0 0 -6.34159391665889e-017 -0.2856 -5.27297648984086e-033 2.42861274828864e-017 0 1 0.2856 0 2 1 3 70 | 71 | TShapes 20 72 | Ve 73 | 1.00000000044569e-007 74 | 2.42861274828864e-017 2.37473749547807e-017 0 75 | 0 0 76 | 77 | 0101101 78 | * 79 | Ve 80 | 1.00000000111022e-007 81 | 1 0 0 82 | 0 0 83 | 84 | 0101101 85 | * 86 | Ed 87 | 1e-007 1 1 0 88 | 1 1 0 0 1 89 | 6 1 1 6 90 | 6 2 2 6 91 | 6 3 3 6 92 | 0 93 | 94 | 0101000 95 | +20 6 -19 6 * 96 | Ve 97 | 1.00000000055511e-007 98 | -0.281446119260535 -0.487478978152337 0 99 | 0 0 100 | 101 | 0101101 102 | * 103 | Ed 104 | 1e-007 1 1 0 105 | 1 2 0 0 1.37103607199385 106 | 6 4 1 9 107 | 0 108 | 109 | 0101000 110 | +19 9 -17 10 * 111 | Ed 112 | 1e-007 1 1 0 113 | 1 3 0 0 0.562892238521071 114 | 6 5 1 9 115 | 0 116 | 117 | 0101000 118 | +17 10 -20 9 * 119 | Wi 120 | 121 | 0101000 122 | -18 5 -16 8 -15 8 * 123 | Fa 124 | 0 1e-007 1 2 125 | 2 1 126 | 0101000 127 | +14 0 * 128 | Ve 129 | 1e-007 130 | -0.281446119260535 0.487478978152337 0 131 | 0 0 132 | 133 | 0101101 134 | * 135 | Ed 136 | 1e-007 1 1 0 137 | 1 4 0 0 0.562892238521071 138 | 6 6 2 9 139 | 0 140 | 141 | 0101000 142 | +20 9 -12 10 * 143 | Ed 144 | 1e-007 1 1 0 145 | 1 5 0 0 1.37103607199385 146 | 6 7 2 9 147 | 0 148 | 149 | 0101000 150 | +12 10 -19 9 * 151 | Wi 152 | 153 | 0101000 154 | -11 8 -10 8 +18 5 * 155 | Fa 156 | 0 1e-007 1 2 157 | 2 2 158 | 0101000 159 | +9 0 * 160 | Ve 161 | 1e-007 162 | 0 -6.34159391665889e-017 -0.2856 163 | 0 0 164 | 165 | 0101101 166 | * 167 | Ed 168 | 1e-007 1 1 0 169 | 1 6 0 0 0.2856 170 | 6 8 3 6 171 | 0 172 | 173 | 0101000 174 | +7 11 -20 6 * 175 | Ed 176 | 1e-007 1 1 0 177 | 1 7 0 0 1.03998430757392 178 | 6 9 3 6 179 | 0 180 | 181 | 0101000 182 | +19 6 -7 11 * 183 | Wi 184 | 185 | 0101000 186 | -18 5 -6 5 -5 5 * 187 | Fa 188 | 0 1e-007 2 4 189 | 2 3 190 | 0101000 191 | +4 0 * 192 | Sh 193 | 194 | 0101000 195 | +13 0 +8 0 +3 0 * 196 | Co 197 | 198 | 1100000 199 | +2 1 * 200 | 201 | +1 0 -------------------------------------------------------------------------------- /shapes/tetra-orimarker.FCStd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepSOIC/Lattice2/8998ad066c5364ebed39faeec3de278175896534/shapes/tetra-orimarker.FCStd -------------------------------------------------------------------------------- /shapes/tetra-orimarker.brep: -------------------------------------------------------------------------------- 1 | DBRep_DrawableShape 2 | 3 | CASCADE Topology V1, (c) Matra-Datavision 4 | Locations 15 5 | 1 6 | 1 0 0 0 7 | 0 1 0 0 8 | 0 0 1 0 9 | 1 10 | 1 0 0 0 11 | 0 1 0 0 12 | 0 0 1 0 13 | 1 14 | 1 0 0 0 15 | 0 1 0 0 16 | 0 0 1 0 17 | 1 18 | 0 0 -1 -0 19 | -1 0 0 -0 20 | 0 1 0 0 21 | 2 4 1 3 1 0 22 | 2 3 -1 4 -1 0 23 | 1 24 | 1 0 0 0 25 | 0 1 0 0 26 | 0 0 1 0 27 | 1 28 | -0.978231976089037 2.77555756156289e-017 0.207514339159822 0.043062200956938 29 | 0.146734796413355 -0.707106781186547 0.691714463866075 0.14354066985646 30 | 0.146734796413356 0.707106781186547 0.691714463866075 0.14354066985646 31 | 1 32 | 1 0 0 0 33 | 0 1 0 0 34 | 0 0 1 0 35 | 2 8 1 9 1 0 36 | 2 9 -1 8 -1 0 37 | 1 38 | 0.978231976089037 2.77555756156289e-017 0.207514339159822 0.043062200956938 39 | 0.146734796413355 0.707106781186547 -0.691714463866075 -0.14354066985646 40 | -0.146734796413356 0.707106781186547 0.691714463866075 0.14354066985646 41 | 1 42 | 1 0 0 0 43 | 0 1 0 0 44 | 0 0 1 0 45 | 2 12 1 13 1 0 46 | 2 13 -1 12 -1 0 47 | Curve2ds 0 48 | Curves 7 49 | 1 0 0 0 1 0 0 50 | 1 -0.29999999999998128 -4.2695787882546599e-015 0 1 1.4231929294183087e-014 0 51 | 1 0.29999999999999999 0 0 -0.70710678118654524 0.70710678118654957 0 52 | 1 2.2233792906188499e-017 0.30000000000000182 0 -0.70710678118651826 -0.70710678118657666 0 53 | 1 0.044020438924006873 -0.21213203435596539 0 -0.9791402332302942 0.20318563844357879 0 54 | 1 -0.04402043892400688 -0.21213203435596534 0 0.9791402332302942 0.20318563844357873 0 55 | 1 -0.97823197608904322 1.214306433183765e-016 0 0.97914023323029442 0.20318563844357873 0 56 | Polygon3D 0 57 | PolygonOnTriangulations 14 58 | 2 1 2 59 | p 0.00316666666666669 1 0 0.3 60 | 2 2 4 61 | p 0.00316666666666669 1 0 0.3 62 | 2 3 1 63 | p 0.00316666666666669 1 0 0.299999999999981 64 | 2 1 2 65 | p 0.00316666666666669 1 0 0.299999999999981 66 | 2 2 4 67 | p 0.00316666666666669 1 0 0.42426406871193 68 | 2 1 3 69 | p 0.00316666666666669 1 0 0.42426406871193 70 | 2 4 3 71 | p 0.00316666666666669 1 0 0.42426406871192 72 | 2 2 3 73 | p 0.00316666666666669 1 0 0.42426406871192 74 | 2 1 3 75 | p 0.00316666666666669 1 0 1.04403065089106 76 | 2 3 1 77 | p 0.00316666666666669 1 0 1.04403065089106 78 | 2 4 3 79 | p 0.00316666666666669 1 0 1.04403065089106 80 | 2 1 2 81 | p 0.00316666666666669 1 0 1.04403065089106 82 | 2 2 3 83 | p 0.00316666666666669 1 0 1.04403065089106 84 | 2 1 2 85 | p 0.00316666666666669 1 0 1.04403065089106 86 | Surfaces 4 87 | 1 0 0 0 1 0 -0 0 0 1 0 -1 0 88 | 1 0 0 0 0 0 1 1 0 -0 -0 1 0 89 | 1 0.41556347085957146 -0.087665479371065044 0.087665479371065141 -0.20751433915982237 0.6917144638660746 -0.69171446386607471 0 -0.70710678118654757 -0.70710678118654746 -0.978231976089037 -0.14673479641335552 0.14673479641335554 90 | 1 0.41556347085957152 0.087665479371065058 0.087665479371065114 -0.20751433915982231 -0.69171446386607471 -0.69171446386607471 0 -0.70710678118654757 0.70710678118654757 -0.97823197608903711 0.14673479641335549 0.14673479641335549 91 | Triangulations 4 92 | 4 2 1 4.2491376305591e-018 93 | 0 0 0 -1.73472347597681e-018 -0.300000000000001 1.38777878078145e-017 1.73472347597681e-018 0.299999999999991 -2.13598435909256e-015 -1.73472347597681e-018 8.31933993028464e-018 0.300000000000002 0 0 1.38777878078145e-017 0.300000000000001 -2.13598435909256e-015 -0.299999999999991 0.300000000000002 0 4 2 1 4 1 3 94 | 4 2 1 7.18933680268093e-016 95 | 1.73472347597681e-018 0.299999999999991 -2.13598435909256e-015 0 0 0 1.00000000000001 -2.77555756156289e-017 -2.08166817117217e-017 -1.73472347597681e-018 -0.300000000000001 1.38777878078145e-017 1.73472347597681e-018 0.299999999999992 0 0 1.00000000000001 0 -1.73472347597681e-018 -0.300000000000001 3 1 2 3 2 4 96 | 3 1 1 6.93889390390723e-017 97 | -1.73472347597681e-018 -0.300000000000001 1.38777878078145e-017 1.00000000000001 -2.77555756156289e-017 -2.08166817117217e-017 -1.73472347597681e-018 8.31933993028464e-018 0.300000000000002 0.212132034355965 0.424810761677399 8.32667268468867e-017 -0.59744165333565 -0.212132034355965 0.4248107616774 1 3 2 98 | 3 1 1 2.692290834716e-015 99 | 1.00000000000001 -2.77555756156289e-017 -2.08166817117217e-017 -1.73472347597681e-018 8.31933993028464e-018 0.300000000000002 1.73472347597681e-018 0.299999999999991 -2.13598435909256e-015 -2.77555756156289e-017 -0.59744165333565 0.212132034355966 0.4248107616774 -0.21213203435596 0.424810761677398 2 3 1 100 | 101 | TShapes 23 102 | Ve 103 | 1e-007 104 | 0 0 0 105 | 0 0 106 | 107 | 0101101 108 | * 109 | Ve 110 | 1.00000000839442e-007 111 | -1.73472347597681e-018 -0.300000000000001 1.38777878078145e-017 112 | 0 0 113 | 114 | 0101101 115 | * 116 | Ed 117 | 1e-007 1 1 0 118 | 1 1 0 0 0.3 119 | 6 1 1 6 120 | 6 2 2 6 121 | 0 122 | 123 | 0101000 124 | +23 6 -22 6 * 125 | Ve 126 | 1.00000010384143e-007 127 | 1.73472347597681e-018 0.299999999999991 -2.13598435909256e-015 128 | 0 0 129 | 130 | 0101101 131 | * 132 | Ed 133 | 1e-007 1 1 0 134 | 1 2 0 0 0.299999999999981 135 | 6 3 1 6 136 | 6 4 2 6 137 | 0 138 | 139 | 0101000 140 | +20 6 -23 6 * 141 | Ve 142 | 1.00000000178043e-007 143 | -1.73472347597681e-018 8.31933993028464e-018 0.300000000000002 144 | 0 0 145 | 146 | 0101101 147 | * 148 | Ed 149 | 1e-007 1 1 0 150 | 1 3 0 0 0.42426406871193 151 | 6 5 1 6 152 | 6 6 3 6 153 | 0 154 | 155 | 0101000 156 | +22 6 -18 6 * 157 | Ed 158 | 1e-007 1 1 0 159 | 1 4 0 0 0.42426406871192 160 | 6 7 1 6 161 | 6 8 4 6 162 | 0 163 | 164 | 0101000 165 | +18 6 -20 6 * 166 | Wi 167 | 168 | 0101000 169 | -21 5 -19 5 -17 5 -16 5 * 170 | Fa 171 | 0 1e-007 1 3 172 | 2 1 173 | 0111000 174 | +15 0 * 175 | Ve 176 | 1.00000000466544e-007 177 | 1.00000000000001 -2.77555756156289e-017 -2.08166817117217e-017 178 | 0 0 179 | 180 | 0101101 181 | * 182 | Ed 183 | 1e-007 1 1 0 184 | 1 5 0 0 1.04403065089106 185 | 6 9 2 11 186 | 6 10 4 11 187 | 0 188 | 189 | 0101000 190 | +20 11 -13 11 * 191 | Ed 192 | 1e-007 1 1 0 193 | 1 6 0 0 1.04403065089106 194 | 6 11 2 15 195 | 6 12 3 15 196 | 0 197 | 198 | 0101000 199 | +22 15 -13 15 * 200 | Wi 201 | 202 | 0101000 203 | +19 5 -12 10 +21 5 +11 14 * 204 | Fa 205 | 0 1e-007 2 7 206 | 2 2 207 | 0111000 208 | +10 0 * 209 | Ed 210 | 1e-007 1 1 0 211 | 1 7 0 0 1.04403065089106 212 | 6 13 3 11 213 | 6 14 4 11 214 | 0 215 | 216 | 0101000 217 | +13 11 -18 11 * 218 | Wi 219 | 220 | 0101000 221 | -11 14 +17 5 -8 10 * 222 | Fa 223 | 0 1e-007 3 13 224 | 2 3 225 | 0111000 226 | +7 0 * 227 | Wi 228 | 229 | 0101000 230 | +8 10 +12 10 +16 5 * 231 | Fa 232 | 0 1e-007 4 9 233 | 2 4 234 | 0111000 235 | +5 0 * 236 | Sh 237 | 238 | 0101000 239 | +14 2 +9 2 +6 2 +4 2 * 240 | So 241 | 242 | 0100000 243 | +3 0 * 244 | Co 245 | 246 | 1100000 247 | -2 1 * 248 | 249 | +1 0 -------------------------------------------------------------------------------- /ui/TaskPlacementShooter.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PartDesignGui::TaskDatumParameters 4 | 5 | 6 | 7 | 0 8 | 0 9 | 272 10 | 604 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Form 21 | 22 | 23 | 24 | 25 | 26 | adding placements to Y 27 | 28 | 29 | Qt::AlignCenter 30 | 31 | 32 | true 33 | 34 | 35 | 36 | 37 | 38 | 39 | don't modify 40 | 41 | 42 | 43 | 44 | 45 | 46 | <html><head/><body><p><span style=" color:#ff0000;">ok</span></p></body></html> 47 | 48 | 49 | true 50 | 51 | 52 | 53 | 54 | 55 | 56 | orientation: 57 | 58 | 59 | 60 | 61 | 62 | align to origin 63 | 64 | 65 | 66 | 67 | 68 | 69 | align to camera 70 | 71 | 72 | 73 | 74 | 75 | 76 | XY plane tangent to surface; Y aligned to global Z 77 | 78 | 79 | tangent, auto aligned 80 | 81 | 82 | true 83 | 84 | 85 | 86 | 87 | 88 | 89 | X aligned to tangent vector by u parameter of the surface/edge 90 | 91 | 92 | tangent, u-aligned 93 | 94 | 95 | false 96 | 97 | 98 | 99 | 100 | 101 | 102 | X aligned to tangent vector by v of the surface 103 | 104 | 105 | tangent, v-aligned 106 | 107 | 108 | false 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | <html><head/><body><p>Click surfaces of objects to drop new placements.</p><p>To edit a placement, select the placement, then click to place it anew.</p></body></html> 119 | 120 | 121 | true 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 10 132 | 133 | 134 | 10 135 | 136 | 137 | true 138 | 139 | 140 | true 141 | 142 | 143 | true 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /ui/pref/pref.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | lattice2-pref-general.ui 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/pref/recompile-rc.bat: -------------------------------------------------------------------------------- 1 | rcc pref.qrc --binary --o pref.rcc 2 | echo Should be compiled now =) 3 | pause 4 | --------------------------------------------------------------------------------