Lattice2/lattice2PolarArray2.py

344 lines
15 KiB
Python

#***************************************************************************
#* *
#* Copyright (c) 2018 - Victor Titov (DeepSOIC) *
#* <vv.titov@gmail.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__="Polar array feature module for lattice workbench for FreeCAD"
__author__ = "DeepSOIC"
__url__ = ""
import math
turn = 2 * math.pi
import FreeCAD as App
import Part
from lattice2Common import *
import lattice2BaseFeature
from lattice2BaseFeature import assureProperty
import lattice2Executer
import lattice2GeomUtils
from lattice2ValueSeriesGenerator import ValueSeriesGenerator
import lattice2Utils as Utils
from lattice2Utils import linkSubList_convertToOldStyle
import lattice2AttachablePlacement as APlm
V = App.Vector
def make():
'''make(): makes a PolarArray object.'''
obj = lattice2BaseFeature.makeLatticeFeature('PolarArray', PolarArray, ViewProviderPolarArray, no_disable_attacher= True)
return obj
def fetchArc(obj, sub):
"""returns None, or tuple (arc_span, arc_radius)"""
if len(sub) > 0:
linkedShape = obj.Shape.getElement(sub)
else:
linkedShape = obj.Shape
if linkedShape.ShapeType == 'Edge':
crv = linkedShape.Curve
if isinstance(crv, Part.Circle):
return linkedShape.LastParameter - linkedShape.FirstParameter, crv.Radius
class PolarArray(APlm.AttachableFeature):
"""The Lattice Polar Array of placements"""
def derivedInit(self,selfobj):
super(PolarArray, self).derivedInit(selfobj)
selfobj.addProperty('App::PropertyLength','Radius',"Polar Array","Radius of the array (set to zero for just rotation).")
selfobj.Radius = 3
selfobj.addProperty('App::PropertyEnumeration', 'UseArcRange', "Polar Array", "If attachment mode is concentric, supporting arc's range can be used as array's Span or Step.")
selfobj.UseArcRange = ['ignore', 'as Span', 'as Step']
selfobj.addProperty('App::PropertyBool', 'UseArcRadius', "Polar Array", "If True, and attachment mode is concentric, supporting arc's radius is used as array radius.")
selfobj.addProperty('App::PropertyEnumeration','OrientMode',"Polar Array","Orientation of placements. Zero - aligns with origin. Static - aligns with self placement.")
selfobj.OrientMode = ['Zero', 'Static', 'Radial', 'Vortex', 'Centrifuge', 'Launchpad', 'Dominoes']
selfobj.OrientMode = 'Radial'
selfobj.addProperty('App::PropertyBool', 'Reverse', "Polar Array", "Reverses array direction.")
selfobj.addProperty('App::PropertyBool', 'FlipX', "Polar Array", "Reverses x axis of every placement.")
selfobj.addProperty('App::PropertyBool', 'FlipZ', "Polar Array", "Reverses z axis of every placement.")
self.assureGenerator(selfobj)
selfobj.ValuesSource = 'Generator'
selfobj.SpanStart = 0
selfobj.SpanEnd = 360
selfobj.EndInclusive = False
selfobj.Step = 55
selfobj.Count = 7
def assureGenerator(self, selfobj):
'''Adds an instance of value series generator, if one doesn't exist yet.'''
if hasattr(self,'generator'):
return
self.generator = ValueSeriesGenerator(selfobj)
self.generator.addProperties(groupname= "Polar Array",
groupname_gen= "Lattice Series Generator",
valuesdoc= "List of angles, in degrees.",
valuestype= 'App::PropertyFloat')
self.updateReadonlyness(selfobj)
def updateReadonlyness(self, selfobj):
self.generator.updateReadonlyness()
arc = self.fetchArc(selfobj)
selfobj.setEditorMode('Radius', 1 if arc and selfobj.UseArcRadius else 0)
self.generator.setPropertyWritable('SpanEnd', False if arc and selfobj.UseArcRange == 'as Span' else True)
self.generator.setPropertyWritable('SpanStart', False if arc and selfobj.UseArcRange == 'as Span' else True)
self.generator.setPropertyWritable('Step', False if arc and selfobj.UseArcRange == 'as Step' else True)
def fetchArc(self, selfobj):
"""returns None, or tuple (arc_span, arc_radius)"""
if selfobj.Support:
lnkobj, sub = selfobj.Support[0]
sub = sub[0]
#resolve the link
return fetchArc(lnkobj, sub)
def derivedExecute(self,selfobj):
self.assureGenerator(selfobj)
self.updateReadonlyness(selfobj)
selfobj.positionBySupport()
# Apply links
if selfobj.UseArcRange != 'ignore' or selfobj.UseArcRadius:
range, radius = self.fetchArc(selfobj)
if selfobj.UseArcRange == 'as Span':
selfobj.SpanStart = 0.0
selfobj.SpanEnd = range/turn*360
elif selfobj.UseArcRange == 'as Step':
selfobj.Step = range/turn*360
if selfobj.UseArcRadius:
selfobj.Radius = radius
self.generator.execute()
# cache properties into variables
radius = float(selfobj.Radius)
values = [float(strv) for strv in selfobj.Values]
irot = selfobj.Placement.inverse().Rotation
# compute internam placement, one behind OrientMode property
baseplm = App.Placement()
is_zero = selfobj.OrientMode == 'Zero'
is_static = selfobj.OrientMode == 'Static'
if is_zero or is_static:
pass
elif selfobj.OrientMode == 'Radial':
baseplm = App.Placement()
elif selfobj.OrientMode == 'Vortex':
baseplm = App.Placement(V(), App.Rotation(
V( 0, 1, 0),
V( ),
V( 0, 0, 1)
))
elif selfobj.OrientMode == 'Centrifuge':
baseplm = App.Placement(V(), App.Rotation(
V( 0, 1, 0),
V( ),
V(-1, 0, 0)
))
elif selfobj.OrientMode == 'Launchpad':
baseplm = App.Placement(V(), App.Rotation(
V( 0, 0, 1),
V( ),
V( 1, 0, 0)
))
elif selfobj.OrientMode == 'Dominoes':
baseplm = App.Placement(V(), App.Rotation(
V( 0, 0, 1),
V( ),
V( 0,-1, 0)
))
else:
raise NotImplementedError()
flipX = selfobj.FlipX
flipZ = selfobj.FlipZ
flipY = flipX ^ flipZ
flipplm = App.Placement(V(), App.Rotation(
V( -1 if flipX else 1, 0, 0),
V( 0, -1 if flipY else 1, 0),
V( 0, 0, -1 if flipZ else 1)
))
baseplm = baseplm.multiply(flipplm)
# Make the array
on_arc = self.isOnArc(selfobj)
angleplus = -90.0 if on_arc else 0.0
mm = -1.0 if selfobj.Reverse else +1.0
output = [] # list of placements
for ang in values:
localrot = App.Rotation(App.Vector(0,0,1), ang * mm + angleplus)
localtransl = localrot.multVec(App.Vector(radius,0,0))
localplm = App.Placement(localtransl, localrot)
resultplm = localplm.multiply(baseplm)
if is_zero:
resultplm.Rotation = irot
resultplm = resultplm.multiply(flipplm)
elif is_static:
resultplm.Rotation = App.Rotation()
resultplm = resultplm.multiply(flipplm)
output.append(resultplm)
return output
def isOnArc(self, selfobj):
return selfobj.MapMode == 'Concentric' and len(linkSubList_convertToOldStyle(selfobj.Support)) == 1
def onChanged(self, selfobj, propname):
super(PolarArray, self).onChanged(selfobj, propname)
if 'Restore' in selfobj.State: return
if propname == 'Reverse' and self.isOnArc(selfobj):
if selfobj.Reverse == True and abs(selfobj.MapPathParameter - 0.0) < ParaConfusion:
selfobj.MapPathParameter = 1.0
elif selfobj.Reverse == False and abs(selfobj.MapPathParameter - 1.0) < ParaConfusion:
selfobj.MapPathParameter = 0.0
class ViewProviderPolarArray(APlm.ViewProviderAttachableFeature):
def getIcon(self):
return getIconPath('Lattice2_PolarArray.svg')
# -------------------------- /document object --------------------------------------------------
# -------------------------- Gui command --------------------------------------------------
def CreatePolarArray(genmode = 'SpanN'):
sublinks = Utils.getSelectionAsPropertyLinkSubList()
FreeCAD.ActiveDocument.openTransaction("Create PolarArray")
FreeCADGui.addModule('lattice2PolarArray2')
FreeCADGui.addModule('lattice2Executer')
FreeCADGui.addModule("lattice2Base.Autosize")
FreeCADGui.doCommand('f = lattice2PolarArray2.make()')
FreeCADGui.doCommand("f.GeneratorMode = {mode}".format(mode= repr(genmode)))
attached = False
if len(sublinks) == 1:
lnk, sub = sublinks[0]
arc = fetchArc(lnk, sub)
if arc:
arcspan, radius = arc
fullcircle = abs(arcspan - turn) < ParaConfusion
endinclusive = not fullcircle
usearcrange = 'as Step' if genmode == 'StepN' and not fullcircle else 'as Span'
FreeCADGui.doCommand(
'f.Support = [(App.ActiveDocument.{lnk}, {sub})]\n'
'f.MapMode = \'Concentric\'\n'
'f.UseArcRange = {usearcrange}\n'
'f.EndInclusive = {endinclusive}\n'
'f.UseArcRadius = True'
.format(lnk= lnk.Name, sub= repr(sub), usearcrange= repr(usearcrange), endinclusive= repr(endinclusive))
)
attached = True
if not attached:
FreeCADGui.doCommand("f.Placement.Base = lattice2Base.Autosize.convenientPosition()")
FreeCADGui.doCommand("f.Radius = lattice2Base.Autosize.convenientModelSize()/2")
FreeCADGui.doCommand('lattice2Executer.executeFeature(f)')
if len(sublinks) > 0 and not attached:
FreeCADGui.addModule('lattice2AttachablePlacement')
FreeCADGui.doCommand('lattice2AttachablePlacement.editNewAttachment(f)')
#commitTransaction will be called by attachment editor
else:
FreeCADGui.doCommand(
'Gui.Selection.clearSelection()\n'
'Gui.Selection.addSelection(f)'
)
FreeCAD.ActiveDocument.commitTransaction()
class CommandPolarArray(object):
"""Command to create PolarArray feature"""
def __init__(self, mode):
self.mode = mode
def GetResources(self):
mode_tooltips = {
'SpanN': "fit N placements into angle Span",
'StepN': "make N placements spaced by Step",
'SpanStep': "fill Span with placements spaced by Step",
'Random': "put N placements into Span randomly",
}
return {'Pixmap' : getIconPath('Lattice2_PolarArray_New.svg'),
'MenuText': "Polar array: {mode}".format(mode= ValueSeriesGenerator.mode_userfriendly_names[self.mode]),
'Accel': "",
'ToolTip': "New polar array. Make a polar array of placements - {mode_tooltip}.".format(mode_tooltip= mode_tooltips[self.mode])}
def Activated(self):
try:
if len(FreeCADGui.Selection.getSelection()) < 2 :
CreatePolarArray(self.mode)
else:
infoMessage("Lattice Polar Array",
"Polar array command. Creates a polar array of placements.\n\n"
"Preselection: you can select nothing -> an array on XY plane is created."
" You can select a circle or arc of circle -> an array on that arc/circle is"
" created, with array span controlled by arc range.\n\n"
"You can later attach the array to anything. Right-click the array in model tree, click 'Attachment...'"
)
except Exception as err:
msgError(err)
def IsActive(self):
if FreeCAD.ActiveDocument:
return True
else:
return False
_listOfSubCommands = []
for m in ValueSeriesGenerator.gen_modes:
cmd_name = 'Lattice2_PolarArray2_'+m
_listOfSubCommands.append(cmd_name)
if FreeCAD.GuiUp:
FreeCADGui.addCommand(cmd_name, CommandPolarArray(m))
class GroupCommandPolarArray:
def GetCommands(self):
global _listOfSubCommands
return tuple(_listOfSubCommands) # a tuple of command names that you want to group
def GetDefaultCommand(self): # return the index of the tuple of the default command. This method is optional and when not implemented '0' is used
return 0
def GetResources(self):
return { 'MenuText': 'Polar Array', 'ToolTip': 'Polar Array: circular array of placements.'}
def IsActive(self): # optional
return FreeCAD.ActiveDocument is not None
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Lattice2_PolarArray2_GroupCommand',GroupCommandPolarArray())
import lattice2Compatibility as Compat
if Compat.attach_extension_era:
exportedCommands = ['Lattice2_PolarArray2_GroupCommand']
else:
import lattice2PolarArray
exportedCommands = lattice2PolarArray.exportedCommands
# -------------------------- /Gui command --------------------------------------------------