* Python3 compatibility

- Compatible with FreeCAD 0.18 with Python3
- Added 'About' under EM menu
- New icon for the FH Solver
- Preparing for VoxHenry
- Minor fixes in the comments
This commit is contained in:
Enrico Di Lorenzo - FastFieldSolvers S.R.L 2019-04-22 12:44:11 +02:00
parent 85041325c3
commit 081ec9fc65
14 changed files with 565 additions and 115 deletions

49
EM.py
View File

@ -38,12 +38,22 @@ __url__ = "http://www.fastfieldsolvers.com"
'''The E.M. module provides tools for ElectroMagnetic analysis'''
import sys
# Python3 compatibility
if sys.version_info >= (3, 4):
from importlib import reload
elif sys.version_info >= (3, 0):
from imp import reload
import FreeCAD
if FreeCAD.GuiUp:
import FreeCADGui
FreeCADGui.updateLocale()
from EM_Globals import *
from EM_About import *
# FastHenry specific
from EM_FHNode import *
from EM_FHSegment import *
from EM_FHPath import *
@ -53,4 +63,43 @@ from EM_FHPort import *
from EM_FHEquiv import *
from EM_FHSolver import *
from EM_FHInputFile import *
## VoxHenry specific
#from EM_VHSolver import *
# for debugging
#import EM_Globals
#reload(EM_Globals)
#from EM_Globals import *
#import EM_About
#reload(EM_About)
#from EM_About import *
#import EM_FHNode
#reload(EM_FHNode)
#from EM_FHNode import *
#import EM_FHSegment
#reload(EM_FHSegment)
#from EM_FHSegment import *
#import EM_FHPath
#reload(EM_FHPath)
#from EM_FHPath import *
#import EM_FHPlaneHole
#reload(EM_FHPlaneHole)
#from EM_FHPlaneHole import *
#import EM_FHPlane
#reload(EM_FHPlane)
#from EM_FHPlane import *
#import EM_FHPort
#reload(EM_FHPort)
#from EM_FHPort import *
#import EM_FHEquiv
#reload(EM_FHEquiv)
#from EM_FHEquiv import *
#import EM_FHSolver
#reload(EM_FHSolver)
#from EM_FHSolver import *
#import EM_FHInputFile
#reload(EM_FHInputFile)
#from EM_FHInputFile import *
#import EM_VHSolver
#reload(EM_VHSolver)
#from EM_VHSolver import *

82
EM_About.py Normal file
View File

@ -0,0 +1,82 @@
#***************************************************************************
#* *
#* Copyright (c) 2018 * *
#* FastFieldSolvers S.R.L. *
#* http://www.fastfieldsolvers.com *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* This program is distributed in the hope that it will be useful, *
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
#* GNU Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
__title__="FreeCAD E.M. Workbench About class"
__author__ = "FastFieldSolvers S.R.L."
__url__ = "http://www.fastfieldsolvers.com"
# imported defines
from EM_Globals import EM_VERSION
# defines
# about information
EM_AUTHOR = 'Copyright 2019 FastFieldSolvers S.R.L. and Efficient Power Conversion Inc.\nhttp://www.fastfieldsolvers.com, http://epc-co.com\nPartially developed by FastFieldSolvers S.R.L. under contract by EPC Inc.\n'
EM_LICENSE = 'Licensed under GNU Lesser General Public License (LGPL) version 2\n'
import FreeCAD, FreeCADGui, Mesh, Part, MeshPart, Draft, DraftGeomUtils, os
from FreeCAD import Vector
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore, QtGui
from DraftTools import translate
from PySide.QtCore import QT_TRANSLATE_NOOP
else:
# \cond
def translate(ctxt,txt, utf8_decode=False):
return txt
def QT_TRANSLATE_NOOP(ctxt,txt):
return txt
# \endcond
__dir__ = os.path.dirname(__file__)
iconPath = os.path.join( __dir__, 'Resources' )
class _CommandAbout:
''' The EM About command definition
'''
def GetResources(self):
# no need of icon or accelerator
return {'MenuText': QT_TRANSLATE_NOOP("EM_About","About"),
'ToolTip': QT_TRANSLATE_NOOP("EM_About","About the ElectroMagnetic Workbench")}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
msg = translate("EM","ElectroMagnetic Workbench version ") + EM_VERSION + "\n\n" + EM_AUTHOR + "\n" + EM_LICENSE
if FreeCAD.GuiUp:
# Simple QMessageBox providing "about" informaiton
diag = QtGui.QMessageBox(QtGui.QMessageBox.Information, translate("EM_About","About ElectroMagnetic workbench"), msg)
diag.setWindowModality(QtCore.Qt.ApplicationModal)
diag.exec_()
else:
FreeCAD.Console.PrintWarning(msg)
if FreeCAD.GuiUp:
FreeCADGui.addCommand('EM_About',_CommandAbout())

View File

