Create workbench

This commit is contained in:
Zheng, Lei 2017-10-07 04:23:57 +08:00
parent 866a20dd71
commit caca713d8d
12 changed files with 701 additions and 47 deletions

View File

@ -0,0 +1,369 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="64px"
height="64px"
id="svg2963"
sodipodi:version="0.32"
inkscape:version="0.48.5 r10040"
sodipodi:docname="Draft_Move.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.1">
<defs
id="defs2965">
<linearGradient
id="linearGradient3354">
<stop
style="stop-color:#2157c7;stop-opacity:1;"
offset="0"
id="stop3356" />
<stop
style="stop-color:#6daaff;stop-opacity:1;"
offset="1"
id="stop3358" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 32 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="64 : 32 : 1"
inkscape:persp3d-origin="32 : 21.333333 : 1"
id="perspective2971" />
<linearGradient
gradientTransform="matrix(0,-1.4500001,1.4705882,0,-15.05882,91.45)"
y2="36.079998"
x2="21.689653"
y1="29.279999"
x1="56.172409"
gradientUnits="userSpaceOnUse"
id="linearGradient3036"
xlink:href="#linearGradient3895"
inkscape:collect="always" />
<linearGradient
id="linearGradient3895">
<stop
style="stop-color:#729fcf;stop-opacity:1;"
offset="0"
id="stop3897" />
<stop
style="stop-color:#204a87;stop-opacity:1;"
offset="1"
id="stop3899" />
</linearGradient>
<linearGradient
y2="36.079998"
x2="21.689653"
y1="29.279999"
x1="56.172409"
gradientTransform="matrix(0,-0.58000003,0.58823527,0,13.176471,38.379999)"
gradientUnits="userSpaceOnUse"
id="linearGradient3918-3"
xlink:href="#linearGradient3895-6"
inkscape:collect="always" />
<linearGradient
id="linearGradient3895-6">
<stop
style="stop-color:#729fcf;stop-opacity:1;"
offset="0"
id="stop3897-7" />
<stop
style="stop-color:#204a87;stop-opacity:1;"
offset="1"
id="stop3899-5" />
</linearGradient>
<linearGradient
y2="36.079998"
x2="21.689653"
y1="29.279999"
x1="56.172409"
gradientTransform="matrix(0.58000003,0,0,0.58823527,25.620001,13.176471)"
gradientUnits="userSpaceOnUse"
id="linearGradient3029-6"
xlink:href="#linearGradient3895-6-2"
inkscape:collect="always" />
<linearGradient
id="linearGradient3895-6-2">
<stop
style="stop-color:#729fcf;stop-opacity:1;"
offset="0"
id="stop3897-7-9" />
<stop
style="stop-color:#204a87;stop-opacity:1;"
offset="1"
id="stop3899-5-1" />
</linearGradient>
<linearGradient
y2="36.079998"
x2="21.689653"
y1="29.279999"
x1="56.172409"
gradientTransform="matrix(0,-0.58000003,0.58823527,0,13.176471,38.379999)"
gradientUnits="userSpaceOnUse"
id="linearGradient3918-0"
xlink:href="#linearGradient3895-9"
inkscape:collect="always" />
<linearGradient
id="linearGradient3895-9">
<stop
style="stop-color:#729fcf;stop-opacity:1;"
offset="0"
id="stop3897-3" />
<stop
style="stop-color:#204a87;stop-opacity:1;"
offset="1"
id="stop3899-6" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3895"
id="linearGradient3154"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0,-0.58000003,0.58823527,0,13.176471,38.379999)"
x1="45.482754"
y1="11.599999"
x2="-23.482759"
y2="52.400002" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3895-6-2"
id="linearGradient3156"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.58000003,0,0,0.58823527,38.379999,13.176471)"
x1="31.689651"
y1="-2.0000007"
x2="-9.6896563"
y2="66" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3895-6"
id="linearGradient3158"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.58000003,0,0,0.58823527,25.620001,13.176471)"
x1="-9.6896563"
y1="-2.0000007"
x2="31.689651"
y2="66" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3895-9"
id="linearGradient3160"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0,0.58000003,0.58823527,0,13.176471,25.620001)"
x1="-23.482759"
y1="11.599999"
x2="45.482754"
y2="52.400002" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3895-9"
id="linearGradient3936"
x1="20"
y1="12"
x2="44"
y2="52"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3895-9"
id="linearGradient3944"
x1="20"
y1="12"
x2="44"
y2="52"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="10.193662"
inkscape:cx="21.49082"
inkscape:cy="36.860521"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:document-units="px"
inkscape:grid-bbox="true"
inkscape:window-width="1600"
inkscape:window-height="837"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:snap-global="true"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid3009"
empspacing="2"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
<inkscape:grid
type="xygrid"
id="grid3011"
empspacing="2"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="16px"
spacingy="16px"
empcolor="#ff0000"
empopacity="0.25098039"
color="#ff0000"
opacity="0.1254902" />
</sodipodi:namedview>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<rect
style="fill:url(#linearGradient3936);fill-opacity:1;stroke:none"
id="rect3126"
width="12"
height="6"
x="26"
y="29" />
<rect
style="fill:url(#linearGradient3944);fill-opacity:1;stroke:none"
id="rect3126-2"
width="6"
height="11.999999"
x="29"
y="26" />
<g
id="g4351"
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/move.png"
inkscape:export-xdpi="6.5591564"
inkscape:export-ydpi="6.5591564"
transform="matrix(0.1378133,0,0,0.1378133,-221.39699,-138.35275)" />
<path
inkscape:connector-curvature="0"
style="fill:url(#linearGradient3154);fill-opacity:1;fill-rule:evenodd;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 37,28 0,-15 6,0 -11,-10 -11,10 6,0 0,15"
id="path3343"
sodipodi:nodetypes="ccccccc"
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/changeprop.png"
inkscape:export-xdpi="4.1683898"
inkscape:export-ydpi="4.1683898" />
<path
inkscape:connector-curvature="0"
style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 35,29 35,11 37.831259,11 32,5.7026937 26.168741,11 29,11 29,29"
id="path3343-2"
sodipodi:nodetypes="ccccccc"
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/changeprop.png"
inkscape:export-xdpi="4.1683898"
inkscape:export-ydpi="4.1683898" />
<path
inkscape:connector-curvature="0"
style="fill:url(#linearGradient3158);fill-opacity:1;fill-rule:evenodd;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 37,37 14,0 0,6 10,-11 -10,-11 0,6 -14,0"
id="path3343-3"
sodipodi:nodetypes="ccccccc"
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/changeprop.png"
inkscape:export-xdpi="4.1683898"
inkscape:export-ydpi="4.1683898" />
<path
inkscape:connector-curvature="0"
style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 35,35 18,0 0,2.831259 L 58.297306,32 53,26.168741 53,29 34,29"
id="path3343-2-5"
sodipodi:nodetypes="ccccccc"
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/changeprop.png"
inkscape:export-xdpi="4.1683898"
inkscape:export-ydpi="4.1683898" />
<path
inkscape:connector-curvature="0"
style="fill:url(#linearGradient3156);fill-opacity:1;fill-rule:evenodd;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 27,37 -14,0 0,6 -10,-11 10,-11 0,6 14,0"
id="path3343-3-2"
sodipodi:nodetypes="ccccccc"
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/changeprop.png"
inkscape:export-xdpi="4.1683898"
inkscape:export-ydpi="4.1683898" />
<path
inkscape:connector-curvature="0"
style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 29,35 11,35 11,37.831259 5.702694,32 11,26.168741 11,29 30,29"
id="path3343-2-5-7"
sodipodi:nodetypes="ccccccc"
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/changeprop.png"
inkscape:export-xdpi="4.1683898"
inkscape:export-ydpi="4.1683898" />
<path
inkscape:connector-curvature="0"
style="fill:url(#linearGradient3160);fill-opacity:1;fill-rule:evenodd;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 37,36 0,15 6,0 -11,10 -11,-10 6,0 0,-15"
id="path3343-0"
sodipodi:nodetypes="ccccccc"
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/changeprop.png"
inkscape:export-xdpi="4.1683898"
inkscape:export-ydpi="4.1683898" />
<path
inkscape:connector-curvature="0"
style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 35,34 0,19 2.831259,0 L 32,58.297306 26.168741,53 29,53 29,34"
id="path3343-2-6"
sodipodi:nodetypes="ccccccc"
inkscape:export-filename="/home/yorik/Documents/Lab/Draft/icons/changeprop.png"
inkscape:export-xdpi="4.1683898"
inkscape:export-ydpi="4.1683898" />
</g>
<metadata
id="metadata5006">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Draft_Move</dc:title>
<cc:license
rdf:resource="" />
<dc:date>Mon Oct 10 13:44:52 2011 +0000</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>[wmayer]</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Draft/Resources/icons/Draft_Move.svg</dc:identifier>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson</dc:title>
</cc:Agent>
</dc:contributor>
<dc:subject>
<rdf:Bag>
<rdf:li>arrow</rdf:li>
<rdf:li>move</rdf:li>
<rdf:li>arrows</rdf:li>
<rdf:li>compass</rdf:li>
<rdf:li>cross</rdf:li>
</rdf:Bag>
</dc:subject>
<dc:description>Four equally sized arrow heads at 90° to eachother, all joined at the tail</dc:description>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

