Compare commits

...

4 commits

7 changed files with 469 additions and 0 deletions

View file

@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
# Set the current view perpendicular to the selected line
# Place la vue perpendiculairement a la line selectionnee
# 2013 Jonathan Wiedemann,
# 2016 Werner Mayer,
# 2022 tchernomax, https://forum.freecadweb.org/viewtopic.php?p=630019#p630019
# 2023 FreeCutter, modifications towards linked objects like in workbench Assembly 4 , https://forum.freecad.org/viewtopic.php?p=718516#p718516
#
__title__ = "Align_View_to_Face"
__author__ = "Jonathan Wiedemann (Rockn)"
__url__ = "https://www.freecadweb.org/"
__Wiki__ = "https://wiki.freecadweb.org/Macro_Align_View_to_Face"
__version__ = "3.1"
__date__ = "2023/11/12" #YYYY/MM/DD
from pivy import coin
import FreeCAD as app
import FreeCADGui as gui
def pointAt(normal, up):
z = normal
y = up
x = y.cross(z)
y = z.cross(x)
rot = App.Matrix()
rot.A11 = x.x
rot.A21 = x.y
rot.A31 = x.z
rot.A12 = y.x
rot.A22 = y.y
rot.A32 = y.z
rot.A13 = z.x
rot.A23 = z.y
rot.A33 = z.z
return App.Placement(rot).Rotation
def get_selection_and_turn_view():
doc = app.activeDocument()
if doc is None:
app.Console.PrintWarning('Align_view_to_face: No file open, nothing to do\n')
return
selection = gui.Selection.getSelectionEx('', 0) # Returns a vector of selection objects
if not selection:
app.Console.PrintWarning('Align_view_to_face: Nothing selected, nothing to do\n')
return
cam = FreeCADGui.ActiveDocument.ActiveView.getCameraNode()
''' used to understand the 'getSelection' results
for selection_object in selection:
object_ = selection_object.Object
sub_fullpaths = selection_object.SubElementNames
if not sub_fullpaths:
# An object is selected, not a face, edge, vertex.
print(object_.Name)
for sub_fullpath in sub_fullpaths:
# One or more subelements are selected.
print(object_.Name, sub_fullpath)
'''
sel = selection[0]
face = sel.SubObjects[0]
if face.Area == 0: # trying to avoid errors due to wrong selected objects
app.Console.PrintWarning('Align_view_to_face: Please select a face - not an edge or vertex\n')
return
dir = face.normalAt(0,0)
if dir.z == 1 :
rot = pointAt(dir, App.Vector(0.0,1.0,0.0))
elif dir.z == -1 :
rot = pointAt(dir, App.Vector(0.0,1.0,0.0))
else :
rot = pointAt(dir, App.Vector(0.0,0.0,1.0))
cam.orientation.setValue(rot.Q)
gui.SendMsgToActiveView("ViewSelection")
app.Console.PrintWarning('Align_view_to_face: Done\n')
if __name__ == '__main__':
get_selection_and_turn_view()

View file

@ -0,0 +1,4 @@
[
"/home/pgp/.var/app/org.freecad.FreeCAD/data/FreeCAD/Macro/MultiExport-FreeCAD/Macro_Align_View_to_Face.png",
"/home/pgp/.var/app/org.freecad.FreeCAD/data/FreeCAD/Macro/MultiExport-FreeCAD/Align_View_to_Face.FCMacro"
]

313
FCHoneycombMaker.FCMacro Normal file
View file

