diff --git a/DISCLAIMER.md b/DISCLAIMER.md new file mode 100644 index 0000000..5a309eb --- /dev/null +++ b/DISCLAIMER.md @@ -0,0 +1,3 @@ +# DISCLAIMER + +This project was funded by the United States Department of Energy, National Energy Technology Laboratory, in part, through a site support contract. Neither the United States Government nor any agency thereof, nor any of their employees, nor the support contractor, nor any of their employees, makes any warranty, express or implied, or assumes any legal liability or responsibility for the accuracy, completeness, or usefulness of any information, apparatus, product, or process disclosed, or represents that its use would not infringe privately owned rights. Reference herein to any specific commercial product, process, or service by trade name, trademark, manufacturer, or otherwise does not necessarily constitute or imply its endorsement, recommendation, or favoring by the United States Government or any agency thereof. The views and opinions of authors expressed herein do not necessarily state or reflect those of the United States Government or any agency thereof. diff --git a/InitGui.py b/InitGui.py new file mode 100644 index 0000000..3908611 --- /dev/null +++ b/InitGui.py @@ -0,0 +1,63 @@ +# *************************************************************************** +# * * +# * Copyright: See License.md file * +# * * +# * Authors: Yash Girish Shah, Grigorios Panagakos * +# * * +# *************************************************************************** + + + +class packedColGenWorkbench (Workbench): + + MenuText = "Packed Columns" + ToolTip = "A workbench for generating packed columns with structured packing" + + def getWorkbenchFolder(self): + + import os.path + from os import path + + recommended_folders = ( + "Mod/packedColumnGen" , + "Mod/FreeCAD-packedColumnGen" + ) + + basedir = str(FreeCAD.getUserAppDataDir()) + folder = "" + + for tryfolder in recommended_folders: + if path.exists(basedir + tryfolder): + folder = basedir + tryfolder + return folder + + + return "" + + + def __init__(self): + + self.__class__.Icon = self.getWorkbenchFolder() + "/Resources/Icons/column_icon.svg" + + def Initialize(self): + """This function is executed when FreeCAD starts""" + import FreeCAD + import columnGen + self.list = ["PackedColumn","PackedColumn2D","Generate"] + self.appendToolbar("ColumnGenerator",self.list) + self.appendMenu("ColumnGenerator",self.list) + #self.appendMenu(["An existing Menu","My submenu"],self.list) + + def Activated(self): + return + + def Deactivated(self): + return + + def ContextMenu(self, recipient): + self.appendContextMenu("ColumnGenerator",self.list) + + def GetClassName(self): + return "Gui::PythonWorkbench" + +Gui.addWorkbench(packedColGenWorkbench()) diff --git a/LICENSE.md b/LICENSE.md index 4efe1a4..fc02d7f 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,72 +1,72 @@ -Copyright and License -===================== - -Copyright (c) 2012 - 2024 - -Copyright Notice ----------------- - -Foqus was produced under the DOE Carbon Capture Simulation Initiative (CCSI), and is copyright (c) -2012 - 2024 by the software owners: Oak Ridge Institute for Science and Education (ORISE), TRIAD -National Security, LLC., Lawrence Livermore National Security, LLC., The Regents of the University -of California, through Lawrence Berkeley National Laboratory, Battelle Memorial Institute, Pacific -Northwest Division through Pacific Northwest National Laboratory, Carnegie Mellon University, West -Virginia University, Boston University, the Trustees of Princeton University, The University of -Texas at Austin, URS Energy & Construction, Inc., et al.. All rights reserved. - -NOTICE. This Software was developed under funding from the U.S. Department of Energy and the -U.S. Government consequently retains certain rights. As such, the U.S. Government has been granted -for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license -in the Software to reproduce, distribute copies to the public, prepare derivative works, and perform -publicly and display publicly, and to permit other to do so. - -License Agreement ------------------ - -Foqus Copyright (c) 2012 - 2024, by the software owners: Oak Ridge Institute for Science and -Education (ORISE), TRIAD National Security, LLC., Lawrence Livermore National Security, LLC., The -Regents of the University of California, through Lawrence Berkeley National Laboratory, Battelle -Memorial Institute, Pacific Northwest Division through Pacific Northwest National Laboratory, -Carnegie Mellon University, West Virginia University, Boston University, the Trustees of Princeton -University, The University of Texas at Austin, URS Energy & Construction, Inc., et al. All rights -reserved. - - -Redistribution and use in source and binary forms, with or without modification, are permitted -provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions - and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions - and the following disclaimer in the documentation and/or other materials provided with the - distribution. - -3. Neither the name of the Carbon Capture Simulation Initiative, U.S. Dept. of Energy, the National - Energy Technology Laboratory, Oak Ridge Institute for Science and Education (ORISE), TRIAD - National Security, LLC., Lawrence Livermore National Security, LLC., the University of - California, Lawrence Berkeley National Laboratory, Battelle Memorial Institute, Pacific Northwest - National Laboratory, Carnegie Mellon University, West Virginia University, Boston University, the - Trustees of Princeton University, the University of Texas at Austin, URS Energy & Construction, - Inc., nor the names of its contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -You are under no obligation whatsoever to provide any bug fixes, patches, or upgrades to the -features, functionality or performance of the source code ("Enhancements") to anyone; however, if -you choose to make your Enhancements available either publicly, or directly to Lawrence Berkeley -National Laboratory, without imposing a separate written license agreement for such Enhancements, -then you hereby grant the following license: a non-exclusive, royalty-free perpetual license to -install, use, modify, prepare derivative works, incorporate into other computer software, -distribute, and sublicense such enhancements or derivative works thereof, in binary and source code -form. +Copyright and License +===================== + +Copyright (c) 2012 - 2024 + +Copyright Notice +---------------- + +PackedColumnGen was produced under the DOE Carbon Capture Simulation Initiative (CCSI), and is copyright (c) +2012 - 2024 by the software owners: Oak Ridge Institute for Science and Education (ORISE), TRIAD +National Security, LLC., Lawrence Livermore National Security, LLC., The Regents of the University +of California, through Lawrence Berkeley National Laboratory, Battelle Memorial Institute, Pacific +Northwest Division through Pacific Northwest National Laboratory, Carnegie Mellon University, West +Virginia University, Boston University, the Trustees of Princeton University, The University of +Texas at Austin, URS Energy & Construction, Inc., et al.. All rights reserved. + +NOTICE. This Software was developed under funding from the U.S. Department of Energy and the +U.S. Government consequently retains certain rights. As such, the U.S. Government has been granted +for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license +in the Software to reproduce, distribute copies to the public, prepare derivative works, and perform +publicly and display publicly, and to permit other to do so. + +License Agreement +----------------- + +PackedColumnGen Copyright (c) 2012 - 2024, by the software owners: Oak Ridge Institute for Science and +Education (ORISE), TRIAD National Security, LLC., Lawrence Livermore National Security, LLC., The +Regents of the University of California, through Lawrence Berkeley National Laboratory, Battelle +Memorial Institute, Pacific Northwest Division through Pacific Northwest National Laboratory, +Carnegie Mellon University, West Virginia University, Boston University, the Trustees of Princeton +University, The University of Texas at Austin, URS Energy & Construction, Inc., et al. All rights +reserved. + + +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions + and the following disclaimer in the documentation and/or other materials provided with the + distribution. + +3. Neither the name of the Carbon Capture Simulation Initiative, U.S. Dept. of Energy, the National + Energy Technology Laboratory, Oak Ridge Institute for Science and Education (ORISE), TRIAD + National Security, LLC., Lawrence Livermore National Security, LLC., the University of + California, Lawrence Berkeley National Laboratory, Battelle Memorial Institute, Pacific Northwest + National Laboratory, Carnegie Mellon University, West Virginia University, Boston University, the + Trustees of Princeton University, the University of Texas at Austin, URS Energy & Construction, + Inc., nor the names of its contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +You are under no obligation whatsoever to provide any bug fixes, patches, or upgrades to the +features, functionality or performance of the source code ("Enhancements") to anyone; however, if +you choose to make your Enhancements available either publicly, or directly to Lawrence Berkeley +National Laboratory, without imposing a separate written license agreement for such Enhancements, +then you hereby grant the following license: a non-exclusive, royalty-free perpetual license to +install, use, modify, prepare derivative works, incorporate into other computer software, +distribute, and sublicense such enhancements or derivative works thereof, in binary and source code +form. diff --git a/README.md b/README.md index 21ceb6d..e880eb6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,29 @@ -# Packed_Column_Gen -Parametric construction of three-dimensional columns with structured packing. +# PackedColumnGen +PackedColumnGen is a tool for building CAD models of absorber columns with customized structured packing designs. The tool generates these designs from a user-defined set of input parameters. This tool is designed as [Workbench](https://wiki.freecad.org/Workbenches) for [FreeCAD](https://www.freecad.org/), a free and open-source CAD software with a user-friendly interface. The tool currently supports the following features: + +* Parametric construction of three-dimensional columns with structured packing. +* Addition of cooling channels to create process intensified structured packing. +* Built-in functions that extend support for scripted packing generation. + +Refer to the [user guide](./doc/README.md) to learn more and get started with example cases. + +## Installation + +### Prerequisites +FreeCAD version 0.19 or newer needs to be installed before running the setup. The latest version can be downloaded [here](https://www.freecad.org/). + +### Installation using the Setup file + +The tool source code is packaged into the `Setup.exe` executable file, which extracts to the default FreeCAD modules directory. On Windows, the module directory is `%APPDATA%\FreeCAD\Mod\`, which is usually `C:\Users\username\Appdata\Roaming\FreeCAD\Mod\`. Restart FreeCAD after extraction for changes to take effect and the module should be accessible as `Packed Columns` workbench in FreeCAD as shown below. + +![Workbench demonstration](./doc/Images/Workbench.png) + +### Manual installation +1. Find the FreeCAD workbench directory by typing `App.getUserAppDataDir()` in FreeCAD [Python console](https://wiki.freecad.org/Python_console). +2. Navigate to the workbench directory and copy or extract the contents of this repository to a new folder within this directory. +3. **IMPORTANT** Rename the folder to "packedColumnGen" (case sensitive). +4. Restart FreeCAD for changes to take effect. The workbench `Packed Columns` should now in the list of workbenches in FreeCAD. + +## Authors + > [Yash Girish Shah](mailto:yashgirish.shah@netl.doe.gov) + > [Grigorios Panagakos](mailto:gpanagak@andrew.cmu.edu) diff --git a/Resources/Icons/columnTree.png b/Resources/Icons/columnTree.png new file mode 100644 index 0000000..c846cfb Binary files /dev/null and b/Resources/Icons/columnTree.png differ diff --git a/Resources/Icons/columnTree.svg b/Resources/Icons/columnTree.svg new file mode 100644 index 0000000..b19a50b --- /dev/null +++ b/Resources/Icons/columnTree.svg @@ -0,0 +1,157 @@ + + + +image/svg+xml diff --git a/Resources/Icons/column_icon.svg b/Resources/Icons/column_icon.svg new file mode 100644 index 0000000..6c792f0 --- /dev/null +++ b/Resources/Icons/column_icon.svg @@ -0,0 +1,1866 @@ + + + +image/svg+xml diff --git a/Resources/Icons/generate.svg b/Resources/Icons/generate.svg new file mode 100644 index 0000000..712c560 --- /dev/null +++ b/Resources/Icons/generate.svg @@ -0,0 +1,98 @@ + + + +image/svg+xml diff --git a/Resources/Icons/settings_icon.svg b/Resources/Icons/settings_icon.svg new file mode 100644 index 0000000..3f15cc5 --- /dev/null +++ b/Resources/Icons/settings_icon.svg @@ -0,0 +1,107 @@ + + + +image/svg+xml diff --git a/Resources/Icons/settings_icon2D.svg b/Resources/Icons/settings_icon2D.svg new file mode 100644 index 0000000..5c4b13f --- /dev/null +++ b/Resources/Icons/settings_icon2D.svg @@ -0,0 +1,105 @@ + + + +image/svg+xml2D diff --git a/columnGen/Functions2D.py b/columnGen/Functions2D.py new file mode 100644 index 0000000..867bcbb --- /dev/null +++ b/columnGen/Functions2D.py @@ -0,0 +1,77 @@ +# *************************************************************************** +# * * +# * Copyright: See License.md file * +# * * +# * Authors: Yash Girish Shah, Grigorios Panagakos * +# * * +# *************************************************************************** + +from .preamble import * +from .commonFunctions import * + +def addline_pack(base,nprev,theta_val,Lpacking = None,attach_point = 2): + vvec = App.Vector(0.,1.,0.) + l1 = base.getPoint(nprev,1) - base.getPoint(nprev,2) + if attach_point == 1: + l1 = -1.*l1 + if Lpacking is None: Lpacking = l1.Length + l1 /= l1.Length + z1 = (l1.x + 1j*(l1.y))*np.exp(1j*theta_val)*Lpacking + base.addGeometry(Part.LineSegment(base.getPoint(nprev,attach_point),base.getPoint(nprev,attach_point) - vvec),False) + ncurr = base.GeometryCount - 1 + base.addConstraint(Sketcher.Constraint("Coincident",ncurr,1,nprev,attach_point)) + + if z1.real > 0: + base.addConstraint(Sketcher.Constraint("DistanceX",nprev,attach_point,ncurr,2,np.abs(z1.real))) + if z1.real < 0: + base.addConstraint(Sketcher.Constraint("DistanceX",ncurr,2,nprev,attach_point,np.abs(z1.real))) + setDatum_distance(base,np.abs(z1.real)) + + if z1.imag > 0: + base.addConstraint(Sketcher.Constraint("DistanceY",nprev,attach_point,ncurr,2,np.abs(z1.imag))) + if z1.imag < 0: + base.addConstraint(Sketcher.Constraint("DistanceY",ncurr,2,nprev,attach_point,np.abs(z1.imag))) + setDatum_distance(base,np.abs(z1.imag)) + return ncurr + +def offsetline_pack(base,noffset,nNext,nattach,tcol): + l1 = base.getPoint(noffset,2) - base.getPoint(noffset,1) + base.addGeometry(Part.LineSegment(base.getPoint(nattach,2),base.getPoint(nattach,2) + l1),False) + ncurr = base.GeometryCount - 1 + base.addConstraint(Sketcher.Constraint("Coincident",ncurr,1,nattach,2)) + base.addConstraint(Sketcher.Constraint("Parallel",ncurr,noffset)) + base.addGeometry(Part.LineSegment(base.getPoint(ncurr,2),base.getPoint(ncurr,2) + App.Vector(0,-1,0)),False) + base.addConstraint(Sketcher.Constraint("Coincident",ncurr+1,1,ncurr,2)) + if nNext > 0: + base.addConstraint(Sketcher.Constraint("PointOnObject",ncurr+1,2,nNext)) + base.addConstraint(Sketcher.Constraint("Perpendicular",ncurr+1,nNext)) + base.addConstraint(Sketcher.Constraint("Distance",ncurr+1,tcol)) + setDatum_distance(base,tcol) + base.toggleConstruction(ncurr+1) + else: + base.addConstraint(Sketcher.Constraint("Coincident",ncurr+1,2,noffset,2)) + base.addConstraint(Sketcher.Constraint("Horizontal",ncurr+1)) + return ncurr + +def get_NpackY(theta1,column_height,dpack,Ldrip,Lpack): + Ny = np.int((column_height - Ldrip - 2.*dpack)/(Lpack*np.sin(theta1*np.pi/180.))) + return Ny + +def get_NpackX(theta1,tcol,pack_gap,column_radius,Lpack): + Lh = 2.*column_radius + Lunit = 2.*Lpack*np.cos(theta1*np.pi/180.) + 2.*tcol/np.sin(theta1*np.pi/180.) + 2.*pack_gap + Nx = np.int((Lh - 1.*pack_gap )/Lunit) + return Nx + +def get_dpack_wall(theta1,tcol,pack_gap,column_radius,Lpack): + Lh = 2.*column_radius + Nx = get_NpackX(theta1,tcol,pack_gap,column_radius,Lpack) + Lunit = 2.*Lpack*np.cos(theta1*np.pi/180.) + 2.*tcol/np.sin(theta1*np.pi/180.) + pack_gap + dpack_wall = (Lh - Nx*Lunit)/(Nx+1) + return dpack_wall + +def Lrange(theta1,tcol,pack_gap,column_radius,dpack_wall = 0., Nx = 2): + Lh = 2.*column_radius + Lunit = 2.*tcol/np.sin(theta1*np.pi/180.) + pack_gap + dpack_wall + Lmax = (Lh - Nx*Lunit - dpack_wall)/(2.*Nx*np.cos(theta1*np.pi/180.)) + return Lmax diff --git a/columnGen/__init__.py b/columnGen/__init__.py new file mode 100644 index 0000000..7bd0684 --- /dev/null +++ b/columnGen/__init__.py @@ -0,0 +1,9 @@ +# *************************************************************************** +# * * +# * Copyright: See License.md file * +# * * +# * Authors: Yash Girish Shah, Grigorios Panagakos * +# * * +# *************************************************************************** + +from .commands import * diff --git a/columnGen/commands.py b/columnGen/commands.py new file mode 100644 index 0000000..d69574a --- /dev/null +++ b/columnGen/commands.py @@ -0,0 +1,135 @@ +# *************************************************************************** +# * * +# * Copyright: See License.md file * +# * * +# * Authors: Yash Girish Shah, Grigorios Panagakos * +# * * +# *************************************************************************** + +from .preamble import * +from .commonFunctions import getWorkbenchFolder as getWorkbenchFolder +import columnGen.packing3D as packing3D +import columnGen.packing2D as packing2D + +class PackedColCommand2D: + + def GetResources(self): + return {'Pixmap' : getWorkbenchFolder() + '/Resources/Icons/settings_icon2D.svg', + 'Accel' : "Shift+O", + 'MenuText': "2D Packed Column", + 'ToolTip' : "Define Parameters for a 2D Packed Column"} + + def Activated(self): + obj=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Packed Column") + packing2D.PackedColumn2D(obj) + packing2D.ViewProviderColumn2D(obj.ViewObject, "Packed Column") + FreeCAD.ActiveDocument.recompute() + FreeCADGui.SendMsgToActiveView("ViewFit") + return + + def IsActive(self): + if FreeCAD.ActiveDocument == None: + return False + else: + return True + +FreeCADGui.addCommand('PackedColumn2D',PackedColCommand2D()) + +class PackedColCommand: + + def GetResources(self): + return {'Pixmap' : getWorkbenchFolder() + '/Resources/Icons/settings_icon.svg', + 'Accel' : "Shift+P", + 'MenuText': "Packed Column", + 'ToolTip' : "Define Parameters for a Packed Column"} + + def Activated(self): + obj=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Packed Column") + packing3D.PackedColumn(obj) + packing3D.ViewProviderColumn(obj.ViewObject, "Packed Column") + FreeCAD.ActiveDocument.recompute() + FreeCADGui.SendMsgToActiveView("ViewFit") + return + + + def IsActive(self): + if FreeCAD.ActiveDocument == None: + return False + else: + return True + +FreeCADGui.addCommand('PackedColumn',PackedColCommand()) + + + + +class GenerateCommand: + + def GetResources(self): + return {'Pixmap' : getWorkbenchFolder() + '/Resources/Icons/generate.svg', + 'Accel' : "Shift+G", + 'MenuText': "Generate", + 'ToolTip' : "Generate the Packed Column"} + + def Activated(self): + selectionList = FreeCADGui.Selection.getSelection() + for item in selectionList: + if item.TypeId == "Part::FeaturePython": + proxy = item.Proxy + + if proxy.Type_ in ["Column2D","Column3D"]: + + if proxy.resetNeeded: + proxy.reset() + FreeCAD.ActiveDocument.recompute() + + if not proxy.columnCreated: + proxy.createColumn() + FreeCAD.ActiveDocument.recompute() + + item.touch() + FreeCAD.ActiveDocument.recompute() + + FreeCADGui.SendMsgToActiveView("ViewFit") + return + + + def IsActive(self): + if FreeCAD.ActiveDocument == None: + return False + else: + return True + +FreeCADGui.addCommand('Generate',GenerateCommand()) + + +class ResetCommand: + + def GetResources(self): + return {'Pixmap' : getWorkbenchFolder() + '/Resources/Icons/hexahedron.svg', + 'Accel' : "Shift+R", + 'MenuText': "Reset", + 'ToolTip' : "Reset the Packed Column"} + + def Activated(self): + Gui = FreeCADGui + selectionList = Gui.Selection.getSelection() + for item in selectionList: + if item.TypeId == "Part::FeaturePython": + proxy = item.Proxy + proxy.reset() + FreeCAD.ActiveDocument.recompute() + + FreeCAD.ActiveDocument.recompute() + FreeCADGui.SendMsgToActiveView("ViewFit") + return + + + def IsActive(self): + if FreeCAD.ActiveDocument == None: + return False + else: + return True + +FreeCADGui.addCommand('Reset',ResetCommand()) + diff --git a/columnGen/commonFunctions.py b/columnGen/commonFunctions.py new file mode 100644 index 0000000..941dc76 --- /dev/null +++ b/columnGen/commonFunctions.py @@ -0,0 +1,292 @@ +# *************************************************************************** +# * * +# * Copyright: See License.md file * +# * * +# * Authors: Yash Girish Shah, Grigorios Panagakos * +# * * +# *************************************************************************** + +from .preamble import * + +def getWorkbenchFolder(): + + import os.path + from os import path + + recommended_folders = ( + "Mod/packedColumnGen" , + "Mod/FreeCAD-packedColumnGen" + ) + + basedir = str(FreeCAD.getUserAppDataDir()) + folder = "" + + for tryfolder in recommended_folders: + if path.exists(basedir + tryfolder): + folder = basedir + tryfolder + return folder + + + return "" + +def datum_settings(S): + S.AttachmentOffset = App.Placement(App.Vector(0.0000000000, 0.0000000000, 0.0000000000), App.Rotation(0.0000000000, 0.0000000000, 0.0000000000)) + S.MapReversed = False + S.MapPathParameter = 0.000000 + +def convertDistanceUnits(l): + return "%.6f mm"%(l) + +def convertAngleUnits(a): + return "%.6f deg"%(a) + +def setDatum_distance(base,l): + base.setDatum(base.ConstraintCount-1,App.Units.Quantity(convertDistanceUnits(l))) + +def setDatum_angle(base,l): + base.setDatum(base.ConstraintCount-1,App.Units.Quantity(convertAngleUnits(l))) + +def line2vec(base,line_i = None): + if line_i is None: line_i = base.GeometryCount - 1 + return base.getPoint(line_i,2) - base.getPoint(line_i,1) + +def setLength(base,l,line_i = None): + if line_i is None: line_i = base.GeometryCount - 1 + base.addConstraint(Sketcher.Constraint("Distance",line_i,l)) + setDatum_distance(base,l) + +def horizontal_constraint(base,line_i = None): + if line_i is None: line_i = base.GeometryCount - 1 + base.addConstraint(Sketcher.Constraint("Horizontal",line_i)) + +def vertical_constraint(base,line_i = None): + if line_i is None: line_i = base.GeometryCount - 1 + base.addConstraint(Sketcher.Constraint("Vertical",line_i)) + +def rotateVector(vec,theta): + v = vec[0] + 1j*vec[1] + v *= np.exp(1j*theta) + return App.Vector(np.real(v),np.imag(v),0.) + +def connectedLineGen(base, i = 1, vec = None, line_i = None): + if vec is None: vec = App.Vector(1.,0.,0.) + if line_i is None: line_i = base.GeometryCount - 1 + base.addGeometry(Part.LineSegment(base.getPoint(line_i,i),base.getPoint(line_i,i) + vec),False) + line_j = base.GeometryCount - 1 + base.addConstraint(Sketcher.Constraint("Coincident",line_j,1,line_i,i)) + return base.GeometryCount - 1 + +def connectedLine1(base,vec = None, line_i = None): + return connectedLineGen(base, i = 1, vec = vec, line_i = line_i) + +def connectedLine2(base,vec = None, line_i = None): + return connectedLineGen(base, i = 2, vec = vec, line_i = line_i) + +def deg2rad(angle): + return angle*np.pi/180. + +def connecterLine(base,line_i,point_i,line_j,point_j): + base.addGeometry(Part.LineSegment(base.getPoint(line_i,point_i),base.getPoint(line_j,point_j)),False) + ii = base.GeometryCount - 1 + base.addConstraint(Sketcher.Constraint("Coincident",ii,1,line_i,point_i)) + base.addConstraint(Sketcher.Constraint("Coincident",ii,2,line_j,point_j)) + return ii + +def setAngle(base,line_i,line_j,theta): + # Sets counter-clockwise angle through distances. The angle is always the one obtained by rotating the line_i in the counter-clockwise direction till line_j is encountered. + # NOTE: setAngle should always be run AFTER setting lengths of the lines involved. + + isect = None + # identify common point and store in isect (i'nter'sect) + for ii,jj in zip([1,1,2,2],[1,2,1,2]): + vtmp = base.getPoint(line_i,ii) - base.getPoint(line_j,jj) + if vtmp.Length < 1e-14: isect = (ii,jj) + + if isect is None: + print("Cannot find intersection") + sys.exit() + + #print("isect = ",isect) + i0 = isect[0]; lst = [1,2]; lst.remove(i0); i1 = lst[0] + j0 = isect[1]; lst = [1,2]; lst.remove(j0); j1 = lst[0] + #print("outer points:",i1,j1) + + vi = base.getPoint(line_i,i1) - base.getPoint(line_i,i0) + vj = base.getPoint(line_j,j1) - base.getPoint(line_j,j0) + vsub = vi - vj + + isign = 1. + if np.cos(theta) < 0.: + isign = -1. + + if np.abs(np.cos(theta)) < 1e-16 : + # Lines are orthogonal + base.addConstraint(Sketcher.Constraint("Perpendicular",line_i,line_j)) + else: + icons = connectedLineGen(base,i = i0,vec = isign*vi.normalize(),line_i = line_i) + base.toggleConstruction(icons) + base.addConstraint(Sketcher.Constraint("Parallel",icons,line_i)) + setLength(base,vj.Length*np.abs(np.cos(theta)),line_i = icons) + + vtmp = base.getPoint(line_j,j1) - base.getPoint(icons,2) + + jcons = connectedLineGen(base,i = 2,vec = vtmp,line_i = icons) + base.toggleConstruction(jcons) + setLength(base,vj.Length*np.abs(np.sin(theta)),line_i = jcons) + #base.addConstraint(Sketcher.Constraint("Perpendicular",jcons,icons)) + base.addConstraint(Sketcher.Constraint("Coincident",jcons,2,line_j,j1)) + +def getLineNum(base): + return base.GeometryCount - 1 + +def cell_outside_circle(bounding_box,vec,rcut): + vec = np.array([vec[0],vec[1]]) + vlist = [] + for i,j in zip(["xmin","xmin","xmax","xmax"],["ymin","ymax","ymin","ymax"]): + vlist.append(np.array([bounding_box[i]*1.,bounding_box[j]*1.]) + vec) + + outside = True + for vi in vlist: + if np.sum(vi**2.) < (rcut*2.)**2.: + outside = False + return outside + +def within(x,x1,x2): + lst = [x1,x2] + x1 = np.min(lst) + x2 = np.max(lst) + bw = False + if (x < x2) & (x > x1): bw = True + return bw + + +def cell_outside_circle2(bounding_box,vec,rcut): + vec = np.array([vec[0],vec[1]]) + xmin = bounding_box["xmin"] + vec[0] + xmax = bounding_box["xmax"] + vec[0] + ymin = bounding_box["ymin"] + vec[1] + ymax = bounding_box["ymax"] + vec[1] + vlist = [] + for i,j in zip(["xmin","xmin","xmax","xmax"],["ymin","ymax","ymin","ymax"]): + vlist.append(np.array([bounding_box[i]*1.,bounding_box[j]*1.]) + vec) + + outside = True + # Case 1: one of vlist is inside the circle + #for vi in vlist: + # if np.sum(vi**2.) < (rcut)**2.: + # outside = False + # print("Case1") + # return outside + + # Case 2: Bbox is outside but one of the Bbox edges intersects the circle + for xi in [xmin,xmax]: + if within(xi,-rcut,rcut): + yi = np.sqrt(rcut**2. - xi**2.) + if within(ymin,-yi,yi) | within(ymax,-yi,yi): + outside = False + print("Case2") + return outside + + for yi in [ymin,ymax]: + if within(yi,-rcut,rcut): + xi = np.sqrt(rcut**2. - yi**2.) + if within(xmin,-xi,xi) | within(xmax,-xi,xi): + outside = False + print("Case2") + return outside + + # Case 3: None of the Bbox edges intersect but the Bbox contains the circle + if within(-rcut,xmin,xmax) & within(rcut,xmin,xmax): + if within(-rcut,ymin,ymax) & within(rcut,ymin,ymax): + print("Case3") + outside = False + return outside + + return outside + +def cell_outside_circle1(original_vertices,vec,rcut): + vlist = [vi + vec for vi in original_vertices] + rmin = np.sqrt(np.min([vi[0]*vi[0] + vi[1]*vi[1] for vi in vlist])) + outside = True + if rmin <= rcut*1.1: outside = False + return outside + +def getFaceNormal(face): + vlist = [i.Point for i in face.Vertexes] + # Compute centroid at o + o = App.Vector(0.,0.,0.) + for v in vlist: + o += v + o /= len(vlist) + if len(vlist) > 2: + v1 = vlist[0] - o + v2 = vlist[1] - o + v = v1.cross(v2).normalize() + return v + +def checkFaceNormal(face,normal): + match = False + if (normal.Length != 0): + normal = normal.normalize() + v = getFaceNormal(face) + if np.abs(np.abs(v.dot(normal)) - v.Length) < 1e-14: + match = True + return match + +def getFaceListMatch(bdy,normal): + faces = bdy.Shape.Faces + flist = [i for i,f in enumerate(faces) if checkFaceNormal(f,normal)] + return flist + +def getLtransfYScale(pol_pat,lproj): + flist = getFaceListMatch(pol_pat,App.Vector(0.,0.,1.)) + Amax = np.max([pol_pat.Shape.Faces[i].Area for i in flist]) + flist = [i for i in flist if pol_pat.Shape.Faces[i].Area/Amax > 0.001] + zlist = np.array([pol_pat.Shape.Faces[i].Vertexes[0].Point[2] for i in flist]) + i1 = int(np.argmin(np.abs(zlist[1:] - zlist[0])) + 1) + f1 = pol_pat.Shape.Faces[flist[0]] + f2 = pol_pat.Shape.Faces[flist[i1]] + vlist1 = [i.Point for i in f1.Vertexes] + vlist2 = [i.Point for i in f2.Vertexes] + d = -1e10 + for i,vi in enumerate(vlist1): + for j,vj in enumerate(vlist2): + dij = np.abs((vi - vj).dot(lproj)) + if dij > d: + d = dij + tup = (i,j) + #print("Faces = %d,%d; tup = %d,%d "%(flist[0]+1,flist[i1]+1,tup[0],tup[1])) + return d + +def getNbodies(top,bot,rcut,tx,ty): + Nx = [] + lx = tx[0] + ly = tx[1] + Nx.append((rcut - top["xmin"])/lx) + Nx.append((top["xmax"] + rcut)/lx) + Nx.append((rcut - bot["xmin"])/lx) + Nx.append((bot["xmax"] + rcut)/lx) + + Nx.append((rcut - top["ymin"])/ly) + Nx.append((top["ymax"] + rcut)/ly) + Nx.append((rcut - bot["ymin"])/ly) + Nx.append((bot["ymax"] + rcut)/ly) + + Ny = [] + lx = ty[0] + ly = ty[1] + Ny.append((rcut - top["xmin"])/lx) + Ny.append((top["xmax"] + rcut)/lx) + Ny.append((rcut - bot["xmin"])/lx) + Ny.append((bot["xmax"] + rcut)/lx) + Ny.append((rcut - top["ymin"])/ly) + Ny.append((top["ymax"] + rcut)/ly) + Ny.append((rcut - bot["ymin"])/ly) + Ny.append((bot["ymax"] + rcut)/ly) + + Nx = int(np.ceil(np.max(Nx))) + 1 + Ny = int(np.ceil(np.max(Ny))) + 1 + return Nx,Ny + + + diff --git a/columnGen/packing2D.py b/columnGen/packing2D.py new file mode 100644 index 0000000..fdf9e23 --- /dev/null +++ b/columnGen/packing2D.py @@ -0,0 +1,557 @@ +# *************************************************************************** +# * * +# * Copyright: See License.md file * +# * * +# * Authors: Yash Girish Shah, Grigorios Panagakos * +# * * +# *************************************************************************** + +from .preamble import * +from .commonFunctions import * +from .Functions2D import * + +class PackedColumn2D: + + + def __init__(self,obj\ + ,column_radius = 38.1 + ,column_height = 100 + ,Ldrip = 3 \ + ,Wdrip = 4 \ + ,Ndrip = 9 \ + ,tcol = 0.89 \ + ,Lpack = 12 \ + ,dpack = 17 \ + ,pack_gap = 1.78\ + ,theta1 = 45\ + ): + + self.Object = obj + obj.Proxy = self + self.Type_ = "Column2D" + + self.propertiesDict = {} + self.removedPropertiesDict = {} + self.MainShp = None + self.objects = [] + self.resetNeeded = False + self.columnCreated = False + + alen = "App::PropertyLength" + aang = "App::PropertyAngle" + abool = "App::PropertyBool" + aenum = "App::PropertyEnumeration" + aint = "App::PropertyInteger" + aflt = "App::PropertyFloat" + + subsect = "Column Dimensions" + self._addProperty(alen,"ColumnWidth",subsect,"Width of the column.").ColumnWidth = 2.*column_radius + self._addProperty(alen,"ColumnHeight",subsect,"Height of the column. ").ColumnHeight = column_height + + subsect = "Packing Parameters" + self._addProperty(alen,"CellLength",subsect,"Length of the repetitive elementary cell").CellLength = Lpack + self._addProperty(alen,"PackingPlacement",subsect,"Distance between the solvent inlet (dripping points) and the top face of the packing.").PackingPlacement = dpack + self._addProperty(alen,"PackingSpacing",subsect,"Spacing between adjacent 2D packing sheets. (along X)").PackingSpacing = pack_gap + self._addProperty(alen,"Thickness",subsect,"Thickness of the packing material").Thickness = tcol + self._addProperty(aang,"PackingAngle",subsect,"Angle of the packing walls from the horizontal.").PackingAngle = theta1 + + subsect = "Dripping Points" + self._addProperty(alen,"DripWidth", subsect,"Width of the dripping points.").DripWidth = Wdrip + self._addProperty(alen,"Depth", subsect,"Depth of the dripping points.").Depth = Ldrip + self._addProperty(aint,"NumberOfDrippingPoints", subsect,"Number of dripping points along the column width.").NumberOfDrippingPoints = Ndrip + + subsect = "General Properties" + self._addProperty(aenum,"GeometryConstructionType",subsect,"Specify whether the geometry to be constructed is 3D or 2D").GeometryConstructionType = ["3D","2D"] + self._addProperty(alen,"ColumnThickness",subsect,"Thickness of the column for 3D construction.").ColumnThickness = tcol + obj.GeometryConstructionType = "2D" + if (obj.GeometryConstructionType == "2D"): + self._removeProperty("ColumnThickness") + + def _addProperty(self,propertyType,propertyName,propertySection,propertyDescription): + if propertyName not in self.propertiesDict: + self.propertiesDict[propertyName] = [propertyType,propertyName,propertySection,propertyDescription] + self.propertiesDict[propertyName].append(self.Object.addProperty(propertyType,propertyName,propertySection,propertyDescription)) + return self.propertiesDict[propertyName][-1] + + def _removeProperty(self,item): + obj = self.Object + if hasattr(obj,item): + self.removedPropertiesDict[item] = obj.__getattribute__(item) + obj.removeProperty(item) + return True + else: + return False + + def _addRemovedProperty(self,item): + obj = self.Object + if (not hasattr(obj,item)) & (item in self.removedPropertiesDict): + obj.addProperty(self.propertiesDict[item][0],self.propertiesDict[item][1],self.propertiesDict[item][2],self.propertiesDict[item][3]) + obj.__setattr__(item,self.removedPropertiesDict.pop(item)) + return True + else: + return False + + def propertiesList(self): + return list(self.propertiesDict.keys()) + + def onChanged(self,obj,prop): + + if prop == "GeometryConstructionType": + if obj.GeometryConstructionType == "2D": + self._removeProperty("ColumnThickness") + + if obj.GeometryConstructionType == "3D": + self._addRemovedProperty("ColumnThickness") + + if hasattr(self,"propertiesDict"): + if prop in self.propertiesList(): + self.resetNeeded = True + + def createColumn(self): + obj = self.Object + column_radius = float(obj.ColumnWidth)/2. + column_height = float(obj.ColumnHeight) + Ldrip = float(obj.Depth) + Wdrip = float(obj.DripWidth) + Ndrip = int(obj.NumberOfDrippingPoints) + Wgas = (2.*column_radius - Ndrip*Wdrip)/(Ndrip + 1) + tcol = float(obj.Thickness) + Lpack = float(obj.CellLength) + dpack = float(obj.PackingPlacement) + pack_gap = float(obj.PackingSpacing) + theta1 = float(obj.PackingAngle) + output_type = obj.GeometryConstructionType + if output_type == "3D": + col_thickness = float(obj.ColumnThickness) + + + + theta2 = 2.*theta1 + NpackY = get_NpackY(theta1,column_height,dpack,Ldrip,Lpack) + NpackX = get_NpackX(theta1,tcol,pack_gap,column_radius,Lpack) + dpack_wall = get_dpack_wall(theta1,tcol,pack_gap,column_radius,Lpack) + + + #print("i = %d, Lmax = %.3g, dpack_wall = %.3g, NpackY = %d, NpackX = %d"%(i,Lrange(theta1,tcol,pack_gap,column_radius,dpack_wall = pack_gap),dpack_wall,NpackY, NpackX)) + + theta1_deg = theta1*1. + theta2_deg = theta2*1. + + theta1 *= np.pi/180. + theta2 *= np.pi/180. + + # Perform checks + if Wgas <= 0.: + QtGui.QMessageBox.information(None,"2D Column Building Error", "CANNOT PROCEED FURTHER as there are too many dripping points than the column width can accommodate. REDUCE THE NUMBER OF DRIPPING POINTS. Building Failed...") + return + + if NpackY <= 1: + QtGui.QMessageBox.information(None,"2D Column Building Error", "Cannot proceed further as the column is not tall enough to accommodate a single unit of packing. Reduce cell length or increase column height. Building Failed...") + return + + if NpackX == 0: + QtGui.QMessageBox.information(None,"2D Column Building Error", "Cannot proceed further as the column is not wide enough to accommodate a single unit of packing. Reduce cell length or increase column width. Building Failed...") + return + + + if theta2 < theta1: + print("CANNOT PROCEED FURTHER. THETA2 SHOULD AT LEAST BE %.3g degrees"%(theta1_deg)) + print("EXITING... \n\n\n") + sys.exit() + + + + Gui = FreeCADGui + E = App.ActiveDocument + F = Gui.ActiveDocument + E.recompute() + + dName = E.Name + + G = Gui.getDocument(dName) + A = App.getDocument(dName) + + body = A.addObject("PartDesign::Body","Body") + + base = body.newObject("Sketcher::SketchObject","Base") + base.Support = (A.getObject("XY_Plane"),[""]) + base.MapMode = "FlatFace" + E.recompute() + + vector = App.Vector + constraint = Sketcher.Constraint + line = Part.LineSegment + + + # Line -- 0 + base.addGeometry(line(vector(0.,0.,0.),vector(-1.*column_radius,0.,0)),False) + base.addConstraint(constraint("Coincident",0,1,-1,1)) + base.addConstraint(constraint("Distance",0,column_radius)) + setDatum_distance(base,column_radius) + base.addConstraint(constraint("Horizontal",0)) + + # Line -- 1 + base.addGeometry(line(vector(0.,0.,0.),vector(column_radius,0.,0)),False) + base.addConstraint(constraint("Coincident",1,1,-1,1)) + base.addConstraint(constraint("Horizontal",1)) + base.addConstraint(constraint("Equal",1,0)) + + # Line -- 2 + base.addGeometry(line(base.getPoint(0,2),base.getPoint(0,2) + vector(0.,1.,0.)*column_height),False) + base.addConstraint(constraint("Coincident",2,1,0,2)) + base.addConstraint(constraint("Vertical",2)) + base.addConstraint(constraint("Distance",2,column_height)) + setDatum_distance(base,column_height) + + # Line -- 3 + base.addGeometry(line(base.getPoint(1,2),base.getPoint(1,2) + vector(0.,1.,0.)*column_height),False) + base.addConstraint(constraint("Coincident",3,1,1,2)) + base.addConstraint(constraint("Vertical",3)) + base.addConstraint(constraint("Equal",3,2)) + + # Line -- 4 + base.addGeometry(line(base.getPoint(2,2),base.getPoint(2,2) + vector(1.,0.,0.)*Wgas),False) + base.addConstraint(constraint("Coincident",4,1,2,2)) + base.addConstraint(constraint("Horizontal",4)) + base.addConstraint(constraint("Distance",4,Wgas)) + setDatum_distance(base,Wgas) + + # Line -- 5 + base.addGeometry(line(base.getPoint(4,2),base.getPoint(4,2) + vector(0.,-1.,0.)*Ldrip),False) + base.addConstraint(constraint("Coincident",5,1,4,2)) + base.addConstraint(constraint("Vertical",5)) + base.addConstraint(constraint("Distance",5,Ldrip)) + setDatum_distance(base,Ldrip) + + + # Line -- 6 + base.addGeometry(line(base.getPoint(5,2),base.getPoint(5,2) + vector(1.,0.,0.)*Wdrip),False) + base.addConstraint(constraint("Coincident",6,1,5,2)) + base.addConstraint(constraint("Horizontal",6)) + base.addConstraint(constraint("Distance",6,Wdrip)) + setDatum_distance(base,Wdrip) + + + num1 = 4 # Wgas + num2 = 5 # Ldrip + num3 = 6 # Wdrip + hvec = vector(1.,0.,0.) + vvec = vector(0.,1.,0.) + + # Line -- 7 + base.addGeometry(line(base.getPoint(6,2),base.getPoint(6,2) + vvec*Ldrip),False) + base.addConstraint(constraint("Coincident",7,1,6,2)) + base.addConstraint(constraint("Vertical",7)) + base.addConstraint(constraint("Equal",7,num2)) + + # Line -- 8 + base.addGeometry(line(base.getPoint(7,2),base.getPoint(7,2) + hvec*Wgas),False) + base.addConstraint(constraint("Coincident",8,1,7,2)) + base.addConstraint(constraint("Horizontal",8)) + base.addConstraint(constraint("Equal",8,num1)) + + n = base.GeometryCount - 1 + if Ndrip > 1: + for i in range(1,Ndrip): + n += 1 + base.addGeometry(line(base.getPoint(n-1,2),base.getPoint(n-1,2) - vvec*Ldrip),False) + base.addConstraint(constraint("Coincident",n,1,n-1,2)) + base.addConstraint(constraint("Vertical",n)) + base.addConstraint(constraint("Equal",n,num2)) + + n += 1 + base.addGeometry(line(base.getPoint(n-1,2),base.getPoint(n-1,2) + hvec*Wdrip),False) + base.addConstraint(constraint("Coincident",n,1,n-1,2)) + base.addConstraint(constraint("Horizontal",n)) + base.addConstraint(constraint("Equal",n,num3)) + + n += 1 + base.addGeometry(line(base.getPoint(n-1,2),base.getPoint(n-1,2) + vvec*Ldrip),False) + base.addConstraint(constraint("Coincident",n,1,n-1,2)) + base.addConstraint(constraint("Vertical",n)) + base.addConstraint(constraint("Equal",n,num2)) + + n += 1 + base.addGeometry(line(base.getPoint(n-1,2),base.getPoint(n-1,2) + hvec*Wgas),False) + base.addConstraint(constraint("Coincident",n,1,n-1,2)) + if i != (Ndrip - 1): + base.addConstraint(constraint("Horizontal",n)) + base.addConstraint(constraint("Equal",n,num1)) + else: + base.addConstraint(constraint("Coincident",n,2,3,2)) + + + + list_start = [] + list_pack = [] + list_offset = [] + + # Line -- n+1 + Nstart = n + 1 + pt1 = base.getPoint(6,1) - vvec*dpack + hvec*(Lpack*np.cos(theta1)) + + base.addGeometry(line(pt1,pt1 + hvec),False) + base.addConstraint(constraint("Horizontal",n+1)) + base.addConstraint(constraint("DistanceY",n+1,1,6,1,dpack)) + setDatum_distance(base,dpack) + # Old code + #s1 = Lpack*np.cos(theta1) - Wgas + dpack_wall + #base.addConstraint(constraint("DistanceX",6,1,n+1,1,s1)) + #setDatum_distance(base,s1) + #s1 += tcol/np.sin(theta1) + #base.addConstraint(constraint("DistanceX",6,1,n+1,2,s1)) + #setDatum_distance(base,s1) + + # New code + s1 = dpack_wall + Lpack*np.cos(theta1) + base.addConstraint(constraint("DistanceX",2,1,n+1,1,s1)) + setDatum_distance(base,s1) + s1 += tcol/np.sin(theta1) + base.addConstraint(constraint("DistanceX",2,1,n+1,2,s1)) + setDatum_distance(base,s1) + + + # Line -- n+2 + Npack_list = [n+2] + base.addGeometry(line(base.getPoint(n+1,1),base.getPoint(n+1,1) - vvec),False) + agl = np.pi - theta1 + agl_deg = 180. - theta1_deg + base.addConstraint(constraint("Coincident",n+2,1,n+1,1)) + base.addConstraint(constraint("Angle",n+2,1,n+1,1,agl)) + setDatum_angle(base,agl_deg) + base.addConstraint(constraint("Distance",n+2,Lpack)) + setDatum_distance(base,Lpack) + + agl = theta2*1. + for i in range(1,NpackY): + agl *= -1. + Npack_list.append(addline_pack(base,Npack_list[-1],agl)) + + Noffset_list = [offsetline_pack(base,Npack_list[0], Npack_list[1],Nstart,tcol)] + for i in range(1,len(Npack_list)): + if i == len(Npack_list)-1: + Noffset_list.append(offsetline_pack(base,Npack_list[i],-1,Noffset_list[-1],tcol)) + else: + Noffset_list.append(offsetline_pack(base,Npack_list[i], Npack_list[i+1],Noffset_list[-1],tcol)) + + list_start.append(Nstart) + list_pack.append(Npack_list) + list_offset.append(Noffset_list) + + Nstart1 = base.GeometryCount + + l1 = base.getPoint(list_start[-1],2) - base.getPoint(list_start[-1],1) + pt1 = base.getPoint(list_start[-1],2) + hvec*pack_gap + base.addGeometry(line(pt1,pt1 + hvec),False) + base.addConstraint(constraint("Horizontal",Nstart1)) + base.addConstraint(constraint("DistanceX",list_start[-1],2,Nstart1,1,pack_gap)) + setDatum_distance(base,pack_gap) + base.addConstraint(constraint("DistanceX",Nstart1,1,Nstart1,2,l1.Length)) + setDatum_distance(base,l1.Length) + base.addConstraint(constraint("DistanceY",Nstart1,1,6,1,dpack)) + setDatum_distance(base,dpack) + + Npack_list1 = [addline_pack(base,Nstart1,-theta1,Lpacking = Lpack,attach_point = 1)] + agl = -1.*theta2 + for i in range(1,NpackY): + agl *= -1. + Npack_list1.append(addline_pack(base,Npack_list1[-1],agl)) + + + Noffset_list1 = [offsetline_pack(base,Npack_list1[0], Npack_list1[1],Nstart1,tcol)] + for i in range(1,len(Npack_list1)): + if i == len(Npack_list1)-1: + Noffset_list1.append(offsetline_pack(base,Npack_list1[i],-1,Noffset_list1[-1],tcol)) + else: + Noffset_list1.append(offsetline_pack(base,Npack_list1[i], Npack_list1[i+1],Noffset_list1[-1],tcol)) + + list_start.append(Nstart1) + list_pack.append(Npack_list1) + list_offset.append(Noffset_list1) + + + + for jj in range(1,NpackX): + + Nstart2 = base.GeometryCount + + l1 = Lpack*np.cos(theta1) + dpack_wall + Lpack*np.cos(theta1) + pt1 = base.getPoint(list_start[-1],2) + hvec*l1*2. + base.addGeometry(line(pt1,pt1+hvec),False) + base.addConstraint(constraint("Horizontal",Nstart2)) + base.addConstraint(constraint("DistanceX",list_start[-1],2,Nstart2,1,l1)) + setDatum_distance(base,l1) + base.addConstraint(constraint("DistanceY",Nstart2,1,6,1,dpack)) + setDatum_distance(base,dpack) + l1 = base.getPoint(list_start[-1],2) - base.getPoint(list_start[-1],1) + base.addConstraint(constraint("DistanceX",Nstart2,1,Nstart2,2,l1.Length)) + setDatum_distance(base,l1.Length) + + Npack_list2 = [addline_pack(base,Nstart2,np.pi + theta1,Lpacking = Lpack,attach_point = 1)] + agl = 1.*theta2 + for i in range(1,NpackY): + agl *= -1. + Npack_list2.append(addline_pack(base,Npack_list2[-1],agl)) + + + Noffset_list2 = [offsetline_pack(base,Npack_list2[0], Npack_list2[1],Nstart2,tcol)] + for i in range(1,len(Npack_list2)): + if i == len(Npack_list2)-1: + Noffset_list2.append(offsetline_pack(base,Npack_list2[i],-1,Noffset_list2[-1],tcol)) + else: + Noffset_list2.append(offsetline_pack(base,Npack_list2[i], Npack_list2[i+1],Noffset_list2[-1],tcol)) + + list_start.append(Nstart2) + list_pack.append(Npack_list2) + list_offset.append(Noffset_list2) + + Nstart2 = base.GeometryCount + + l1 = base.getPoint(list_start[-1],2) - base.getPoint(list_start[-1],1) + pt1 = base.getPoint(list_start[-1],2) + hvec*pack_gap + base.addGeometry(line(pt1,pt1 + hvec),False) + base.addConstraint(constraint("Horizontal",Nstart2)) + base.addConstraint(constraint("DistanceX",list_start[-1],2,Nstart2,1,pack_gap)) + setDatum_distance(base,pack_gap) + base.addConstraint(constraint("DistanceX",Nstart2,1,Nstart2,2,l1.Length)) + setDatum_distance(base,l1.Length) + base.addConstraint(constraint("DistanceY",Nstart2,1,6,1,dpack)) + setDatum_distance(base,dpack) + + Npack_list2 = [addline_pack(base,Nstart2,-theta1,Lpacking = Lpack,attach_point = 1)] + agl = -1.*theta2 + for i in range(1,NpackY): + agl *= -1. + Npack_list2.append(addline_pack(base,Npack_list2[-1],agl)) + + + Noffset_list2 = [offsetline_pack(base,Npack_list2[0], Npack_list2[1],Nstart2,tcol)] + for i in range(1,len(Npack_list2)): + if i == len(Npack_list2)-1: + Noffset_list2.append(offsetline_pack(base,Npack_list2[i],-1,Noffset_list2[-1],tcol)) + else: + Noffset_list2.append(offsetline_pack(base,Npack_list2[i], Npack_list2[i+1],Noffset_list2[-1],tcol)) + + list_start.append(Nstart2) + list_pack.append(Npack_list2) + list_offset.append(Noffset_list2) + + + pad = body.newObject('PartDesign::Pad',"Pad") + pad.Profile = base + if output_type == "3D": + pad.Length = col_thickness + else: + pad.Length = 1. + pad.Reversed = True + E.recompute() + + base.Visibility = False + E.recompute() + + self.Children = [] + + if output_type == "2D": + flist = pad.Shape.Faces + normalZ = vector(0.,0.,1.) + flist_indices = [i for i,f in enumerate(flist) if np.abs(getFaceNormal(f).dot(normalZ)) == 1.] + flist_indices = [i for i in flist_indices if flist[i].CenterOfMass[2] == 0.] + if len(flist_indices) == 0: + QtGui.QMessageBox.information(None, "Column Building Error", "Error occured in building column. Building Failed...") + return + i = flist_indices[0] + + faceShp = Part.getShape(body,"Pad.Face%d"%(i+1),needSubElement = True,refine = True) + self.MainShp = faceShp + else: + self.MainShp = body.Shape + + obj.Shape = self.MainShp + + body.removeObjectsFromDocument() + A.removeObject(body.Label) + A.recompute() + + self.columnCreated = True + self.resetNeeded = False + + def execute (self,obj): + #if self.MainShp is not None: + #obj.Shape = self.MainShp + pass + + def reset(self): + Gui = FreeCADGui + E = App.ActiveDocument + F = Gui.ActiveDocument + dName = E.Name + G = Gui.getDocument(dName) + A = App.getDocument(dName) + + self.resetNeeded = False + self.columnCreated = False + + +class ViewProviderColumn2D: + + obj_name = "Packed Column" + + def __init__(self, vobj, obj_name): + self.obj_name = obj_name + self.Object = vobj.Object # Original Object + vobj.Proxy = self + + def attach(self, vobj): + #vobj.addExtension("Gui::ViewProviderOriginGroupExtensionPython") + self.ViewObject = vobj + return + + def updateData(self, fp, prop): + return + + def getDisplayModes(self,obj): + return "As Is" + + def getDefaultDisplayMode(self): + return "As Is" + + def setDisplayMode(self,mode): + return "As Is" + + def onChanged(self, vobj, prop): + pass + + def getIcon(self): + return getWorkbenchFolder() + "/Resources/Icons/columnTree.svg" # + (self.obj_name).lower() + '.svg'" + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + def onDelete(self,feature,subelements): + try: + self.Object.Proxy.reset() + except: + msg = "Test: This object is being deleted" + #App.Console.PrintMessage(msg) + return True + + + def claimChildren(self): + objs = [] + if hasattr(self.Object.Proxy,"Children"): + objs.extend(self.Object.Proxy.Children) + if hasattr(self.Object.Proxy,"Base"): + objs.append(self.Object.Proxy.Base) + if hasattr(self.Object,"Base"): + objs.append(self.Object.Base) + if hasattr(self.Object,"Objects"): + objs.extend(self.Object.Objects) + if hasattr(self.Object,"Components"): + objs.extend(self.Object.Components) + if hasattr(self.Object,"Children"): + objs.extend(self.Object.Children) + + return objs diff --git a/columnGen/packing3D.py b/columnGen/packing3D.py new file mode 100644 index 0000000..ce086f6 --- /dev/null +++ b/columnGen/packing3D.py @@ -0,0 +1,995 @@ +# *************************************************************************** +# * * +# * Copyright: See License.md file * +# * * +# * Authors: Yash Girish Shah, Grigorios Panagakos * +# * * +# *************************************************************************** + +from .preamble import * +from .commonFunctions import * + + +class PackedColumn: + + def __init__(self, obj, H = 14.75, t1 = 0.890955, t2 = 0.18, h = 0.891155 \ + ,beta_angle = 90. \ + ,gamma_angle = 45. \ + ,rcut = 38.1 \ + ,hcol = 100 \ + ,NbodiesX = 7 \ + ,NbodiesY = 7 \ + ,NbodiesZ = 4 \ + ,nInlet = 4 \ + ,rInlet = 2 \ + ,hInlet = 15 - 8 \ + ,intensified_device = False \ + ,packing_height = 55.5 \ + ,tcol = 0.890955*1./3. \ + ,column_Z_offset = 17.5 \ + ): + + self.Object = obj + obj.Proxy = self + self.Type_ = "Column3D" + + self.propertiesDict = {} + self.removedPropertiesDict = {} + self._addProperty("App::PropertyLength","CellLength","Packing Dimensions","Length of the repetitive elementary cell").CellLength = H + self._addProperty("App::PropertyLength","Thickness1", "Packing Dimensions","Thickness of the packing material").Thickness1 = t1 + + self._addProperty("App::PropertyEnumeration","HeightSpecificationMethod", "Packing Dimensions","Method for defining packing height. \n (1) Define by height. \n (2) Define by number of packing layers.").HeightSpecificationMethod = ["Packing Height", "Layers"] + obj.HeightSpecificationMethod = "Packing Height" + self._addProperty("App::PropertyLength","PackingHeight", "Packing Dimensions","Packing height. Applicable only if packing height is specified as the method of determining height.").PackingHeight = packing_height + self._addProperty("App::PropertyInteger","PackingLayers", "Packing Dimensions","Number of layers of the elementary cells along packing height. Applicable only if layers is specified as the method of determining height.").PackingLayers = NbodiesZ + self._removeProperty("PackingLayers") + #self._PackingLayers = obj.PackingLayers; obj.removeProperty("PackingLayers") + self._addProperty("App::PropertyLength","PackingPlacement", "Packing Dimensions","Distance between the solvent inlet (dripping points) and the top face of the packing.").PackingPlacement = column_Z_offset + + #self._addProperty("App::PropertyInteger","AlongX", "Number of packing elementary cells","Number of packing elementary cell units along the X coordinate").AlongX = NbodiesX + #self._addProperty("App::PropertyInteger","AlongY", "Number of packing elementary cells","Number of packing elementary cell units along the Y coordinate").AlongY = NbodiesY + + self._addProperty("App::PropertyLength","DripRadius", "Dripping Points","Radius of the dripping points.").DripRadius = rInlet + self._addProperty("App::PropertyLength","Depth", "Dripping Points","Depth of the dripping points.").Depth = hInlet + self._addProperty("App::PropertyInteger","NumberAlongRadius", "Dripping Points","Number of dripping points along the column radius.").NumberAlongRadius = nInlet + + self._addProperty("App::PropertyAngle","CorrugationAngle", "Packing Angles","Corrugation angle (beta) of the packing. ").CorrugationAngle = beta_angle + self._addProperty("App::PropertyAngle","SweepAngle", "Packing Angles","Sweep angle (gamma) of the packing. ").SweepAngle = gamma_angle + + self._addProperty("App::PropertyLength","ColumnRadius", "Column Dimensions","Inner radius of the column body. ").ColumnRadius = rcut + self._addProperty("App::PropertyLength","ColumnHeight", "Column Dimensions","Height of the column. ").ColumnHeight = hcol + self._addProperty("App::PropertyLength","ColumnWallThickness", "Column Dimensions","Thickness of the column wall. ").ColumnWallThickness = tcol + + self._addProperty("App::PropertyBool","EnableIntensifiedDevice", "Intensified Device Settings","Specify whether to construct intensified packing. ").EnableIntensifiedDevice = intensified_device + self._addProperty("App::PropertyLength","CoolingChannelWidth", "Intensified Device Settings","Width of the cooling channels (only applicable if creating intensified packing).").CoolingChannelWidth = h + self._addProperty("App::PropertyLength","Thickness2", "Intensified Device Settings","Thickness of the packing material at cell edges. (only applicable if creating intensified packing)").Thickness2 = t2 + if not intensified_device: + self._removeProperty("CoolingChannelWidth") + self._removeProperty("Thickness2") + + self.MainShp = None + self.objects = [] + self.resetNeeded = False + self.columnCreated = False + #self.createColumn() + + def _addProperty(self,propertyType,propertyName,propertySection,propertyDescription): + if propertyName not in self.propertiesDict: + self.propertiesDict[propertyName] = [propertyType,propertyName,propertySection,propertyDescription] + self.propertiesDict[propertyName].append(self.Object.addProperty(propertyType,propertyName,propertySection,propertyDescription)) + return self.propertiesDict[propertyName][-1] + + def _removeProperty(self,item): + obj = self.Object + if hasattr(obj,item): + self.removedPropertiesDict[item] = obj.__getattribute__(item) + obj.removeProperty(item) + return True + else: + return False + + def _addRemovedProperty(self,item): + obj = self.Object + if (not hasattr(obj,item)) & (item in self.removedPropertiesDict): + obj.addProperty(self.propertiesDict[item][0],self.propertiesDict[item][1],self.propertiesDict[item][2],self.propertiesDict[item][3]) + obj.__setattr__(item,self.removedPropertiesDict.pop(item)) + return True + else: + return False + + def propertiesList(self): + return list(self.propertiesDict.keys()) + + def onChanged(self,obj,prop): + + if prop == "HeightSpecificationMethod": + + if obj.HeightSpecificationMethod == "Packing Height": + self._removeProperty("PackingLayers") + self._addRemovedProperty("PackingHeight") + + if obj.HeightSpecificationMethod == "Layers": + self._removeProperty("PackingHeight") + self._addRemovedProperty("PackingLayers") + + if prop == "EnableIntensifiedDevice": + + for item in "CoolingChannelWidth,Thickness2".split(","): + if obj.EnableIntensifiedDevice: + self._addRemovedProperty(item) + else: + self._removeProperty(item) + + + if hasattr(self,"propertiesDict"): + if prop in self.propertiesList(): + self.resetNeeded = True + + def createColumn(self): + obj = self.Object + H = float(obj.CellLength) #14.75 #M327Y equivalent + beta_angle = float(obj.CorrugationAngle) #67.06 #89. # 90. + gamma_angle = float(obj.SweepAngle) # 45. + t1 = float(obj.Thickness1) #0.890955*0.33 # 0.890955 + rcol = float(obj.ColumnRadius) #38.1 # 1.5 inch + rcut = rcol #+ 6.*t1 + #NbodiesX = int(obj.AlongX) + #NbodiesY = int(obj.AlongY) + tcol = float(obj.ColumnWallThickness) # Column thickness + hcol = float(obj.ColumnHeight) # Column height + nInlet = int(obj.NumberAlongRadius) + rInlet = float(obj.DripRadius) + hInlet = float(obj.Depth) # Inlet dripping points depth + column_Z_offset = float(obj.PackingPlacement) + intensified_device = obj.EnableIntensifiedDevice + + if obj.HeightSpecificationMethod == "Packing Height": + derive_Nz_from_packing_height = True + packing_height = float(obj.PackingHeight) + else: + derive_Nz_from_packing_height = False + NbodiesZ = int(obj.PackingLayers) + + if intensified_device: + t2 = float(obj.Thickness2) #0.18*0.33 # 0.18 + h = float(obj.CoolingChannelWidth) #0.891155*0.33 # 0.891155 + else: + t1 *= 1./3. + h = t1*1. + t2 = t1/5. + + + cut = True + alpha_angle = 90. - beta_angle/2. # <-- Parallelity + axis_overlap = 4e-3 # 4e-5 + modify_hcol = False + apply_fusion = False + apply_compound = True + + + t3 = t1/2.*np.tan(alpha_angle*np.pi/180.) #0.45 # 0.72 + + + # Perform checks + xr = rcol/(nInlet+1) + if (xr - 2.*rInlet) <= tcol: + QtGui.QMessageBox.information(None, "Column Building Error", "Too many dripping points. Building Failed. Reduce either the inlet radius or the number of dripping points.") + return + + if t1 <= 0: + QtGui.QMessageBox.information(None, "Column Building Error", "A non-zero thickness is required to build the column. Buliding Failed...") + return + + if intensified_device: + if (h <= 0) | (t2 <= 0): + QtGui.QMessageBox.information(None, "Column Building Error", "A non-zero thickness and cooling channel width is required to build the column. Buliding Failed...") + return + + if (beta_angle < 30.) | (beta_angle > 90.) \ + | (gamma_angle < 30.) | (gamma_angle > 60.): + QtGui.QMessageBox.information(None,"Warning","The range of angles tested are between 30 and 90 degrees for the corrugation angle, and between 30 and 60 degrees for the sweep angle. Proceed with caution.") + + + + + Gui = FreeCADGui + E = App.ActiveDocument + F = Gui.ActiveDocument + E.recompute() + + #Gui.activateWorkbench("PartDesignWorkbench") + + dName = E.Name + + G = Gui.getDocument(dName) + A = App.getDocument(dName) + + body = A.addObject("PartDesign::Body","Body") + + + base = body.newObject("Sketcher::SketchObject","Base") + base.Support = (A.getObject("XY_Plane"),[""]) + base.MapMode = "FlatFace" + E.recompute() + + vector = App.Vector + constraint = Sketcher.Constraint + line = Part.LineSegment + hvec = vector(1.,0.,0.) + vvec = vector(0.,1.,0.) + + lines = [] + + # Line -- 0 + base.addGeometry(line(vector(0.,0.,0.),vector(-1.,1.,0)),False) + lines.append(getLineNum(base)) + base.addConstraint(Sketcher.Constraint("Coincident",lines[0],1,-1,1)) + + s1 = np.abs((2*t1 + h)/np.cos(deg2rad(alpha_angle))) + #print(convertDistanceUnits(s1)) + setLength(base,s1) + E.recompute() + + # Line -- 1 + lines.append(connectedLine2(base,vec = vector(-1,0,0)*H)) + horizontal_constraint(base) + setLength(base,H) + setAngle(base,lines[1],lines[0],deg2rad(alpha_angle + 90.)) + E.recompute() + + # Line -- 2 + x1 = np.abs(base.getPoint(lines[1],2)[0] + (2*t1+h)/np.tan(deg2rad(beta_angle/2.))) + base.addGeometry(line(vector(0.,0.,0.),vector(-1.,0.,0.)*x1),False) + lines.append(getLineNum(base)) + base.addConstraint(constraint("Coincident",lines[2],1,lines[0],1)) + base.addConstraint(constraint("Parallel",lines[2],lines[1])) + E.recompute() + + + # Line -- 3 + lines.append(connecterLine(base,lines[1],2,lines[2],2)) + setLength(base,(2*t1+h)/np.sin(deg2rad(beta_angle/2.))) + base.toggleConstruction(lines[3]) + E.recompute() + + + # Line -- 4 + dirvec = line2vec(base,lines[3]) + dirvec = dirvec.normalize() + l1 = np.abs(t1/np.sin(deg2rad(beta_angle/2.))) + #print(convertDistanceUnits(l1)) + pt1 = base.getPoint(lines[1],2) + dirvec*l1 + + base.addGeometry(line(pt1 ,pt1 + hvec),False) + lines.append(getLineNum(base)) + base.addConstraint(constraint("PointOnObject",lines[4],1,lines[3])) + base.addConstraint(constraint("Parallel",lines[4],lines[1])) + if not intensified_device: + base.toggleConstruction(lines[4]) # Toggled construction to remove cooling channels + E.recompute() + + # Line -- 5 + pt2 = base.getPoint(lines[2],2) - dirvec*l1 + base.addGeometry(line(pt2,pt2 + hvec),False) + lines.append(getLineNum(base)) + base.addConstraint(constraint("PointOnObject",lines[5],1,lines[3])) + base.addConstraint(constraint("Parallel",lines[5],lines[4])) + if not intensified_device: + base.toggleConstruction(lines[5]) # Toggled construction to remove cooling channels + E.recompute() + + + # Line -- 6 + lines.append(connecterLine(base,lines[4],2,lines[5],2)) + base.addConstraint(constraint('Parallel',lines[6],lines[0])) + if not intensified_device: + base.toggleConstruction(lines[6]) # Toggled construction to remove cooling channels + E.recompute() + + + # Line -- 7 + lines.append(connecterLine(base,lines[1],2,lines[4],1)) + setLength(base,l1) + base.toggleConstruction(lines[7]) + E.recompute() + + # Line -- 8 + lines.append(connecterLine(base,lines[5],1,lines[2],2)) + base.toggleConstruction(lines[8]) + base.addConstraint(constraint("Equal",lines[7],lines[8])) + E.recompute() + + + # Line -- 9 + lines.append(connectedLine2(base,hvec,lines[4])) + base.addConstraint(constraint("PointOnObject",lines[9],2,lines[0])) + base.addConstraint(constraint("Perpendicular",lines[0],lines[9])) + base.toggleConstruction(lines[-1]) + setLength(base,t2) + + E.recompute() + + + # Line -- 10 + dirvec = -1.*line2vec(base,lines[1]) + dirvec = rotateVector(dirvec,-deg2rad(beta_angle)).normalize() + lines.append(connectedLine2(base,dirvec*H,lines[1])) + base.addConstraint(constraint("Equal",lines[-1],lines[1])) + setAngle(base,lines[-1],lines[1],deg2rad(beta_angle)) + E.recompute() + + # Line -- 11 + lines.append(connectedLine2(base,dirvec,lines[2])) + base.addConstraint(constraint('Equal',lines[-1],lines[2])) + base.addConstraint(constraint('Parallel',lines[-1],lines[10])) + E.recompute() + + # Line -- 12 + lines.append(connecterLine(base,lines[10],2,lines[11],2)) + E.recompute() + + + # Line -- 13 + lth = line2vec(base,lines[4]).Length + lines.append(connectedLine1(base,dirvec*lth,lines[4])) + base.addConstraint(constraint("Equal",lines[-1],lines[4])) + base.addConstraint(constraint('Parallel',lines[-1],lines[10])) + #setAngle(base,lines[-1],lines[4],deg2rad(beta_angle)) + if not intensified_device: + base.toggleConstruction(lines[-1]) # Toggled construction to remove cooling channels + E.recompute() + + # Line -- 14 + lth = line2vec(base,lines[5]).Length + lines.append(connectedLine1(base,dirvec*lth,lines[5])) + base.addConstraint(constraint("Equal",lines[-1],lines[5])) + base.addConstraint(constraint('Parallel',lines[-1],lines[10])) + #setAngle(base,lines[-1],lines[5],deg2rad(beta_angle)) + if not intensified_device: + base.toggleConstruction(lines[-1]) # Toggled construction to remove cooling channels + E.recompute() + + # Line -- 15 + lines.append(connecterLine(base,lines[13],2,lines[14],2)) + if not intensified_device: + base.toggleConstruction(lines[-1]) # Toggled construction to remove cooling channels + E.recompute() + E.recompute() + E.recompute() + + lsweep = (base.getPoint(lines[2],1) - base.getPoint(lines[11],2)).Length + #print("l1 = ",convertDistanceUnits(l1)) + L = l1/np.abs(np.sin(deg2rad(gamma_angle))) + + if not derive_Nz_from_packing_height: + packing_height = lsweep*NbodiesZ + + column_Z_offset_bottom = hcol - column_Z_offset - packing_height - hInlet - tcol + + + # Datum line -- Diagonal + + diagonal_datum = body.newObject("PartDesign::Line","Diagonal") + diagonal_datum.Support = [(base,"Vertex1"),(base,"Vertex5")] + diagonal_datum.MapMode = 'TwoPointLine' + diagonal_datum.Visibility = False + E.recompute() + + diagPlane = body.newObject("PartDesign::Plane","DiagPlane") + diagPlane.Support = [(diagonal_datum,"")] + diagPlane.MapMode = 'ObjectYZ' + diagPlane.Visibility = False + E.recompute() + + + sweepProfile = body.newObject("Sketcher::SketchObject","sweepProfile") + sweepProfile.Support = (diagPlane,[""]) + sweepProfile.MapMode = "FlatFace" + E.recompute() + + + # Line -- 0 + sweepProfile.addGeometry(line(vector(0.,0.,0.),vector(-1.,-1.,0.)*L),False) + sweepProfile.addConstraint(constraint("Coincident",0,1,-1,1)) + lsweep1 = packing_height*1. + setLength(sweepProfile,lsweep1/np.cos(deg2rad(gamma_angle))) + E.recompute() + + + # Line -- 1 (Construction) + connectedLine2(sweepProfile,vector(0.,lsweep1,0.),0) + sweepProfile.toggleConstruction(1) + sweepProfile.addConstraint(constraint("PointOnObject",1,2,-1)) + sweepProfile.addConstraint(constraint("Perpendicular",-1,1)) + setLength(sweepProfile,lsweep1) + E.recompute() + + + + # 3D Sweep + halfCell = body.newObject("PartDesign::AdditivePipe","HalfCell") + halfCell.Profile = base + halfCell.Spine = sweepProfile + base.Visibility = False + sweepProfile.Visibility = False + E.recompute() + + + + oSk = body.newObject("Sketcher::SketchObject","oSk") + oSk.Support = (halfCell,["Face8"]) + oSk.MapMode = "FlatFace" + E.recompute() + + + # Line -- 0 + oSk.addGeometry(line(vector(0.,0.,0.),vector(-1.,0.,0.)),False) + oSk.addConstraint(constraint("Coincident",0,1,-1,1)) + oSk.addConstraint(constraint("PointOnObject",0,2,-1)) + oSk.addConstraint(constraint("Distance",0,t3)) + oSk.setDatum(2,App.Units.Quantity(convertDistanceUnits(t3))) + E.recompute() + oSk.Visibility = False + + + YZof = body.newObject("PartDesign::Plane","YZof") + YZof.Support = [(oSk,"Vertex2"),(A.getObject("Y_Axis"),""),(A.getObject("Z_Axis"),"")] + YZof.MapMode = 'OXY' + E.recompute() + + + + dum = body.newObject('Sketcher::SketchObject','dum') + dum.Support = (A.getObject("YZ_Plane"),[""]) + dum.MapMode = 'FlatFace' + E.recompute() + + dum.addGeometry(Part.LineSegment(vector(0.000000,0.000000,0),vector(0.,-1.,0)),False) + #dum.addConstraint(constraint("Coincident",0,1,-1,1)) + dum.addConstraint(constraint("PointOnObject",0,1,-1)) # <-- + dum.addConstraint(constraint("Vertical",0)) + setLength(dum,1.) + #dum.addConstraint(constraint("DistanceX",-1,1,0,1,t1/2.)) #<-- + dum.addConstraint(constraint("DistanceX",-1,1,0,1,axis_overlap)) #<-- + #dum.setDatum(3,App.Units.Quantity(convertDistanceUnits(t1/2.))) #<-- + dum.setDatum(3,App.Units.Quantity(convertDistanceUnits(axis_overlap))) #<-- + E.recompute() + + YZof.Visibility = False + + sbd = body.newObject('PartDesign::ShapeBinder','Rotation_axis') + sbd.Support = [(dum,"Edge1")] + E.recompute() + + dum.Visibility = False + + pol_pat = body.newObject('PartDesign::PolarPattern','PolarPattern') + E.recompute() + pol_pat.Originals = [halfCell,] + pol_pat.Axis = (sbd,['Edge1']) + pol_pat.Angle = 360 + pol_pat.Occurrences = 2 + body.Tip = pol_pat + E.recompute() + + sbd.Visibility = False + + bounding_box = {} + vlist = [i.Point for i in pol_pat.Shape.Vertexes] + self.vlist = vlist + xlist = np.array([i[0] for i in vlist]) + ylist = np.array([i[1] for i in vlist]) + zlist = np.array([i[2] for i in vlist]) + bounding_box["xmin"] = xlist.min() + bounding_box["ymin"] = ylist.min() + bounding_box["zmin"] = zlist.min() + bounding_box["xmax"] = xlist.max() + bounding_box["ymax"] = ylist.max() + bounding_box["zmax"] = zlist.max() + #print(bounding_box) + topBB = {} + vtop = [i for i in vlist if np.abs(i[2] - bounding_box["zmax"]) < 1e-5] + topBB["xmin"] = np.min([i[0] for i in vtop]) + topBB["xmax"] = np.max([i[0] for i in vtop]) + topBB["ymin"] = np.min([i[1] for i in vtop]) + topBB["ymax"] = np.max([i[1] for i in vtop]) + #print(topBB) + + botBB = {} + vbot = [i for i in vlist if np.abs(i[2] - bounding_box["zmin"]) < 1e-5] + botBB["xmin"] = np.min([i[0] for i in vbot]) + botBB["xmax"] = np.max([i[0] for i in vbot]) + botBB["ymin"] = np.min([i[1] for i in vbot]) + botBB["ymax"] = np.max([i[1] for i in vbot]) + #print(botBB) + + org_bounding_box = {} + bb = pol_pat.Shape.BoundBox + org_bounding_box["xmin"] = bb.XMin + org_bounding_box["ymin"] = bb.YMin + org_bounding_box["zmin"] = bb.ZMin + org_bounding_box["xmax"] = bb.XMax + org_bounding_box["ymax"] = bb.YMax + org_bounding_box["zmax"] = bb.ZMax + + + # Lid covers + + try: + shp = Part.getShape(body,"",needSubElement=True,refine=True) + except: + shp = Part.getShape(body,"",needSubElement=True,refine=False) + + lTransfX = (base.getPoint(lines[2],1) - base.getPoint(lines[11],2)) + lTransfX *= (lTransfX.Length)/lTransfX.Length + lTransfY = lTransfX.cross(vector(0,0,-1)) + lTransfY /= lTransfY.Length + + ll = getLtransfYScale(pol_pat,lTransfY) + lTransfY *= ll + + self.lx = lTransfX + self.ly = lTransfY + + NbodiesX,NbodiesY = getNbodies(topBB,botBB,rcut,lTransfX,lTransfY) + self.NbodiesX = NbodiesX + self.NbodiesY = NbodiesY + #print("Computed Nbodies:", tstX,tstY) + + # Cut Packing in a cylinder + body_cut = A.addObject('PartDesign::Body','BodyCut') + + base_cut = body_cut.newObject("Sketcher::SketchObject","BaseCut") + base_cut.Support = (A.getObject("XY_Plane001"),[""]) + base_cut.MapMode = "FlatFace" + base_cut.Visibility = False + E.recompute() + + + # Line -- 0 (Circle) + base_cut.addGeometry(Part.Circle(vector(0.,0.,0.),vector(0.,0.,1.),rcut),False) + base_cut.addConstraint(constraint("Coincident",0,3,-1,1)) + base_cut.addConstraint(constraint("Radius",0,rcut)) + base_cut.setDatum(1,App.Units.Quantity(convertDistanceUnits(rcut))) + E.recompute() + + cut_pad = body_cut.newObject('PartDesign::Pad',"cut_pad") + cut_pad.Profile = base_cut + cut_pad.Length = packing_height + cut_pad.Reversed = True + E.recompute() + + shp_cut = Part.getShape(body_cut,"",needSubElement=False,refine=False) + + commons = [A.addObject("Part::MultiCommon","Common")] + commons[-1].Shapes = [body_cut,body] + commons[-1].Refine = True + E.recompute() + + coords = [(0,0)] + vertical_faces_lst = [getFaceListMatch(commons[-1],vector(0.,0.,1.))] + + + + bd = [body] + bd_cut = [body_cut] + matrix = {} + matrix[(0,0)] = 0 + for k in range(-NbodiesY,NbodiesY): + for j in range(-NbodiesX,NbodiesX): + if np.abs(j) + np.abs(k) == 0: + continue + if cell_outside_circle(bounding_box,lTransfX*j + lTransfY*k,rcut): + continue + + #if cell_outside_circle2(org_bounding_box,lTransfX*j + lTransfY*k,rcut): + #continue + #if cell_outside_circle1(vlist,lTransfX*j + lTransfY*k,rcut): + #continue + + bdname = "bd_%d_%d"%(j,k) + matrix[(j,k)] = len(bd) + bd.append(A.addObject("Part::Feature","Body")) + bd[-1].Shape = shp + bd[-1].Label = bdname + bd[-1].Placement = App.Placement(lTransfX*j + lTransfY*k,App.Rotation(vector(0,0,1),0),vector(0,0,0)) + + if cut: + bdcutname = "bdcut_%d_%d"%(j,k) + bd_cut.append(A.addObject("Part::Feature","Body")) + bd_cut[-1].Shape = shp_cut + bd_cut[-1].Label = bdcutname + + # Check if volume of the intersection is non-zero and do not proceed if it is zero. + vol = bd[-1].Shape.common(bd_cut[-1].Shape).Volume + if vol < 1e-16: + A.removeObject(bd_cut.pop().Name) + A.removeObject(bd.pop().Name) + matrix.pop((j,k)) + continue + + + commons.append(A.addObject("Part::MultiCommon","Common")) + commons[-1].Shapes = [bd_cut[-1],bd[-1]] + commons[-1].Refine = True + vertical_faces_lst.append(getFaceListMatch(commons[-1],vector(0.,0.,1.))) + coords.append((j,k)) + #if commons[-1].Label == "Common001": + # App.Console.PrintMessage("Column Building Complete! %s \n"%(commons[-1].Label)) + # App.Console.PrintMessage("j = %d, k = %d \n"%(j,k)) + # print("Shape BB:",bd[-1].Shape.BoundBox) + # print("Min Radii:",np.min([np.sqrt(item.Point[0]**2. + item.Point[1]**2.) for item in bd[-1].Shape.Vertexes])) + E.recompute() + + E.recompute() + + + ilist = [] + for i in range(len(commons)): + if (commons[i].Shape.Volume < 1e-16): + labs = (commons[i].Shapes[0].Name,commons[i].Shapes[1].Name) + A.removeObject(commons[i].Label) + A.recompute() + A.removeObject(labs[0]) + A.recompute() + A.removeObject(labs[1]) + A.recompute() + ilist.append(i) + + tmp = commons + [] + commons = [tmp[i] for i in range(len(commons)) if i not in ilist] + + tmp = vertical_faces_lst + [] + vertical_faces_lst = [tmp[i] for i in range(len(vertical_faces_lst)) if i not in ilist] + + tmp = coords + [] + coords = [tmp[i] for i in range(len(coords)) if i not in ilist] + + column = A.addObject('PartDesign::Body','ColumnAlias') + + colPadPlane = column.newObject("PartDesign::Plane","colPadPlane") + + colPadPlane.AttachmentOffset = App.Placement(vector(0.,0.,1.)*(packing_height - hcol/2.) + vector(0.,0.,column_Z_offset_bottom), App.Rotation(0.,0.,0.)) + + colPadPlane.Support = [A.getObject("XY_Plane002"),""] + colPadPlane.MapMode = "FlatFace" + colPadPlane.MapReversed = True + colPadPlane.Visibility = False + E.recompute() + + column_profile = column.newObject("Sketcher::SketchObject","column_profile") + #s = A.getObject("column_profile") + s = column_profile + s.Support = (colPadPlane,[""]) + s.MapMode = "FlatFace" + s.Visibility = False + E.recompute() + + + + # Line -- 0 (Circle) + s.addGeometry(Part.Circle(vector(0.,0.,0.),vector(0.,0.,1.),rcol),False) + s.addConstraint(constraint("Coincident",0,3,-1,1)) + s.addConstraint(constraint("Radius",0,rcol)) + s.setDatum(1,App.Units.Quantity(convertDistanceUnits(rcol))) + E.recompute() + + # Line -- 1 (Circle) + s.addGeometry(Part.Circle(vector(0.,0.,0.),vector(0.,0.,1.),rcol+tcol),False) + s.addConstraint(constraint("Coincident",1,3,-1,1)) + s.addConstraint(constraint("Radius",1,rcol+tcol)) + s.setDatum(3,App.Units.Quantity(convertDistanceUnits(rcol+tcol))) + E.recompute() + + col_pad = column.newObject('PartDesign::Pad',"col_pad") + #col_pad = A.getObject("col_pad") + col_pad.Profile = s + col_pad.Length = hcol + col_pad.Midplane = 1 + E.recompute() + + s = column.newObject("Sketcher::SketchObject","lid_sketch") + #s = A.getObject("lid_sketch") + s.Support = (col_pad,["Face3"]) + s.MapMode = "FlatFace" + s.Visibility = False + + # Line -- 0 (Circle) + s.addGeometry(Part.Circle(vector(0.,0.,0.),vector(0.,0.,1.),rcol),False) + s.addConstraint(constraint("Coincident",0,3,-1,1)) + s.addConstraint(constraint("Radius",0,rcol)) + s.setDatum(1,App.Units.Quantity(convertDistanceUnits(rcol))) + E.recompute() + + lid = column.newObject("PartDesign::Pad","lid") + #lid = A.getObject("lid") + lid.Profile = s + lid.Length = tcol + lid.Reversed = True + + lid_sketch = s + + + k = 0 + hname = "inletSketch_%d"%(k) + s = column.newObject("Sketcher::SketchObject",hname) + inlet_sketch = s + #s = A.getObject(hname) + s.Support = (lid,["Face4"]) + s.MapMode = "FlatFace" + s.Visibility = False + E.recompute() + + for i in range(-nInlet-3,nInlet+4): + for j in range(-nInlet-3,nInlet+4): + #s.Visibility = False + vec1 = np.array([xr*i,xr*j]) + val = 1. + rInlet/(np.sqrt(np.sum(np.power(vec1,2.))) + 1e-10) + vec1 *= val + val = np.sum(np.power(vec1,2.)) - (rcol - tcol)**2. + if (val >= 0): + continue + s.addGeometry(Part.Circle(vector(xr*i,xr*j,0.),vector(0.,0.,1.),rInlet),False) + s.addConstraint(constraint("DistanceX",k,3,xr*i)) + s.addConstraint(constraint("DistanceY",k,3,xr*j)) + s.addConstraint(constraint("Radius",k,rInlet)) + #print("hname = ",hname,xr*i,xr*j) + s.setDatum(2+3*k,App.Units.Quantity(convertDistanceUnits(rInlet))) + E.recompute() + + k += 1 + + + hname = "gOutlet_pocket" + gpkt = column.newObject("PartDesign::Pocket",hname) + #gpkt = A.getObject(hname) + gpkt.Profile = s + gpkt.Length = tcol + + k = 0 + hname = "inletSketch_1" + s = column.newObject("Sketcher::SketchObject",hname) + inletsketch1 = s + #s = A.getObject(hname) + s.Support = (lid,["Face6"]) + s.MapMode = "FlatFace" + s.AttachmentOffset = App.Placement(App.Vector(0.0000000000, 0.0000000000, -tcol/2.), App.Rotation(0.0000000000, 0.0000000000, 0.0000000000)) + s.Visibility = False + E.recompute() + + for i in range(-nInlet-3,nInlet+4): + for j in range(-nInlet-3,nInlet+4): + #s.Visibility = False + vec1 = np.array([xr*i,xr*j]) + val = 1. + rInlet/(np.sqrt(np.sum(np.power(vec1,2.))) + 1e-10) + vec1 *= val + val = np.sum(np.power(vec1,2.)) - (rcol - tcol)**2. + if (val >= 0): + continue + s.addGeometry(Part.Circle(vector(xr*i,xr*j,0.),vector(0.,0.,1.),rInlet),False) + s.addConstraint(constraint("DistanceX",k,3,xr*i)) + s.addConstraint(constraint("DistanceY",k,3,xr*j)) + s.addConstraint(constraint("Radius",k,rInlet)) + #print("hname = ",hname,xr*i,xr*j) + s.setDatum(2+3*k,App.Units.Quantity(convertDistanceUnits(rInlet))) + E.recompute() + + k += 1 + + hname = "inlet" + #column.newObject("PartDesign::Pocket",hname) + hole = column.newObject("PartDesign::Pad",hname) + #hole = A.getObject(hname) + hole.Profile = s + hole.Length = tcol/2. + hInlet + hole.Reversed = False + #inlet = A.getObject(hname) + + column.ViewObject.Transparency = 70 + + E.recompute() + E.recompute() + Gui.SendMsgToActiveView("ViewFit") + + commonLabsList = [i.Label for i in commons] + common_indices = np.array([0] + [int(i.replace("Common","")) for i in commonLabsList[1:]],dtype = np.int) + + xlist = np.sort(np.unique([i[1] for i in coords])) + arrlist = "A,B,C,D,E,F,G,H,I,J,K,L,M,N".split(",") + + j = 0 + fusions = [] + if apply_fusion: + for xi,x in enumerate(xlist): + clist = [commons[i] for i in range(len(commons)) if coords[i][1] == x] + + if len(clist) < 2: + fusions.append(clist[0]) + continue + + fusionName = "fusion%d"%(j) + fusionObject = A.addObject("Part::Fuse",fusionName) + #fusionObject = A.getObject(fusionName) + fusionObject.Base = clist[0] + fusionObject.Tool = clist[1] + fusions = fusions + [fusionObject] + fusionObject = None + j += 1 + E.recompute() + + if len(clist) > 2: + for kk in range(2,len(clist)): + fusionName = "fusion%d"%(j) + fusionObject = A.addObject("Part::Fuse",fusionName) + #fusionObject = A.getObject(fusionName) + fusionObject.Base = clist[kk] + fusionObject.Tool = fusions[-1] + fusions = fusions + [fusionObject] + fusionObject = None + j += 1 + E.recompute() + + fusions[-1].Label = "Fusion" + arrlist[xi] + + if apply_compound: + packing = A.addObject("Part::Compound","Packing") + packing.Links = commons #+ [column] + E.recompute() + + # create column alias + column_final = A.addObject("Part::Feature","Column") + column_final.Shape = Part.getShape(column,"",needSubElement=True,refine=True) + column_final.ViewObject.Transparency = 70 + + + self.MainShp = packing.Shape + #self.objects = commons + [column,packing] + self.objects = [column_final] + #self.Object.Children = [column,packing] + self.Children = [column_final] #[column,packing] + obj.Shape = self.MainShp + + + + # Cleanup + A.removeObject(packing.Label) + A.recompute() + column.removeObjectsFromDocument() + A.removeObject(column.Label) + A.recompute() + + for O in commons: + common_child_objects = [item for item in O.Shapes] + labs = [item.Name for item in O.Shapes] + A.removeObject(O.Label) + A.recompute() + for ishape in common_child_objects: + ilab = ishape.Name + if "PartDesign::" in ishape.TypeId: + ishape.removeObjectsFromDocument() + A.removeObject(ilab) + A.recompute() + + + self.columnCreated = True + self.resetNeeded = False + + + #def attach(self,obj): + #obj.addExtension("App::OriginGroupExtensionPython") + #obj.Origin = FreeCAD.ActiveDocument.addObject("App::Origin","Origin") + + + def execute (self,obj): + #if self.MainShp is not None: + #obj.Shape = self.MainShp + pass + + def reset(self): + + Gui = FreeCADGui + E = App.ActiveDocument + F = Gui.ActiveDocument + dName = E.Name + G = Gui.getDocument(dName) + A = App.getDocument(dName) + + # Break compounds first + ilist = [] + for i,item in enumerate(self.objects): + if item.TypeId == "Part::Compound": + ilist.append(i) + A.removeObject(item.Label) + A.recompute() + tmp = self.objects + [] + self.objects = [tmp[i] for i in range(len(tmp)) if i not in ilist] + + #for O in self.objects: + while len(self.objects) > 0: + O = self.objects.pop() + if O.TypeId == "Part::MultiCommon": + common_child_objects = [item for item in O.Shapes] + labs = [item.Name for item in O.Shapes] + A.removeObject(O.Label) + A.recompute() + for ishape in common_child_objects: + ilab = ishape.Name + if "PartDesign::" in ishape.TypeId: + ishape.removeObjectsFromDocument() + A.removeObject(ilab) + A.recompute() + elif "PartDesign::" in O.TypeId: + O.removeObjectsFromDocument() + A.removeObject(O.Label) + A.recompute() + else: + A.removeObject(O.Label) + A.recompute() + + self.resetNeeded = False + self.columnCreated = False + + +class ViewProviderColumn: + + obj_name = "Packed Column" + + def __init__(self, vobj, obj_name): + self.obj_name = obj_name + self.Object = vobj.Object # Original Object + vobj.Proxy = self + + def attach(self, vobj): + #vobj.addExtension("Gui::ViewProviderOriginGroupExtensionPython") + self.ViewObject = vobj + return + + def updateData(self, fp, prop): + return + + def getDisplayModes(self,obj): + return "As Is" + + def getDefaultDisplayMode(self): + return "As Is" + + def setDisplayMode(self,mode): + return "As Is" + + def onChanged(self, vobj, prop): + pass + + def getIcon(self): + return getWorkbenchFolder() + "/Resources/Icons/columnTree.svg" # + (self.obj_name).lower() + '.svg'" + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + def onDelete(self,feature,subelements): + try: + self.Object.Proxy.reset() + except: + msg = "Test: This object is being deleted" + #App.Console.PrintMessage(msg) + return True + + + def claimChildren(self): + objs = [] + if hasattr(self.Object.Proxy,"Children"): + objs.extend(self.Object.Proxy.Children) + if hasattr(self.Object.Proxy,"Base"): + objs.append(self.Object.Proxy.Base) + if hasattr(self.Object,"Base"): + objs.append(self.Object.Base) + if hasattr(self.Object,"Objects"): + objs.extend(self.Object.Objects) + if hasattr(self.Object,"Components"): + objs.extend(self.Object.Components) + if hasattr(self.Object,"Children"): + objs.extend(self.Object.Children) + + return objs diff --git a/columnGen/preamble.py b/columnGen/preamble.py new file mode 100644 index 0000000..c545694 --- /dev/null +++ b/columnGen/preamble.py @@ -0,0 +1,25 @@ +# *************************************************************************** +# * * +# * Copyright: See License.md file * +# * * +# * Authors: Yash Girish Shah, Grigorios Panagakos * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +import FreeCAD as App +from FreeCAD import Base +import Part +import PartDesign +import PartDesignGui +import math +import sys +import Sketcher +import numpy as np +#import File +import ImportGui +import Mesh +from PySide import QtGui +from PySide import QtCore + diff --git a/doc/Images/DrippingPointsParameters.png b/doc/Images/DrippingPointsParameters.png new file mode 100644 index 0000000..7ca9c31 Binary files /dev/null and b/doc/Images/DrippingPointsParameters.png differ diff --git a/doc/Images/Workbench.png b/doc/Images/Workbench.png new file mode 100644 index 0000000..6f06ad2 Binary files /dev/null and b/doc/Images/Workbench.png differ diff --git a/doc/Images/default_column.png b/doc/Images/default_column.png new file mode 100644 index 0000000..d53701d Binary files /dev/null and b/doc/Images/default_column.png differ diff --git a/doc/Images/figures.svg b/doc/Images/figures.svg new file mode 100644 index 0000000..98a4f2c --- /dev/null +++ b/doc/Images/figures.svg @@ -0,0 +1,15386 @@ + + + +PackedColumnsWorkbenchproperty editorDepthRadiusNumber AlongRadiusCorrugation angleSweep anglePackingHeightPackingPlacementCell LengthThickness1 diff --git a/doc/Images/intensified_column.png b/doc/Images/intensified_column.png new file mode 100644 index 0000000..4e40cff Binary files /dev/null and b/doc/Images/intensified_column.png differ diff --git a/doc/Images/intensified_settings.png b/doc/Images/intensified_settings.png new file mode 100644 index 0000000..f10db81 Binary files /dev/null and b/doc/Images/intensified_settings.png differ diff --git a/doc/Images/menu_generate.png b/doc/Images/menu_generate.png new file mode 100644 index 0000000..b219606 Binary files /dev/null and b/doc/Images/menu_generate.png differ diff --git a/doc/Images/packed_column_menu.png b/doc/Images/packed_column_menu.png new file mode 100644 index 0000000..674a9ed Binary files /dev/null and b/doc/Images/packed_column_menu.png differ diff --git a/doc/Images/packed_column_treeview.png b/doc/Images/packed_column_treeview.png new file mode 100644 index 0000000..76b6a4a Binary files /dev/null and b/doc/Images/packed_column_treeview.png differ diff --git a/doc/Images/packingAngles.png b/doc/Images/packingAngles.png new file mode 100644 index 0000000..1cc676f Binary files /dev/null and b/doc/Images/packingAngles.png differ diff --git a/doc/Images/packingDimensions.png b/doc/Images/packingDimensions.png new file mode 100644 index 0000000..f0b64ce Binary files /dev/null and b/doc/Images/packingDimensions.png differ diff --git a/doc/Images/property_editor.png b/doc/Images/property_editor.png new file mode 100644 index 0000000..a511bf6 Binary files /dev/null and b/doc/Images/property_editor.png differ diff --git a/doc/User_Guide.html b/doc/User_Guide.html new file mode 100644 index 0000000..55d61f0 --- /dev/null +++ b/doc/User_Guide.html @@ -0,0 +1,81 @@ +