27
InitGui.py Normal file
View File

@ -0,0 +1,27 @@
import FreeCAD, FreeCADGui
class Assembly3Workbench(FreeCADGui.Workbench):
import asm3
MenuText = 'Assembly 3'
Icon = asm3.utils.addIconToFCAD('AssemblyWorkbench.svg')
def Activated(self):
import asm3
asm3.constraint.Observer.attach()
def Deactivated(self):
import asm3
asm3.constraint.Observer.detach()
def Initialize(self):
import asm3
cmds = asm3.gui.AsmCmdType.getInfo().TypeNames
asm3.utils.logger.debug(cmds)
self.appendToolbar('asm3',cmds)
self.appendMenu('&Assembly3', cmds)
self.appendToolbar('asm3 Constraint',
asm3.constraint.Constraint.CommandList)
# FreeCADGui.addPreferencePage(
# ':/assembly3/ui/assembly3_prefs.ui','Assembly3')
FreeCADGui.addWorkbench(Assembly3Workbench)

View File

@ -1,5 +1,5 @@
import FreeCAD, FreeCADGui, Part
from asm3 import utils,assembly,solver,constraint,system
from asm3 import proxy,utils,assembly,solver,constraint,system,gui
from asm3.utils import logger
from asm3.assembly import Assembly,AsmConstraint
try:

