constraint: optimize constraint multiplication

This commit is contained in:
Zheng, Lei 2018-08-17 19:10:03 +08:00
parent 677ba607f1
commit 7d4b184394
4 changed files with 101 additions and 26 deletions

View File

@ -365,12 +365,20 @@ class AsmElement(AsmBase):
else: else:
parentShape = Part.getShape(info.Part, info.Subname, parentShape = Part.getShape(info.Part, info.Subname,
transform=False, needSubElement=False) transform=False, needSubElement=False)
shapes = [] found = False
shapes = [info.Shape]
pla = info.Shape.Placement
for edge in parentShape.Edges: for edge in parentShape.Edges:
if info.Shape.isCoplanar(edge) and \ if not info.Shape.isCoplanar(edge) or \
utils.isSameValue( not utils.isSameValue(
utils.getElementCircular(edge,True),obj.Radius): utils.getElementCircular(edge,True),obj.Radius):
edge.transformShape(mat,True) continue
edge.transformShape(mat,True)
if not found and utils.isSamePlacement(pla,edge.Placement):
found = True
# make sure the direct referenced edge is the first one
shapes[0] = edge
else:
shapes.append(edge) shapes.append(edge)
shape = shapes shape = shapes

View File

@ -934,25 +934,43 @@ class BaseMulti(Base):
logger.warn('{} no first part shape'.format(cstrName(obj))) logger.warn('{} no first part shape'.format(cstrName(obj)))
return return
idx = 0 idx = 0
updates = []
for element in elements[1:]: for element in elements[1:]:
for info in element.Proxy.getInfo(expand=True): infos = element.Proxy.getInfo(expand=True)
info0 = firstInfo[idx] if not infos:
partInfo0 = solver.getPartInfo(info0) continue
partInfo = solver.getPartInfo(info) info0 = firstInfo[idx]
e0 = cls._entityDef[0]( partInfo0 = solver.getPartInfo(info0,infos)
solver,partInfo0,info0.Subname,info0.Shape) info = infos[0]
e = cls._entityDef[0]( partInfo = solver.getPartInfo(info)
solver,partInfo,info.Subname,info.Shape) e0 = cls._entityDef[0](
params = props + [e0,e] solver,partInfo0,info0.Subname,info0.Shape)
solver.system.checkRedundancy(obj,partInfo0,partInfo) e = cls._entityDef[0](
h = func(*params,group=solver.group) solver,partInfo,info.Subname,info.Shape)
if isinstance(h,(list,tuple)): params = props + [e0,e]
ret += list(h) solver.system.checkRedundancy(obj,partInfo0,partInfo)
else: h = func(*params,group=solver.group)
ret.append(h) if isinstance(h,(list,tuple)):
idx += 1 ret += list(h)
if idx >= count: else:
return ret ret.append(h)
idx += 1
if idx >= count:
return ret
if len(infos)>1:
updates.append((partInfo0,element,len(infos)-1))
for partInfo0,element,infoCount in updates:
if partInfo0.Update:
logger.warn('{} used in more than one constraint '
'multiplication'.format(partInfo0.PartName))
continue
partInfo0.Update.append(idx)
partInfo0.Update.append(element)
idx += infoCount
if idx >= count:
break
return ret return ret
parts = set() parts = set()

View File