@ -101,12 +101,12 @@ class _FHEquiv:
'''
if obj.Node1 == None:
return
elif Draft.getType(obj.Node1) <> "FHNode":
elif Draft.getType(obj.Node1) != "FHNode":
FreeCAD.Console.PrintWarning(translate("EM","Node1 is not a FHNode"))
return
if obj.Node2 == None:
return
elif Draft.getType(obj.Node2) <> "FHNode":
elif Draft.getType(obj.Node2) != "FHNode":
FreeCAD.Console.PrintWarning(translate("EM","Node2 is not a FHNode"))
return
# and finally, if everything is ok, make and assign the shape

View File

@ -161,7 +161,7 @@ class _FHPath:
# of the vertexes, and the segments cross-section orientation will be
# calculated in absolute coordinates from the Positions rotations.
# This last part is different from FHSegment.
if obj.Placement <> FreeCAD.Placement():
if obj.Placement != FreeCAD.Placement():
obj.Placement = FreeCAD.Placement()
# define nodes and segments
edges_raw = []

View File

@ -164,8 +164,8 @@ class _FHPlane:
obj.addProperty("App::PropertyLength","segwid2","EM",QT_TRANSLATE_NOOP("App::Property","Width of segments along the plane width direction ('segwid2' plane parameter)"))
obj.addProperty("App::PropertyLinkList","Nodes","EM",QT_TRANSLATE_NOOP("App::Property","Nodes for connections to the plane"))
obj.addProperty("App::PropertyLinkList","Holes","EM",QT_TRANSLATE_NOOP("App::Property","Holes in the plane"))
obj.addProperty("App::PropertyBool","FineMesh","Component",QT_TRANSLATE_NOOP("App::Property","Specifies if this the plane fine mesh is shown (i.e. composing segments)"))
obj.addProperty("App::PropertyBool","ShowNodes","Component",QT_TRANSLATE_NOOP("App::Property","Show the internal node grid supporting the plane"))
obj.addProperty("App::PropertyBool","FineMesh","EM",QT_TRANSLATE_NOOP("App::Property","Specifies if this the plane fine mesh is shown (i.e. composing segments)"))
obj.addProperty("App::PropertyBool","ShowNodes","EM",QT_TRANSLATE_NOOP("App::Property","Show the internal node grid supporting the plane"))
obj.Proxy = self
self.Type = "FHPlane"
self.FineMesh = False
@ -232,7 +232,7 @@ class _FHPlane:
obj.segwid2 = segwid2
FreeCAD.Console.PrintWarning(translate("EM","Plane segwid2 would cause segments overlap, re-setting segwid2 to the maximum possible"))
# if needed, apply the same Placement of the Base object to the FHPlane object
if obj.Placement <> obj.Base.Placement:
if obj.Placement != obj.Base.Placement:
obj.Placement = obj.Base.Placement
# Start node and hole repositioning in relative coordinate system of the conductive plane
#
@ -241,14 +241,14 @@ class _FHPlane:
# that once a node is child of a plane, it cannot be moved independently by changing
# its placement
for node in obj.Nodes:
if node.Placement <> obj.Placement:
if node.Placement != obj.Placement:
node.Placement = obj.Placement
# These holes have already been adopted by the plane, if they are in the obj.Holes list;
# therefore, must just make sure they track the plane placement. Also, this assures
# that once a hole is child of a plane, it cannot be moved independently by changing
# its placement
for hole in obj.Holes:
if hole.Placement <> obj.Placement:
if hole.Placement != obj.Placement:
hole.Placement = obj.Placement
# Check if the user selected a coarse or a fine mesh.
if obj.FineMesh == False:

View File

@ -99,12 +99,12 @@ class _FHPort:
'''
if obj.NodePos == None:
return
elif Draft.getType(obj.NodePos) <> "FHNode":
elif Draft.getType(obj.NodePos) != "FHNode":
FreeCAD.Console.PrintWarning(translate("EM","NodePos is not a FHNode"))
return
if obj.NodeNeg == None:
return
elif Draft.getType(obj.NodeNeg) <> "FHNode":
elif Draft.getType(obj.NodeNeg) != "FHNode":
FreeCAD.Console.PrintWarning(translate("EM","NodeNeg is not a FHNode"))
return
if obj.NodePos == obj.NodeNeg:
@ -246,7 +246,7 @@ class _CommandFHPort:
endNode = selobj.Object
else:
FreeCAD.Console.PrintWarning(translate("EM","More than two FHNodes selected when creating a FHPort. Using only the first two."))
if startNode <> None and endNode <> None:
if startNode != None and endNode != None:
FreeCAD.ActiveDocument.openTransaction(translate("EM","Create FHPort"))
FreeCADGui.addModule("EM")
FreeCADGui.doCommand('obj=EM.makeFHPort(nodePos=FreeCAD.ActiveDocument.'+startNode.Name+',nodeNeg=FreeCAD.ActiveDocument.'+endNode.Name+')')

View File