View File

@ -121,6 +121,7 @@ class AsmPartGroup(AsmGroup):
obj = parent.Document.addObject("App::FeaturePython",name,
AsmPartGroup(parent),None,True)
ViewProviderAsmPartGroup(obj.ViewObject)
obj.purgeTouched()
return obj
@ -130,6 +131,11 @@ class ViewProviderAsmPartGroup(ViewProviderAsmBase):
def onDelete(self,_obj,_subs):
return False
def canDropObject(self,obj):
return isTypeOf(obj,Assembly) or not isTypeOf(obj,AsmBase)
def canDropObjects(self):
return True
class AsmElement(AsmBase):
def __init__(self,parent):
@ -373,7 +379,7 @@ class AsmElementLink(AsmBase):
if not ret:
# It is from a non assembly child part, then use our own element
# group as the holder for elements
ret = [Assembly.Selection(assembly.obj,owner,subname)]
ret = [Assembly.Info(assembly.obj,owner,subname)]
if not isTypeOf(ret[-1].Object,AsmPartGroup):
raise RuntimeError('Invalid element link ' + subname)
@ -436,7 +442,8 @@ class AsmElementLink(AsmBase):
if not isTypeOf(part,Assembly,True) and \
not Constraint.isDisabled(self.parent.obj) and \
not Constraint.isLocked(self.parent.obj):
getter = getattr(part.getLinkedObject(True),'getLinkExtProperty')
getter = getattr(part.getLinkedObject(True),
'getLinkExtProperty',None)
# special treatment of link array (i.e. when ElementCount!=0), we
# allow the array element to be moveable by the solver
@ -543,6 +550,7 @@ class ViewProviderAsmElementLink(ViewProviderAsmBase):
class AsmConstraint(AsmGroup):
def __init__(self,parent):
self._initializing = True
self.elements = None
self.parent = getProxy(parent,AsmConstraintGroup)
super(AsmConstraint,self).__init__()
@ -585,8 +593,10 @@ class AsmConstraint(AsmGroup):
obj.recompute()
def execute(self,_obj):
self.checkSupport()
self.getElements(True)
if not getattr(self,'_initializing',False) and\
getattr(self,'parent',None):
self.checkSupport()
self.getElements(True)
return False
def getElements(self,refresh=False):
@ -691,6 +701,8 @@ class AsmConstraint(AsmGroup):
for e in selection.Elements:
AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e))
cstr.Proxy._initializing = False
cstr.recompute()
return cstr
@ -727,6 +739,7 @@ class AsmConstraintGroup(AsmGroup):
obj = parent.Document.addObject("App::FeaturePython",name,
AsmConstraintGroup(parent),None,True)
ViewProviderAsmConstraintGroup(obj.ViewObject)
obj.purgeTouched()
return obj
@ -750,6 +763,7 @@ class AsmElementGroup(AsmGroup):
obj = parent.Document.addObject("App::FeaturePython",name,
AsmElementGroup(parent),None,True)
ViewProviderAsmElementGroup(obj.ViewObject)
obj.purgeTouched()
return obj
@ -797,17 +811,23 @@ class Assembly(AsmGroup):
System.touch(obj)
return False # return False to call LinkBaseExtension::execute()
def onSolverChanged(self):
def onSolverChanged(self,setup=False):
for obj in self.getConstraintGroup().Group:
# setup==True usually means we are restoring, so try to restore the
# non-touched state if possible, since recompute() below will touch
# the constraint object
touched = not setup or 'Touched' in obj.State
obj.recompute()
if not touched:
obj.purgeTouched()
def buildShape(self):
import Part
obj = self.obj
if not obj.BuildShape:
obj.Shape.nullify()
if obj.BuildShape == BuildShapeNone:
obj.Shape = Part.Shape()
return
import Part
shape = []
partGroup = self.getPartGroup(obj)
group = partGroup.Group
@ -855,7 +875,8 @@ class Assembly(AsmGroup):
# all groups exist. The order of the group is important to make sure
# correct rendering and picking behavior
self.getPartGroup(True)
self.onSolverChanged()
self.onSolverChanged(True)
def onChanged(self, obj, prop):
if prop == 'BuildShape':
@ -958,10 +979,28 @@ class Assembly(AsmGroup):
"Part::FeaturePython",name,Assembly(),None,True)
ViewProviderAssembly(obj.ViewObject)
obj.Visibility = True
obj.purgeTouched()
return obj
Info = namedtuple('AssemblyInfo',('Assembly','Object','Subname'))
@staticmethod
def find(sels=None):
'Find all assembly objects among the current selection'
objs = set()
if sels is None:
sels = FreeCADGui.Selection.getSelectionEx('',False)
for sel in sels:
if not sel.SubElementNames:
if isTypeOf(sel.Object,Assembly):
objs.add(sel.Object)
continue
for subname in sel.SubElementNames:
ret = Assembly.findChild(sel.Object,subname,recursive=True)
if ret:
objs.add(ret[-1].Assembly)
return tuple(objs)
@staticmethod
def findChild(obj,subname,childType=None,
recursive=False,relativeToChild=True):
@ -991,7 +1030,7 @@ class Assembly(AsmGroup):
if isTypeOf(obj,Assembly,True):
assembly = obj
subs = subname if isinstance(subname,list) else subname.split('.')
for i,name in enumerate(subs[:-2]):
for i,name in enumerate(subs[:-1]):
obj = obj.getSubObject(name+'.',1)
if not obj:
raise RuntimeError('Cannot find sub object {}'.format(name))
@ -1043,12 +1082,21 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
def canDragObjects(self):
return False
def canDropObject(self,_child):
return False
@property
def PartGroup(self):
return self.ViewObject.Object.Proxy.getPartGroup()
def canDropObject(self,obj):
self.PartGroup.ViewObject.canDropObject(obj)
def canDropObjects(self):
return False
return True
def dropObjectEx(self,_vobj,obj,owner,subname):
self.PartGroup.ViewObject.dropObject(obj,owner,subname)
def getIcon(self):
return System.getIcon(self.ViewObject.Object)