@ -1,5 +1,5 @@
import random, math import random, math
from collections import namedtuple from collections import namedtuple,defaultdict
import FreeCAD, FreeCADGui import FreeCAD, FreeCADGui
from .assembly import Assembly, isTypeOf, setPlacement from .assembly import Assembly, isTypeOf, setPlacement
from . import utils from . import utils
@ -21,8 +21,12 @@ from .system import System
# CstrMap: map from other part to the constrains between this and the othe part. # CstrMap: map from other part to the constrains between this and the othe part.
# This is for auto constraint DOF reduction. Only some composite # This is for auto constraint DOF reduction. Only some composite
# constraints will be mapped. # constraints will be mapped.
# Update: in case the constraint uses the `Multiplication` feature, only the
# first element of all the coplanar edges will be actually constrainted.
# The rest ElementInfo will be stored here for later update by matrix
# transformation.
PartInfo = namedtuple('SolverPartInfo', ('Part','PartName','Placement', PartInfo = namedtuple('SolverPartInfo', ('Part','PartName','Placement',
'Params','Workplane','EntityMap','Group','CstrMap')) 'Params','Workplane','EntityMap','Group','CstrMap','Update'))
class Solver(object): class Solver(object):
def __init__(self,assembly,reportFailed,dragPart,recompute,rollback): def __init__(self,assembly,reportFailed,dragPart,recompute,rollback):
@ -116,6 +120,7 @@ class Solver(object):
self.system.log('done solving') self.system.log('done solving')
touched = False touched = False
updates = []
for part,partInfo in self._partMap.items(): for part,partInfo in self._partMap.items():
if part in self._fixedParts: if part in self._fixedParts:
continue continue
@ -147,6 +152,8 @@ class Solver(object):
touched = True touched = True
part.Points = points part.Points = points
else: else:
if partInfo.Update:
updates.append(partInfo)
params = [self.system.getParam(h).val for h in partInfo.Params] params = [self.system.getParam(h).val for h in partInfo.Params]
p = params[:3] p = params[:3]
q = (params[4],params[5],params[6],params[3]) q = (params[4],params[5],params[6],params[3])
@ -161,6 +168,8 @@ class Solver(object):
rollback.append((partInfo.PartName, rollback.append((partInfo.PartName,
part, part,
partInfo.Placement.copy())) partInfo.Placement.copy()))
partInfo.Placement.Base = pla.Base
partInfo.Placement.Rotation = pla.Rotation
setPlacement(part,pla) setPlacement(part,pla)
if utils.isDraftCircle(part): if utils.isDraftCircle(part):
@ -198,6 +207,45 @@ class Solver(object):
if recompute and touched: if recompute and touched:
assembly.recompute(True) assembly.recompute(True)
# Update parts with constraint multiplication, which auto expands
# coplanar circular edges of the same radius. For performance sake, only
# the first edge of each expansion is used for constraint. We simply
# translate the rest of the parts with the same relative offset.
touches = defaultdict(set)
for partInfo in updates:
idx,element = partInfo.Update
element0 = element.Proxy.parent.Object.Group[0]
infos0 = element0.Proxy.getInfo(expand=True)
count = len(infos0)
infos = element.Proxy.getInfo(expand=True)
pos0 = infos[0].Placement.multVec(
utils.getElementPos(infos[0].Shape))
touched = False
for info in infos[1:]:
if idx >= count:
break
pos = info.Placement.multVec(
utils.getElementPos(info.Shape))
pla = partInfo.Placement.copy()
pla.Base += pos-pos0
info0 = infos0[idx]
idx += 1
if isSamePlacement(info0.Placement,pla):
self.system.log('not moving {}'.format(info0.PartName))
else:
self.system.log('moving {} {}'.format(
partInfo.PartName,pla))
touched = True
if rollback is not None:
rollback.append((info0.PartName,
info0.Part,
info0.Placement.copy()))
setPlacement(info0.Part,pla)
if touched:
touches[partInfo.Part[0].Document].add(partInfo.Part[0])
for doc,objs in touches.items():
doc.recompute(list(objs))
def isFixedPart(self,part): def isFixedPart(self,part):
if isinstance(part,tuple) and part[0] in self._fixedParts: if isinstance(part,tuple) and part[0] in self._fixedParts:
return True return True
@ -257,7 +305,8 @@ class Solver(object):
Workplane = h, Workplane = h,
EntityMap = {}, EntityMap = {},
Group = group if group else g, Group = group if group else g,
CstrMap = {}) CstrMap = {},
Update = [])
self.system.log('{}, {}'.format(partInfo,g)) self.system.log('{}, {}'.format(partInfo,g))

View File

@ -138,7 +138,7 @@ class SystemExtension(object):
if not yaw and not pitch and not roll: if not yaw and not pitch and not roll:
n = n2.entity n = n2.entity
else: else:
rot = n2.rot.multiply(FreeCAD.Rotation(yaw,pitch,roll)) rot = FreeCAD.Rotation(yaw,pitch,roll).multiply(n2.rot)
e = self.addNormal3dV(*getNormal(rot)) e = self.addNormal3dV(*getNormal(rot))
n = self.addTransform(e,*n2.params,group=group) n = self.addTransform(e,*n2.params,group=group)
h.append(self.addSameOrientation(n1.entity,n,group=group)) h.append(self.addSameOrientation(n1.entity,n,group=group))