@ -137,12 +137,12 @@ class _FHSegment:
#FreeCAD.Console.PrintWarning("_FHSegment execute()\n") #debug
if obj.NodeStart == None:
return
elif Draft.getType(obj.NodeStart) <> "FHNode":
elif Draft.getType(obj.NodeStart) != "FHNode":
FreeCAD.Console.PrintWarning(translate("EM","NodeStart is not a FHNode"))
return
if obj.NodeEnd == None:
return
elif Draft.getType(obj.NodeEnd) <> "FHNode":
elif Draft.getType(obj.NodeEnd) != "FHNode":
FreeCAD.Console.PrintWarning(translate("EM","NodeEnd is not a FHNode"))
return
# the FHSegment has no Placement in itself:
@ -154,7 +154,7 @@ class _FHSegment:
# So let's keep the FHSegment placement at zero, and use the FHNodes to move the segment position.
# This is also necessary as we should not change the segment cross-section orientation
# using the Placement, otherwise it will not be relative to the global axis system
if obj.Placement <> FreeCAD.Placement():
if obj.Placement != FreeCAD.Placement():
obj.Placement = FreeCAD.Placement()
# check if we have a 'Base' object; if so, if segment end nodes
# were already defined, re-set them according to the 'Base' object;
@ -170,13 +170,13 @@ class _FHSegment:
return
# ok, it's valid. Let's verify if this is a Wire.
if Draft.getType(obj.Base) == "Wire":
if obj.NodeStart <> None:
if obj.NodeStart != None:
abs_pos = obj.NodeStart.Proxy.getAbsCoord()
# 'obj.Base.Start' is an absolute position
# if 'NodeStart' is not in that position, move it
if (abs_pos-obj.Base.Start).Length > EMFHSEGMENT_LENTOL:
obj.NodeStart.Proxy.setAbsCoord(obj.Base.Start)
if obj.NodeEnd <> None:
if obj.NodeEnd != None:
abs_pos = obj.NodeEnd.Proxy.getAbsCoord()
# 'obj.Base.Start' is an absolute position
# if 'NodeStart' is not in that position, move it
@ -328,7 +328,7 @@ class _CommandFHSegment:
endNode = selobj.Object
else:
FreeCAD.Console.PrintWarning(translate("EM","More than two FHNodes selected when creating a FHSegment. Using only the first two."))
if startNode <> None and endNode <> None:
if startNode != None and endNode != None:
FreeCAD.ActiveDocument.openTransaction(translate("EM","Create FHSegment"))
FreeCADGui.addModule("EM")
FreeCADGui.doCommand('obj=EM.makeFHSegment(nodeStart=FreeCAD.ActiveDocument.'+startNode.Name+',nodeEnd=FreeCAD.ActiveDocument.'+endNode.Name+')')

View File

