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:
Zheng, Lei 2018-01-22 19:14:58 +08:00
parent c56a9d85d7
commit cc412bdc22
8 changed files with 312 additions and 96 deletions

View File

@ -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

View File

@ -487,6 +487,8 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop):
AsmElement.make(AsmElement.Selection(Element=vobj.Object, AsmElement.make(AsmElement.Selection(Element=vobj.Object,
Group=owner, Subname=subname),undo=True) Group=owner, Subname=subname),undo=True)
ElementInfo = namedtuple('AsmElementInfo', ('Parent','SubnameRef','Part',
'PartName','Placement','Object','Subname','Shape'))
def getElementInfo(parent, subname): def getElementInfo(parent, subname):
'''Return a named tuple containing the part object element information '''Return a named tuple containing the part object element information
@ -630,7 +632,7 @@ def getElementInfo(parent, subname):
obj = part.getLinkedObject(False) obj = part.getLinkedObject(False)
partName = part.Name partName = part.Name
return utils.ElementInfo(Parent = parent, return ElementInfo(Parent = parent,
SubnameRef = subnameRef, SubnameRef = subnameRef,
Part = part, Part = part,
PartName = partName, PartName = partName,
@ -703,7 +705,7 @@ class AsmElementLink(AsmBase):
return '{}.{}.{}'.format(prefix, assembly.getPartGroup().Name, return '{}.{}.{}'.format(prefix, assembly.getPartGroup().Name,
element.getElementSubname()) element.getElementSubname())
def setLink(self,owner,subname): def setLink(self,owner,subname,checkOnly=False):
# check if there is any sub assembly in the reference # check if there is any sub assembly in the reference
ret = Assembly.find(owner,subname) ret = Assembly.find(owner,subname)
if not ret: if not ret:
@ -739,6 +741,7 @@ class AsmElementLink(AsmBase):
linked[0]==owner and linked[1]==subname: linked[0]==owner and linked[1]==subname:
raise RuntimeError('duplicate element link {} in constraint ' raise RuntimeError('duplicate element link {} in constraint '
'{}'.format(objName(sibling),objName(self.parent.Object))) '{}'.format(objName(sibling),objName(self.parent.Object)))
if not checkOnly:
self.Object.setLink(owner,subname) self.Object.setLink(owner,subname)
def getInfo(self,refresh=False): def getInfo(self,refresh=False):
@ -802,7 +805,7 @@ class ViewProviderAsmElementLink(ViewProviderAsmOnTop):
def canDropObjectEx(self,_obj,owner,subname): def canDropObjectEx(self,_obj,owner,subname):
if logger.catchTrace('Cannot drop to AsmLink {}'.format( if logger.catchTrace('Cannot drop to AsmLink {}'.format(
objName(self.ViewObject.Object)), objName(self.ViewObject.Object)),
self.ViewObject.Object.Proxy.prepareLink, self.ViewObject.Object.Proxy.setLink,
owner, subname, True): owner, subname, True):
return True return True
return False return False
@ -874,16 +877,16 @@ class AsmConstraint(AsmGroup):
ret = getattr(self,'elements',None) ret = getattr(self,'elements',None)
if ret or Constraint.isDisabled(obj): if ret or Constraint.isDisabled(obj):
return ret return ret
infos = [] elementInfo = []
elements = [] elements = []
for o in obj.Group: for o in obj.Group:
checkType(o,AsmElementLink) checkType(o,AsmElementLink)
info = o.Proxy.getInfo() info = o.Proxy.getInfo()
if not info: if not info:
return return
infos.append(info) elementInfo.append(info)
elements.append(o) elements.append(o)
Constraint.check(obj,infos,True) Constraint.check(obj,elementInfo,True)
self.elements = elements self.elements = elements
return self.elements return self.elements
@ -919,6 +922,7 @@ class AsmConstraint(AsmGroup):
sel = sels[0] sel = sels[0]
cstr = None cstr = None
elements = [] elements = []
elementInfo = []
assembly = None assembly = None
selSubname = None selSubname = None
for sub in subs: for sub in subs:
@ -976,13 +980,18 @@ class AsmConstraint(AsmGroup):
elements.append((found.Object,sub)) elements.append((found.Object,sub))
elementInfo.append(getElementInfo(
assembly,found.Object.Name+'.'+sub))
if not Constraint.isDisabled(cstr): if not Constraint.isDisabled(cstr):
if cstr: if cstr:
typeid = Constraint.getTypeID(cstr) typeid = Constraint.getTypeID(cstr)
check = [o.Proxy.getInfo() for o in cstr.Group] + elements check = []
else: for o in cstr.Group:
check = elements check.append(o.Proxy.getInfo())
Constraint.check(typeid,check) elementInfo = check + elementInfo
Constraint.check(typeid,elementInfo)
return AsmConstraint.Selection(SelObject=sel.Object, return AsmConstraint.Selection(SelObject=sel.Object,
SelSubname=selSubname, SelSubname=selSubname,

View File

@ -7,6 +7,21 @@ from .proxy import ProxyType, PropertyInfo, propGet, propGetValue
import os import os
_iconPath = os.path.join(utils.iconPath,'constraints') _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): def _p(solver,partInfo,subname,shape,retAll=False):
'return a handle of a transformed point derived from "shape"' 'return a handle of a transformed point derived from "shape"'
if not solver: if not solver:
@ -18,6 +33,7 @@ def _p(solver,partInfo,subname,shape,retAll=False):
subname,objName(partInfo))) subname,objName(partInfo)))
return return
part = partInfo.Part
key = subname+'.p' key = subname+'.p'
h = partInfo.EntityMap.get(key,None) h = partInfo.EntityMap.get(key,None)
system = solver.system system = solver.system
@ -25,7 +41,7 @@ def _p(solver,partInfo,subname,shape,retAll=False):
system.log('cache {}: {}'.format(key,h)) system.log('cache {}: {}'.format(key,h))
return h if retAll else h[0] return h if retAll else h[0]
if utils.isDraftWire(partInfo.Part): if utils.isDraftWire(part):
v = utils.getElementPos(shape) v = utils.getElementPos(shape)
nameTag = partInfo.PartName + '.' + key nameTag = partInfo.PartName + '.' + key
v = partInfo.Placement.multVec(v) v = partInfo.Placement.multVec(v)
@ -38,8 +54,15 @@ def _p(solver,partInfo,subname,shape,retAll=False):
h = [e, params] h = [e, params]
system.log('{}: add draft point {},{}'.format(key,h,v)) system.log('{}: add draft point {},{}'.format(key,h,v))
elif utils.isDraftCircle(partInfo.Part): if system.sketchPlane and not solver.isFixedElement(part,subname):
shape = utils.getElementShape((partInfo.Part,'Edge1'),Part.Edge) 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': if subname == 'Vertex1':
e = _c(solver,partInfo,'Edge1',shape,retAll=True) e = _c(solver,partInfo,'Edge1',shape,retAll=True)
h = [e[2]] h = [e[2]]
@ -48,7 +71,7 @@ def _p(solver,partInfo,subname,shape,retAll=False):
h = [e[1]] h = [e[1]]
else: else:
raise RuntimeError('Invalid draft circle vertex {} of ' 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)) system.log('{}: add circle point {},{}'.format(key,h,e))
@ -58,7 +81,7 @@ def _p(solver,partInfo,subname,shape,retAll=False):
system.NameTag = nameTag system.NameTag = nameTag
e = system.addPoint3dV(*v) e = system.addPoint3dV(*v)
system.NameTag = nameTag + 't' 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] h = [h,e]
system.log('{}: {},{}'.format(key,h,partInfo.Group)) system.log('{}: {},{}'.format(key,h,partInfo.Group))
@ -70,7 +93,7 @@ def _n(solver,partInfo,subname,shape,retAll=False):
if not solver: if not solver:
if not utils.isPlanar(shape): if not utils.isPlanar(shape):
return 'an edge or face with a surface normal' 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' logger.warn('Use draft wire {} for normal. Draft wire placement'
' is not transformable'.format(partInfo.PartName)) ' is not transformable'.format(partInfo.PartName))
return return
@ -106,17 +129,19 @@ def _l(solver,partInfo,subname,shape,retAll=False):
if not solver: if not solver:
if not utils.isLinearEdge(shape): if not utils.isLinearEdge(shape):
return 'a linear edge' return 'a linear edge'
if not utils.isDraftWire(partInfo.Part): if not utils.isDraftWire(partInfo):
return return
part = partInfo vname1,vname2 = utils.edge2VertexIndex(partInfo,subname)
vname1,vname2 = utils.edge2VertexIndex(subname)
if not vname1: if not vname1:
raise RuntimeError('Invalid draft subname {} or {}'.format( raise RuntimeError('Invalid draft subname {} or {}'.format(
subname,objName(part))) subname,objName(partInfo)))
v = shape.Edges[0].Vertexes v = shape.Edges[0].Vertexes
return _p(solver,partInfo,vname1,v[0]) or \ ret = _p(solver,partInfo,vname1,v[0])
_p(solver,partInfo,vname2,v[1]) if ret:
return ret
return _p(solver,partInfo,vname2,v[1])
part = partInfo.Part
key = subname+'.l' key = subname+'.l'
h = partInfo.EntityMap.get(key,None) h = partInfo.EntityMap.get(key,None)
system = solver.system system = solver.system
@ -125,11 +150,11 @@ def _l(solver,partInfo,subname,shape,retAll=False):
else: else:
nameTag = partInfo.PartName + '.' + key nameTag = partInfo.PartName + '.' + key
v = shape.Edges[0].Vertexes v = shape.Edges[0].Vertexes
if utils.isDraftWire(partInfo.Part): if utils.isDraftWire(part):
vname1,vname2 = utils.edge2VertexIndex(subname) vname1,vname2 = utils.edge2VertexIndex(part,subname)
if not vname1: if not vname1:
raise RuntimeError('Invalid draft subname {} or {}'.format( raise RuntimeError('Invalid draft subname {} or {}'.format(
subname,objName(partInfo.Part))) subname,partInfo.PartName))
tp1 = _p(solver,partInfo,vname1,v[0]) tp1 = _p(solver,partInfo,vname1,v[0])
tp2 = _p(solver,partInfo,vname2,v[1]) tp2 = _p(solver,partInfo,vname2,v[1])
else: else:
@ -155,7 +180,7 @@ def _dl(solver,partInfo,subname,shape,retAll=False):
if not solver: if not solver:
if utils.isDraftWire(partInfo): if utils.isDraftWire(partInfo):
return 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) return _l(solver,partInfo,subname,shape,retAll)
def _ln(solver,partInfo,subname,shape,retAll=False): 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): if utils.isDraftCircle(partInfo.Part):
part = partInfo.Part part = partInfo.Part
w,p,n = partInfo.Workplane 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 part.FirstAngle == part.LastAngle:
if requireArc: if requireArc:
raise RuntimeError('expecting an arc from {}'.format( raise RuntimeError('expecting an arc from {}'.format(
@ -347,7 +381,11 @@ class Constraint(ProxyType):
def register(mcs,cls): def register(mcs,cls):
super(Constraint,mcs).register(cls) super(Constraint,mcs).register(cls)
if cls._id>=0 and cls._iconName is not Base._iconName: if cls._id>=0 and cls._iconName is not Base._iconName:
try:
gui.AsmCmdManager.register(ConstraintCommand(cls)) gui.AsmCmdManager.register(ConstraintCommand(cls))
except Exception:
logger.error('failed to register {}'.format(cls.getName()))
raise
@classmethod @classmethod
def attach(mcs,obj,checkType=True): def attach(mcs,obj,checkType=True):
@ -508,11 +546,8 @@ class Base(object):
def check(cls,group,checkCount=False): def check(cls,group,checkCount=False):
entities = cls.getEntityDef(group,checkCount) entities = cls.getEntityDef(group,checkCount)
for i,e in enumerate(entities): for i,e in enumerate(entities):
o = group[i] info = group[i]
if isinstance(o,utils.ElementInfo): msg = e(None,info.Part,info.Subname,info.Shape)
msg = e(None,o.Part,o.Subname,o.Shape)
else:
msg = e(None,None,None,o)
if not msg: if not msg:
continue continue
if i == len(cls._entityDef): if i == len(cls._entityDef):
@ -546,6 +581,7 @@ class Base(object):
params = cls.getPropertyValues(obj) + cls.getEntities(obj,solver) params = cls.getPropertyValues(obj) + cls.getEntities(obj,solver)
ret = func(*params,group=solver.group) ret = func(*params,group=solver.group)
solver.system.log('{}: {}'.format(cstrName(obj),ret)) solver.system.log('{}: {}'.format(cstrName(obj),ret))
return ret
else: else:
logger.warn('{} no constraint func'.format(cstrName(obj))) logger.warn('{} no constraint func'.format(cstrName(obj)))
@ -583,7 +619,7 @@ class Locked(Base):
info = e.Proxy.getInfo() info = e.Proxy.getInfo()
if not utils.isVertex(info.Shape) and \ if not utils.isVertex(info.Shape) and \
not utils.isLinearEdge(info.Shape) and \ not utils.isLinearEdge(info.Shape) and \
not utils.isDraftCircle(info): not utils.isDraftCircle(info.Part):
ret.append(info) ret.append(info)
return ret return ret
@ -594,7 +630,7 @@ class Locked(Base):
ret = [] ret = []
for e in obj.Proxy.getElements(): for e in obj.Proxy.getElements():
info = e.Proxy.getInfo() info = e.Proxy.getInfo()
if utils.isDraftObject(info): if utils.isDraftObject(info.Part):
continue continue
shape = None shape = None
if utils.isVertex(info.Shape) or \ if utils.isVertex(info.Shape) or \
@ -613,26 +649,31 @@ class Locked(Base):
system = solver.system system = solver.system
isVertex = utils.isVertex(info.Shape) isVertex = utils.isVertex(info.Shape)
if not isVertex and utils.isDraftCircle(info): 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.getPartInfo(info,True,solver.group)
solver.addFixedElement(info.Part,info.Subname)
return ret return ret
if not isVertex and not utils.isLinearEdge(info.Shape): if not isVertex and not utils.isLinearEdge(info.Shape):
return ret return ret
if solver.isFixedPart(info):
logger.warn('redundant locking element "{}" in constraint '
'{}'.format(info.Subname,info.PartName))
return ret
partInfo = solver.getPartInfo(info) partInfo = solver.getPartInfo(info)
fixPoint = False fixPoint = False
if isVertex: if isVertex:
names = [info.Subname] 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 fixPoint = True
names = utils.edge2VertexIndex(info.Subname) names = utils.edge2VertexIndex(info.Part,info.Subname)
else: else:
names = [info.Subname+'.fp1', info.Subname+'.fp2'] names = [info.Subname+'.fp1', info.Subname+'.fp2']
@ -647,13 +688,18 @@ class Locked(Base):
# Get the entity for the point expressed in variable parameters # Get the entity for the point expressed in variable parameters
e2 = _p(solver,partInfo,names[i],v) e2 = _p(solver,partInfo,names[i],v)
solver.addFixedElement(info.Part,names[i])
if i==0 or fixPoint: if i==0 or fixPoint:
# We are fixing a vertex, or a linear edge. Either way, we # We are fixing a vertex, or a linear edge. Either way, we
# shall add a point coincidence constraint here. # shall add a point coincidence constraint here.
e0 = e1 e0 = e1
system.NameTag = nameTag + surfix 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( system.log('{}: fix point {},{},{}'.format(
info.PartName,e,e1,e2)) info.PartName,e,e1,e2))
else: else:
@ -683,7 +729,7 @@ class Locked(Base):
@classmethod @classmethod
def check(cls,group,_checkCount=False): 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 ' raise RuntimeError('Constraint "{}" requires all children to be '
'of element (Vertex, Edge or Face)'.format(cls.getName())) 'of element (Vertex, Edge or Face)'.format(cls.getName()))
@ -697,11 +743,8 @@ class BaseMulti(Base):
if len(group)<2: if len(group)<2:
raise RuntimeError('Constraint "{}" requires at least two ' raise RuntimeError('Constraint "{}" requires at least two '
'elements'.format(cls.getName())) 'elements'.format(cls.getName()))
for o in group: for info in group:
if isinstance(o,utils.ElementInfo): msg = cls._entityDef[0](None,info.Part,info.Subname,info.Shape)
msg = cls._entityDef[0](None,o.Part,o.Subname,o.Shape)
else:
msg = cls._entityDef[0](None,None,None,o)
if msg: if msg:
raise RuntimeError('Constraint "{}" requires all the element ' raise RuntimeError('Constraint "{}" requires all the element '
'to be of {}'.format(cls.getName())) 'to be of {}'.format(cls.getName()))
@ -725,7 +768,7 @@ class BaseMulti(Base):
cstrName(obj),info.PartName)) cstrName(obj),info.PartName))
continue continue
parts.add(info.Part) parts.add(info.Part)
if solver.isFixedPart(info): if solver.isFixedPart(info.Part):
if ref: if ref:
logger.warn('{} skip more than one fixed part {}'.format( logger.warn('{} skip more than one fixed part {}'.format(
cstrName(obj),info.PartName)) cstrName(obj),info.PartName))
@ -777,7 +820,7 @@ class BaseCascade(BaseMulti):
partInfo = solver.getPartInfo(info) partInfo = solver.getPartInfo(info)
e2 = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape) e2 = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape)
prev = info prev = info
if solver.isFixedPart(info): if solver.isFixedPart(info.Part):
params = props + [e1,e2] params = props + [e1,e2]
else: else:
params = props + [e2,e1] params = props + [e2,e1]
@ -1021,6 +1064,23 @@ class BaseSketch(Base):
_toolbarName = 'Assembly3 Sketch Constraints' _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): class BaseDraftWire(BaseSketch):
_id = -1 _id = -1
@ -1029,11 +1089,11 @@ class BaseDraftWire(BaseSketch):
super(BaseDraftWire,cls).check(group,checkCount) super(BaseDraftWire,cls).check(group,checkCount)
if not checkCount: if not checkCount:
return return
for o in group: for info in group:
if utils.isDraftWire(o): if utils.isDraftWire(info.Part):
return return
raise RuntimeError('Constraint "{}" requires at least one linear edge ' 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())) cls.getName()))
class LineLength(BaseSketch): class LineLength(BaseSketch):
@ -1042,8 +1102,7 @@ class LineLength(BaseSketch):
_workplane = True _workplane = True
_props = ["Length"] _props = ["Length"]
_iconName = 'Assembly_ConstraintLineLength.svg' _iconName = 'Assembly_ConstraintLineLength.svg'
_tooltip='Add a "{}" constrain the length of a non-closed-or-subdivided '\ _tooltip='Add a "{}" constrain the length of a none-subdivided Draft.Wire'
'Draft.Wire'
@classmethod @classmethod
def prepare(cls,obj,solver): def prepare(cls,obj,solver):
@ -1053,6 +1112,7 @@ class LineLength(BaseSketch):
params = cls.getPropertyValues(obj) + [p1,p2] params = cls.getPropertyValues(obj) + [p1,p2]
ret = func(*params,group=solver.group) ret = func(*params,group=solver.group)
solver.system.log('{}: {}'.format(cstrName(obj),ret)) solver.system.log('{}: {}'.format(cstrName(obj),ret))
return ret
else: else:
logger.warn('{} no constraint func'.format(cstrName(obj))) logger.warn('{} no constraint func'.format(cstrName(obj)))
@ -1103,14 +1163,14 @@ class EqualLineArcLength(BaseSketch):
super(EqualLineArcLength,cls).check(group,checkCount) super(EqualLineArcLength,cls).check(group,checkCount)
if not checkCount: if not checkCount:
return return
for i,o in enumerate(group): for i,info in enumerate(group):
if i: if i:
if utils.isDraftCircle(o): if utils.isDraftCircle(info.Part):
return return
elif utils.isDraftWire(o): elif utils.isDraftWire(info.Part):
return return
raise RuntimeError('Constraint "{}" requires at least one ' 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())) cls.getName()))
@ -1141,8 +1201,8 @@ class EqualRadius(BaseSketch):
super(EqualRadius,cls).check(group,checkCount) super(EqualRadius,cls).check(group,checkCount)
if not checkCount: if not checkCount:
return return
for o in group: for info in group:
if utils.isDraftCircle(o): if utils.isDraftCircle(info.Part):
return return
raise RuntimeError('Constraint "{}" requires at least one ' raise RuntimeError('Constraint "{}" requires at least one '
'Draft.Circle'.format(cls.getName())) 'Draft.Circle'.format(cls.getName()))