@ -0,0 +1,313 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
* Copyright (c) 2018-2019 <TheMarkster> *
* *
* This file is a supplement to the FreeCAD CAx development system. *
* *
* 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. *
* *
* This software 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 at http://www.gnu.org/licenses *
* for more details. *
* *
* For more information about the GNU Library General Public License *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307 USA *
* *
***************************************************************************
"""
"""
FCHoneycombMaker
This is a macro to aid in creation of a honeycomb grid. Parameters,
including hexagon radius, separation between hexagons, etc., can be modified via an included spreadsheet.
"""
__title__ = "FCHoneycombMaker"
__author__ = "TheMarkster"
__url__ = "https://github.com/mwganson/FCHoneycombMaker"
__Wiki__ = "https://github.com/mwganson/FCHoneycombMaker/blob/master/README.md"
__date__ = "2020.10.30" #year.month.date
__version__ = __date__
import FreeCAD
import math
import Part,Draft,DraftTools
from PySide import QtCore, QtGui
import ProfileLib.RegularPolygon
import Sketcher
RADIUS = 2
SEPARATION = .25
PLATE_WIDTH = 20
PLATE_LENGTH = 55
PLATE_HEIGHT = 5
FreeCAD.Console.PrintMessage ('\nFCHoneycombMaker v'+__version__+'\n')
def makeHexagonSketch(sketchName):
#would use the polygon tool, but is buggy and sometimes creates squares instead of hexagons, so the manual way
sketch = App.ActiveDocument.getObject(sketchName)
#we just place the vertices any old where and then constrain them into place later
sketch.addGeometry(Part.LineSegment(App.Vector(-2.000000,1.448548,0),App.Vector(0.500515,2.469759,0)),False)
sketch.addGeometry(Part.LineSegment(App.Vector(0.500515,2.469759,0),App.Vector(2.000000,1.696951,0)),False)
sketch.addConstraint(Sketcher.Constraint('Coincident',0,2,1,1))
sketch.addGeometry(Part.LineSegment(App.Vector(2.000000,1.696951,0),App.Vector(2.736140,-0.676675,0)),False)
sketch.addConstraint(Sketcher.Constraint('Coincident',1,2,2,1))
sketch.addGeometry(Part.LineSegment(App.Vector(2.736140,-0.676675,0),App.Vector(1.000000,-2.415493,0)),False)
sketch.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1))
sketch.addGeometry(Part.LineSegment(App.Vector(1.000000,-2.415493,0),App.Vector(-3.000000,-1.000000,0)),False)
sketch.addConstraint(Sketcher.Constraint('Coincident',3,2,4,1))
sketch.addGeometry(Part.LineSegment(App.Vector(-3.000000,-1.000000,0),App.Vector(-2.000000,1.365748,0)),False)
sketch.addConstraint(Sketcher.Constraint('Coincident',4,2,5,1))
sketch.addConstraint(Sketcher.Constraint('Coincident',5,2,0,1))
sketch.addGeometry(Part.Circle(App.Vector(0.000000,0.000000,0),App.Vector(0,0,1),3.535498),False) #construction circle
sketch.addConstraint(Sketcher.Constraint('Coincident',6,3,-1,1))
sketch.addConstraint(Sketcher.Constraint('Radius',6,3.535498))
sketch.setDatum(7,App.Units.Quantity('2.000000 mm'))
sketch.addConstraint(Sketcher.Constraint('PointOnObject',4,2,6)) #constrain all vertices to the circumference
sketch.addConstraint(Sketcher.Constraint('PointOnObject',0,1,6))
sketch.addConstraint(Sketcher.Constraint('PointOnObject',0,2,6))
sketch.addConstraint(Sketcher.Constraint('PointOnObject',1,2,6))
sketch.addConstraint(Sketcher.Constraint('PointOnObject',2,2,6))
sketch.addConstraint(Sketcher.Constraint('PointOnObject',3,2,6))
sketch.toggleConstruction(6)
sketch.addConstraint(Sketcher.Constraint('Equal',5,0)) #set all lines equal
sketch.addConstraint(Sketcher.Constraint('Equal',0,1))
sketch.addConstraint(Sketcher.Constraint('Equal',1,2))
sketch.addConstraint(Sketcher.Constraint('Equal',2,3))
sketch.addConstraint(Sketcher.Constraint('Equal',3,4))
sketch.addConstraint(Sketcher.Constraint('Horizontal',0)) #make one of them horizontal
sketch.setExpression('Constraints[7]', u'EditMe.radius')
if not App.ActiveDocument:
App.newDocument()
if hasattr(App.ActiveDocument,"EditMe"):
raise StandardError("FCHoneycombMaker Error: Please run only once per document. Create a new document and try again.\n")
worksheet = App.ActiveDocument.addObject("Spreadsheet::Sheet", "EditMe")
worksheet.setColumnWidth('A',150)
set = worksheet.set
worksheet.mergeCells('A11:G25')
msg1 = """Instructions:
You should only run the macro once unless you want to start again from scratch.
Edit the values in column B to define your honeycomb. You can also edit the objects (plate, arrays, etc.), but it's
probably better to do all the editing here in the spreadsheet at least until you get it more or less like you want it before
doing some final tweaking directly on the objects.
Hexagon radius -- the circumradius of the individual hexagons (circle with each vertex on its circumference).
Hexagon separation -- distance between each hexagon, the thickness of the grid produced after cutting the hexagons from the plate.
Plate dimensions -- sets the dimensions of the plate out of which the honeycomb can be cut. These values are used to calculate
the countX and countY variables. You can delete the plate object if you wish to apply the hexagon arrays to a different structure.
Tweak X,Y,Z -- Edit these to move both hexagon arrays independently of the plate object, for example to center the arrays inside
the plate.
CountX and CountY -- number of hexagons in the 2 arrays. These are calculated based on the plate size, radius of hexagons, and
separation between them, but you will probably want to modify these manually. Just remember if you modify them you are replacing
the formulas in those cells with immediate values, and thus they won't be recalulated for you if other changes are made.
If you would prefer a round plate, simply delete the plate object and replace it with a cylinder, and then use the Tweak values
and countX and countY variables to arrange the hexagon arrays to your liking.
"""
set('A13', msg1)
aliases={'radius':'B2', 'separation':'B3', 'width':'B4', 'length':'B5', 'height':'B6', 'tweakX':'B8','tweakY':'B9','tweakZ':'B10',
'xInterval':'E2', 'yInterval':'E3', 'firstX':'E4', 'firstY':'E5','countX':'E6', 'countY':'E7','array2XPos':'E8','array2YPos':'E9',
}
for k,v in aliases.items():
worksheet.setAlias(v,k)
set('A1', 'User Variables')
set('D1','Calculated Values')
set('A2', 'Hexagon Radius:')
set(aliases['radius'], str(RADIUS))
set('A3', 'Hexagon Separation:')
set(aliases['separation'], str(SEPARATION))
set('A4', 'Grid Width:')
set(aliases['width'], str(PLATE_WIDTH))
set('A5', 'Grid Length:')
set(aliases['length'], str(PLATE_LENGTH))
set('A6', 'Grid Height:')
set(aliases['height'], str(PLATE_HEIGHT))
set('A8', 'Tweak X:')
set(aliases['tweakX'],u'0')
set('A9', 'Tweak Y:')
set(aliases['tweakY'],u'0')
set('A10', 'Tweak Z:')
set(aliases['tweakZ'],u'0')
set('D2','X Interval:')
set(aliases['xInterval'],'=2*sin(60deg)*(B2*2+(B3-0.267949*B2))')
set('D3', 'Y Interval:')
set(aliases['yInterval'], '=2*B2+(B3-0.267949*B2)' )
set('D4', 'First X:')
set(aliases['firstX'], '=B2')
set('D5', 'First Y:')
set(aliases['firstY'], '=B2')
set('D6', 'Count X:')
set(aliases['countX'], '=round((B5) / E2) + 2')
set('D7', 'Count Y:')
set(aliases['countY'], '=round((B4) / E3) + 2')
set ('D8', 'Array2 XPos:')
set (aliases['array2XPos'], '=sin(60deg)*(B2*2+B3-0.26794899999999999*B2)')
set ('D9', 'Array2 YPos:')
set (aliases['array2YPos'],'=E3/2')
#xInterval = float(worksheet.getContents(aliases['xInterval']))
xInterval = 2*math.sin(math.pi/180.0*60)*(RADIUS*2+(SEPARATION-0.267949))
yInterval = 2*RADIUS+(SEPARATION-0.267949)
#yInterval = float(worksheet.getContents(aliases['yInterval']))
firstX = RADIUS
firstY = RADIUS
countY = int((PLATE_LENGTH) / yInterval)
countX = int((PLATE_WIDTH) / xInterval)
App.ActiveDocument.addObject("Part::RegularPolygon","HoneycombHexagon")
App.ActiveDocument.HoneycombHexagon.Polygon=6
App.ActiveDocument.HoneycombHexagon.setExpression('Circumradius','EditMe.radius')
hexagonObject = App.ActiveDocument.getObject("HoneycombHexagon")
Gui.ActiveDocument.getObject("HoneycombHexagon").Visibility=False
extrudedHexagonObject = App.ActiveDocument.addObject('Part::Extrusion', 'ExtrudedHexagon')
extrudedHexagonObject.Base = hexagonObject
extrudedHexagonObject.setExpression('LengthFwd','EditMe.height')
extrudedHexagonObject.Solid=True
#extrudedHexagonObject.Placement=App.Placement(App.Vector(firstX,firstY,0),App.Rotation(0,0,0),App.Vector(0,0,0))
extrudedHexagonObject.setExpression('Placement.Base.x','EditMe.firstX+EditMe.tweakX')
extrudedHexagonObject.setExpression('Placement.Base.y','EditMe.firstY+EditMe.tweakY')
extrudedHexagonObject.setExpression('Placement.Base.z', 'EditMe.tweakZ')
xvector = App.Vector(xInterval,0,0)
yvector = App.Vector(0, yInterval,0)
if not hasattr(Draft,"make_ortho_array"):
row1Array = Draft.makeArray(extrudedHexagonObject, xvector,yvector,countX,countY,name="HoneycombArray1")
row2Array = Draft.makeArray(extrudedHexagonObject, xvector,yvector,countX,countY,name="HoneycombArray2")
else:
row1Array = Draft.make_ortho_array(extrudedHexagonObject, v_x=xvector,v_y=yvector,n_x=countX,n_y=countY,use_link=False)
row2Array = Draft.make_ortho_array(extrudedHexagonObject, v_x=xvector,v_y=yvector,n_x=countX,n_y=countY,use_link=False)
App.ActiveDocument.recompute()
row2Array.Placement = App.Placement(App.Vector(math.sin(60*math.pi/180.0)*(RADIUS*2+(SEPARATION-0.267949)),yInterval,0),App.Rotation(0,0,0),App.Vector(0,0,0))
#row2Array.setExpression('Placement.Base.x','sin(60deg) * (EditMe.radius * 2 + (EditMe.separation-0.267949*EditMe.radius))')
row2Array.setExpression('Placement.Base.x','EditMe.array2XPos')
#row2Array.setExpression('Placement.Base.y','EditMe.yInterval/2.0')
row2Array.setExpression('Placement.Base.y','EditMe.array2YPos')
row1Array.setExpression('IntervalX.x','EditMe.xInterval')
row1Array.setExpression('IntervalY.y','EditMe.yInterval')
row1Array.setExpression('NumberX','EditMe.countX')
row1Array.setExpression('NumberY','EditMe.countY')
row2Array.setExpression('IntervalX.x','EditMe.xInterval')
row2Array.setExpression('IntervalY.y','EditMe.yInterval')
row2Array.setExpression('NumberX','EditMe.countX')
row2Array.setExpression('NumberY','EditMe.countY')
App.activeDocument().addObject("Part::MultiFuse","Fused_Arrays")
#App.activeDocument().Fused_Arrays.Shapes = [App.activeDocument().HoneycombArray1,App.activeDocument().HoneycombArray2,]
App.activeDocument().Fused_Arrays.Shapes = [row1Array,row2Array,]
if not hasattr(Draft,"make_ortho_array"):
Gui.activeDocument().HoneycombArray1.Visibility=False
Gui.activeDocument().HoneycombArray2.Visibility=False
else:
row1Array.Visibility=False
row2Array.Visibility=False
window = QtGui.QApplication.activeWindow()
items=('Part Design Workbench Body','Part Workbench Object')
item,ok = QtGui.QInputDialog.getItem(window, "Select object type", "Select your object type. It can be a Part Design body or a Part Cut object.", items, 0, False)
if ok:
if item == items[0]: #PD Body
App.ActiveDocument.addObject("Part::Plane", "HoneycombPlane")
App.ActiveDocument.HoneycombPlane.setExpression('Length', u'EditMe.length')
App.ActiveDocument.HoneycombPlane.setExpression('Width', u'EditMe.width')
App.ActiveDocument.HoneycombPlane.setExpression('Placement.Base.x', u'EditMe.array2XPos + EditMe.radius * 2')
App.ActiveDocument.HoneycombPlane.setExpression('Placement.Base.y', u'EditMe.array2YPos + EditMe.radius * 2')
App.activeDocument().addObject("Part::Cut","BaseFeatureFace")
App.ActiveDocument.getObject("BaseFeatureFace").ViewObject.Visibility=False
App.activeDocument().BaseFeatureFace.Base = App.activeDocument().HoneycombPlane
App.activeDocument().BaseFeatureFace.Tool = App.activeDocument().Fused_Arrays
Gui.activeDocument().HoneycombPlane.Visibility=False
Gui.activeDocument().Fused_Arrays.Visibility=False
App.ActiveDocument.recompute()
App.activeDocument().addObject('PartDesign::Body','HoneycombBody')
App.activeDocument().HoneycombBody.BaseFeature = App.activeDocument().BaseFeatureFace
Gui.activeView().setActiveObject('pdHoneycombBody', App.activeDocument().HoneycombBody)
App.activeDocument().HoneycombBody.newObject("PartDesign::Pad","HoneycombPad")
App.activeDocument().HoneycombPad.Profile = (App.activeDocument().BaseFeature, ["Face1"])
App.activeDocument().HoneycombPad.Length = 10.0
App.ActiveDocument.recompute()
Gui.activeDocument().setEdit('HoneycombPad', 0)
Gui.activeDocument().hide("BaseFeature")
App.ActiveDocument.HoneycombPad.setExpression('Length', u'EditMe.height')
App.ActiveDocument.HoneycombPad.Length2 = 100.000000
App.ActiveDocument.HoneycombPad.Type = 0
App.ActiveDocument.HoneycombPad.UpToFace = None
App.ActiveDocument.HoneycombPad.Reversed = 0
App.ActiveDocument.HoneycombPad.Midplane = 0
App.ActiveDocument.HoneycombPad.Offset = 0.000000
App.ActiveDocument.recompute()
Gui.activeDocument().resetEdit()
else: #Part object
#plate = Part.makeBox(PLATE_WIDTH,PLATE_LENGTH,PLATE_HEIGHT)
#plate = Part.makeBox(worksheet.B4, worksheet.B5, worksheet.B6)
App.ActiveDocument.addObject("Part::Box", "Plate")
plateObject = App.ActiveDocument.getObject("Plate")
App.ActiveDocument.Plate.setExpression('Length', u'EditMe.length')
App.ActiveDocument.Plate.setExpression('Width', u'EditMe.width')
App.ActiveDocument.Plate.setExpression('Height', u'EditMe.height')
App.ActiveDocument.Plate.setExpression('Placement.Base.x', u'EditMe.array2XPos + EditMe.radius * 2')
App.ActiveDocument.Plate.setExpression('Placement.Base.y', u'EditMe.array2YPos + EditMe.radius * 2')
App.activeDocument().addObject("Part::Cut","HoneycombGrid")
App.activeDocument().HoneycombGrid.Base = App.activeDocument().Plate
App.activeDocument().HoneycombGrid.Tool = App.activeDocument().Fused_Arrays
Gui.activeDocument().Plate.Visibility=False
Gui.activeDocument().Fused_Arrays.Visibility=False
App.ActiveDocument.recompute()
Gui.SendMsgToActiveView("ViewFit")

View file

@ -0,0 +1,4 @@
[
"/home/pgp/.var/app/org.freecad.FreeCAD/data/FreeCAD/Macro/MultiExport-FreeCAD/FCHoneycombMaker.FCMacro",
"/home/pgp/.var/app/org.freecad.FreeCAD/data/FreeCAD/Macro/MultiExport-FreeCAD/FCHoneycombMakerIcon.png"
]

BIN
FCHoneycombMakerIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

61
start_WF.FCMacro Normal file
View file

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
import sys
import os
# Change this by your own FreeCAD lib path to import FreeCAD
if not sys.path.__contains__("/usr/lib/freecad/lib"):
sys.path.append("/usr/lib/freecad/lib")
def launchWorkFeature():
import WorkFeature.WF as WF
print()
print("INFO: WF module path is\n{0:s}".format(os.path.abspath(WF.__file__)))
print("INFO: WF release is\n{0:s}".format(str(WF.myRelease)))
WF.myDialog = WF.WorkFeatureTab()
def updatePYTHONPATH(current_path):
# remove file name from the full path name
m_current_path = os.path.dirname(current_path)
# check if this path belongs to the PYTHONPATH variable and if not add it
if not sys.path.__contains__(str(m_current_path)):
sys.path.append(str(m_current_path))
if not sys.path.__contains__(str(m_current_path) + os.sep + "WorkFeature"):
sys.path.append(str(m_current_path) + os.sep + "WorkFeature")
try:
m_current_path = os.path.realpath(__file__)
if os.path.islink(m_current_path):
m_current_path = os.path.realpath(m_current_path)
updatePYTHONPATH(m_current_path)
launchWorkFeature()
except ImportError as error:
print(sys.path)
print(error.__class__.__name__ + ": " + error.msg)
try:
import FreeCAD
# first check if the path to WorkFeature was set in the preferences
param = FreeCAD.ParamGet('User parameter:Plugins/workfeature')
m_current_path = param.GetString('destination', '')
if not m_current_path:
# get the path of the current python script
m_current_path = os.path.realpath(__file__)
if os.path.islink(m_current_path):
m_current_path = os.path.realpath(m_current_path)
m_current_path = os.path.dirname(m_current_path)
updatePYTHONPATH(m_current_path)
# retry import now
try:
launchWorkFeature()
except ImportError:
# we still cannot find WorkFeature. Inform the user
from PySide import QtGui
msgBox = QtGui.QMessageBox()
msgBox.setText("ERROR: Cannot load FreeCAD WorkFeature macro path ! \nCheck the installation !")
msgBox.exec_()
print("ERROR:cannot load FreeCAD WorkFeature macro path !")
print("Check the installation !")
except ImportError:
print("ERROR: Cannot load FreeCAD WorkFeature macro path !")
print("Check the installation !")