View File

@ -145,14 +145,83 @@ def _a(solver,partInfo,subname,shape):
return _c(solver,partInfo,subname,shape,True)
class ConstraintCommand:
def __init__(self,tp):
self.tp = tp
def GetResources(self):
return self.tp.GetResources()
def Activated(self):
from asm3.assembly import AsmConstraint
AsmConstraint.make(self.tp._id)
def IsActive(self):
return FreeCADGui.ActiveDocument and self.tp._active
class SelectionObserver:
def __init__(self):
self._attached = False
def onChanged(self):
from asm3.assembly import AsmConstraint
for cls in Constraint._cmdTypes:
try:
AsmConstraint.getSelection()
except Exception as e:
logger.trace('selection "{}" exception: {}'.format(
cls.getName(),e.message),frame=1)
cls._active = False
else:
cls._active = True
def addSelection(self,*_args):
self.onChanged()
def removeSelection(self,*_args):
self.onChanged()
def setSelection(self,*_args):
self.onChanged()
def clearSelection(self,*_args):
logger.trace('selection cleared')
for cls in Constraint._cmdTypes:
cls._active = False
def attach(self):
if not self._attached:
FreeCADGui.Selection.addObserver(self)
self._attached = True
self.onChanged()
def detach(self):
if self._attached:
FreeCADGui.Selection.removeObserver(self)
self._attached = False
self.clearSelection('')
Observer = SelectionObserver()
class Constraint(ProxyType):
'constraint meta class'
_typeID = '_ConstraintType'
_typeEnum = 'ConstraintType'
_disabled = 'Disabled'
CommandList = []
_cmdTypes = []
def register(cls):
super(Constraint,cls).register()
if cls._menuItem:
name = 'asm3Add'+cls.getName()
mcs = cls.__class__
mcs.CommandList.append(name)
mcs._cmdTypes.append(cls)
FreeCADGui.addCommand(name,ConstraintCommand(cls))
@classmethod
def attach(mcs,obj,checkType=True):
if checkType:
@ -220,8 +289,12 @@ class Base(with_metaclass(Constraint,object)):
_props = []
_iconName = 'Assembly_ConstraintGeneral.svg'
_menuText = 'Add a "{}" constraint'
_active = False
_menuItem = False
def __init__(self,_obj):
self._supported = True
pass
@classmethod
def getPropertyInfoList(cls):
@ -295,10 +368,29 @@ class Base(with_metaclass(Constraint,object)):
else:
logger.warn('{} no constraint func'.format(cstrName(obj)))
@classmethod
def getMenuText(cls):
return cls._menuText.format(cls.getName())
@classmethod
def getToolTip(cls):
tooltip = getattr(cls,'_tooltip',None)
if not tooltip:
return cls.getMenuText()
return tooltip.format(cls.getName())
@classmethod
def GetResources(cls):
return {'Pixmap':utils.addIconToFCAD(cls._iconName,_iconPath),
'MenuText':cls.getMenuText(),
'ToolTip':cls.getToolTip()}
class Locked(Base):
_id = 0
_iconName = 'Assembly_ConstraintLock.svg'
_menuItem = True
_tooltip = 'Add a "{}" constraint to fix part(s)'
@classmethod
def prepare(cls,obj,solver):
@ -309,6 +401,7 @@ class Locked(Base):
def check(cls,_group):
pass
class BaseMulti(Base):
_id = -1
_entityDef = (_wa,)
@ -414,23 +507,36 @@ class PlaneCoincident(BaseCascade):
_id = 35
_iconName = 'Assembly_ConstraintCoincidence.svg'
_props = ['Cascade','Offset']
_menuItem = True
_tooltip = \
'Add a "{}" constraint to conincide planes of two or more parts.\n'\
'The planes are coincided at their centers with an optional distance.'
class PlaneAlignment(BaseCascade):
_id = 37
_iconName = 'Assembly_ConstraintAlignment.svg'
_props = ['Cascade','Offset']
_menuItem = True
_tooltip = 'Add a "{}" constraint to rotate planes of two or more parts\n'\
'into the same orientation'
class AxialAlignment(BaseMulti):
_id = 36
_iconName = 'Assembly_ConstraintAxial.svg'
_menuItem = True
_tooltip = 'Add a "{}" constraint to align planes of two or more parts.\n'\
'The planes are aligned at the direction of their surface normal axis.'
class SameOrientation(BaseMulti):
_id = 2
_entityDef = (_n,)
_iconName = 'Assembly_ConstraintOrientation.svg'
_menuItem = True
_tooltip = 'Add a "{}" constraint to align planes of two or more parts.\n'\
'The planes are aligned to have the same orientation (i.e. rotation)'
class Angle(Base):
@ -439,6 +545,9 @@ class Angle(Base):
_workplane = True
_props = ["Angle","Supplement"]
_iconName = 'Assembly_ConstraintAngle.svg'
_menuItem = True
_tooltip = 'Add a "{}" constraint to set the angle of planes or linear\n'\
'edges of two parts.'
class Perpendicular(Base):
@ -446,19 +555,28 @@ class Perpendicular(Base):
_entityDef = (_ln,_ln)
_workplane = True
_iconName = 'Assembly_ConstraintPerpendicular.svg'
_menuItem = True
_tooltip = 'Add a "{}" constraint to make planes or linear edges of two\n'\
'parts perpendicular.'
class Parallel(Base):
_id = 29
_id = -1
_entityDef = (_ln,_ln)
_workplane = True
_iconName = 'Assembly_ConstraintParallel.svg'
_menuItem = True
_tooltip = 'Add a "{}" constraint to make planes or linear edges of two\n'\
'parts parallel.'
class MultiParallel(BaseMulti):
_id = 291
_entityDef = (_ln,)
_iconName = 'Assembly_ConstraintMultiParallel.svg'
_menuItem = True
_tooltip = 'Add a "{}" constraint to make planes or linear edges of two\n'\
'or more parts parallel.'
class PointsCoincident(Base):