User Guide

+

Getting started

+

Example 1: Default structured packing

+

1. Create a new document FileNew and select the Packed Columns Workbench as shown below.

+
+Workbench figure
Workbench figure
+
+

2. In the menu bar, select ColumnGeneratorPacked Column to create an instance of packed column.

+
+Packed Column Selection
Packed Column Selection
+
+

A Packed_Column object appears in the tree view.

+
+Packed Column treeview
Packed Column treeview
+
+

3. Select the packed column instance and review the column parameters in the property editor. Make any changes if needed.

+
+Property editor
Property editor
+
+

4. With the packed column selected, click on ColumnGeneratorGenerate to generate the column.

+
+Generate
Generate
+
+

5. This constructs the default column with default structured packing. For a detailed description of the column parameters refer to the Parameter Description section below.

+
+Default Column
Default Column
+
+

Example 2: Intensified structured packing

+

1. Repeat the steps 1-3 in Example 1. Select the Enable Intensified Device option as true. This populates two new parameters, Cooling Channel Width and Thickness2 in the Property editor. Review these parameters and make modifications if needed.

+
+Intensified Settings
Intensified Settings
+
+

2. With the packed column selected, click on ColumnGeneratorGenerate to generate the column.

+
+Generate
Generate
+
+

3. This constructs the default column with the default intensified structured packing.

+
+Intensified Column
Intensified Column
+
+

Parameter Description

+

Column Dimensions

+ + + + + + + + + + + + + + + + + + + + + +
ParameterDescription
Column HeightHeight of the column.
Column RadiusInner radius of the column body.
Column Wall ThicknessThickness of the column wall.
+

Dripping Points

+
+Dripping point parameters
Dripping point parameters
+
+

Packing Angles

+
+Packing Angles
Packing Angles
+
+

Packing Dimensions

+
+Packing Dimensions
Packing Dimensions
+
+

Authors

+
+

Yash Girish Shah
+Grigorios Panagakos

+