Add SketchPlane constraint for easy 2D sketch
The SketchPlane expects the first element to be a planar face/edge to define a work plane for any following draft wire/circle/arc elements. The 2D constrainted elements can either be inside the defining SketchPlane constraint, or other constrains following the SketchPlane in the constraint group. Add a new SketchPlane to define a new work plane for any following draft elements. Add an empty SketchPlane to undefine the work plane, effectively making any following draft elements free in 3D space.
This commit is contained in:
parent
c56a9d85d7
commit
cc412bdc22
|
@ -0,0 +1,136 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<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"
|
||||
version="1.1"
|
||||
id="svg2816"
|
||||
height="64px"
|
||||
width="64px"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="Assembly_ConstraintSketchPlane.svg">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1375"
|
||||
inkscape:window-height="876"
|
||||
id="namedview8"
|
||||
showgrid="true"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:zoom="7.375"
|
||||
inkscape:cx="27.11081"
|
||||
inkscape:cy="40.388169"
|
||||
inkscape:window-x="65"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2816"
|
||||
inkscape:snap-bbox="true">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid2985"
|
||||
empspacing="2"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs2818">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient3783">
|
||||
<stop
|
||||
style="stop-color:#a40000;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3785" />
|
||||
<stop
|
||||
style="stop-color:#ef2929;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop3787" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient3783"
|
||||
id="linearGradient3789"
|
||||
x1="35.55761"
|
||||
y1="28.313709"
|
||||
x2="29.113543"
|
||||
y2="5.5904126"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata2821">
|
||||
<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></dc:title>
|
||||
<dc:title>Path-Heights</dc:title>
|
||||
<dc:date>2016-05-15</dc:date>
|
||||
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier>FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Heights.svg</dc:identifier>
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD LGPL2+</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title>[agryson] Alexander Gryson</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="g4242"
|
||||
transform="translate(-0.38351502,2.1894493)"
|
||||
style="opacity:1">
|
||||
<g
|
||||
transform="matrix(0.97634224,0.21623093,-0.21623093,0.97634224,4.6115742,5.372201)"
|
||||
style="opacity:0.8"
|
||||
id="g3809">
|
||||
<path
|
||||
style="opacity:1;fill:url(#linearGradient3789);fill-opacity:1;fill-rule:evenodd;stroke:#280000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 27.000001,3 61,9 61,9 39,31 3,25 Z"
|
||||
id="path4225-1-7"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ef2929;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 27.610169,5.1440678 56.868167,10.287637 38.322034,28.855932 7.3728814,23.711864 Z"
|
||||
id="path4225-1-7-1"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.97650191,0.21550873,-0.21550873,0.97650191,4.6183399,5.2009574)"
|
||||
style="opacity:0.8;fill:none"
|
||||
id="g3809-1">
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#280000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 27.000001,3 61,9 61,9 39,31 3,25 Z"
|
||||
id="path4225-1-7-15"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccccc" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
31
assembly.py
31
assembly.py
|
@ -487,6 +487,8 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop):
|
|||
AsmElement.make(AsmElement.Selection(Element=vobj.Object,
|
||||
Group=owner, Subname=subname),undo=True)
|
||||
|
||||
ElementInfo = namedtuple('AsmElementInfo', ('Parent','SubnameRef','Part',
|
||||
'PartName','Placement','Object','Subname','Shape'))
|
||||
|
||||
def getElementInfo(parent, subname):
|
||||
'''Return a named tuple containing the part object element information
|
||||
|
@ -630,7 +632,7 @@ def getElementInfo(parent, subname):
|
|||
obj = part.getLinkedObject(False)
|
||||
partName = part.Name
|
||||
|
||||
return utils.ElementInfo(Parent = parent,
|
||||
return ElementInfo(Parent = parent,
|
||||
SubnameRef = subnameRef,
|
||||
Part = part,
|
||||
PartName = partName,
|
||||
|
@ -703,7 +705,7 @@ class AsmElementLink(AsmBase):
|
|||
return '{}.{}.{}'.format(prefix, assembly.getPartGroup().Name,
|
||||
element.getElementSubname())
|
||||
|
||||
def setLink(self,owner,subname):
|
||||
def setLink(self,owner,subname,checkOnly=False):
|
||||
# check if there is any sub assembly in the reference
|
||||
ret = Assembly.find(owner,subname)
|
||||
if not ret:
|
||||
|
@ -739,7 +741,8 @@ class AsmElementLink(AsmBase):
|
|||
linked[0]==owner and linked[1]==subname:
|
||||
raise RuntimeError('duplicate element link {} in constraint '
|
||||
'{}'.format(objName(sibling),objName(self.parent.Object)))
|
||||
self.Object.setLink(owner,subname)
|
||||
if not checkOnly:
|
||||
self.Object.setLink(owner,subname)
|
||||
|
||||
def getInfo(self,refresh=False):
|
||||
if not refresh:
|
||||
|
@ -802,7 +805,7 @@ class ViewProviderAsmElementLink(ViewProviderAsmOnTop):
|
|||
def canDropObjectEx(self,_obj,owner,subname):
|
||||
if logger.catchTrace('Cannot drop to AsmLink {}'.format(
|
||||
objName(self.ViewObject.Object)),
|
||||
self.ViewObject.Object.Proxy.prepareLink,
|
||||
self.ViewObject.Object.Proxy.setLink,
|
||||
owner, subname, True):
|
||||
return True
|
||||
return False
|
||||
|
@ -874,16 +877,16 @@ class AsmConstraint(AsmGroup):
|
|||
ret = getattr(self,'elements',None)
|
||||
if ret or Constraint.isDisabled(obj):
|
||||
return ret
|
||||
infos = []
|
||||
elementInfo = []
|
||||
elements = []
|
||||
for o in obj.Group:
|
||||
checkType(o,AsmElementLink)
|
||||
info = o.Proxy.getInfo()
|
||||
if not info:
|
||||
return
|
||||
infos.append(info)
|
||||
elementInfo.append(info)
|
||||
elements.append(o)
|
||||
Constraint.check(obj,infos,True)
|
||||
Constraint.check(obj,elementInfo,True)
|
||||
self.elements = elements
|
||||
return self.elements
|
||||
|
||||
|
@ -919,6 +922,7 @@ class AsmConstraint(AsmGroup):
|
|||
sel = sels[0]
|
||||
cstr = None
|
||||
elements = []
|
||||
elementInfo = []
|
||||
assembly = None
|
||||
selSubname = None
|
||||
for sub in subs:
|
||||
|
@ -976,13 +980,18 @@ class AsmConstraint(AsmGroup):
|
|||
|
||||
elements.append((found.Object,sub))
|
||||
|
||||
elementInfo.append(getElementInfo(
|
||||
assembly,found.Object.Name+'.'+sub))
|
||||
|
||||
if not Constraint.isDisabled(cstr):
|
||||
if cstr:
|
||||
typeid = Constraint.getTypeID(cstr)
|
||||
check = [o.Proxy.getInfo() for o in cstr.Group] + elements
|
||||
else:
|
||||
check = elements
|
||||
Constraint.check(typeid,check)
|
||||
check = []
|
||||
for o in cstr.Group:
|
||||
check.append(o.Proxy.getInfo())
|
||||
elementInfo = check + elementInfo
|
||||
|
||||
Constraint.check(typeid,elementInfo)
|
||||
|
||||
return AsmConstraint.Selection(SelObject=sel.Object,
|
||||
SelSubname=selSubname,
|
||||
|
|
166
constraint.py
166
constraint.py
|
@ -7,6 +7,21 @@ from .proxy import ProxyType, PropertyInfo, propGet, propGetValue
|
|||
import os
|
||||
_iconPath = os.path.join(utils.iconPath,'constraints')
|
||||
|
||||
def _d(solver,partInfo,subname,shape,retAll=False):
|
||||
'return a handle of any supported element of a draft object'
|
||||
if not solver:
|
||||
if utils.isDraftObject(partInfo):
|
||||
return
|
||||
raise RuntimeError('Expects only elements from a draft wire or '
|
||||
'draft circle/arc')
|
||||
if subname.startswith('Vertex'):
|
||||
return _p(solver,partInfo,subname,shape,retAll)
|
||||
elif subname.startswith('Edge'):
|
||||
return _l(solver,partInfo,subname,shape,retAll)
|
||||
else:
|
||||
raise RuntimeError('Invalid element {} of object {}'.format(subname,
|
||||
partInfo.PartName))
|
||||
|
||||
def _p(solver,partInfo,subname,shape,retAll=False):
|
||||
'return a handle of a transformed point derived from "shape"'
|
||||
if not solver:
|
||||
|
@ -18,6 +33,7 @@ def _p(solver,partInfo,subname,shape,retAll=False):
|
|||
subname,objName(partInfo)))
|
||||
return
|
||||
|
||||
part = partInfo.Part
|
||||
key = subname+'.p'
|
||||
h = partInfo.EntityMap.get(key,None)
|
||||
system = solver.system
|
||||
|
@ -25,7 +41,7 @@ def _p(solver,partInfo,subname,shape,retAll=False):
|
|||
system.log('cache {}: {}'.format(key,h))
|
||||
return h if retAll else h[0]
|
||||
|
||||
if utils.isDraftWire(partInfo.Part):
|
||||
if utils.isDraftWire(part):
|
||||
v = utils.getElementPos(shape)
|
||||
nameTag = partInfo.PartName + '.' + key
|
||||
v = partInfo.Placement.multVec(v)
|
||||
|
@ -38,8 +54,15 @@ def _p(solver,partInfo,subname,shape,retAll=False):
|
|||
h = [e, params]
|
||||
system.log('{}: add draft point {},{}'.format(key,h,v))
|
||||
|
||||
elif utils.isDraftCircle(partInfo.Part):
|
||||
shape = utils.getElementShape((partInfo.Part,'Edge1'),Part.Edge)
|
||||
if system.sketchPlane and not solver.isFixedElement(part,subname):
|
||||
system.NameTag = nameTag + '.i'
|
||||
e2 = system.addPointInPlane(e,system.sketchPlane[0],
|
||||
group=partInfo.Group)
|
||||
system.log('{}: add draft point in plane {},{}'.format(
|
||||
partInfo.PartName,e2,system.sketchPlane[0]))
|
||||
|
||||
elif utils.isDraftCircle(part):
|
||||
shape = utils.getElementShape((part,'Edge1'),Part.Edge)
|
||||
if subname == 'Vertex1':
|
||||
e = _c(solver,partInfo,'Edge1',shape,retAll=True)
|
||||
h = [e[2]]
|
||||
|
@ -48,7 +71,7 @@ def _p(solver,partInfo,subname,shape,retAll=False):
|
|||
h = [e[1]]
|
||||
else:
|
||||
raise RuntimeError('Invalid draft circle vertex {} of '
|
||||
'{}'.format(subname,objName(partInfo.Part)))
|
||||
'{}'.format(subname,partInfo.PartName))
|
||||
|
||||
system.log('{}: add circle point {},{}'.format(key,h,e))
|
||||
|
||||
|
@ -58,7 +81,7 @@ def _p(solver,partInfo,subname,shape,retAll=False):
|
|||
system.NameTag = nameTag
|
||||
e = system.addPoint3dV(*v)
|
||||
system.NameTag = nameTag + 't'
|
||||
h = system.addTransform(e[0],*partInfo.Params,group=partInfo.Group)
|
||||
h = system.addTransform(e,*partInfo.Params,group=partInfo.Group)
|
||||
h = [h,e]
|
||||
system.log('{}: {},{}'.format(key,h,partInfo.Group))
|
||||
|
||||
|
@ -70,7 +93,7 @@ def _n(solver,partInfo,subname,shape,retAll=False):
|
|||
if not solver:
|
||||
if not utils.isPlanar(shape):
|
||||
return 'an edge or face with a surface normal'
|
||||
if utils.isDraftWire(partInfo.Part):
|
||||
if utils.isDraftWire(partInfo):
|
||||
logger.warn('Use draft wire {} for normal. Draft wire placement'
|
||||
' is not transformable'.format(partInfo.PartName))
|
||||
return
|
||||
|
@ -106,17 +129,19 @@ def _l(solver,partInfo,subname,shape,retAll=False):
|
|||
if not solver:
|
||||
if not utils.isLinearEdge(shape):
|
||||
return 'a linear edge'
|
||||
if not utils.isDraftWire(partInfo.Part):
|
||||
if not utils.isDraftWire(partInfo):
|
||||
return
|
||||
part = partInfo
|
||||
vname1,vname2 = utils.edge2VertexIndex(subname)
|
||||
vname1,vname2 = utils.edge2VertexIndex(partInfo,subname)
|
||||
if not vname1:
|
||||
raise RuntimeError('Invalid draft subname {} or {}'.format(
|
||||
subname,objName(part)))
|
||||
subname,objName(partInfo)))
|
||||
v = shape.Edges[0].Vertexes
|
||||
return _p(solver,partInfo,vname1,v[0]) or \
|
||||
_p(solver,partInfo,vname2,v[1])
|
||||
ret = _p(solver,partInfo,vname1,v[0])
|
||||
if ret:
|
||||
return ret
|
||||
return _p(solver,partInfo,vname2,v[1])
|
||||
|
||||
part = partInfo.Part
|
||||
key = subname+'.l'
|
||||
h = partInfo.EntityMap.get(key,None)
|
||||
system = solver.system
|
||||
|
@ -125,11 +150,11 @@ def _l(solver,partInfo,subname,shape,retAll=False):
|
|||
else:
|
||||
nameTag = partInfo.PartName + '.' + key
|
||||
v = shape.Edges[0].Vertexes
|
||||
if utils.isDraftWire(partInfo.Part):
|
||||
vname1,vname2 = utils.edge2VertexIndex(subname)
|
||||
if utils.isDraftWire(part):
|
||||
vname1,vname2 = utils.edge2VertexIndex(part,subname)
|
||||
if not vname1:
|
||||
raise RuntimeError('Invalid draft subname {} or {}'.format(
|
||||
subname,objName(partInfo.Part)))
|
||||
subname,partInfo.PartName))
|
||||
tp1 = _p(solver,partInfo,vname1,v[0])
|
||||
tp2 = _p(solver,partInfo,vname2,v[1])
|
||||
else:
|
||||
|
@ -155,7 +180,7 @@ def _dl(solver,partInfo,subname,shape,retAll=False):
|
|||
if not solver:
|
||||
if utils.isDraftWire(partInfo):
|
||||
return
|
||||
raise RuntimeError('Requires a non-closed-or-subdivided draft wire')
|
||||
raise RuntimeError('Requires a non-subdivided draft wire')
|
||||
return _l(solver,partInfo,subname,shape,retAll)
|
||||
|
||||
def _ln(solver,partInfo,subname,shape,retAll=False):
|
||||
|
@ -219,6 +244,15 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
|
|||
if utils.isDraftCircle(partInfo.Part):
|
||||
part = partInfo.Part
|
||||
w,p,n = partInfo.Workplane
|
||||
|
||||
if system.sketchPlane and not solver.isFixedElement(part,subname):
|
||||
system.NameTag = nameTag + '.o'
|
||||
e1 = system.addSameOrientation(n,system.sketchPlane[2],group=g)
|
||||
system.NameTag = nameTag + '.i'
|
||||
e2 = system.addPointInPlane(p,system.sketchPlane[0],group=g)
|
||||
system.log('{}: fix draft circle in plane {},{}'.format(
|
||||
partInfo.PartName,e1,e2))
|
||||
|
||||
if part.FirstAngle == part.LastAngle:
|
||||
if requireArc:
|
||||
raise RuntimeError('expecting an arc from {}'.format(
|
||||
|
@ -347,7 +381,11 @@ class Constraint(ProxyType):
|
|||
def register(mcs,cls):
|
||||
super(Constraint,mcs).register(cls)
|
||||
if cls._id>=0 and cls._iconName is not Base._iconName:
|
||||
gui.AsmCmdManager.register(ConstraintCommand(cls))
|
||||
try:
|
||||
gui.AsmCmdManager.register(ConstraintCommand(cls))
|
||||
except Exception:
|
||||
logger.error('failed to register {}'.format(cls.getName()))
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def attach(mcs,obj,checkType=True):
|
||||
|
@ -508,11 +546,8 @@ class Base(object):
|
|||
def check(cls,group,checkCount=False):
|
||||
entities = cls.getEntityDef(group,checkCount)
|
||||
for i,e in enumerate(entities):
|
||||
o = group[i]
|
||||
if isinstance(o,utils.ElementInfo):
|
||||
msg = e(None,o.Part,o.Subname,o.Shape)
|
||||
else:
|
||||
msg = e(None,None,None,o)
|
||||
info = group[i]
|
||||
msg = e(None,info.Part,info.Subname,info.Shape)
|
||||
if not msg:
|
||||
continue
|
||||
if i == len(cls._entityDef):
|
||||
|
@ -546,6 +581,7 @@ class Base(object):
|
|||
params = cls.getPropertyValues(obj) + cls.getEntities(obj,solver)
|
||||
ret = func(*params,group=solver.group)
|
||||
solver.system.log('{}: {}'.format(cstrName(obj),ret))
|
||||
return ret
|
||||
else:
|
||||
logger.warn('{} no constraint func'.format(cstrName(obj)))
|
||||
|
||||
|
@ -583,7 +619,7 @@ class Locked(Base):
|
|||
info = e.Proxy.getInfo()
|
||||
if not utils.isVertex(info.Shape) and \
|
||||
not utils.isLinearEdge(info.Shape) and \
|
||||
not utils.isDraftCircle(info):
|
||||
not utils.isDraftCircle(info.Part):
|
||||
ret.append(info)
|
||||
return ret
|
||||
|
||||
|
@ -594,7 +630,7 @@ class Locked(Base):
|
|||
ret = []
|
||||
for e in obj.Proxy.getElements():
|
||||
info = e.Proxy.getInfo()
|
||||
if utils.isDraftObject(info):
|
||||
if utils.isDraftObject(info.Part):
|
||||
continue
|
||||
shape = None
|
||||
if utils.isVertex(info.Shape) or \
|
||||
|
@ -613,26 +649,31 @@ class Locked(Base):
|
|||
system = solver.system
|
||||
|
||||
isVertex = utils.isVertex(info.Shape)
|
||||
if not isVertex and utils.isDraftCircle(info):
|
||||
solver.getPartInfo(info,True,solver.group)
|
||||
if solver.isFixedElement(info.Part,info.Subname):
|
||||
return ret
|
||||
|
||||
if not isVertex and utils.isDraftCircle(info.Part):
|
||||
if solver.sketchPlane:
|
||||
_c(solver,solver.getPartInfo(info),info.Subname,info.Shape)
|
||||
else:
|
||||
solver.getPartInfo(info,True,solver.group)
|
||||
solver.addFixedElement(info.Part,info.Subname)
|
||||
return ret
|
||||
|
||||
if not isVertex and not utils.isLinearEdge(info.Shape):
|
||||
return ret
|
||||
|
||||
if solver.isFixedPart(info):
|
||||
logger.warn('redundant locking element "{}" in constraint '
|
||||
'{}'.format(info.Subname,info.PartName))
|
||||
return ret
|
||||
|
||||
partInfo = solver.getPartInfo(info)
|
||||
|
||||
fixPoint = False
|
||||
if isVertex:
|
||||
names = [info.Subname]
|
||||
elif utils.isDraftObject(info):
|
||||
if utils.isDraftCircle(info.Part):
|
||||
_c(solver,partInfo,'Edge1',info.Shape)
|
||||
solver.addFixedElement(info.Part,'Edge1')
|
||||
elif utils.isDraftWire(info.Part):
|
||||
fixPoint = True
|
||||
names = utils.edge2VertexIndex(info.Subname)
|
||||
names = utils.edge2VertexIndex(info.Part,info.Subname)
|
||||
else:
|
||||
names = [info.Subname+'.fp1', info.Subname+'.fp2']
|
||||
|
||||
|
@ -647,13 +688,18 @@ class Locked(Base):
|
|||
|
||||
# Get the entity for the point expressed in variable parameters
|
||||
e2 = _p(solver,partInfo,names[i],v)
|
||||
solver.addFixedElement(info.Part,names[i])
|
||||
|
||||
if i==0 or fixPoint:
|
||||
# We are fixing a vertex, or a linear edge. Either way, we
|
||||
# shall add a point coincidence constraint here.
|
||||
e0 = e1
|
||||
system.NameTag = nameTag + surfix
|
||||
e = system.addPointsCoincident(e1,e2,group=solver.group)
|
||||
if system.sketchPlane and utils.isDraftObject(info.Part):
|
||||
w = system.sketchPlane[0]
|
||||
else:
|
||||
w = 0
|
||||
e = system.addPointsCoincident(e1,e2,w,group=solver.group)
|
||||
system.log('{}: fix point {},{},{}'.format(
|
||||
info.PartName,e,e1,e2))
|
||||
else:
|
||||
|
@ -683,7 +729,7 @@ class Locked(Base):
|
|||
|
||||
@classmethod
|
||||
def check(cls,group,_checkCount=False):
|
||||
if not all([utils.isElement(o) for o in group]):
|
||||
if not all([utils.isElement(info.Shape) for info in group]):
|
||||
raise RuntimeError('Constraint "{}" requires all children to be '
|
||||
'of element (Vertex, Edge or Face)'.format(cls.getName()))
|
||||
|
||||
|
@ -697,11 +743,8 @@ class BaseMulti(Base):
|
|||
if len(group)<2:
|
||||
raise RuntimeError('Constraint "{}" requires at least two '
|
||||
'elements'.format(cls.getName()))
|
||||
for o in group:
|
||||
if isinstance(o,utils.ElementInfo):
|
||||
msg = cls._entityDef[0](None,o.Part,o.Subname,o.Shape)
|
||||
else:
|
||||
msg = cls._entityDef[0](None,None,None,o)
|
||||
for info in group:
|
||||
msg = cls._entityDef[0](None,info.Part,info.Subname,info.Shape)
|
||||
if msg:
|
||||
raise RuntimeError('Constraint "{}" requires all the element '
|
||||
'to be of {}'.format(cls.getName()))
|
||||
|
@ -725,7 +768,7 @@ class BaseMulti(Base):
|
|||
cstrName(obj),info.PartName))
|
||||
continue
|
||||
parts.add(info.Part)
|
||||
if solver.isFixedPart(info):
|
||||
if solver.isFixedPart(info.Part):
|
||||
if ref:
|
||||
logger.warn('{} skip more than one fixed part {}'.format(
|
||||
cstrName(obj),info.PartName))
|
||||
|
@ -777,7 +820,7 @@ class BaseCascade(BaseMulti):
|
|||
partInfo = solver.getPartInfo(info)
|
||||
e2 = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape)
|
||||
prev = info
|
||||
if solver.isFixedPart(info):
|
||||
if solver.isFixedPart(info.Part):
|
||||
params = props + [e1,e2]
|
||||
else:
|
||||
params = props + [e2,e1]
|
||||
|
@ -1021,6 +1064,23 @@ class BaseSketch(Base):
|
|||
_toolbarName = 'Assembly3 Sketch Constraints'
|
||||
|
||||
|
||||
class SketchPlane(BaseSketch):
|
||||
_id = 38
|
||||
_iconName = 'Assembly_ConstraintSketchPlane.svg'
|
||||
_tooltip='Add a "{0}" to define the work plane of any draft element\n'\
|
||||
'inside or following this constraint. Add an empty "{0}" to\n'\
|
||||
'undefine the previous work plane'
|
||||
|
||||
@classmethod
|
||||
def getEntityDef(cls,group,checkCount,obj=None):
|
||||
# if there is any child element in this constraint, we expect the first
|
||||
# one to be a planar face or edge to define the work plane. The rest of
|
||||
# entities must be from some draft wire or circle/arc.
|
||||
if not group:
|
||||
return
|
||||
return [_wa] + [_d]*(len(group)-1)
|
||||
|
||||
|
||||
class BaseDraftWire(BaseSketch):
|
||||
_id = -1
|
||||
|
||||
|
@ -1029,11 +1089,11 @@ class BaseDraftWire(BaseSketch):
|
|||
super(BaseDraftWire,cls).check(group,checkCount)
|
||||
if not checkCount:
|
||||
return
|
||||
for o in group:
|
||||
if utils.isDraftWire(o):
|
||||
for info in group:
|
||||
if utils.isDraftWire(info.Part):
|
||||
return
|
||||
raise RuntimeError('Constraint "{}" requires at least one linear edge '
|
||||
'from a non-closed-or-subdivided Draft.Wire'.format(
|
||||
'from a none-subdivided Draft.Wire'.format(
|
||||
cls.getName()))
|
||||
|
||||
class LineLength(BaseSketch):
|
||||
|
@ -1042,8 +1102,7 @@ class LineLength(BaseSketch):
|
|||
_workplane = True
|
||||
_props = ["Length"]
|
||||
_iconName = 'Assembly_ConstraintLineLength.svg'
|
||||
_tooltip='Add a "{}" constrain the length of a non-closed-or-subdivided '\
|
||||
'Draft.Wire'
|
||||
_tooltip='Add a "{}" constrain the length of a none-subdivided Draft.Wire'
|
||||
|
||||
@classmethod
|
||||
def prepare(cls,obj,solver):
|
||||
|
@ -1053,6 +1112,7 @@ class LineLength(BaseSketch):
|
|||
params = cls.getPropertyValues(obj) + [p1,p2]
|
||||
ret = func(*params,group=solver.group)
|
||||
solver.system.log('{}: {}'.format(cstrName(obj),ret))
|
||||
return ret
|
||||
else:
|
||||
logger.warn('{} no constraint func'.format(cstrName(obj)))
|
||||
|
||||
|
@ -1103,14 +1163,14 @@ class EqualLineArcLength(BaseSketch):
|
|||
super(EqualLineArcLength,cls).check(group,checkCount)
|
||||
if not checkCount:
|
||||
return
|
||||
for i,o in enumerate(group):
|
||||
for i,info in enumerate(group):
|
||||
if i:
|
||||
if utils.isDraftCircle(o):
|
||||
if utils.isDraftCircle(info.Part):
|
||||
return
|
||||
elif utils.isDraftWire(o):
|
||||
elif utils.isDraftWire(info.Part):
|
||||
return
|
||||
raise RuntimeError('Constraint "{}" requires at least one '
|
||||
'non-closed-or-subdivided Draft.Wire or one Draft.Circle'.format(
|
||||
'non-subdivided Draft.Wire or one Draft.Circle'.format(
|
||||
cls.getName()))
|
||||
|
||||
|
||||
|
@ -1141,8 +1201,8 @@ class EqualRadius(BaseSketch):
|
|||
super(EqualRadius,cls).check(group,checkCount)
|
||||
if not checkCount:
|
||||
return
|
||||
for o in group:
|
||||
if utils.isDraftCircle(o):
|
||||
for info in group:
|
||||
if utils.isDraftCircle(info.Part):
|
||||
return
|
||||
raise RuntimeError('Constraint "{}" requires at least one '
|
||||
'Draft.Circle'.format(cls.getName()))
|
||||
|
|
4
mover.py
4
mover.py
|
@ -71,7 +71,7 @@ class AsmMovingPart(object):
|
|||
def update(self):
|
||||
info = getElementInfo(self.info.Parent,self.info.SubnameRef)
|
||||
self.info = info
|
||||
if utils.isDraftObject(info):
|
||||
if utils.isDraftObject(info.Part):
|
||||
pos = utils.getElementPos(info.Shape)
|
||||
rot = utils.getElementRotation(info.Shape)
|
||||
pla = info.Placement.multiply(FreeCAD.Placement(pos,rot))
|
||||
|
@ -105,7 +105,7 @@ class AsmMovingPart(object):
|
|||
return
|
||||
change = [idx]
|
||||
else:
|
||||
change = utils.draftWireEdge2PointIndex(part,info.Subname)
|
||||
change = utils.edge2VertexIndex(part,info.Subname,True)
|
||||
if change[0] is None or change[1] is None:
|
||||
logger.error('Invalid draft wire edge {} {}'.format(
|
||||
info.Subname, info.PartName))
|
||||
|
|
5
proxy.py
5
proxy.py
|
@ -207,8 +207,9 @@ class ProxyType(type):
|
|||
return
|
||||
info = mcs.getInfo()
|
||||
if cls._id in info.TypeMap:
|
||||
raise RuntimeError('Duplicate {} type id {}'.format(
|
||||
mcs.getMetaName(),cls._id))
|
||||
raise RuntimeError('Duplicate {} type id {}, {} conflict with '
|
||||
'{}'.format(mcs.getMetaName(),cls._id,cls.getName(),
|
||||
info.TypeMap[cls._id].getName()))
|
||||
info.TypeMap[cls._id] = cls
|
||||
info.TypeNameMap[cls.getName()] = cls
|
||||
info.TypeNames.append(cls.getName())
|
||||
|
|
11
solver.py
11
solver.py
|
@ -32,6 +32,7 @@ class Solver(object):
|
|||
self.group = 1 # the solving group
|
||||
self._partMap = {}
|
||||
self._cstrMap = {}
|
||||
self._fixedElements = set()
|
||||
|
||||
self.system.GroupHandle = self._fixedGroup
|
||||
|
||||
|
@ -176,8 +177,14 @@ class Solver(object):
|
|||
if recompute and touched:
|
||||
assembly.recompute(True)
|
||||
|
||||
def isFixedPart(self,info):
|
||||
return info.Part in self._fixedParts
|
||||
def isFixedPart(self,part):
|
||||
return part in self._fixedParts
|
||||
|
||||
def isFixedElement(self,part,subname):
|
||||
return self.isFixedPart(part) or (part,subname) in self._fixedElements
|
||||
|
||||
def addFixedElement(self,part,subname):
|
||||
self._fixedElements.add((part,subname))
|
||||
|
||||
def getPartInfo(self,info,fixed=False,group=0):
|
||||
partInfo = self._partMap.get(info.Part,None)
|
||||
|
|
|
@ -110,6 +110,12 @@ class SystemBase(object):
|
|||
class SystemExtension(object):
|
||||
def __init__(self):
|
||||
self.NameTag = ''
|
||||
self.sketchPlane = None
|
||||
|
||||
def addSketchPlane(self,*args,**kargs):
|
||||
_ = kargs
|
||||
self.sketchPlane = args[0] if args else None
|
||||
return self.sketchPlane
|
||||
|
||||
def addPlaneCoincident(self,d,lockAngle,angle,e1,e2,group=0):
|
||||
if not group:
|
||||
|
|
49
utils.py
49
utils.py
|
@ -126,30 +126,24 @@ def getElementShape(obj,tp):
|
|||
if len(f)==1:
|
||||
return f[0]
|
||||
|
||||
ElementInfo = namedtuple('AsmElementInfo', ('Parent','SubnameRef','Part',
|
||||
'PartName','Placement','Object','Subname','Shape'))
|
||||
|
||||
def isDraftWire(obj):
|
||||
if isinstance(obj,ElementInfo):
|
||||
obj = obj.Part
|
||||
proxy = getattr(obj,'Proxy',None)
|
||||
return isinstance(proxy,Draft._Wire) and \
|
||||
not obj.Closed and \
|
||||
not obj.Subdivisions
|
||||
if isinstance(proxy,Draft._Wire) and not obj.Subdivisions:
|
||||
return obj
|
||||
|
||||
def isDraftCircle(obj):
|
||||
if isinstance(obj,ElementInfo):
|
||||
obj = obj.Part
|
||||
proxy = getattr(obj,'Proxy',None)
|
||||
return isinstance(proxy,Draft._Circle)
|
||||
if isinstance(proxy,Draft._Circle):
|
||||
return obj
|
||||
|
||||
def isDraftObject(obj):
|
||||
return isDraftWire(obj) or isDraftCircle(obj)
|
||||
o = isDraftWire(obj)
|
||||
if o:
|
||||
return o
|
||||
return isDraftCircle(obj)
|
||||
|
||||
def isElement(obj):
|
||||
if isinstance(obj,ElementInfo):
|
||||
shape = obj.Shape
|
||||
elif not isinstance(obj,(tuple,list)):
|
||||
if not isinstance(obj,(tuple,list)):
|
||||
shape = obj
|
||||
else:
|
||||
sobj,_,shape = obj[0].getSubObject(obj[1],2)
|
||||
|
@ -490,7 +484,7 @@ def isSamePlacement(pla1,pla2):
|
|||
isSameValue(pla1.Rotation.Q,pla2.Rotation.Q)
|
||||
|
||||
def getElementIndex(name,check=None):
|
||||
'Return element index, 0 if invalid'
|
||||
'Return element index (starting with 1), 0 if invalid'
|
||||
for i,c in enumerate(reversed(name)):
|
||||
if not c.isdigit():
|
||||
if not i:
|
||||
|
@ -503,24 +497,27 @@ def getElementIndex(name,check=None):
|
|||
|
||||
def draftWireVertex2PointIndex(obj,name):
|
||||
'Convert vertex index to draft wire point index, None if invalid'
|
||||
obj = isDraftWire(obj)
|
||||
if not obj:
|
||||
return
|
||||
idx = getElementIndex(name,'Vertex')
|
||||
# We don't support subdivision yet (checked in isDraftWire())
|
||||
if idx <= 0 or not isDraftWire(obj):
|
||||
if idx <= 0:
|
||||
return
|
||||
idx -= 1
|
||||
if idx < len(obj.Points):
|
||||
return idx
|
||||
|
||||
def draftWireEdge2PointIndex(obj,name):
|
||||
vname1,vname2 = edge2VertexIndex(name)
|
||||
if not vname1:
|
||||
return None,None
|
||||
return (draftWireVertex2PointIndex(obj,vname1),
|
||||
draftWireVertex2PointIndex(obj,vname2))
|
||||
|
||||
def edge2VertexIndex(name):
|
||||
def edge2VertexIndex(obj,name,retInteger=False):
|
||||
'deduct the vertex index from the edge index'
|
||||
idx = getElementIndex(name,'Edge')
|
||||
if not idx:
|
||||
return None,None
|
||||
return 'Vertex{}'.format(idx),'Vertex{}'.format(idx+1)
|
||||
dwire = isDraftWire(obj)
|
||||
if dwire and dwire.Closed and idx==len(dwire.Points):
|
||||
idx2 = 1
|
||||
else:
|
||||
idx2 = idx+1
|
||||
if retInteger:
|
||||
return idx-1,idx2-1
|
||||
return 'Vertex{}'.format(idx),'Vertex{}'.format(idx2)
|
||||
|
|
Loading…
Reference in New Issue
Block a user