View File

@ -71,7 +71,7 @@ class AsmMovingPart(object):
def update(self): def update(self):
info = getElementInfo(self.info.Parent,self.info.SubnameRef) info = getElementInfo(self.info.Parent,self.info.SubnameRef)
self.info = info self.info = info
if utils.isDraftObject(info): if utils.isDraftObject(info.Part):
pos = utils.getElementPos(info.Shape) pos = utils.getElementPos(info.Shape)
rot = utils.getElementRotation(info.Shape) rot = utils.getElementRotation(info.Shape)
pla = info.Placement.multiply(FreeCAD.Placement(pos,rot)) pla = info.Placement.multiply(FreeCAD.Placement(pos,rot))
@ -105,7 +105,7 @@ class AsmMovingPart(object):
return return
change = [idx] change = [idx]
else: else:
change = utils.draftWireEdge2PointIndex(part,info.Subname) change = utils.edge2VertexIndex(part,info.Subname,True)
if change[0] is None or change[1] is None: if change[0] is None or change[1] is None:
logger.error('Invalid draft wire edge {} {}'.format( logger.error('Invalid draft wire edge {} {}'.format(
info.Subname, info.PartName)) info.Subname, info.PartName))

View File

@ -207,8 +207,9 @@ class ProxyType(type):
return return
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 {}, {} conflict with '
mcs.getMetaName(),cls._id)) '{}'.format(mcs.getMetaName(),cls._id,cls.getName(),
info.TypeMap[cls._id].getName()))
info.TypeMap[cls._id] = cls info.TypeMap[cls._id] = cls
info.TypeNameMap[cls.getName()] = cls info.TypeNameMap[cls.getName()] = cls
info.TypeNames.append(cls.getName()) info.TypeNames.append(cls.getName())

