From 1b789cfe8a18b273f11b57ac366e6eace0f54d86 Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Sun, 27 Jun 2021 09:40:32 +0100 Subject: [PATCH 01/22] Stack --- freecad/gdml/GDMLCommands.py | 119 ++++++++++++++++++++++++++++++----- freecad/gdml/init_gui.py | 2 + 2 files changed, 107 insertions(+), 14 deletions(-) diff --git a/freecad/gdml/GDMLCommands.py b/freecad/gdml/GDMLCommands.py index 41fa1ad6d..0e9ef1067 100644 --- a/freecad/gdml/GDMLCommands.py +++ b/freecad/gdml/GDMLCommands.py @@ -660,6 +660,109 @@ def __init__(self,label,len, value,parent=None) : def sizeHint(self) : return(QtCore.QSize(10,5)) +class AddDecimateWidget(QtGui.QWidget): + def __init__(self, Obj,*args): + QtGui.QWidget.__init__(self,*args) + #bboxGroup = QtGui.QGroupBox('Objects Bounding Box') + #laybbox = QtGui.QHBoxLayout() + #laybbox.addWidget(QtGui.QLabel('Width : '+str(Shape.BoundBox.XLength))) + #laybbox.addWidget(QtGui.QLabel('Height : '+str(Shape.BoundBox.YLength))) + #laybbox.addWidget(QtGui.QLabel('Depth : '+str(Shape.BoundBox.ZLength) )) + #bboxGroup.setLayout(laybbox) + #maxl = int((Shape.BoundBox.XLength + Shape.BoundBox.YLength + \ + # Shape.BoundBox.ZLength) / 15) + self.type = QtGui.QComboBox() + #self.type.addItems(['sp4cerat','MeshLab','Blender']) + self.type.addItems(['sp4cerat']) + self.group = QtGui.QGroupBox('Decimate Parameters') + self.maxLen = iField('Max Length',5,'1') + self.decimateParmsLayout=QtGui.QGridLayout() + self.decimateParmsLayout.addWidget(self.type,0,0) + self.decimateParmsLayout.addWidget(self.maxLen,0,1) + self.group.setLayout(self.decimateParmsLayout) + self.buttonDecimate = QtGui.QPushButton(translate('GDML','Decimate')) + layoutAction=QtGui.QHBoxLayout() + layoutAction.addWidget(self.buttonDecimate) + self.Vlayout= QtGui.QVBoxLayout() + self.Vlayout.addWidget(self.group) + self.Vlayout.addLayout(layoutAction) + self.setLayout(self.Vlayout) + self.setWindowTitle(translate('GDML','Decimate')) + + def leaveEvent(self, event) : + print('Leave Event') + QtCore.QTimer.singleShot(0, lambda :FreeCADGui.Control.closeDialog()) + + def retranslateUi(self, widget=None): + self.buttonMesh.setText(translate('GDML','Decimate')) + self.setWindowTitle(translate('GDML','Decimate')) + +class AddDecimateTask: + + def __init__(self, Obj): + self.obj = Obj + self.form = AddDecimateWidget(Obj) + self.form.buttonDecimate.clicked.connect(self.actionDecimate) + + def getStandardButtons(self): + return int(QtGui.QDialogButtonBox.Close) + + def isAllowedAlterSelection(self): + return True + + def isAllowedAlterView(self): + return True + + def isAllowedAlterDocument(self): + return True + + def actionDecimate(self) : + print('Action Decimate : '+self.obj.Name) + + def leaveEvent(self, event) : + print('Leave Event II') + + def focusOutEvent(self, event) : + print('Out of Focus II') + +class DecimateFeature : + + def Activated(self) : + import Mesh + import MeshPart + from .GDMLObjects import GDMLTessellated, GDMLTriangular, \ + ViewProvider, ViewProviderExtension + + for obj in FreeCADGui.Selection.getSelection(): + #if len(obj.InList) == 0: # allowed only for for top level objects + print('Action Decimate') + self.decimatable = self.isDecimatable(obj) + if self.decimatable > 0 : + if FreeCADGui.Control.activeDialog() == False : + print('Build panel for Decimate') + panel = AddDecimateTask(obj) + FreeCADGui.Control.showDialog(panel) + else : + print('Already an Active Task') + return + + def GetResources(self): + return {'Pixmap' : 'GDML_Decimate', 'MenuText': \ + QtCore.QT_TRANSLATE_NOOP('GDML_TessGroup',\ + 'Decimate Selected Object'), 'Decimate': \ + QtCore.QT_TRANSLATE_NOOP('GDML_TessGroup', \ + 'Decimate Selected Object')} + + def isDecimatable(self, obj) : + if hasattr(obj,'Proxy') : + print(obj.Proxy.Type) + if obj.Proxy.Type == 'GDMLGmshTessellated' or \ + obj.Proxy.Type == 'GDMLTessellated' : + return 1 + if hasattr(obj,'Mesh') : + return 2 + return -1 + class AddTessellateWidget(QtGui.QWidget): def __init__(self, Shape,*args): QtGui.QWidget.__init__(self,*args) @@ -705,21 +808,10 @@ def leaveEvent(self, event) : QtCore.QTimer.singleShot(0, lambda :FreeCADGui.Control.closeDialog()) def retranslateUi(self, widget=None): - self.buttoniMesh.setText(translate('GDML','Mesh')) - #self.buttonload.setText(translate('OpenSCAD','Load')) - #self.buttonsave.setText(translate('OpenSCAD','Save')) - #self.buttonrefresh.setText(translate('OpenSCAD','Refesh')) - #self.buttonclear.setText(translate('OpenSCAD','Clear')) - #self.checkboxmesh.setText(translate('OpenSCAD','as Mesh')) + self.buttonMesh.setText(translate('GDML','Mesh')) self.setWindowTitle(translate('GDML','Tessellate with Gmsh')) class AddTessellateTask: - import ObjectsFem - #from .GmshUtils import initialize, meshObject, \ - # getVertex, getFacets, getMeshLen, printMeshInfo, printMyInfo - - - from femmesh.gmshtools import GmshTools def __init__(self, Obj): self.obj = Obj @@ -907,8 +999,6 @@ def Activated(self) : from .GmshUtils import initialize, meshObject, \ getVertex, getFacets, getMeshLen, printMeshInfo, printMyInfo - from femmesh.gmshtools import GmshTools - from .GDMLObjects import GDMLGmshTessellated, GDMLTriangular, \ ViewProvider, ViewProviderExtension @@ -1300,6 +1390,7 @@ def GetResources(self): FreeCADGui.addCommand('AddCompound',CompoundFeature()) FreeCADGui.addCommand('TessellateCommand',TessellateFeature()) FreeCADGui.addCommand('TessellateGmshCommand',TessellateGmshFeature()) +FreeCADGui.addCommand('DecimateCommand',DecimateFeature()) FreeCADGui.addCommand('Mesh2TessCommand',Mesh2TessFeature()) FreeCADGui.addCommand('Tess2MeshCommand',Tess2MeshFeature()) FreeCADGui.addCommand('TetrahedronCommand',TetrahedronFeature()) diff --git a/freecad/gdml/init_gui.py b/freecad/gdml/init_gui.py index 0f14288ae..f8f309265 100644 --- a/freecad/gdml/init_gui.py +++ b/freecad/gdml/init_gui.py @@ -81,6 +81,7 @@ def QT_TRANSLATE_NOOP(scope, text): 'BooleanCutCommand','BooleanIntersectionCommand', \ 'BooleanUnionCommand', \ 'AddCompound','TessellateCommand','TessellateGmshCommand', \ + 'DecimateCommand', \ 'Mesh2TessCommand','Tess2MeshCommand','TetrahedronCommand'] toolbarcommands=['CycleCommand','ColourMapCommand','ExpandCommand', @@ -90,6 +91,7 @@ def QT_TRANSLATE_NOOP(scope, text): 'BooleanCutCommand','BooleanIntersectionCommand', \ 'BooleanUnionCommand', \ 'AddCompound','TessellateCommand','TessellateGmshCommand', \ + 'DecimateCommand', \ 'Mesh2TessCommand','Tess2MeshCommand','TetrahedronCommand'] #parttoolbarcommands = ['Part_Cut','Part_Fuse','Part_Common'] From cb0fc2a03bff3e8c2430a7317e27d83360927a0e Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Sun, 27 Jun 2021 12:19:00 +0100 Subject: [PATCH 02/22] Stack --- freecad/gdml/GDMLCommands.py | 67 ++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/freecad/gdml/GDMLCommands.py b/freecad/gdml/GDMLCommands.py index 0e9ef1067..5da4af55c 100644 --- a/freecad/gdml/GDMLCommands.py +++ b/freecad/gdml/GDMLCommands.py @@ -674,18 +674,28 @@ def __init__(self, Obj,*args): self.type = QtGui.QComboBox() #self.type.addItems(['sp4cerat','MeshLab','Blender']) self.type.addItems(['sp4cerat']) - self.group = QtGui.QGroupBox('Decimate Parameters') - self.maxLen = iField('Max Length',5,'1') - self.decimateParmsLayout=QtGui.QGridLayout() - self.decimateParmsLayout.addWidget(self.type,0,0) - self.decimateParmsLayout.addWidget(self.maxLen,0,1) - self.group.setLayout(self.decimateParmsLayout) - self.buttonDecimate = QtGui.QPushButton(translate('GDML','Decimate')) - layoutAction=QtGui.QHBoxLayout() - layoutAction.addWidget(self.buttonDecimate) + self.group1 = QtGui.QGroupBox('Decimate Reduction') + self.tolerance = iField('Tolerance',5,'5.0') + self.reduction = iField('Reduction',5,'0.1') + self.parms1layout = QtGui.QHBoxLayout() + self.parms1layout.addWidget(self.tolerance) + self.parms1layout.addWidget(self.reduction) + self.grpLay1 = QtGui.QVBoxLayout() + self.grpLay1.addLayout(self.parms1layout) + self.buttonReduction = QtGui.QPushButton(translate('GDML','Decimate Reduction')) + self.grpLay1.addWidget(self.buttonReduction) + self.group1.setLayout(self.grpLay1) + self.group2 = QtGui.QGroupBox('Decimate to Size') + self.targetSize = iField('Target Size',5,'100') + self.grpLay2 = QtGui.QVBoxLayout() + self.grpLay2.addWidget(self.targetSize) + self.buttonToSize = QtGui.QPushButton(translate('GDML','Decimate To Size')) + self.grpLay2.addWidget(self.buttonToSize) + self.group2.setLayout(self.grpLay2) self.Vlayout= QtGui.QVBoxLayout() - self.Vlayout.addWidget(self.group) - self.Vlayout.addLayout(layoutAction) + self.Vlayout.addWidget(self.type) + self.Vlayout.addWidget(self.group1) + self.Vlayout.addWidget(self.group2) self.setLayout(self.Vlayout) self.setWindowTitle(translate('GDML','Decimate')) @@ -702,7 +712,8 @@ class AddDecimateTask: def __init__(self, Obj): self.obj = Obj self.form = AddDecimateWidget(Obj) - self.form.buttonDecimate.clicked.connect(self.actionDecimate) + self.form.buttonReduction.clicked.connect(self.actionReduction) + self.form.buttonToSize.clicked.connect(self.actionToSize) def getStandardButtons(self): return int(QtGui.QDialogButtonBox.Close) @@ -716,8 +727,27 @@ def isAllowedAlterView(self): def isAllowedAlterDocument(self): return True - def actionDecimate(self) : - print('Action Decimate : '+self.obj.Name) + def actionReduction(self) : + from .GmshUtils import Tessellated2Mesh + + print('Action Decimate Reduction : '+self.obj.Name) + print(dir(self)) + if hasattr(self.obj,'Mesh') : + mesh = self.obj.Mesh + else : + mesh = Tessellated2Mesh(self.obj) + print(dir(mesh.decimate)) + + def actionToSize(self) : + from .GmshUtils import Tessellated2Mesh + + print('Action Decimatei To Size : '+self.obj.Name) + print(dir(self)) + if hasattr(self.obj,'Mesh') : + mesh = self.obj.Mesh + else : + mesh = Tessellated2Mesh(self.obj) + print(dir(mesh.decimate)) def leaveEvent(self, event) : print('Leave Event II') @@ -736,8 +766,7 @@ def Activated(self) : for obj in FreeCADGui.Selection.getSelection(): #if len(obj.InList) == 0: # allowed only for for top level objects print('Action Decimate') - self.decimatable = self.isDecimatable(obj) - if self.decimatable > 0 : + if self.isDecimatable(obj) : if FreeCADGui.Control.activeDialog() == False : print('Build panel for Decimate') panel = AddDecimateTask(obj) @@ -758,10 +787,10 @@ def isDecimatable(self, obj) : print(obj.Proxy.Type) if obj.Proxy.Type == 'GDMLGmshTessellated' or \ obj.Proxy.Type == 'GDMLTessellated' : - return 1 + return True if hasattr(obj,'Mesh') : - return 2 - return -1 + return True + return False class AddTessellateWidget(QtGui.QWidget): def __init__(self, Shape,*args): From 0b8e61266fa6fa8c41a3f4a7fdf244d3024c5553 Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Sun, 27 Jun 2021 17:30:34 +0100 Subject: [PATCH 03/22] Decimate One --- freecad/gdml/GDMLCommands.py | 33 +++++++++++++++++++++++++++------ freecad/gdml/GDMLObjects.py | 8 ++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/freecad/gdml/GDMLCommands.py b/freecad/gdml/GDMLCommands.py index 5da4af55c..1ca62193d 100644 --- a/freecad/gdml/GDMLCommands.py +++ b/freecad/gdml/GDMLCommands.py @@ -676,7 +676,7 @@ def __init__(self, Obj,*args): self.type.addItems(['sp4cerat']) self.group1 = QtGui.QGroupBox('Decimate Reduction') self.tolerance = iField('Tolerance',5,'5.0') - self.reduction = iField('Reduction',5,'0.1') + self.reduction = iField('Reduction',5,'0.8') self.parms1layout = QtGui.QHBoxLayout() self.parms1layout.addWidget(self.tolerance) self.parms1layout.addWidget(self.reduction) @@ -731,23 +731,44 @@ def actionReduction(self) : from .GmshUtils import Tessellated2Mesh print('Action Decimate Reduction : '+self.obj.Name) - print(dir(self)) + #print(dir(self)) if hasattr(self.obj,'Mesh') : mesh = self.obj.Mesh else : mesh = Tessellated2Mesh(self.obj) - print(dir(mesh.decimate)) + try : + tolerance = float(self.form.tolerance.value.text()) + reduction = float(self.form.reduction.value.text()) + print('Tolerance : '+str(tolerance)) + print('Reduction : '+str(reduction)) + mesh.decimate(tolerance,reduction) + except : + print('Invalid Float Values') + + #print(dir(self.obj)) + self.obj.Proxy.updateParams(mesh.Topology[0],mesh.Topology[1]) + self.obj.recompute() + self.obj.ViewObject.Visibility = True + FreeCADGui.SendMsgToActiveView("ViewFit") + FreeCADGui.updateGui() def actionToSize(self) : from .GmshUtils import Tessellated2Mesh - print('Action Decimatei To Size : '+self.obj.Name) + print('Action Decimate To Size : '+self.obj.Name) print(dir(self)) if hasattr(self.obj,'Mesh') : mesh = self.obj.Mesh else : mesh = Tessellated2Mesh(self.obj) - print(dir(mesh.decimate)) + + try : + targetSize = int(self.form.targetSize.value.text()) + print('Target Size : '+str(targetSize)) + mesh.decimate(targetSize) + + except : + print('Invalid Float Values') def leaveEvent(self, event) : print('Leave Event II') @@ -1066,7 +1087,7 @@ def GetResources(self): 'Mesh & Tessellate Selected Planar Object')} class Mesh2TessFeature : - + def Activated(self) : from .GDMLObjects import GDMLTessellated, GDMLTriangular, \ diff --git a/freecad/gdml/GDMLObjects.py b/freecad/gdml/GDMLObjects.py index a0ecf955b..ab3da91ed 100644 --- a/freecad/gdml/GDMLObjects.py +++ b/freecad/gdml/GDMLObjects.py @@ -2287,6 +2287,14 @@ def __init__(self, obj, vertex, facets, lunit, material, colour = None) : def getMaterial(self): return obj.material + + def updateParams(self, vertex, facets) : + print('Update Params') + print(len(vertex)) + self.Vertex = vertex + self.Facets = facets + self.facets = len(facets) + self.vertex = len(vertex) def onChanged(self, fp, prop): '''Do something when a property has changed''' From 5078b01ba594af51a730f147346e4d271551a990 Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Fri, 2 Jul 2021 17:36:50 +0100 Subject: [PATCH 04/22] Decimate with FreeCAD --- .../gdml/Resources/icons/GDML_Decimate.svg | 382 ++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 freecad/gdml/Resources/icons/GDML_Decimate.svg diff --git a/freecad/gdml/Resources/icons/GDML_Decimate.svg b/freecad/gdml/Resources/icons/GDML_Decimate.svg new file mode 100644 index 000000000..40fe9f30d --- /dev/null +++ b/freecad/gdml/Resources/icons/GDML_Decimate.svg @@ -0,0 +1,382 @@ + + + + + Mesh_Mesh_from_Shape + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Mesh_Mesh_from_Shape + + Solid cube with arrow pointing towards a tessellated cube mesh + + + [agryson] Alexander Gryson + + + + + cube + mesh + solid + convert + + + http://www.freecadweb.org/wiki/index.php?title=Artwork + FreeCAD/src/Mod/Mesh/Gui/Resources/icons/Mesh_Mesh_from_Shape.svg + + + FreeCAD LGPL2+ + + + + + FreeCAD + + + Sat Dec 14 00:58:58 2013 +1100 + + + [jmaustpc] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a629b67ff511f43e84478a6d47e0e731d79c2133 Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Sat, 17 Jul 2021 20:49:54 +0100 Subject: [PATCH 05/22] Part1 --- freecad/gdml/__init__.py | 1 + freecad/gdml/importOBJ.py | 118 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 freecad/gdml/importOBJ.py diff --git a/freecad/gdml/__init__.py b/freecad/gdml/__init__.py index b4aa9c4e4..6df95f76d 100644 --- a/freecad/gdml/__init__.py +++ b/freecad/gdml/__init__.py @@ -1,6 +1,7 @@ import FreeCAD FreeCAD.addImportType("GDML (*.gdml)","freecad.gdml.importGDML") FreeCAD.addImportType("XML (*.xml)","freecad.gdml.importGDML") +FreeCAD.addImportType("OBJ -> GDML-Tessellated (*.obj)","freecad.gdml.importOBJ") FreeCAD.addExportType("GDML (*.gdml)","freecad.gdml.exportGDML") FreeCAD.addExportType("GDML (*.GDML)","freecad.gdml.exportGDML") FreeCAD.addExportType("XML (*.XML)","freecad.gdml.exportGDML") diff --git a/freecad/gdml/importOBJ.py b/freecad/gdml/importOBJ.py new file mode 100644 index 000000000..cb8267a06 --- /dev/null +++ b/freecad/gdml/importOBJ.py @@ -0,0 +1,118 @@ +# -*- coding: utf8 -*- +#************************************************************************** +#* * +#* Copyright (c) 2021 Keith Sloan * +#* * +#* This program is free software; you can redistribute it and/or modify * +#* it under the terms of the GNU Lesser General Public License (LGPL) * +#* as published by the Free Software Foundation; either version 2 of * +#* the License, or (at your option) any later version. * +#* for detail see the LICENCE text file. * +#* * +#* This program is distributed in the hope that it will be useful, * +#* but WITHOUT ANY WARRANTY; without even the implied warranty of * +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +#* GNU Library General Public License for more details. * +#* * +#* You should have received a copy of the GNU Library General Public * +#* License along with this program; if not, write to the Free Software * +#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +#* USA * +#* * +#* Acknowledgements : +#* * +#* * +#************************************************************************** +__title__="FreeCAD - OBJ -> GDML Tessellated importer" +__author__ = "Keith Sloan " +__url__ = ["https://github.com/KeithSloan/FreeCAD_GDML"] + +import FreeCAD, FreeCADGui +import os, io, sys, re +import Part, Draft + +def joinDir(path) : + import os + __dirname__ = os.path.dirname(__file__) + return(os.path.join(__dirname__,path)) + +if open.__module__ == '__builtin__': + pythonopen = open # to distinguish python built-in open function from the one declared her + +def open(filename): + "called when freecad opens a file." + + print('Open : '+filename) + docName = os.path.splitext(os.path.basename(filename)) [0] + print('path : '+filename) + if filename.lower().endswith('.obj') : + try : + doc = FreeCAD.ActiveDocument() + print('Active Doc') + + except : + print('New Doc') + doc = FreeCAD.newDocument(docName) + + processOBJ(doc,filename) + return doc + +def insert(filename,docname): + "called when freecad imports a file" + print('Insert filename : '+filename+' docname : '+docname) + global doc + groupname = os.path.splitext(os.path.basename(filename))[0] + try: + doc=FreeCAD.getDocument(docname) + except NameError: + doc=FreeCAD.newDocument(docname) + if filename.lower().endswith('.obj'): + processOBJ(doc,filename) + +class switch(object): + value = None + def __new__(class_, value): + class_.value = value + return True + +def case(*args): + return any((arg == switch.value for arg in args)) + +def getSelectedMaterial() : + from .exportGDML import nameFromLabel + from .GDMLObjects import GDMLmaterial + + list = FreeCADGui.Selection.getSelection() + if list != None : + for obj in list : + if hasattr(obj,'Proxy') : + if isinstance(obj.Proxy,GDMLmaterial) == True : + return nameFromLabel(obj.Label) + + return 0 + +def processOBJ(doc,filename) : + + from .GDMLObjects import GDMLTessellated, ViewProvider + print("import OBJ as GDML Tessellated") + obj = doc.addObject("Part::FeaturePython","GDMLTessellated") + vertex = [] + faces = [] + f = io.open(filename, 'r', encoding="utf8") + for line in f: + print(line) + while switch(line[0]) : + if case('v') : + print('Vertex') + vertex.append(line[1:]) + break + + if case('f') : + print('Face') + faces.append(line[1:]) + break + + print('Tag : '+line[0]) + break + GDMLTessellated(obj,vertex,faces,'mm',getSelectedMaterial()) + ViewProvider(obj.ViewObject) From 68b418f7fcb6a82ea37008436e97f933446740ce Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Sun, 18 Jul 2021 08:41:41 +0100 Subject: [PATCH 06/22] Add import of OBJ with Tri and Quad Faces to FreeCAD GDMLTessellated Object --- freecad/gdml/importOBJ.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/freecad/gdml/importOBJ.py b/freecad/gdml/importOBJ.py index cb8267a06..4dcd77f5f 100644 --- a/freecad/gdml/importOBJ.py +++ b/freecad/gdml/importOBJ.py @@ -100,19 +100,37 @@ def processOBJ(doc,filename) : faces = [] f = io.open(filename, 'r', encoding="utf8") for line in f: - print(line) + #print(line) + items = line.split(' ') while switch(line[0]) : if case('v') : - print('Vertex') - vertex.append(line[1:]) + #print('Vertex') + vertex.append(FreeCAD.Vector(float(items[1]), \ + float(items[2]), \ + float(items[3]))) + #print(FreeCAD.Vector(float(items[1]), \ + # float(items[2]), \ + # float(items[3]))) break if case('f') : - print('Face') - faces.append(line[1:]) + #print('Face') + l = len(items) - 1 + if l == 3 : + faces.append([int(items[1]) - 1, \ + int(items[2]) - 1, \ + int(items[3]) - 1]) + elif l == 4 : + faces.append([int(items[1]) - 1, \ + int(items[2]) - 1, \ + int(items[3]) - 1, \ + int(items[4]) - 1]) + else : + print('Number = '+l+'Polygon not yet supported') break print('Tag : '+line[0]) break GDMLTessellated(obj,vertex,faces,'mm',getSelectedMaterial()) ViewProvider(obj.ViewObject) + obj.recompute() From deaedc14db50c18978c3b290f2604e540d86558f Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Sun, 18 Jul 2021 11:47:53 +0100 Subject: [PATCH 07/22] fix vt and faces with / --- freecad/gdml/importOBJ.py | 43 ++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/freecad/gdml/importOBJ.py b/freecad/gdml/importOBJ.py index 4dcd77f5f..4cc95d9a3 100644 --- a/freecad/gdml/importOBJ.py +++ b/freecad/gdml/importOBJ.py @@ -91,6 +91,14 @@ def getSelectedMaterial() : return 0 +def getVert(s) : + if '/' in s : + ret = int(s[:s.index('/')])-1 + print(ret) + else : + ret = int(s)-1 + return(ret) + def processOBJ(doc,filename) : from .GDMLObjects import GDMLTessellated, ViewProvider @@ -102,12 +110,17 @@ def processOBJ(doc,filename) : for line in f: #print(line) items = line.split(' ') - while switch(line[0]) : + l = len(items) - 1 + while switch(items[0]) : if case('v') : - #print('Vertex') - vertex.append(FreeCAD.Vector(float(items[1]), \ - float(items[2]), \ - float(items[3]))) + #print('Vertex - len : '+str(l)) + if l >= 3 : + vertex.append(FreeCAD.Vector(float(items[1]), \ + float(items[2]), \ + float(items[3]))) + else : + print('Invalid Vertex') + print(items) #print(FreeCAD.Vector(float(items[1]), \ # float(items[2]), \ # float(items[3]))) @@ -115,22 +128,24 @@ def processOBJ(doc,filename) : if case('f') : #print('Face') - l = len(items) - 1 if l == 3 : - faces.append([int(items[1]) - 1, \ - int(items[2]) - 1, \ - int(items[3]) - 1]) + faces.append([getVert(items[1]), getVert(items[2]), \ + getVert(items[3])]) elif l == 4 : - faces.append([int(items[1]) - 1, \ - int(items[2]) - 1, \ - int(items[3]) - 1, \ - int(items[4]) - 1]) + faces.append([getVert(items[1]), getVert(items[2]), \ + getVert(items[3]), getVert(items[4])]) else : print('Number = '+l+'Polygon not yet supported') break - print('Tag : '+line[0]) + if case('#') : # Comment ignore + break + + if case('vt') : + break + print('Tag : '+items[0]) break + GDMLTessellated(obj,vertex,faces,'mm',getSelectedMaterial()) ViewProvider(obj.ViewObject) obj.recompute() From b7437e2ae69c9ba5ede99bdce30146b92cce02fb Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Sun, 18 Jul 2021 15:46:03 +0100 Subject: [PATCH 08/22] Fix Polygon not yet supported msg --- freecad/gdml/importOBJ.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freecad/gdml/importOBJ.py b/freecad/gdml/importOBJ.py index 4cc95d9a3..78f43952e 100644 --- a/freecad/gdml/importOBJ.py +++ b/freecad/gdml/importOBJ.py @@ -94,7 +94,7 @@ def getSelectedMaterial() : def getVert(s) : if '/' in s : ret = int(s[:s.index('/')])-1 - print(ret) + #print(ret) else : ret = int(s)-1 return(ret) @@ -135,7 +135,7 @@ def processOBJ(doc,filename) : faces.append([getVert(items[1]), getVert(items[2]), \ getVert(items[3]), getVert(items[4])]) else : - print('Number = '+l+'Polygon not yet supported') + print('Number of Face Vertex = '+str(l)+' Polygon not yet supported') break if case('#') : # Comment ignore @@ -143,6 +143,7 @@ def processOBJ(doc,filename) : if case('vt') : break + print('Tag : '+items[0]) break From 2f7310cb4a4ba06b9abeb08d5fb91e79a209e916 Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Sun, 18 Jul 2021 16:29:05 +0100 Subject: [PATCH 09/22] Attempt at convert Polygon to Triangles --- freecad/gdml/importOBJ.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/freecad/gdml/importOBJ.py b/freecad/gdml/importOBJ.py index 78f43952e..651b21b1b 100644 --- a/freecad/gdml/importOBJ.py +++ b/freecad/gdml/importOBJ.py @@ -135,7 +135,11 @@ def processOBJ(doc,filename) : faces.append([getVert(items[1]), getVert(items[2]), \ getVert(items[3]), getVert(items[4])]) else : - print('Number of Face Vertex = '+str(l)+' Polygon not yet supported') + print('Warning Polygon : Number of Face Vertex = '+str(l)) + print('Converting to Triangle Faces') + for i in range(2,l-2 ) : + faces.append([getVert(items[1]), getVert(items[i]), \ + getVert(items[i+1])]) break if case('#') : # Comment ignore @@ -144,6 +148,9 @@ def processOBJ(doc,filename) : if case('vt') : break + if case('vn') : + break + print('Tag : '+items[0]) break From bec379fe74841e69ec5f7fb9af6b1f7bfea82d23 Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Mon, 19 Jul 2021 13:11:06 +0100 Subject: [PATCH 10/22] Fix Polygon to Triangles --- freecad/gdml/importOBJ.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/freecad/gdml/importOBJ.py b/freecad/gdml/importOBJ.py index 651b21b1b..6bf4ae80b 100644 --- a/freecad/gdml/importOBJ.py +++ b/freecad/gdml/importOBJ.py @@ -137,9 +137,25 @@ def processOBJ(doc,filename) : else : print('Warning Polygon : Number of Face Vertex = '+str(l)) print('Converting to Triangle Faces') - for i in range(2,l-2 ) : - faces.append([getVert(items[1]), getVert(items[i]), \ - getVert(items[i+1])]) + verts = [] + for i in range(1,l+1) : + v = vertex[getVert(items[i])] + #print(v) + verts.append(v) + #print(verts) + verts.append(verts[0]) + poly = Part.makePolygon(verts) + c = poly.CenterOfMass + ic = len(vertex) + #print(len(vertex)) + vertex.append(c) + #print(len(vertex)) + #print(c) + #print(ic) + for i in range(1,l) : + #print(i) + faces.append([ic,getVert(items[i]),getVert(items[i+1])]) + faces.append([ic,getVert(items[i+1]),getVert(items[1])]) break if case('#') : # Comment ignore From d2c4ff33744746779eaf33bb87a130b1afe03c0c Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Tue, 20 Jul 2021 13:03:32 +0100 Subject: [PATCH 11/22] Fix getGDMLname and Change export Tessellated to use Shape --- freecad/gdml/exportGDML.py | 50 +++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/freecad/gdml/exportGDML.py b/freecad/gdml/exportGDML.py index acdeda9bb..f754e64ef 100644 --- a/freecad/gdml/exportGDML.py +++ b/freecad/gdml/exportGDML.py @@ -168,6 +168,13 @@ def exportDefine(name, v) : ET.SubElement(define,'position',{'name' : name, 'unit': 'mm', \ 'x': str(v[0]), 'y': str(v[1]), 'z': str(v[2]) }) +def exportDefineVertex(name, v) : + global define + #print('define Vertex : '+name) + #print(v) + ET.SubElement(define,'position',{'name' : name + str(v.hashCode()), \ + 'unit': 'mm', 'x': str(v.X), 'y': str(v.Y), 'z': str(v.Z) }) + def defineWorldBox(bbox): global solids for obj in FreeCAD.ActiveDocument.Objects : @@ -695,7 +702,12 @@ def addVolRef(volxml, volName, solidName, obj) : #print(ET.tostring(volxml)) def nameOfGDMLobject(obj) : - return(obj.Label.split('_',1)[1]) + name = obj.Label + if len(name) > 4 : + if name[0:4] == 'GDML' : + if '_' in name : + return(name.split('_',1)[1]) + return name def processGDMLArb8Object(obj, flag) : # Needs unique Name @@ -909,30 +921,26 @@ def processGDMLTessellatedObject(obj, flag) : # Use more readable version tessVname = tessName + '_' #print(dir(obj)) - #print(dir(obj.Proxy)) if flag == True : tess = ET.SubElement(solids, 'tessellated',{'name': tessName}) - for x in range(0,len(obj.Proxy.Vertex)) : - #print(obj.Proxy.Vertex[x]) - exportDefine(tessVname+str(x),obj.Proxy.Vertex[x]) + for v in obj.Shape.Vertexes : + exportDefineVertex(tessVname,v) - for f in obj.Proxy.Facets : - if len(f) == 3 : - #print(f) + for f in obj.Shape.Faces : + if len(f.Edges) == 3 : ET.SubElement(tess,'triangular',{ \ - 'vertex1': tessVname+str(f[0]), \ - 'vertex2': tessVname+str(f[1]), \ - 'vertex3': tessVname+str(f[2]), \ + 'vertex1': tessVname+str(f.Vertexes[0].hashCode()), \ + 'vertex2': tessVname+str(f.Vertexes[1].hashCode()), \ + 'vertex3': tessVname+str(f.Vertexes[2].hashCode()), \ 'type':'ABSOLUTE'}) - else : - #print(f) + elif len(f.Edges) == 4 : ET.SubElement(tess,'quadrangular',{ \ - 'vertex1': tessVname+str(f[0]), \ - 'vertex2': tessVname+str(f[1]), \ - 'vertex3': tessVname+str(f[2]), \ - 'vertex4': tessVname+str(f[3]), \ - 'type':'ABSOLUTE'}) + 'vertex1': tessVname+str(f.Vertexes[0].hashCode()), \ + 'vertex2': tessVname+str(f.Vertexes[1].hashCode()), \ + 'vertex3': tessVname+str(f.Vertexes[2].hashCode()), \ + 'vertex4': tessVname+str(f.Vertexes[3].hashCode()), \ + 'type':'ABSOLUTE'}) return tess, tessName @@ -1310,7 +1318,9 @@ def processGDMLSolid(obj, addVolsFlag) : if case("GDMLTessellated") : #print(" GDMLTessellated") - return(processGDMLTessellatedObject(obj, addVolsFlag)) + ret = processGDMLTessellatedObject(obj, addVolsFlag) + return ret + #return(processGDMLTessellatedObject(obj, addVolsFlag)) break if case("GDMLGmshTessellated") : @@ -1567,7 +1577,7 @@ def processObject(cnt, idx, obj, xmlVol, volName, \ else : parentName = None print(obj.Label) - print(dir(obj)) + #print(dir(obj)) processVolAssem(obj, xmlVol, volName, True) return idx + 1 From 49ab513d12f7696ea02ff1fc73efdbfb4c2d24fb Mon Sep 17 00:00:00 2001 From: luz paz Date: Wed, 21 Jul 2021 10:17:45 -0400 Subject: [PATCH 12/22] Fix various tweaks --- freecad/gdml/GDMLObjects.py | 4 ++-- freecad/gdml/GDMLShared.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freecad/gdml/GDMLObjects.py b/freecad/gdml/GDMLObjects.py index ab3da91ed..1dbfcc20e 100644 --- a/freecad/gdml/GDMLObjects.py +++ b/freecad/gdml/GDMLObjects.py @@ -107,13 +107,13 @@ def translate(shape,base) : return retShape def make_face3(v1,v2,v3): - # helper mehod to create the faces + # helper method to create the faces wire = Part.makePolygon([v1,v2,v3,v1]) face = Part.Face(wire) return face def make_face4(v1,v2,v3,v4): - # helper mehod to create the faces + # helper method to create the faces wire = Part.makePolygon([v1,v2,v3,v4,v1]) face = Part.Face(wire) return face diff --git a/freecad/gdml/GDMLShared.py b/freecad/gdml/GDMLShared.py index a55421e14..dbc667e79 100644 --- a/freecad/gdml/GDMLShared.py +++ b/freecad/gdml/GDMLShared.py @@ -417,14 +417,14 @@ def getVertex(v): return(FreeCAD.Vector(x,y,z)) def triangle(v1,v2,v3) : - # passsed vertex return face + # passed vertex return face #print('v1 : '+str(v1)+' v2 : '+str(v2)+' v3 : '+str(v3)) w1 = Part.makePolygon([v1,v2,v3,v1]) f1 = Part.Face(w1) return(f1) def quad(v1,v2,v3,v4) : - # passsed vertex return face + # passed vertex return face w1 = Part.makePolygon([v1,v2,v3,v4,v1]) f1 = Part.Face(w1) return(f1) From 4a8b9f618a828578a716ab89345f6d4f15e741d6 Mon Sep 17 00:00:00 2001 From: luz paz Date: Wed, 21 Jul 2021 10:17:55 -0400 Subject: [PATCH 13/22] Fix source typo --- freecad/gdml/GmshUtils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freecad/gdml/GmshUtils.py b/freecad/gdml/GmshUtils.py index 5331b53b6..970d612d9 100644 --- a/freecad/gdml/GmshUtils.py +++ b/freecad/gdml/GmshUtils.py @@ -362,7 +362,7 @@ def printMeshInfo() : partitions = gmsh.model.getPartitions(e[0], e[1]) if len(partitions): print(" - Partition tag(s): " + str(partitions) + \ - " - parent entity " + str(iself.Gmsh.model.getParent(e[0], e[1]))) + " - parent entity " + str(self.Gmsh.model.getParent(e[0], e[1]))) for t in elemTypes: name, dim, order, numv, parv, _ = \ gmsh.model.mesh.getElementProperties(t) From d957f73491524c89d50db66bbdcd898f2d6399c6 Mon Sep 17 00:00:00 2001 From: luz paz Date: Wed, 21 Jul 2021 10:18:08 -0400 Subject: [PATCH 14/22] README: tweaks --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e19ac6b20..3be3a8395 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## GDML Workbench -For +The GDML workbench can be used for: * Viewing * Creation @@ -8,7 +8,7 @@ For of GDML models as can be used in GEANT4 and ROOT -### Viewing CERN's LHCBVelo.gdml using LinkStage 3 Daily +### Viewing CERN's LHCBVelo.gdml using LinkStage3 Daily ![LHCB1](Images/LHCBVelo1.jpg) ![LHCB2](Images/LHCBVelo2.jpg) ![LHCB3](Images/LHCBVelo3.jpg) @@ -512,7 +512,7 @@ as per the above link. ## Roadmap - - [ ] Change structure of xml handing to use Python class rather than global variables + - [ ] Change structure of xml handling to use Python class rather than global variables - [ ] Check handling of different Positioning between GDML & FreeCAD - [ ] Add support for quantity - [ ] Add further GDML Objects @@ -536,7 +536,7 @@ For NIST Materials database see http://physics.nist.gov/PhysRefData ## Development Notes - based on gdml.xsd + Based on `gdml.xsd` * 'Volumes' @@ -569,7 +569,7 @@ For NIST Materials database see http://physics.nist.gov/PhysRefData * Hilden Timo * Atanu Quant -* FreeCAD forum members (Apologies if I left anybody off ) : +* FreeCAD forum members (Apologies if I left anybody out): * wmayer * Joel_graff @@ -585,10 +585,9 @@ For NIST Materials database see http://physics.nist.gov/PhysRefData * OpenBrain * OpenCascade Forum members: - * Sergey Slyadnev + * Sergey Slyadnev * Stack Overflow - * Daniel Haley ## For NIST Materials database see http://physics.nist.gov/PhysRefData From a261e5ad588fb5fbb9681eb53ebdc2366b02d327 Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Thu, 22 Jul 2021 11:00:14 +0100 Subject: [PATCH 15/22] Add image for wiki --- Images/NewFile.jpg | Bin 0 -> 33609 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Images/NewFile.jpg diff --git a/Images/NewFile.jpg b/Images/NewFile.jpg new file mode 100644 index 0000000000000000000000000000000000000000..95bcac9f74ecd1341369af2d726222ff70fae9e0 GIT binary patch literal 33609 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 literal 0 HcmV?d00001 From 5b270f52fd478e0f773bf1bcefe379c82805ce20 Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Thu, 22 Jul 2021 11:19:51 +0100 Subject: [PATCH 16/22] Remove image --- Images/NewFile.jpg | Bin 33609 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Images/NewFile.jpg diff --git a/Images/NewFile.jpg b/Images/NewFile.jpg deleted file mode 100644 index 95bcac9f74ecd1341369af2d726222ff70fae9e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33609 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 From 6a96f040ed82427ed68db050a1796395ea33e11f Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Sun, 25 Jul 2021 18:10:06 +0100 Subject: [PATCH 17/22] Attempt at Gmsh fix --- freecad/gdml/GmshUtils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freecad/gdml/GmshUtils.py b/freecad/gdml/GmshUtils.py index 970d612d9..9e731bfa1 100644 --- a/freecad/gdml/GmshUtils.py +++ b/freecad/gdml/GmshUtils.py @@ -59,7 +59,7 @@ def initialize() : gmsh.initialize() #gmsh.option.setNumber('Mesh.Algorithm',6) gmsh.option.setNumber('Mesh.Algorithm3D',1) - gmsh.option.setNumber("Geometry.OCCFixDegenerated", 1) + #gmsh.option.setNumber("Geometry.OCCFixDegenerated", 1) gmsh.option.setNumber("Mesh.SaveGroupsOfNodes", 1) gmsh.option.setNumber("Mesh.SaveAll", 0) #gmsh.option.setNumber("Mesh.OptimizeNetgen", 1) From 80821913596014881554f1de76b7c328a3e93b78 Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Tue, 27 Jul 2021 16:14:34 +0100 Subject: [PATCH 18/22] Update README.md --- README.md | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 3be3a8395..f80f99ecb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## GDML Workbench +## GDML Workbench - Introduction The GDML workbench can be used for: @@ -8,13 +8,15 @@ The GDML workbench can be used for: of GDML models as can be used in GEANT4 and ROOT + +## Examples ### Viewing CERN's LHCBVelo.gdml using LinkStage3 Daily ![LHCB1](Images/LHCBVelo1.jpg) ![LHCB2](Images/LHCBVelo2.jpg) ![LHCB3](Images/LHCBVelo3.jpg) -## **** VERY IMPORTANT **** +# NOTICE BOARD -# *** ATTENTION WINDOWS USERS *** +## *** ATTENTION WINDOWS USERS *** lxml should have been installed in prebuilt versions of FreeCAD. It seems with FreeCAD 0.19.1 going to production this slipped through the net, @@ -64,25 +66,21 @@ In order to support copies of GDML Volumes the following changes have been made ## **** New experimental export for GEMC **** -## **** Wiki Under Construction **** - -## Installable FreeCAD Python Workbench - -FreeCAD's python Importer & Exporter for GDML files. +# INSTALLATION & CONFIGURATION -## Installation of FreeCAD +### Installation of FreeCAD see https://wiki.freecadweb.org/Installing#Choose_Your_Operating_System -## Installation of Workbench +### Installation of Workbench GDML can be installed via the Addon Manager https://wiki.freecadweb.org/Std_AddonMgr Install by use of FreeCAD Addon Manager ==> GDML ==> update -## Required python libraries - lxml & gmsh +### Required python libraries - lxml & gmsh -### lxml - python library +#### lxml - python library lxml which should be installed as part of FreeCAD @@ -139,16 +137,11 @@ will create D:\FreeCAD 0.19\FreeCAD_0.19\bin\Lib\site-packages\gmsh-4.6.0-py3.8.egg-info D:\FreeCAD 0.19\FreeCAD_0.19\bin\Lib\site-packages\Lib\site-packages\gmsh-4.6.0-Windows64-sdk -## Details of GDML - -For more information on GDML see - -[GDML User Guide](https://gdml.web.cern.ch/GDML/doc/GDMLmanual.pdf) - -[GDML Solids](http://geant4-userdoc.web.cern.ch/geant4-userdoc/UsersGuides/ForApplicationDeveloper/html/Detector/Geometry/geomSolids.html) +# GDML WORKBENCH USAGE & DETAILS +For details on using the workbench please see the wiki [GDML Workbench WIKI](https://github.com/KeithSloan/GDML/wiki) -## Usage +## FOLLOWING TO BE MOVED TO WIKI ### GDML Solids GDML Solids are implemented as FreeCAD Python Objects and have the same properties as defined by GDML. By selecting an Object the properties can be changed via the FreeCAD properties windows and the resulting changes displayed. From c3117f05062b6d7fe0b0fff51bc3da27cbea309c Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Tue, 27 Jul 2021 17:19:25 +0100 Subject: [PATCH 19/22] Update README.md --- README.md | 54 ------------------------------------------------------ 1 file changed, 54 deletions(-) diff --git a/README.md b/README.md index f80f99ecb..440d96a2d 100644 --- a/README.md +++ b/README.md @@ -142,22 +142,6 @@ For details on using the workbench please see the wiki [GDML Workbench WIKI](htt ## FOLLOWING TO BE MOVED TO WIKI -### GDML Solids - -GDML Solids are implemented as FreeCAD Python Objects and have the same properties as defined by GDML. By selecting an Object the properties can be changed via the FreeCAD properties windows and the resulting changes displayed. - -### FreeCAD View settings - -It is suggested that you have the **View** | **Toolbars** -set to - -* Workbench -* Structure - -and when GDML workbench active - -* GDMLTools -* GDML Part tools ### Create a new GDML design @@ -207,44 +191,6 @@ Upon switching to the GDML workbench, one will notice a number of icons that bec * If a Part(GDML Volume) is selected at the time of clicking on the icon, then the new Part(GDML volume ) and GDML object will be created as a subvolume of the one selected, otherwise the created Part can then be dragged to the appropriate part of model structure -### GDML Objects Currently Supported for creation via the GUI are - -#### GDMLBox -![GDML_Box-Icon](Source_Icon_Designs/GDML_Box_mauve_blackline.svg) -_Short decription_ - -#### GDMLCone -![GDML_Clone-Icon](Source_Icon_Designs/GDML_Polycone_Mauve_blackline.svg) -_Short decription_ - -#### GDMLElTube -![GDML_EllipticalTube-Icon](Source_Icon_Designs/GDML_EllipticalTube_Mauve_blackline.svg) -_Short decription_ - -#### GDMLEllipsoid -![GDML_Ellipsoid-Icon](Source_Icon_Designs/GDML_Ellipsoid_Mauve_blackline.svg) -_Short decription_ - -#### GDMLSphere -![GDML_Sphere-Icon](Source_Icon_Designs/GDML_Sphere_mauve.svg) -_Short decription_ - -#### GDMLTrap -![GDML_Trapezoid-Icon](Source_Icon_Designs/GDML_Trapezoid_Mauve_blackline.svg) -_Short decription_ - -#### GDMLTube -![GDML_Tube-Icon](Source_Icon_Designs/GDML_Tube_mauve_blackline.svg) -_Short decription_ - -Given a lot more solids are supported for import, it is not too difficult to add more, -so if you feel you need a particular solid to be added please contact me. - -### Boolean Operations - -Select two Parts/Logical Volumes and then click on the appropriate boolean icon - - ## GDML Tessellated Objects The following icons are available for Tessellated operations From 08cc93f0ed1163782f02b87804afe0284cc42cf2 Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Tue, 3 Aug 2021 21:33:59 +0100 Subject: [PATCH 20/22] Fix decimate function to use Shape --- freecad/gdml/GDMLCommands.py | 24 +++++++++++++----------- freecad/gdml/GDMLObjects.py | 4 ++-- freecad/gdml/GmshUtils.py | 27 ++++++++++++++++++++++++--- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/freecad/gdml/GDMLCommands.py b/freecad/gdml/GDMLCommands.py index 1ca62193d..c6a8f8a85 100644 --- a/freecad/gdml/GDMLCommands.py +++ b/freecad/gdml/GDMLCommands.py @@ -728,39 +728,41 @@ def isAllowedAlterDocument(self): return True def actionReduction(self) : - from .GmshUtils import Tessellated2Mesh + from .GmshUtils import TessellatedShape2Mesh print('Action Decimate Reduction : '+self.obj.Name) #print(dir(self)) if hasattr(self.obj,'Mesh') : mesh = self.obj.Mesh else : - mesh = Tessellated2Mesh(self.obj) + mesh = TessellatedShape2Mesh(self.obj) try : tolerance = float(self.form.tolerance.value.text()) reduction = float(self.form.reduction.value.text()) print('Tolerance : '+str(tolerance)) print('Reduction : '+str(reduction)) mesh.decimate(tolerance,reduction) - except : - print('Invalid Float Values') + + except Exception as e: + print(e) #print(dir(self.obj)) self.obj.Proxy.updateParams(mesh.Topology[0],mesh.Topology[1]) self.obj.recompute() self.obj.ViewObject.Visibility = True FreeCADGui.SendMsgToActiveView("ViewFit") + print('Update Gui') FreeCADGui.updateGui() def actionToSize(self) : - from .GmshUtils import Tessellated2Mesh + from .GmshUtils import TessellatedShape2Mesh print('Action Decimate To Size : '+self.obj.Name) print(dir(self)) if hasattr(self.obj,'Mesh') : mesh = self.obj.Mesh else : - mesh = Tessellated2Mesh(self.obj) + mesh = TessellatedShape2Mesh(self.obj) try : targetSize = int(self.form.targetSize.value.text()) @@ -1131,7 +1133,7 @@ def Activated(self) : from .GDMLObjects import GDMLTessellated, GDMLTriangular, \ ViewProvider, ViewProviderExtension - from .GmshUtils import Tessellated2Mesh, Tetrahedron2Mesh + from .GmshUtils import TessellatedShape2Mesh, Tetrahedron2Mesh for obj in FreeCADGui.Selection.getSelection(): print('Action Tessellate 2 Mesh') @@ -1139,12 +1141,12 @@ def Activated(self) : if hasattr(obj.Proxy,'Type') : mesh = None if obj.Proxy.Type == 'GDMLTessellated' : - print('Tessellated2Mesh') - mesh = Tessellated2Mesh(obj) + print('TessellatedShape2Mesh') + mesh = TessellatedShape2Mesh(obj) if obj.Proxy.Type == 'GDMLGmshTessellated' : - print('GmshTessellated2Mesh') - mesh = Tessellated2Mesh(obj) + print('GmshTessellatedShape2Mesh') + mesh = TessellatedShape2Mesh(obj) if obj.Proxy.Type == 'GDMLTetrahedron' : print('Tetrahedron2Mesh') diff --git a/freecad/gdml/GDMLObjects.py b/freecad/gdml/GDMLObjects.py index 1dbfcc20e..5ad9cf73c 100644 --- a/freecad/gdml/GDMLObjects.py +++ b/freecad/gdml/GDMLObjects.py @@ -2159,11 +2159,11 @@ def __init__(self, obj, sourceObj,meshLen, vertex, facets, lunit, \ def updateParams(self, vertex, facets) : print('Update Params') - print(len(vertex)) self.Vertex = vertex self.Facets = facets self.facets = len(facets) self.vertex = len(vertex) + print(f"Vertex : {self.vertex} Facets : {self.facets}") def getMaterial(self): return obj.material @@ -2290,11 +2290,11 @@ def getMaterial(self): def updateParams(self, vertex, facets) : print('Update Params') - print(len(vertex)) self.Vertex = vertex self.Facets = facets self.facets = len(facets) self.vertex = len(vertex) + print(f"Vertex : {self.vertex} Facets : {self.facets}") def onChanged(self, fp, prop): '''Do something when a property has changed''' diff --git a/freecad/gdml/GmshUtils.py b/freecad/gdml/GmshUtils.py index 9e731bfa1..e49557eda 100644 --- a/freecad/gdml/GmshUtils.py +++ b/freecad/gdml/GmshUtils.py @@ -271,20 +271,39 @@ def getTetrahedrons(): FreeCAD.Console.PrintWarning('Unable to create quad faces for this shape') return None +def vertex2Vector(v) : + return(FreeCAD.Vector(v.X, v.Y, v.Z)) + def addFacet(msh, v0,v1,v2) : #print('Add Facet') #msh.addFacet(v0[0],v0[1],v0[2],v1[0],v1[1],v1[2],v2[0],v2[1],v2[2]) #print(v0) #print(v1) #print(v2) - msh.addFacet(FreeCAD.Vector(v0),FreeCAD.Vector(v1),FreeCAD.Vector(v2)) + #msh.addFacet(FreeCAD.Vector(v0),FreeCAD.Vector(v1),FreeCAD.Vector(v2)) + msh.addFacet(vertex2Vector(v0),vertex2Vector(v1),vertex2Vector(v2)) + +def TessellatedShape2Mesh(obj) : + import Mesh + msh = Mesh.Mesh() + #v = obj.Shape.Vertexes + for f in obj.Shape.Faces : + #print('Deal with Triangular Faces') + #addFacet(msh,v[f.Vertexes[0]], v[f.Vertexes[1]], v[f.Vertexes[2]]) + addFacet(msh,f.Vertexes[0], f.Vertexes[1], f.Vertexes[2]) + if len(f.Edges) == 4 : + #print('Deal with Quad Faces') + #addFacet(msh,v[f.Vertexes[0]], v[f.vertexes[2]], v[f.vertexes[3]]) + addFacet(msh,f.Vertexes[0], f.vertexes[2], f.vertexes[3]) + return msh def Tessellated2Mesh(obj) : + # Should now be redundant as replaced by TessellatedShape2Mesh import Mesh - print('Tessellated 2 Mesh') + print('Tessellated 2 Mesh : '+obj.Label) if hasattr(obj.Proxy,'Facets') : - #print('Create Mesh') + print('Has proxy - Create Mesh') msh = Mesh.Mesh() v = obj.Proxy.Vertex #print(v) @@ -295,6 +314,8 @@ def Tessellated2Mesh(obj) : if len(f) == 4 : addFacet(msh,v[f[0]], v[f[2]], v[f[3]]) return msh + else : + print(dir(obj)) def Tetrahedron2Mesh(obj) : import Mesh From 47f4b08d284bdca05f4ec7be8c19b3027f088ecf Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Wed, 4 Aug 2021 10:28:56 +0100 Subject: [PATCH 21/22] Fixes to Material Export --- freecad/gdml/exportGDML.py | 113 ++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 51 deletions(-) diff --git a/freecad/gdml/exportGDML.py b/freecad/gdml/exportGDML.py index f754e64ef..6980e573e 100644 --- a/freecad/gdml/exportGDML.py +++ b/freecad/gdml/exportGDML.py @@ -1096,7 +1096,7 @@ def processGDML2dVertex(obj, flag) : #print("Process 2d Vertex") ET.SubElement(solids, 'twoDimVertex',{'x': obj.x, 'y': obj.y}) -def processElement(obj, item): # maybe part of material or element (common code) +def processIsotope(obj, item): # maybe part of material or element (common code) if hasattr(obj,'Z') : #print(dir(obj)) item.set('Z',str(obj.Z)) @@ -1119,7 +1119,7 @@ def processElement(obj, item): # maybe part of material or element (common code) atom.set('value',str(obj.atom_value)) def processMaterials() : - #print("\nProcess Materials") + print("\nProcess Materials") global materials for GName in ['Materials','Elements','Isotopes'] : @@ -1128,29 +1128,74 @@ def processMaterials() : for obj in Grp.Group: #print(obj.TypeId+" : "+obj.Name) #processMaterialObject(obj) - if processMaterialObject(obj) == False : + if processMaterialGroups(obj) == False : break -def processMaterialObject(obj) : +def processFractions(obj, item) : + # Fractions are used in Material and Elements + if isinstance(obj.Proxy,GDMLfraction) : + print("GDML fraction :" + obj.Name) + # need to strip number making it unique + ET.SubElement(item,'fraction',{'n': str(obj.n), \ + 'ref': nameFromLabel(obj.Label)}) + +def processMaterial(obj) : + print(obj.Label) + item = ET.SubElement(materials,'material',{'name': \ + nameFromLabel(obj.Label)}) + + # process common options material / element + processIsotope(obj, item) + if len(obj.Group) > 0 : + for o in obj.Group : + processFractions(o,item) + + if hasattr(obj,'Dunit') or hasattr(obj,'Dvalue') : + print("Dunit or DValue") + D = ET.SubElement(item,'D') + if hasattr(obj,'Dunit') : + D.set('unit',str(obj.Dunit)) + + if hasattr(obj,'Dvalue') : + D.set('value',str(obj.Dvalue)) + + if hasattr(obj,'Tunit') and hasattr(obj,'Tvalue') : + ET.SubElement(item,'T',{'unit': obj.Tunit, \ + 'value': str(obj.Tvalue)}) + + if hasattr(obj,'MEEunit') : + ET.SubElement(item,'MEE',{'unit': obj.MEEunit, \ + 'value': str(obj.MEEvalue)}) + + +def processElement(obj) : + item = ET.SubElement(materials,'element',{'name': \ + nameFromLabel(obj.Label)}) + + if len(obj.Group) > 0 : + for o in obj.Group : + processFractions(o,item) + +def processMaterialGroups(obj) : global item - #print(obj.Label) + print(obj.Label) while switch(obj.TypeId) : if case("App::DocumentObjectGroupPython"): - #print(" Object List : "+obj.Name) + print(" Object List : "+obj.Name) #print(obj) while switch(obj.Name) : if case("Materials") : - #print("Materials") + print("Materials") #return True break if case("Isotopes") : - #print("Isotopes") + print("Isotopes") #return True break if case("Elements") : - #print("Elements") + print("Elements") #return True break @@ -1175,53 +1220,21 @@ def processMaterialObject(obj) : #return if isinstance(obj.Proxy,GDMLmaterial) : - #print("GDML material") - #print(dir(obj)) - - item = ET.SubElement(materials,'material',{'name': \ - nameFromLabel(obj.Label)}) - - # process common options material / element - processElement(obj, item) - - if hasattr(obj,'Dunit') or hasattr(obj,'Dvalue') : - D = ET.SubElement(item,'D') - if hasattr(obj,'Dunit') : - D.set('unit',str(obj.Dunit)) - - if hasattr(obj,'Dvalue') : - D.set('value',str(obj.Dvalue)) - - if hasattr(obj,'Tunit') and hasattr(obj,'Tvalue') : - ET.SubElement(item,'T',{'unit': obj.Tunit, \ - 'value': str(obj.Tvalue)}) - - if hasattr(obj,'MEEunit') : - ET.SubElement(item,'MEE',{'unit': obj.MEEunit, \ - 'value': str(obj.MEEvalue)}) + print("GDML material") + print(dir(obj)) + print(obj.Group) + processMaterial(obj) - #return True - #break - - if isinstance(obj.Proxy,GDMLfraction) : - - #print("GDML fraction :" + obj.Name) - # need to strip number making it unique - ET.SubElement(item,'fraction',{'n': str(obj.n), \ - 'ref': nameFromLabel(obj.Label)}) - - #return True - break if isinstance(obj.Proxy,GDMLcomposite) : - #print("GDML Composite") + print("GDML Composite") ET.SubElement(item,'composite',{'n': str(obj.n), \ 'ref': nameFromLabel(obj.Label)}) #return True break if isinstance(obj.Proxy,GDMLisotope) : - #print("GDML isotope") + print("GDML isotope") item = ET.SubElement(materials,'isotope',{'N': str(obj.N), \ 'Z': str(obj.Z), \ 'name' : obj.Name}) @@ -1231,10 +1244,8 @@ def processMaterialObject(obj) : break if isinstance(obj.Proxy,GDMLelement) : - #print("GDML element") - item = ET.SubElement(materials,'element',{'name': \ - nameFromLabel(obj.Label)}) - processElement(obj,item) + print("GDML element "+obj.Label) + processElement(obj) #return True break From 5a96daab3ff8b4da972bb6646fb7a19053475cd0 Mon Sep 17 00:00:00 2001 From: Keith Sloan Date: Wed, 4 Aug 2021 14:37:59 +0100 Subject: [PATCH 22/22] Fix materials export screw up --- freecad/gdml/exportGDML.py | 220 ++++++++++++++++++------------------- 1 file changed, 108 insertions(+), 112 deletions(-) diff --git a/freecad/gdml/exportGDML.py b/freecad/gdml/exportGDML.py index 6980e573e..57006fc96 100644 --- a/freecad/gdml/exportGDML.py +++ b/freecad/gdml/exportGDML.py @@ -1122,143 +1122,138 @@ def processMaterials() : print("\nProcess Materials") global materials - for GName in ['Materials','Elements','Isotopes'] : + for GName in ['Constants','Variables','Isotopes','Elements','Materials'] : Grp = FreeCAD.ActiveDocument.getObject(GName) if Grp is not None : - for obj in Grp.Group: - #print(obj.TypeId+" : "+obj.Name) - #processMaterialObject(obj) - if processMaterialGroups(obj) == False : - break + #print(Grp.TypeId+" : "+Grp.Name) + print(Grp.Name) + if processGroup(Grp) == False : + break -def processFractions(obj, item) : +def processFractionsComposites(obj, item) : # Fractions are used in Material and Elements if isinstance(obj.Proxy,GDMLfraction) : - print("GDML fraction :" + obj.Name) + #print("GDML fraction :" + obj.Name) # need to strip number making it unique ET.SubElement(item,'fraction',{'n': str(obj.n), \ 'ref': nameFromLabel(obj.Label)}) -def processMaterial(obj) : - print(obj.Label) - item = ET.SubElement(materials,'material',{'name': \ - nameFromLabel(obj.Label)}) - - # process common options material / element - processIsotope(obj, item) - if len(obj.Group) > 0 : - for o in obj.Group : - processFractions(o,item) - - if hasattr(obj,'Dunit') or hasattr(obj,'Dvalue') : - print("Dunit or DValue") - D = ET.SubElement(item,'D') - if hasattr(obj,'Dunit') : - D.set('unit',str(obj.Dunit)) + if isinstance(obj.Proxy,GDMLcomposite) : + print("GDML Composite") + ET.SubElement(item,'composite',{'n': str(obj.n), \ + 'ref': nameFromLabel(obj.Label)}) + +def createMaterials(group): + global materials + for obj in group : + item = ET.SubElement(materials,'material',{'name': \ + nameFromLabel(obj.Label)}) + + # process common options material / element + processIsotope(obj, item) + if len(obj.Group) > 0 : + for o in obj.Group : + processFractionsComposites(o,item) + + if hasattr(obj,'Dunit') or hasattr(obj,'Dvalue') : + #print("Dunit or DValue") + D = ET.SubElement(item,'D') + if hasattr(obj,'Dunit') : + D.set('unit',str(obj.Dunit)) - if hasattr(obj,'Dvalue') : - D.set('value',str(obj.Dvalue)) + if hasattr(obj,'Dvalue') : + D.set('value',str(obj.Dvalue)) - if hasattr(obj,'Tunit') and hasattr(obj,'Tvalue') : - ET.SubElement(item,'T',{'unit': obj.Tunit, \ + if hasattr(obj,'Tunit') and hasattr(obj,'Tvalue') : + ET.SubElement(item,'T',{'unit': obj.Tunit, \ 'value': str(obj.Tvalue)}) - if hasattr(obj,'MEEunit') : - ET.SubElement(item,'MEE',{'unit': obj.MEEunit, \ + if hasattr(obj,'MEEunit') : + ET.SubElement(item,'MEE',{'unit': obj.MEEunit, \ 'value': str(obj.MEEvalue)}) - -def processElement(obj) : - item = ET.SubElement(materials,'element',{'name': \ +def createElements(group) : + global materials + for obj in group : + item = ET.SubElement(materials,'element',{'name': \ nameFromLabel(obj.Label)}) - if len(obj.Group) > 0 : - for o in obj.Group : - processFractions(o,item) - -def processMaterialGroups(obj) : - global item - print(obj.Label) - while switch(obj.TypeId) : - if case("App::DocumentObjectGroupPython"): - print(" Object List : "+obj.Name) - #print(obj) - while switch(obj.Name) : - if case("Materials") : - print("Materials") - #return True - break + if len(obj.Group) > 0 : + for o in obj.Group : + processFractionsComposites(o,item) - if case("Isotopes") : - print("Isotopes") - #return True - break - - if case("Elements") : - print("Elements") - #return True - break - - break - - if isinstance(obj.Proxy,GDMLconstant) : - #print("GDML constant") - #print(dir(obj)) +def createConstants(group) : + global define + for obj in group : + if isinstance(obj.Proxy,GDMLconstant) : + #print("GDML constant") + #print(dir(obj)) - item = ET.SubElement(define,'constant',{'name': obj.Name, \ + item = ET.SubElement(define,'constant',{'name': obj.Name, \ 'value': obj.value }) - #return True - #return - if isinstance(obj.Proxy,GDMLvariable) : - #print("GDML variable") - #print(dir(obj)) +def createVariables(group) : + global define + for obj in group : + if isinstance(obj.Proxy,GDMLvariable) : + #print("GDML variable") + #print(dir(obj)) - item = ET.SubElement(define,'variable',{'name': obj.Name, \ + item = ET.SubElement(define,'variable',{'name': obj.Name, \ 'value': obj.value }) - #return True - #return - - if isinstance(obj.Proxy,GDMLmaterial) : - print("GDML material") - print(dir(obj)) - print(obj.Group) - processMaterial(obj) +def createIsotopes(group) : + global materials + for obj in group : + if isinstance(obj.Proxy,GDMLisotope) : + print("GDML isotope") + #item = ET.SubElement(materials,'isotope',{'N': str(obj.N), \ + # 'Z': str(obj.Z), \ + # 'name' : obj.Name}) + #ET.SubElement(item,'atom',{'unit': obj.unit, \ + # 'value': str(obj.value)}) + item = ET.SubElement(materials,'isotope',{'name' : obj.Name}) + processIsotope(obj,item) + +def processGroup(obj) : + print('Process Group '+obj.Label) + print(obj.TypeId) + print(obj.Group) + # if hasattr(obj,'Group') : + #return + if hasattr(obj,'Group') : + #print(" Object List : "+obj.Name) + #print(obj) + while switch(obj.Name) : + if case("Constants") : + print("Constants") + createConstants(obj.Group) + break - if isinstance(obj.Proxy,GDMLcomposite) : - print("GDML Composite") - ET.SubElement(item,'composite',{'n': str(obj.n), \ - 'ref': nameFromLabel(obj.Label)}) - #return True - break - - if isinstance(obj.Proxy,GDMLisotope) : - print("GDML isotope") - item = ET.SubElement(materials,'isotope',{'N': str(obj.N), \ - 'Z': str(obj.Z), \ - 'name' : obj.Name}) - ET.SubElement(item,'atom',{'unit': obj.unit, \ - 'value': str(obj.value)}) - #return True - break - - if isinstance(obj.Proxy,GDMLelement) : - print("GDML element "+obj.Label) - processElement(obj) - #return True - break - - # Commented out as individual objects will also exist - #if len(obj.Group) > 1 : - # for grp in obj.Group : - # processObject(grp, addVolsFlag) - # All non Material Objects should terminate Loop - #return False - break + if case("Variables") : + print("Variables") + createVariables(obj.Group) + break - return False - break + if case("Isotopes") : + print("Isotopes") + createIsotopes(obj.Group) + break + + if case("Elements") : + print("Elements") + createElements(obj.Group) + break + + if case("Materials") : + print("Materials") + createMaterials(obj.Group) + break + + if case("Geant4") : + # Do not export predefine in Geant4 + print("Geant4") + break def processGDMLSolid(obj, addVolsFlag) : # Deal with GDML Solids first @@ -1272,6 +1267,7 @@ def processGDMLSolid(obj, addVolsFlag) : return(processGDMLArb8Object(obj, addVolsFlag)) break + if case("GDMLBox") : #print(" GDMLBox") return(processGDMLBoxObject(obj, addVolsFlag)) @@ -2034,7 +2030,7 @@ def exportGDML(first, filepath, fileExt) : #GDMLShared.setTrace(True) GDMLShared.trace('exportGDML') - print("====> Start GDML Export 1.4") + print("====> Start GDML Export 1.5") print('File extension : '+fileExt) GDMLstructure()