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 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.utils import logger
from asm3.assembly import Assembly,AsmConstraint from asm3.assembly import Assembly,AsmConstraint
try: try:

View File

@ -121,6 +121,7 @@ class AsmPartGroup(AsmGroup):
obj = parent.Document.addObject("App::FeaturePython",name, obj = parent.Document.addObject("App::FeaturePython",name,
AsmPartGroup(parent),None,True) AsmPartGroup(parent),None,True)
ViewProviderAsmPartGroup(obj.ViewObject) ViewProviderAsmPartGroup(obj.ViewObject)
obj.purgeTouched()
return obj return obj
@ -130,6 +131,11 @@ class ViewProviderAsmPartGroup(ViewProviderAsmBase):
def onDelete(self,_obj,_subs): def onDelete(self,_obj,_subs):
return False return False
def canDropObject(self,obj):
return isTypeOf(obj,Assembly) or not isTypeOf(obj,AsmBase)
def canDropObjects(self):
return True
class AsmElement(AsmBase): class AsmElement(AsmBase):
def __init__(self,parent): def __init__(self,parent):
@ -373,7 +379,7 @@ class AsmElementLink(AsmBase):
if not ret: if not ret:
# It is from a non assembly child part, then use our own element # It is from a non assembly child part, then use our own element
# group as the holder for elements # 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): if not isTypeOf(ret[-1].Object,AsmPartGroup):
raise RuntimeError('Invalid element link ' + subname) raise RuntimeError('Invalid element link ' + subname)
@ -436,7 +442,8 @@ class AsmElementLink(AsmBase):
if not isTypeOf(part,Assembly,True) and \ if not isTypeOf(part,Assembly,True) and \
not Constraint.isDisabled(self.parent.obj) and \ not Constraint.isDisabled(self.parent.obj) and \
not Constraint.isLocked(self.parent.obj): 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 # special treatment of link array (i.e. when ElementCount!=0), we
# allow the array element to be moveable by the solver # allow the array element to be moveable by the solver
@ -543,6 +550,7 @@ class ViewProviderAsmElementLink(ViewProviderAsmBase):
class AsmConstraint(AsmGroup): class AsmConstraint(AsmGroup):
def __init__(self,parent): def __init__(self,parent):
self._initializing = True
self.elements = None self.elements = None
self.parent = getProxy(parent,AsmConstraintGroup) self.parent = getProxy(parent,AsmConstraintGroup)
super(AsmConstraint,self).__init__() super(AsmConstraint,self).__init__()
@ -585,8 +593,10 @@ class AsmConstraint(AsmGroup):
obj.recompute() obj.recompute()
def execute(self,_obj): def execute(self,_obj):
self.checkSupport() if not getattr(self,'_initializing',False) and\
self.getElements(True) getattr(self,'parent',None):
self.checkSupport()
self.getElements(True)
return False return False
def getElements(self,refresh=False): def getElements(self,refresh=False):
@ -691,6 +701,8 @@ class AsmConstraint(AsmGroup):
for e in selection.Elements: for e in selection.Elements:
AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e)) AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e))
cstr.Proxy._initializing = False
cstr.recompute()
return cstr return cstr
@ -727,6 +739,7 @@ class AsmConstraintGroup(AsmGroup):
obj = parent.Document.addObject("App::FeaturePython",name, obj = parent.Document.addObject("App::FeaturePython",name,
AsmConstraintGroup(parent),None,True) AsmConstraintGroup(parent),None,True)
ViewProviderAsmConstraintGroup(obj.ViewObject) ViewProviderAsmConstraintGroup(obj.ViewObject)
obj.purgeTouched()
return obj return obj
@ -750,6 +763,7 @@ class AsmElementGroup(AsmGroup):
obj = parent.Document.addObject("App::FeaturePython",name, obj = parent.Document.addObject("App::FeaturePython",name,
AsmElementGroup(parent),None,True) AsmElementGroup(parent),None,True)
ViewProviderAsmElementGroup(obj.ViewObject) ViewProviderAsmElementGroup(obj.ViewObject)
obj.purgeTouched()
return obj return obj
@ -797,17 +811,23 @@ class Assembly(AsmGroup):
System.touch(obj) System.touch(obj)
return False # return False to call LinkBaseExtension::execute() return False # return False to call LinkBaseExtension::execute()
def onSolverChanged(self): def onSolverChanged(self,setup=False):
for obj in self.getConstraintGroup().Group: 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() obj.recompute()
if not touched:
obj.purgeTouched()
def buildShape(self): def buildShape(self):
import Part
obj = self.obj obj = self.obj
if not obj.BuildShape: if obj.BuildShape == BuildShapeNone:
obj.Shape.nullify() obj.Shape = Part.Shape()
return return
import Part
shape = [] shape = []
partGroup = self.getPartGroup(obj) partGroup = self.getPartGroup(obj)
group = partGroup.Group group = partGroup.Group
@ -855,7 +875,8 @@ class Assembly(AsmGroup):
# all groups exist. The order of the group is important to make sure # all groups exist. The order of the group is important to make sure
# correct rendering and picking behavior # correct rendering and picking behavior
self.getPartGroup(True) self.getPartGroup(True)
self.onSolverChanged()
self.onSolverChanged(True)
def onChanged(self, obj, prop): def onChanged(self, obj, prop):
if prop == 'BuildShape': if prop == 'BuildShape':
@ -958,10 +979,28 @@ class Assembly(AsmGroup):
"Part::FeaturePython",name,Assembly(),None,True) "Part::FeaturePython",name,Assembly(),None,True)
ViewProviderAssembly(obj.ViewObject) ViewProviderAssembly(obj.ViewObject)
obj.Visibility = True obj.Visibility = True
obj.purgeTouched()
return obj return obj
Info = namedtuple('AssemblyInfo',('Assembly','Object','Subname')) 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 @staticmethod
def findChild(obj,subname,childType=None, def findChild(obj,subname,childType=None,
recursive=False,relativeToChild=True): recursive=False,relativeToChild=True):
@ -991,7 +1030,7 @@ class Assembly(AsmGroup):
if isTypeOf(obj,Assembly,True): if isTypeOf(obj,Assembly,True):
assembly = obj assembly = obj
subs = subname if isinstance(subname,list) else subname.split('.') 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) obj = obj.getSubObject(name+'.',1)
if not obj: if not obj:
raise RuntimeError('Cannot find sub object {}'.format(name)) raise RuntimeError('Cannot find sub object {}'.format(name))
@ -1043,12 +1082,21 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
def canDragObjects(self): def canDragObjects(self):
return False return False
def canDropObject(self,_child): @property
return False def PartGroup(self):
return self.ViewObject.Object.Proxy.getPartGroup()
def canDropObject(self,obj):
self.PartGroup.ViewObject.canDropObject(obj)
def canDropObjects(self): def canDropObjects(self):
return False return True
def dropObjectEx(self,_vobj,obj,owner,subname):
self.PartGroup.ViewObject.dropObject(obj,owner,subname)
def getIcon(self): def getIcon(self):
return System.getIcon(self.ViewObject.Object) 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) 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): class Constraint(ProxyType):
'constraint meta class' 'constraint meta class'
_typeID = '_ConstraintType' _typeID = '_ConstraintType'
_typeEnum = 'ConstraintType' _typeEnum = 'ConstraintType'
_disabled = 'Disabled' _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 @classmethod
def attach(mcs,obj,checkType=True): def attach(mcs,obj,checkType=True):
if checkType: if checkType:
@ -220,8 +289,12 @@ class Base(with_metaclass(Constraint,object)):
_props = [] _props = []
_iconName = 'Assembly_ConstraintGeneral.svg' _iconName = 'Assembly_ConstraintGeneral.svg'
_menuText = 'Add a "{}" constraint'
_active = False
_menuItem = False
def __init__(self,_obj): def __init__(self,_obj):
self._supported = True pass
@classmethod @classmethod
def getPropertyInfoList(cls): def getPropertyInfoList(cls):
@ -295,10 +368,29 @@ class Base(with_metaclass(Constraint,object)):
else: else:
logger.warn('{} no constraint func'.format(cstrName(obj))) 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): class Locked(Base):
_id = 0 _id = 0
_iconName = 'Assembly_ConstraintLock.svg' _iconName = 'Assembly_ConstraintLock.svg'
_menuItem = True
_tooltip = 'Add a "{}" constraint to fix part(s)'
@classmethod @classmethod
def prepare(cls,obj,solver): def prepare(cls,obj,solver):
@ -309,6 +401,7 @@ class Locked(Base):
def check(cls,_group): def check(cls,_group):
pass pass
class BaseMulti(Base): class BaseMulti(Base):
_id = -1 _id = -1
_entityDef = (_wa,) _entityDef = (_wa,)
@ -414,23 +507,36 @@ class PlaneCoincident(BaseCascade):
_id = 35 _id = 35
_iconName = 'Assembly_ConstraintCoincidence.svg' _iconName = 'Assembly_ConstraintCoincidence.svg'
_props = ['Cascade','Offset'] _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): class PlaneAlignment(BaseCascade):
_id = 37 _id = 37
_iconName = 'Assembly_ConstraintAlignment.svg' _iconName = 'Assembly_ConstraintAlignment.svg'
_props = ['Cascade','Offset'] _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): class AxialAlignment(BaseMulti):
_id = 36 _id = 36
_iconName = 'Assembly_ConstraintAxial.svg' _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): class SameOrientation(BaseMulti):
_id = 2 _id = 2
_entityDef = (_n,) _entityDef = (_n,)
_iconName = 'Assembly_ConstraintOrientation.svg' _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): class Angle(Base):
@ -439,6 +545,9 @@ class Angle(Base):
_workplane = True _workplane = True
_props = ["Angle","Supplement"] _props = ["Angle","Supplement"]
_iconName = 'Assembly_ConstraintAngle.svg' _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): class Perpendicular(Base):
@ -446,19 +555,28 @@ class Perpendicular(Base):
_entityDef = (_ln,_ln) _entityDef = (_ln,_ln)
_workplane = True _workplane = True
_iconName = 'Assembly_ConstraintPerpendicular.svg' _iconName = 'Assembly_ConstraintPerpendicular.svg'
_menuItem = True
_tooltip = 'Add a "{}" constraint to make planes or linear edges of two\n'\
'parts perpendicular.'
class Parallel(Base): class Parallel(Base):
_id = 29 _id = -1
_entityDef = (_ln,_ln) _entityDef = (_ln,_ln)
_workplane = True _workplane = True
_iconName = 'Assembly_ConstraintParallel.svg' _iconName = 'Assembly_ConstraintParallel.svg'
_menuItem = True
_tooltip = 'Add a "{}" constraint to make planes or linear edges of two\n'\
'parts parallel.'
class MultiParallel(BaseMulti): class MultiParallel(BaseMulti):
_id = 291 _id = 291
_entityDef = (_ln,) _entityDef = (_ln,)
_iconName = 'Assembly_ConstraintMultiParallel.svg' _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): 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') return getattr(getattr(obj,self.Name),'Value')
class PropertyInfo(object): class PropertyInfo(object):
'For holding information to create dynamic properties'
def __init__(self,host,name,tp,doc='', enum=None, def __init__(self,host,name,tp,doc='', enum=None,
getter=propGet,group='Base',internal=False,duplicate=False): getter=propGet,group='Base',internal=False,duplicate=False):
self.Name = name self.Name = name
@ -21,9 +23,15 @@ class PropertyInfo(object):
self.Key = host.addPropertyInfo(self,duplicate) self.Key = host.addPropertyInfo(self,duplicate)
class ProxyType(type): 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' _typeID = '_ProxyType'
_typeEnum = 'ProxyType' _typeEnum = 'ProxyType'
_typeGroup = 'Base' _propGroup = 'Base'
_proxyName = '_proxy' _proxyName = '_proxy'
_registry = {} _registry = {}
@ -48,7 +56,7 @@ class ProxyType(type):
for tp in info.Types: for tp in info.Types:
tp._idx = -1 tp._idx = -1
mcs.getInfo().Types.append(tp) mcs.getInfo().Types.append(tp)
mcs.register(tp) tp.register()
@classmethod @classmethod
def getType(mcs,tp): def getType(mcs,tp):
@ -99,8 +107,6 @@ class ProxyType(type):
obj.addProperty(prop.Type,prop.Name,prop.Group,prop.Doc) obj.addProperty(prop.Type,prop.Name,prop.Group,prop.Doc)
if prop.Enum: if prop.Enum:
setattr(obj,prop.Name,prop.Enum) setattr(obj,prop.Name,prop.Enum)
else:
obj.setPropertyStatus(prop.Name,'-Hidden')
setattr(obj.Proxy,mcs._proxyName,cls(obj)) setattr(obj.Proxy,mcs._proxyName,cls(obj))
obj.ViewObject.signalChangeIcon() obj.ViewObject.signalChangeIcon()
@ -114,7 +120,6 @@ class ProxyType(type):
proxy.__class__.__name__)) proxy.__class__.__name__))
for key in proxy.getPropertyInfoList(): for key in proxy.getPropertyInfoList():
prop = mcs.getPropertyInfo(key) prop = mcs.getPropertyInfo(key)
# obj.setPropertyStatus(prop.Name,'Hidden')
obj.removeProperty(prop.Name) obj.removeProperty(prop.Name)
callback = getattr(proxy,'onDetach',None) callback = getattr(proxy,'onDetach',None)
if callback: if callback:
@ -143,14 +148,14 @@ class ProxyType(type):
if checkType: if checkType:
if mcs._typeID not in obj.PropertiesList: if mcs._typeID not in obj.PropertiesList:
obj.addProperty("App::PropertyInteger", obj.addProperty("App::PropertyInteger",
mcs._typeID,mcs._typeGroup,'',0,False,True) mcs._typeID,mcs._propGroup,'',0,False,True)
mcs.setDefaultTypeID(obj) mcs.setDefaultTypeID(obj)
if mcs._typeEnum not in obj.PropertiesList: if mcs._typeEnum not in obj.PropertiesList:
logger.debug('type enum {}, {}'.format(mcs._typeEnum, logger.debug('type enum {}, {}'.format(mcs._typeEnum,
mcs._typeGroup)) mcs._propGroup))
obj.addProperty("App::PropertyEnumeration", obj.addProperty("App::PropertyEnumeration",
mcs._typeEnum,mcs._typeGroup,'',2) mcs._typeEnum,mcs._propGroup,'',2)
mcs.setTypeName(obj,info.TypeNames) mcs.setTypeName(obj,info.TypeNames)
idx = 0 idx = 0
@ -179,10 +184,9 @@ class ProxyType(type):
cls._idx = -1 cls._idx = -1
mcs = cls.__class__ mcs = cls.__class__
mcs.getInfo().Types.append(cls) mcs.getInfo().Types.append(cls)
mcs.register(cls) cls.register()
@classmethod def register(cls):
def register(mcs,cls):
''' '''
Register a class to this meta class Register a class to this meta class
@ -193,6 +197,7 @@ class ProxyType(type):
''' '''
if cls._id < 0: if cls._id < 0:
return return
mcs = cls.__class__
info = mcs.getInfo() info = mcs.getInfo()
if cls._id in info.TypeMap: if cls._id in info.TypeMap:
raise RuntimeError('Duplicate {} type id {}'.format( raise RuntimeError('Duplicate {} type id {}'.format(

View File

@ -22,15 +22,10 @@ class Solver(object):
self.system = System.getSystem(assembly) self.system = System.getSystem(assembly)
cstrs = assembly.Proxy.getConstraints() cstrs = assembly.Proxy.getConstraints()
if not cstrs: if not cstrs:
self.system.log('no constraint found in assembly ' logger.warn('no constraint found in assembly '
'{}'.format(objName(assembly))) '{}'.format(objName(assembly)))
return 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._fixedGroup = 2
self.group = 1 # the solving group self.group = 1 # the solving group
self._partMap = {} self._partMap = {}
@ -41,16 +36,24 @@ class Solver(object):
self.system.GroupHandle = self._fixedGroup self.system.GroupHandle = self._fixedGroup
firstPart = None
firstPartName = None
for cstr in cstrs: for cstr in cstrs:
if Constraint.isLocked(cstr): if Constraint.isLocked(cstr):
Constraint.prepare(cstr,self) Constraint.prepare(cstr,self)
elif not Constraint.isDisabled(cstr) and \ elif not Constraint.isDisabled(cstr) and \
System.isConstraintSupported( System.isConstraintSupported(
assembly,Constraint.getTypeName(cstr)): 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) self._cstrs.append(cstr)
if not self._fixedParts: if not self._fixedParts:
self.system.log('lock first part {}'.format(objName(parts[0]))) self.system.log('lock first part {}'.format(firstPartName))
self._fixedParts.add(parts[0]) self._fixedParts.add(firstPart)
for cstr in self._cstrs: for cstr in self._cstrs:
self.system.log('preparing {}'.format(cstrName(cstr))) self.system.log('preparing {}'.format(cstrName(cstr)))
@ -162,9 +165,17 @@ class Solver(object):
self._partMap[info.Part] = partInfo self._partMap[info.Part] = partInfo
return 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: 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)): elif not isinstance(objs,(list,tuple)):
objs = [objs] objs = [objs]

View File

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

View File

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

View File

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