View File

@ -32,6 +32,7 @@ class Solver(object):
self.group = 1 # the solving group self.group = 1 # the solving group
self._partMap = {} self._partMap = {}
self._cstrMap = {} self._cstrMap = {}
self._fixedElements = set()
self.system.GroupHandle = self._fixedGroup self.system.GroupHandle = self._fixedGroup
@ -176,8 +177,14 @@ class Solver(object):
if recompute and touched: if recompute and touched:
assembly.recompute(True) assembly.recompute(True)
def isFixedPart(self,info): def isFixedPart(self,part):
return info.Part in self._fixedParts 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): def getPartInfo(self,info,fixed=False,group=0):
partInfo = self._partMap.get(info.Part,None) partInfo = self._partMap.get(info.Part,None)

View File

@ -110,6 +110,12 @@ class SystemBase(object):
class SystemExtension(object): class SystemExtension(object):
def __init__(self): def __init__(self):
self.NameTag = '' 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): def addPlaneCoincident(self,d,lockAngle,angle,e1,e2,group=0):
if not group: if not group:

View File

@ -126,30 +126,24 @@ def getElementShape(obj,tp):
if len(f)==1: if len(f)==1:
return f[0] return f[0]
ElementInfo = namedtuple('AsmElementInfo', ('Parent','SubnameRef','Part',
'PartName','Placement','Object','Subname','Shape'))
def isDraftWire(obj): def isDraftWire(obj):
if isinstance(obj,ElementInfo):
obj = obj.Part
proxy = getattr(obj,'Proxy',None) proxy = getattr(obj,'Proxy',None)
return isinstance(proxy,Draft._Wire) and \ if isinstance(proxy,Draft._Wire) and not obj.Subdivisions:
not obj.Closed and \ return obj
not obj.Subdivisions
def isDraftCircle(obj): def isDraftCircle(obj):
if isinstance(obj,ElementInfo):
obj = obj.Part
proxy = getattr(obj,'Proxy',None) proxy = getattr(obj,'Proxy',None)
return isinstance(proxy,Draft._Circle) if isinstance(proxy,Draft._Circle):
return obj
def isDraftObject(obj): def isDraftObject(obj):
return isDraftWire(obj) or isDraftCircle(obj) o = isDraftWire(obj)
if o:
return o
return isDraftCircle(obj)
def isElement(obj): def isElement(obj):
if isinstance(obj,ElementInfo): if not isinstance(obj,(tuple,list)):
shape = obj.Shape
elif not isinstance(obj,(tuple,list)):
shape = obj shape = obj
else: else:
sobj,_,shape = obj[0].getSubObject(obj[1],2) sobj,_,shape = obj[0].getSubObject(obj[1],2)
@ -490,7 +484,7 @@ def isSamePlacement(pla1,pla2):
isSameValue(pla1.Rotation.Q,pla2.Rotation.Q) isSameValue(pla1.Rotation.Q,pla2.Rotation.Q)
def getElementIndex(name,check=None): 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)): for i,c in enumerate(reversed(name)):
if not c.isdigit(): if not c.isdigit():
if not i: if not i:
@ -503,24 +497,27 @@ def getElementIndex(name,check=None):
def draftWireVertex2PointIndex(obj,name): def draftWireVertex2PointIndex(obj,name):
'Convert vertex index to draft wire point index, None if invalid' 'Convert vertex index to draft wire point index, None if invalid'
obj = isDraftWire(obj)
if not obj:
return
idx = getElementIndex(name,'Vertex') idx = getElementIndex(name,'Vertex')
# We don't support subdivision yet (checked in isDraftWire()) # We don't support subdivision yet (checked in isDraftWire())
if idx <= 0 or not isDraftWire(obj): if idx <= 0:
return return
idx -= 1 idx -= 1
if idx < len(obj.Points): if idx < len(obj.Points):
return idx return idx
def draftWireEdge2PointIndex(obj,name): def edge2VertexIndex(obj,name,retInteger=False):
vname1,vname2 = edge2VertexIndex(name)
if not vname1:
return None,None
return (draftWireVertex2PointIndex(obj,vname1),
draftWireVertex2PointIndex(obj,vname2))
def edge2VertexIndex(name):
'deduct the vertex index from the edge index' 'deduct the vertex index from the edge index'
idx = getElementIndex(name,'Edge') idx = getElementIndex(name,'Edge')
if not idx: if not idx:
return None,None 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)