@ -87,8 +87,8 @@ def makeFHSolver(units=None,sigma=None,nhinc=None,nwinc=None,rh=None,rw=None,fmi
'ndec' is the float value defining how many frequency points per decade
will be simulated
'folder' is the folder into which the FastHenry file will be saved.
Defaults to the user's home path (e.g. in Windows "C:\Documents
and Settings\username\My Documents", in Linux "/home/username")
Defaults to the user's home path (e.g. in Windows "C:\\Documents
and Settings\\username\\My Documents", in Linux "/home/username")
'filename' is the name of the file that will be exported.
Defaults to EMFHSOLVER_DEF_FILENAME
'name' is the name of the object
@ -98,7 +98,7 @@ def makeFHSolver(units=None,sigma=None,nhinc=None,nwinc=None,rh=None,rw=None,fmi
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
obj.Label = translate("EM", name)
# this adds the relevant properties to the object
#'obj' (e.g. 'Base' property) making it a _FHSegment
#'obj' (e.g. 'Base' property) making it a _FHSolver
_FHSolver(obj)
# manage ViewProvider object
if FreeCAD.GuiUp:

View File

@ -33,6 +33,8 @@ from FreeCAD import Vector
# defines
#
# version information
EM_VERSION = '1.0.1'
# default node color
EMFHNODE_DEF_NODECOLOR = (1.0,0.0,0.0)
# tolerance in degrees when verifying if vectors are parallel

View File

@ -42,8 +42,11 @@ class EMWorkbench(Workbench):
# import the EM module (and therefore all commands makeXXX)
import EM
# E.M. tools
self.emtools = ["EM_About"]
self.emfhtools = ["EM_FHSolver", "EM_FHNode", "EM_FHSegment", "EM_FHPath", "EM_FHPlane",
"EM_FHPlaneHole", "EM_FHPlaneAddRemoveNodeHole", "EM_FHEquiv", "EM_FHPort", "EM_FHInputFile"]
#self.emvhtools = ["EM_VHSolver"]
self.emvhtools = []
# draft tools
# setup menus
self.draftcmdList = ["Draft_Line","Draft_Rectangle"]
@ -59,10 +62,11 @@ class EMWorkbench(Workbench):
'Draft_Snap_Dimensions','Draft_Snap_WorkingPlane']
def QT_TRANSLATE_NOOP(scope, text): return text
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench","E.M. tools"),self.emfhtools)
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench","E.M. FastHenry tools"),self.emfhtools)
#self.appendToolbar(QT_TRANSLATE_NOOP("Workbench","E.M. VoxHenry tools"),self.emvhtools)
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench","Draft creation tools"),self.draftcmdList)
self.appendToolbar(QT_TRANSLATE_NOOP("Workbench","Draft mod tools"),self.draftmodtools)
self.appendMenu(QT_TRANSLATE_NOOP("EM","&EM"),self.emfhtools)
self.appendMenu(QT_TRANSLATE_NOOP("EM","&EM"),self.emfhtools + self.emvhtools + self.emtools)
self.appendMenu(QT_TRANSLATE_NOOP("EM","&Draft"),self.draftcmdList+self.draftmodtools+self.treecmdList)
self.appendMenu([QT_TRANSLATE_NOOP("EM","&Draft"),QT_TRANSLATE_NOOP("arch","Snapping")],self.snapList)
#FreeCADGui.addIconPath(":/icons")

View File

@ -4,7 +4,7 @@
### FastHenry support
Copyright (c) 2018
Copyright (c) 2019
Efficient Power Conversion Corporation, Inc. http://epc-co.com
Developed by FastFieldSolvers S.R.L. http://www.fastfieldsolvers.com under contract by EPC
@ -12,7 +12,7 @@ Developed by FastFieldSolvers S.R.L. http://www.fastfieldsolvers.com under cont
### FasterCap and FastCap support
Copyright (c) 2018
Copyright (c) 2019
FastFieldSolvers S.R.L. http://www.fastfieldsolvers.com
## Description
@ -25,14 +25,25 @@ At present, the workbench supports:
- [FastHenry](https://www.fastfieldsolvers.com/fasthenry2.htm) inductance solver: ongoing development including GUI support
- [FasterCap](https://www.fastfieldsolvers.com/fastercap.htm) capacitance solver: ongoing development, today at the stage of Python macro only, for creating an input file
## Version
The current version of the ElectroMagnetic workbench can be shown, once installed, from the **EM** menu, selecting **About**.
The version number is also reported in the sources, in the global variable **EM_VERSION** in the file EM_Globals.py.
## Installing
The ElectroMagnetic workbench is managed as a FreeCAD addon. This addon can be downloaded by clicking the **Download ZIP** button found on top of the page, or using **Git**. The addon must be placed in your user's FreeCAD/Mod folder.
The ElectroMagnetic workbench is managed as a FreeCAD addon. It can be installed from within FreeCAD using the add-ons manager under the <b>Tools</b> menu, see the [FreeCAD add-on documentation](https://www.freecadweb.org/wiki/Std_AddonMgr) for more specific instructions
### Manual installation
This addon can also be manually installed, but this method is not recommended, as the add-ons manager provides a more user friendly experience.
If you still wish to manually install the workbench, you can download it by clicking the **Download ZIP** button found on top of the page, or using **Git**. The addon must be un-zipped in your user's FreeCAD/Mod folder.
**Note**: Your user's FreeCAD folder location is obtained by typing in FreeCAD's python console: `FreeCAD.ConfigGet("UserAppData")`.
You must then rename the new folder 'EM'. I.e., the new folder structure must be <UserAppData>/Mod/EM. Opening, or closing/reopening FreeCAD then reloads the workbenches, and the E.M. workbench will show up in the pull-down workbenches menu in FreeCAD.
You must then rename the new folder 'EM', i.e. the new folder structure must be <UserAppData>/Mod/EM. Opening, or closing/reopening FreeCAD then reloads the workbenches, and the E.M. workbench will show up in the pull-down workbenches menu in FreeCAD.
## Additional information

View File

@ -91,6 +91,45 @@
y1="7.794034"
x2="47.247158"
y2="52.521305"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.1846386,0,0,1.1846386,-9.501508,-5.4148589)" />
<linearGradient
gradientTransform="translate(9.9161935,2.2059659)"
inkscape:collect="always"
xlink:href="#linearGradient4150"
id="linearGradient4148-7"
x1="16.556818"
y1="7.794034"
x2="47.247158"
y2="52.521305"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4150"
id="linearGradient4159"
x1="16.556818"
y1="7.794034"
x2="47.247158"
y2="52.521305"
gradientUnits="userSpaceOnUse" />
<linearGradient
gradientTransform="matrix(0.50862782,0,0,0.50862782,15.408648,27.042116)"
inkscape:collect="always"
xlink:href="#linearGradient4150"
id="linearGradient4148-5"
x1="16.556818"
y1="7.794034"
x2="47.247158"
y2="52.521305"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4150"
id="linearGradient4172"
x1="16.556818"
y1="7.794034"
x2="47.247158"
y2="52.521305"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
@ -101,17 +140,31 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.5"
inkscape:cx="-82.818182"
inkscape:cy="26.181817"
inkscape:cx="-22.636364"
inkscape:cy="26.215803"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:document-units="px"
inkscape:grid-bbox="true"
inkscape:window-width="725"
inkscape:window-height="403"
inkscape:window-x="1097"
inkscape:window-y="363"
inkscape:window-maximized="0" />
inkscape:window-width="1920"
inkscape:window-height="1018"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:snap-grids="true"
showguides="false"
inkscape:snap-nodes="true"
inkscape:snap-others="true"
inkscape:snap-to-guides="false"
inkscape:snap-global="true">
<inkscape:grid
type="xygrid"
id="grid4187"
spacingx="1"
spacingy="1"
visible="true"
empspacing="2" />
</sodipodi:namedview>
<metadata
id="metadata2865">
<rdf:RDF>
@ -147,15 +200,48 @@
inkscape:groupmode="layer">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:64px;line-height:125%;font-family:'DejaVu Serif';-inkscape-font-specification:'DejaVu Serif';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient4148);fill-opacity:1;stroke:#241c1c;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4.0999999"
x="9.272727"
y="54"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:75.81687164px;line-height:125%;font-family:Georgia;-inkscape-font-specification:Georgia;letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient4148);fill-opacity:1;stroke:#241c1c;stroke-width:2.918185;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4.0999999"
x="1.483322"
y="58.55563"
id="text3014"
sodipodi:linespacing="125%"><tspan
sodipodi:linespacing="125%"
transform="scale(1.0619928,0.94162596)"><tspan
sodipodi:role="line"
id="tspan3016"
x="9.272727"
y="54"
style="fill-opacity:1;fill:url(#linearGradient4148)">S</tspan></text>
x="1.483322"
y="58.55563"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Georgia;-inkscape-font-specification:Georgia;fill:url(#linearGradient4148);fill-opacity:1;stroke-width:2.918185;stroke-miterlimit:4;stroke-dasharray:none">S</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:64px;line-height:125%;font-family:'DejaVu Serif';-inkscape-font-specification:'DejaVu Serif';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient4148-7);fill-opacity:1;stroke:#241c1c;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4.0999999"
x="19.188921"
y="56.205967"
id="text3014-3"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan3016-8"
x="19.188921"
y="56.205967"
style="fill:url(#linearGradient4148-7);fill-opacity:1" /></text>
<rect
style="fill:#ffffff;fill-opacity:0.85789472;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4204"
width="45.81818"
height="32"
x="16.181818"
y="30" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32.55217743px;line-height:125%;font-family:Georgia;-inkscape-font-specification:Georgia;letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient4148-5);fill-opacity:1;stroke:#241c1c;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:4.0999999"
x="20.125015"
y="54.508018"
id="text3014-6"
sodipodi:linespacing="125%"
transform="scale(0.91885746,1.0883081)"><tspan
sodipodi:role="line"
id="tspan3016-1"
x="20.125015"
y="54.508018"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Georgia;-inkscape-font-specification:Georgia;fill:url(#linearGradient4148-5);fill-opacity:1;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none">FH</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -27,7 +27,8 @@
import FreeCAD, Mesh, Part, MeshPart, DraftGeomUtils, os
from FreeCAD import Vector
import numpy as np
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtCore, QtGui
@ -562,25 +563,25 @@ def meshSolidWithSegments(obj=None,delta=1.0,deltaX=0.0,deltaY=0.0,deltaZ=0.0,st
for step_y in range(0,stepsY+1):
for step_x in range(0,stepsX):
# if the node and the next are inside the object shape, create the segment
if nodes[step_x,step_y,step_z] <> None and nodes[step_x+1,step_y,step_z] <> None:
if nodes[step_x,step_y,step_z] != None and nodes[step_x+1,step_y,step_z] != None:
segment = EM_FHSegment.makeFHSegment(nodeStart=nodes[step_x,step_y,step_z],nodeEnd=nodes[step_x+1,step_y,step_z],width=deltaX,height=deltaZ)
# then along y
for step_z in range(0,stepsZ+1):
for step_x in range(0,stepsX+1):
for step_y in range(0,stepsY):
# if the node and the next are inside the object shape, create the segment
if nodes[step_x,step_y,step_z] <> None and nodes[step_x,step_y+1,step_z] <> None:
if nodes[step_x,step_y,step_z] != None and nodes[step_x,step_y+1,step_z] != None:
segment = EM_FHSegment.makeFHSegment(nodeStart=nodes[step_x,step_y,step_z],nodeEnd=nodes[step_x,step_y+1,step_z],width=deltaY,height=deltaZ)
# finally along z
for step_x in range(0,stepsX+1):
for step_y in range(0,stepsY+1):
for step_z in range(0,stepsZ):
# if the node and the next are inside the object shape, create the segment
if nodes[step_x,step_y,step_z] <> None and nodes[step_x,step_y,step_z+1] <> None:
if nodes[step_x,step_y,step_z] != None and nodes[step_x,step_y,step_z+1] != None:
segment = EM_FHSegment.makeFHSegment(nodeStart=nodes[step_x,step_y,step_z],nodeEnd=nodes[step_x,step_y,step_z+1],width=deltaX,height=deltaY)
def meshSolidWithVoxels(obj=None,delta=1.0,stayInside=False):
''' Voxel a solid object
def meshSolidWithVoxels(obj=None,delta=1.0):
''' Voxelize a solid object
'''
if obj == None:
return
@ -612,74 +613,289 @@ def meshSolidWithVoxels(obj=None,delta=1.0,stayInside=False):
pos_y = pos_y + delta
pos_x = pos_x + delta
return isNode
# if we must don't need to stay within the object shape boundaries,
# the voxel will overlap the shape contour
# nodes=np.full((stepsX+1,stepsY+1,stepsZ+1), None, np.object)
# if stayInside == False:
# pos_x = bbox.XMin + deltaSideX
# for step_x in range(0,stepsX+1):
# pos_y = bbox.YMin + deltaSideY
# for step_y in range(0,stepsY+1):
# pos_z = bbox.ZMin + deltaSideZ
# for step_z in range(0,stepsZ+1):
# # if the point is inside the object shape, or on the surface, flag it
# if isNode[step_x,step_y,step_z] == True:
# # create the node
# node = EM_FHNode.makeFHNode(X=pos_x, Y=pos_y, Z=pos_z)
# # store it in the array
# nodes[step_x,step_y,step_z] = node
# pos_z = pos_z + deltaZ
# pos_y = pos_y + deltaY
# pos_x = pos_x + deltaX
# # if we must stay within the object shape boundaries (within the accuracy
# # of the point sampling)
# else:
# pos_x = bbox.XMin + deltaSideX
# for step_x in range(0,stepsX):
# pos_y = bbox.YMin + deltaSideY
# for step_y in range(0,stepsY):
# pos_z = bbox.ZMin + deltaSideZ
# for step_z in range(0,stepsZ):
# # if all the eight cube corners are inside the object shape,
# # we consider the center point well inside the object shape, i.e. also
# # for a segment lying on a plane parallel to the plane xy,
# # with width=deltaX, height=deltaY we are within the object
# if (isNode[step_x,step_y,step_z] == True and isNode[step_x+1,step_y,step_z] == True and
# isNode[step_x,step_y+1,step_z] == True and isNode[step_x+1,step_y+1,step_z] == True and
# isNode[step_x,step_y,step_z+1] == True and isNode[step_x+1,step_y,step_z+1] == True and
# isNode[step_x,step_y+1,step_z+1] == True and isNode[step_x+1,step_y+1,step_z+1] == True):
# # create the node
# node = EM_FHNode.makeFHNode(X=pos_x+deltaX/2.0, Y=pos_y+deltaY/2.0, Z=pos_z+deltaZ/2.0)
# # store it in the array
# nodes[step_x,step_y,step_z] = node
# pos_z = pos_z + deltaZ
# pos_y = pos_y + deltaY
# pos_x = pos_x + deltaX
# # now create the grid of segments
# # first along x
# for step_z in range(0,stepsZ+1):
# for step_y in range(0,stepsY+1):
# for step_x in range(0,stepsX):
# # if the node and the next are inside the object shape, create the segment
# if nodes[step_x,step_y,step_z] <> None and nodes[step_x+1,step_y,step_z] <> None:
# segment = EM_FHSegment.makeFHSegment(nodeStart=nodes[step_x,step_y,step_z],nodeEnd=nodes[step_x+1,step_y,step_z],width=deltaX,height=deltaZ)
# # then along y
# for step_z in range(0,stepsZ+1):
# for step_x in range(0,stepsX+1):
# for step_y in range(0,stepsY):
# # if the node and the next are inside the object shape, create the segment
# if nodes[step_x,step_y,step_z] <> None and nodes[step_x,step_y+1,step_z] <> None:
# segment = EM_FHSegment.makeFHSegment(nodeStart=nodes[step_x,step_y,step_z],nodeEnd=nodes[step_x,step_y+1,step_z],width=deltaY,height=deltaZ)
# # finally along z
# for step_x in range(0,stepsX+1):
# for step_y in range(0,stepsY+1):
# for step_z in range(0,stepsZ):
# # if the node and the next are inside the object shape, create the segment
# if nodes[step_x,step_y,step_z] <> None and nodes[step_x,step_y,step_z+1] <> None:
# segment = EM_FHSegment.makeFHSegment(nodeStart=nodes[step_x,step_y,step_z],nodeEnd=nodes[step_x,step_y,step_z+1],width=deltaX,height=deltaY)
#
def getContainingBBox(objs):
''' Get the bounding box containing all the listed objects
'objs' is the list of FreeCAD objects
Returns the global bounding box.
If the list is None, or is not a list, or if the object have no Shape,
the returned BoundBox is None
'''
# create an empty bbox
gbbox = None
isfirst = True
# if 'objs' is not None
if objs:
if isinstance(objs,list):
for obj in objs:
if hasattr(obj,"Shape"):
if isfirst:
gbbox = obj.Shape.BoundBox
isfirst = False
else:
gbbox.add(obj.Shape.BoundBox)
return gbbox
def createVoxelSpace(bbox,delta):
''' Creates the voxel tensor (3D array) in the given bounding box
'bbox' is the overall FreeCAD.BoundBox bounding box
'delta' is the voxels size length
Returns a voxel tensor as a Numpy 3D array.
If gbbox is None, returns None
'''
if bbox == None:
return None
if delta == None:
return None
# add 1.0 to always cover the bbox space with the voxels
stepsX = int(bbox.XLength/delta + 1.0)
stepsY = int(bbox.YLength/delta + 1.0)
stepsZ = int(bbox.ZLength/delta + 1.0)
# debug
print("X="+str(stepsX)+" Y="+str(stepsY)+" Z="+str(stepsZ)+" tot="+str(stepsX*stepsY*stepsZ))
# create the 3D array of nodes as 16-bit integers (max 65k different conductivities)
voxelSpace=np.full((stepsX+1,stepsY+1,stepsZ+1), 0, np.int16)
return voxelSpace
def voxelizeConductor(obj,condIndex,gbbox,delta,voxelSpace):
''' Voxelize a solid object. The function will modify the 'voxelSpace'
by marking with 'condIndex' all the voxels that sample the object
'obj' internal.
'obj' is the object to voxelize
'condIndex' (integer) is the index of the object. It defines the object conductivity.
'gbbox' (FreeCAD.BoundBox) is the overall bounding box
'delta' is the voxels size length
'voxelSpace' (Numpy 3D array) is the voxel tensor of the overall space
'''
if obj == None:
return
if not hasattr(obj,"Shape"):
return
# get this object bbox
bbox = obj.Shape.BoundBox
# now must find the voxel set that contains the object bounding box
# find the voxel that contains the bbox min point
min_x = int((bbox.XMin - gbbox.XMin)/delta)
min_y = int((bbox.YMin - gbbox.YMin)/delta)
min_z = int((bbox.ZMin - gbbox.ZMin)/delta)
# find the voxel that contains the bbox max point
max_x = int((bbox.XMax - gbbox.XMin)/delta)
max_y = int((bbox.YMax - gbbox.YMin)/delta)
max_z = int((bbox.ZMax - gbbox.ZMin)/delta)
# and now iterate to find which voxel is inside the object 'obj',
# sampling based on the voxel centers
pos_x = gbbox.XMin + min_x * delta + delta/2.0
for step_x in range(min_x,max_x+1):
pos_y = gbbox.YMin + min_y * delta + delta/2.0
for step_y in range(min_y,max_y+1):
pos_z = gbbox.ZMin + min_z * delta + delta/2.0
for step_z in range(min_z,max_z+1):
# if the point is inside the object shape, or on the surface, flag it
if obj.Shape.isInside(Vector(pos_x,pos_y,pos_z),0.0,True):
# debug
#print("pos_x="+str(pos_x)+" pos_y="+str(pos_y)+" pos_z="+str(pos_z))
voxelSpace[step_x,step_y,step_z] = condIndex
pos_z = pos_z + delta
pos_y = pos_y + delta
pos_x = pos_x + delta
def createVoxelShell(obj,condIndex,gbbox,delta,voxelSpace=None):
''' Creates a shell composed by the external faces of a voxelized object.
'obj' is the object whose shell must be created
'condIndex' (integer) is the index of the object. It defines the object conductivity.
'gbbox' (FreeCAD.BoundBox) is the overall bounding box
'delta' is the voxels size length
'voxelSpace' (Numpy 3D array) is the voxel tensor of the overall space
'''
if voxelSpace == None:
return
if not hasattr(obj,"Shape"):
return
surfList = []
# get the object's bbox
bbox = obj.Shape.BoundBox
# now must find the voxel set that contains the object bounding box
# find the voxel that contains the bbox min point
min_x = int((bbox.XMin - gbbox.XMin)/delta)
min_y = int((bbox.YMin - gbbox.YMin)/delta)
min_z = int((bbox.ZMin - gbbox.ZMin)/delta)
# find the voxel that contains the bbox max point
max_x = int((bbox.XMax - gbbox.XMin)/delta)
max_y = int((bbox.YMax - gbbox.YMin)/delta)
max_z = int((bbox.ZMax - gbbox.ZMin)/delta)
# this is half the side of the voxel
halfdelta = delta/2.0
# array to find the six neighbour
sides = [(1,0,0), (-1,0,0), (0,1,0), (0,-1,0), (0,0,1), (0,0,-1)]
# vertexes of the six faces
vertexes = [[Vector(delta,0,0), Vector(delta,delta,0), Vector(delta,delta,delta), Vector(delta,0,delta)],
[Vector(0,0,0), Vector(0,0,delta), Vector(0,delta,delta), Vector(0,delta,0)],
[Vector(0,delta,0), Vector(0,delta,delta), Vector(delta,delta,delta), Vector(delta,delta,0)],
[Vector(0,0,0), Vector(delta,0,0), Vector(delta,0,delta), Vector(0,0,delta)],
[Vector(0,0,delta), Vector(delta,0,delta), Vector(delta,delta,delta), Vector(0,delta,delta)],
[Vector(0,0,0), Vector(0,delta,0), Vector(delta,delta,0), Vector(delta,0,0)]]
# and now iterate to find which voxel is inside the object 'obj',
# sampling based on the voxel centers
vbase = Vector(gbbox.XMin + min_x * delta, gbbox.YMin + min_y * delta, gbbox.ZMin + min_z * delta)
for step_x in range(min_x,max_x+1):
vbase.y = gbbox.YMin + min_y * delta
for step_y in range(min_y,max_y+1):
vbase.z = gbbox.ZMin + min_z * delta
for step_z in range(min_z,max_z+1):
# check if voxel is belonging to the given object
if voxelSpace[step_x,step_y,step_z] == condIndex:
# scan the six neighbour voxels, to see if they are belonging to the same conductor or not.
# If they are not belonging to the same conductor, or if the voxel space is finished, the current voxel
# side in the direction of the empty voxel is an external surface
for side, vertex in zip(sides,vertexes):
is_surface = False
nextVoxelIndexes = [step_x+side[0],step_y+side[1],step_z+side[2]]
if (nextVoxelIndexes[0] > max_x or nextVoxelIndexes[0] < 0 or
nextVoxelIndexes[1] > max_y or nextVoxelIndexes[1] < 0 or
nextVoxelIndexes[2] > max_z or nextVoxelIndexes[2] < 0):
is_surface = True
else:
if voxelSpace[nextVoxelIndexes[0],nextVoxelIndexes[1],nextVoxelIndexes[2]] != condIndex:
is_surface = True
if is_surface == True:
# debug
#print("pos_x="+str(vbase.x)+" pos_y="+str(vbase.y)+" pos_z="+str(vbase.z))
# create the face
# calculate the vertexes
v11 = vbase + vertex[0]
v12 = vbase + vertex[1]
v13 = vbase + vertex[2]
v14 = vbase + vertex[3]
# now make the face
poly = Part.makePolygon( [v11,v12,v13,v14,v11])
face = Part.Face(poly)
surfList.append(face)
vbase.z += delta
vbase.y += delta
vbase.x += delta
# create a shell. Does not need to be solid.
objShell = Part.makeShell(surfList)
return objShell
def findContactVoxelSurfaces(face,condIndex,gbbox,delta,voxelSpace=None,createShell=False):
''' Find the voxel surface sides corresponding to the given contact surface
(face) of an object. The object must have already been voxelized.
'face' is the object face
'condIndex' (integer) is the index of the object to which the face belongs.
It defines the object conductivity.
'gbbox' (FreeCAD.BoundBox) is the overall bounding box
'delta' is the voxels size length
'voxelSpace' (Numpy 3D array) is the voxel tensor of the overall space
'createShell' (bool) creates a shell out of the contact faces
Returns a list of surfaces in the format [x,y,z,voxside] where
x, y, z are the voxel position indexes, while voxside is '+x', '-x',
'+y', '-y', '+z', '-z' according the the impacted surface of the voxel
'''
if voxelSpace == None:
return
surfList = []
contactList = []
# get the face's bbox
bbox = face.BoundBox
# now must find the voxel set that contains the face bounding box
# with a certain slack - it could be the next voxel,
# if the surface is at the boundary between voxels.
# Find the voxel that contains the bbox min point
min_x = int((bbox.XMin - gbbox.XMin)/delta)-1
min_y = int((bbox.YMin - gbbox.YMin)/delta)-1
min_z = int((bbox.ZMin - gbbox.ZMin)/delta)-1
# find the voxel that contains the bbox max point
max_x = int((bbox.XMax - gbbox.XMin)/delta)+1
max_y = int((bbox.YMax - gbbox.YMin)/delta)+1
max_z = int((bbox.ZMax - gbbox.ZMin)/delta)+1
# debug
#print(str(min_x)+" "+str(min_y)+" "+str(min_z)+" "+str(max_x)+" "+str(max_y)+" "+str(max_z))
# create a Part.Vertex that we can use to test the distance
# to the face (as it is a TopoShape)
vec = FreeCAD.Vector(0,0,0)
testVertex = Part.Vertex(vec)
# this is half the side of the voxel
halfdelta = delta/2.0
# small displacement w.r.t. delta
epsdelta = delta/100.0
# array to find the six neighbour
sides = [(1,0,0), (-1,0,0), (0,1,0), (0,-1,0), (0,0,1), (0,0,-1)]
# string describing the side
sideStrs = ['+x', '-x', '+y', '-y', '+z', '-z']
# centers of the sides, with respect to the lower corner (with the smallest coordinates)
sideCenters = [Vector(delta,halfdelta,halfdelta), Vector(0.0,halfdelta,halfdelta),
Vector(halfdelta,delta,halfdelta), Vector(halfdelta,0.0,halfdelta),
Vector(halfdelta,halfdelta,delta), Vector(halfdelta,halfdelta,0.0)]
# vertexes of the six faces (with a slight offset)
vertexes = [[Vector(delta+epsdelta,0,0), Vector(delta+epsdelta,delta,0), Vector(delta+epsdelta,delta,delta), Vector(delta+epsdelta,0,delta)],
[Vector(-epsdelta,0,0), Vector(-epsdelta,0,delta), Vector(-epsdelta,delta,delta), Vector(-epsdelta,delta,0)],
[Vector(0,delta+epsdelta,0), Vector(0,delta+epsdelta,delta), Vector(delta,delta+epsdelta,delta), Vector(delta,delta+epsdelta,0)],
[Vector(0,-epsdelta,0), Vector(delta,-epsdelta,0), Vector(delta,-epsdelta,delta), Vector(0,-epsdelta,delta)],
[Vector(0,0,delta+epsdelta), Vector(delta,0,delta+epsdelta), Vector(delta,delta,delta+epsdelta), Vector(0,delta,delta+epsdelta)],
[Vector(0,0,-epsdelta), Vector(0,delta,-epsdelta), Vector(delta,delta,-epsdelta), Vector(delta,0,-epsdelta)]]
# and now iterate to find which voxel is inside the bounding box of the 'face',
vbase = Vector(gbbox.XMin + min_x * delta, gbbox.YMin + min_y * delta, gbbox.ZMin + min_z * delta)
for step_x in range(min_x,max_x+1):
vbase.y = gbbox.YMin + min_y * delta
for step_y in range(min_y,max_y+1):
vbase.z = gbbox.ZMin + min_z * delta
for step_z in range(min_z,max_z+1):
# check if voxel is belonging to the given object
if voxelSpace[step_x,step_y,step_z] == condIndex:
# scan the six neighbour voxels, to see if they are belonging to the same conductor or not.
# If they are not belonging to the same conductor, or if the voxel space is finished, the current voxel
# side in the direction of the empty voxel is an external surface
for side, sideStr, sideCenter, vertex in zip(sides,sideStrs,sideCenters,vertexes):
is_surface = False
nextVoxelIndexes = [step_x+side[0],step_y+side[1],step_z+side[2]]
if (nextVoxelIndexes[0] > max_x or nextVoxelIndexes[0] < 0 or
nextVoxelIndexes[1] > max_y or nextVoxelIndexes[1] < 0 or
nextVoxelIndexes[2] > max_z or nextVoxelIndexes[2] < 0):
is_surface = True
else:
if voxelSpace[nextVoxelIndexes[0],nextVoxelIndexes[1],nextVoxelIndexes[2]] != condIndex:
is_surface = True
if is_surface == True:
# debug
#print("pos_x="+str(vbase.x)+" pos_y="+str(vbase.y)+" pos_z="+str(vbase.z))
testVertex.Placement.Base = vbase + sideCenter
# if the point is close enough to the face, we consider
# the voxel surface as belonging to the voxelized face
dist = testVertex.distToShape(face)
# debug
#print(str(dist))
if abs(dist[0]) < halfdelta:
contactList.append([step_x,step_y,step_z,sideStr])
if createShell:
# create the face
# calculate the vertexes
v11 = vbase + vertex[0]
v12 = vbase + vertex[1]
v13 = vbase + vertex[2]
v14 = vbase + vertex[3]
# now make the face
poly = Part.makePolygon( [v11,v12,v13,v14,v11])
contFace = Part.Face(poly)
surfList.append(contFace)
vbase.z += delta
vbase.y += delta
vbase.x += delta
contactShell = None
if createShell:
if surfList != []:
# create a shell. Does not need to be solid.
contactShell = Part.makeShell(surfList)
return [contactList,contactShell]
#bb = App.BoundBox();
#
#objects = App.ActiveDocument.findObjects("Part::Feature")

View File

@ -180,7 +180,7 @@ def parse_3D_input_file(fileinname, parentFid, parentFilePosMap, isdiel = False,
# build the file map (for single input file)
ret = create_file_map(fileinname, fid, filePosMap)
if ret <> True:
if ret != True:
return ret
panelVertexes = []
@ -223,14 +223,14 @@ def parse_3D_input_file(fileinname, parentFid, parentFilePosMap, isdiel = False,
# conductor name because in the same file called more than once)
if m_iParseLevel == 0:
localGroupname = "g"
else
else:
localGroupname = groupname
localGroupname = localGroupname + str(m_iGroupNum[m_iParseLevel]) + '_'
# read optional values
if len(splitLine) >= 7:
# read optional '+'. If not a '+', increment the group
if splitLine[6] <> '+':
if splitLine[6] != '+':
# increase group name
m_iGroupNum[m_iParseLevel] = m_iGroupNum[m_iParseLevel] + 1