67
gui.py Normal file
View File

@ -0,0 +1,67 @@
from future.utils import with_metaclass
import FreeCAD, FreeCADGui
from asm3.utils import logger,objName,addIconToFCAD
from asm3.assembly import Assembly,AsmConstraint
from asm3.proxy import ProxyType
class AsmCmdType(ProxyType):
def register(cls):
super(AsmCmdType,cls).register()
if cls._id >= 0:
FreeCADGui.addCommand(cls.getName(),cls())
class AsmCmdBase(with_metaclass(AsmCmdType,object)):
_id = -1
_active = True
@classmethod
def getName(cls):
return 'asm3'+cls.__name__[3:]
@classmethod
def GetResources(cls):
return {
'Pixmap':addIconToFCAD(cls._iconName),
'MenuText':cls.getMenuText(),
'ToolTip':cls.getToolTip()
}
@classmethod
def getMenuText(cls):
return cls._menuText
@classmethod
def getToolTip(cls):
return getattr(cls,'_tooltip',cls.getMenuText())
@classmethod
def IsActive(cls):
if FreeCAD.ActiveDocument and cls._active:
return True
class AsmCmdNew(AsmCmdBase):
_id = 0
_menuText = 'Create a new assembly'
_iconName = 'Assembly_New_Assembly.svg'
def Activated(self):
Assembly.make()
class AsmCmdSolve(AsmCmdBase):
_id = 1
_menuText = 'Solve the constraints of assembly(s)'
_iconName = 'AssemblyWorkbench.svg'
def Activated(self):
import asm3.solver as solver
solver.solve()
class AsmCmdMove(AsmCmdBase):
_id = 2
_menuText = 'Move assembly'
_iconName = 'Assembly_Move.svg'
def Activated(self):
pass

View File

@ -9,6 +9,8 @@ def propGetValue(self,obj):
return getattr(getattr(obj,self.Name),'Value')
class PropertyInfo(object):
'For holding information to create dynamic properties'
def __init__(self,host,name,tp,doc='', enum=None,
getter=propGet,group='Base',internal=False,duplicate=False):
self.Name = name
@ -21,9 +23,15 @@ class PropertyInfo(object):
self.Key = host.addPropertyInfo(self,duplicate)
class ProxyType(type):
'''
Meta class for managing other "proxy" like classes whose instances can be
dynamically attached to or detached from FCAD FeaturePython Proxy objects.
In other word, it is meant for managing proxies of Proxies
'''
_typeID = '_ProxyType'
_typeEnum = 'ProxyType'
_typeGroup = 'Base'
_propGroup = 'Base'
_proxyName = '_proxy'
_registry = {}
@ -48,7 +56,7 @@ class ProxyType(type):
for tp in info.Types:
tp._idx = -1
mcs.getInfo().Types.append(tp)
mcs.register(tp)
tp.register()
@classmethod
def getType(mcs,tp):
@ -99,8 +107,6 @@ class ProxyType(type):
obj.addProperty(prop.Type,prop.Name,prop.Group,prop.Doc)
if prop.Enum:
setattr(obj,prop.Name,prop.Enum)
else:
obj.setPropertyStatus(prop.Name,'-Hidden')
setattr(obj.Proxy,mcs._proxyName,cls(obj))
obj.ViewObject.signalChangeIcon()
@ -114,7 +120,6 @@ class ProxyType(type):
proxy.__class__.__name__))
for key in proxy.getPropertyInfoList():
prop = mcs.getPropertyInfo(key)
# obj.setPropertyStatus(prop.Name,'Hidden')
obj.removeProperty(prop.Name)
callback = getattr(proxy,'onDetach',None)
if callback:
@ -143,14 +148,14 @@ class ProxyType(type):
if checkType:
if mcs._typeID not in obj.PropertiesList:
obj.addProperty("App::PropertyInteger",
mcs._typeID,mcs._typeGroup,'',0,False,True)
mcs._typeID,mcs._propGroup,'',0,False,True)
mcs.setDefaultTypeID(obj)
if mcs._typeEnum not in obj.PropertiesList:
logger.debug('type enum {}, {}'.format(mcs._typeEnum,
mcs._typeGroup))
mcs._propGroup))
obj.addProperty("App::PropertyEnumeration",
mcs._typeEnum,mcs._typeGroup,'',2)
mcs._typeEnum,mcs._propGroup,'',2)
mcs.setTypeName(obj,info.TypeNames)
idx = 0
@ -179,10 +184,9 @@ class ProxyType(type):
cls._idx = -1
mcs = cls.__class__
mcs.getInfo().Types.append(cls)
mcs.register(cls)
cls.register()
@classmethod
def register(mcs,cls):
def register(cls):
'''
Register a class to this meta class
@ -193,6 +197,7 @@ class ProxyType(type):
'''
if cls._id < 0:
return
mcs = cls.__class__
info = mcs.getInfo()
if cls._id in info.TypeMap:
raise RuntimeError('Duplicate {} type id {}'.format(

View File

@ -22,15 +22,10 @@ class Solver(object):
self.system = System.getSystem(assembly)
cstrs = assembly.Proxy.getConstraints()
if not cstrs:
self.system.log('no constraint found in assembly '
logger.warn('no constraint found in assembly '
'{}'.format(objName(assembly)))
return
parts = assembly.Proxy.getPartGroup().Group
if len(parts)<=1:
self.system.log('not enough parts in {}'.format(objName(assembly)))
return
self._fixedGroup = 2
self.group = 1 # the solving group
self._partMap = {}
@ -41,16 +36,24 @@ class Solver(object):
self.system.GroupHandle = self._fixedGroup
firstPart = None
firstPartName = None
for cstr in cstrs:
if Constraint.isLocked(cstr):
Constraint.prepare(cstr,self)
elif not Constraint.isDisabled(cstr) and \
System.isConstraintSupported(
assembly,Constraint.getTypeName(cstr)):
if not firstPart:
elements = cstr.Proxy.getElements()
if elements:
info = elements[0].Proxy.getInfo()
firstPart = info.Part
firstPartName = info.PartName
self._cstrs.append(cstr)
if not self._fixedParts:
self.system.log('lock first part {}'.format(objName(parts[0])))
self._fixedParts.add(parts[0])
self.system.log('lock first part {}'.format(firstPartName))
self._fixedParts.add(firstPart)
for cstr in self._cstrs:
self.system.log('preparing {}'.format(cstrName(cstr)))
@ -162,9 +165,17 @@ class Solver(object):
self._partMap[info.Part] = partInfo
return partInfo
def solve(objs=None,recursive=True,reportFailed=True,recompute=True):
def solve(objs=None,recursive=None,reportFailed=True,recompute=True):
if not objs:
objs = FreeCAD.ActiveDocument.Objects
sels = FreeCADGui.Selection.getSelectionEx('',False)
if len(sels):
objs = asm.Assembly.find()
if not objs:
raise RuntimeError('No assembly found in selection')
else:
objs = FreeCAD.ActiveDocument.Objects
if recursive is None:
recursive = True
elif not isinstance(objs,(list,tuple)):
objs = [objs]

View File

@ -14,12 +14,12 @@ class _AlgoType(ProxyType):
_typeID = '_AlgorithmType'
_typeEnum = 'AlgorithmType'
_typeGroup = 'SolverAlgorithm'
_propGroup = 'SolverAlgorithm'
_proxyName = '_algo'
def _makeProp(name,doc='',tp='App::PropertyFloat',group=None):
if not group:
group = _AlgoType._typeGroup
group = _AlgoType._propGroup
info = PropertyInfo(_AlgoType,name,tp,doc,duplicate=True,group=group)
return info.Key

View File

@ -9,7 +9,7 @@ class System(ProxyType):
_typeID = '_SolverType'
_typeEnum = 'SolverType'
_typeGroup = 'Solver'
_propGroup = 'Solver'
_iconName = 'Assembly_Assembly_Tree.svg'
@classmethod

View File

@ -5,6 +5,11 @@ Most of the functions are borrowed directly from assembly2lib.py or lib3D.py in
assembly2
'''
import FreeCAD, FreeCADGui, Part
import numpy as np
from asm3.FCADLogger import FCADLogger
logger = FCADLogger('asm3')
import sys, os
modulePath = os.path.dirname(os.path.realpath(__file__))
@ -29,12 +34,16 @@ def getIcon(obj,disabled=False,path=None):
obj._iconDisabled = QIcon(pixmap)
return obj._iconDisabled
import FreeCAD, FreeCADGui, Part
import numpy as np
import asm3.FCADLogger
logger = asm3.FCADLogger.FCADLogger('assembly3')
def addIconToFCAD(iconFile,path=None):
iconName = ':asm3/icons/' + iconFile
if not path:
path = iconPath
try:
path = os.path.join(path,iconFile)
FreeCADGui.addIcon(iconName,path)
except AssertionError:
pass
return iconName
def objName(obj):
if obj.Label == obj.Name: