11from functools import reduce
22from typing import Union , Optional , List , Dict , Any , overload , Tuple , Iterator , cast
33from typing_extensions import Literal
4+ from typish import instance_of
45from uuid import uuid1 as uuid
56
67from .cq import Workplane
7- from .occ_impl .shapes import Shape , Compound , Face , Edge , Wire
8- from .occ_impl .geom import Location , Vector , Plane
8+ from .occ_impl .shapes import Shape , Compound
9+ from .occ_impl .geom import Location
910from .occ_impl .assembly import Color
1011from .occ_impl .solver import (
1112 ConstraintSolver ,
12- ConstraintMarker ,
13- Constraint as ConstraintPOD ,
13+ ConstraintSpec as Constraint ,
14+ UnaryConstraintKind ,
15+ BinaryConstraintKind ,
1416)
1517from .occ_impl .exporters .assembly import (
1618 exportAssembly ,
2123)
2224
2325from .selectors import _expression_grammar as _selector_grammar
24- from OCP .BRepTools import BRepTools
25- from OCP .gp import gp_Pln , gp_Pnt
26- from OCP .Precision import Precision
2726
2827# type definitions
2928AssemblyObjects = Union [Shape , Workplane , None ]
@@ -67,112 +66,6 @@ def _define_grammar():
6766_grammar = _define_grammar ()
6867
6968
70- class Constraint (object ):
71- """
72- Geometrical constraint between two shapes of an assembly.
73- """
74-
75- objects : Tuple [str , ...]
76- args : Tuple [Shape , ...]
77- sublocs : Tuple [Location , ...]
78- kind : ConstraintKinds
79- param : Any
80-
81- def __init__ (
82- self ,
83- objects : Tuple [str , ...],
84- args : Tuple [Shape , ...],
85- sublocs : Tuple [Location , ...],
86- kind : ConstraintKinds ,
87- param : Any = None ,
88- ):
89- """
90- Construct a constraint.
91-
92- :param objects: object names referenced in the constraint
93- :param args: subshapes (e.g. faces or edges) of the objects
94- :param sublocs: locations of the objects (only relevant if the objects are nested in a sub-assembly)
95- :param kind: constraint kind
96- :param param: optional arbitrary parameter passed to the solver
97- """
98-
99- self .objects = objects
100- self .args = args
101- self .sublocs = sublocs
102- self .kind = kind
103- self .param = param
104-
105- def _getAxis (self , arg : Shape ) -> Vector :
106-
107- if isinstance (arg , Face ):
108- rv = arg .normalAt ()
109- elif isinstance (arg , Edge ) and arg .geomType () != "CIRCLE" :
110- rv = arg .tangentAt ()
111- elif isinstance (arg , Edge ) and arg .geomType () == "CIRCLE" :
112- rv = arg .normal ()
113- else :
114- raise ValueError (f"Cannot construct Axis for { arg } " )
115-
116- return rv
117-
118- def _getPln (self , arg : Shape ) -> gp_Pln :
119-
120- if isinstance (arg , Face ):
121- rv = gp_Pln (self ._getPnt (arg ), arg .normalAt ().toDir ())
122- elif isinstance (arg , (Edge , Wire )):
123- normal = arg .normal ()
124- origin = arg .Center ()
125- plane = Plane (origin , normal = normal )
126- rv = plane .toPln ()
127- else :
128- raise ValueError (f"Can not construct a plane for { arg } ." )
129-
130- return rv
131-
132- def _getPnt (self , arg : Shape ) -> gp_Pnt :
133-
134- # check for infinite face
135- if isinstance (arg , Face ) and any (
136- Precision .IsInfinite_s (x ) for x in BRepTools .UVBounds_s (arg .wrapped )
137- ):
138- # fall back to gp_Pln center
139- pln = arg .toPln ()
140- center = Vector (pln .Location ())
141- else :
142- center = arg .Center ()
143-
144- return center .toPnt ()
145-
146- def toPOD (self ) -> ConstraintPOD :
147- """
148- Convert the constraint to a representation used by the solver.
149- """
150-
151- rv : List [Tuple [ConstraintMarker , ...]] = []
152-
153- for idx , (arg , loc ) in enumerate (zip (self .args , self .sublocs )):
154-
155- arg = arg .located (loc * arg .location ())
156-
157- if self .kind == "Axis" :
158- rv .append ((self ._getAxis (arg ).toDir (),))
159- elif self .kind == "Point" :
160- rv .append ((self ._getPnt (arg ),))
161- elif self .kind == "Plane" :
162- rv .append ((self ._getAxis (arg ).toDir (), self ._getPnt (arg )))
163- elif self .kind == "PointInPlane" :
164- if idx == 0 :
165- rv .append ((self ._getPnt (arg ),))
166- else :
167- rv .append ((self ._getPln (arg ),))
168- else :
169- raise ValueError (f"Unknown constraint kind { self .kind } " )
170-
171- rv .append (self .param )
172-
173- return cast (ConstraintPOD , tuple (rv ))
174-
175-
17669class Assembly (object ):
17770 """Nested assembly of Workplane and Shape objects defining their relative positions."""
17871
@@ -389,6 +282,12 @@ def constrain(
389282 ) -> "Assembly" :
390283 ...
391284
285+ @overload
286+ def constrain (
287+ self , q1 : str , kind : ConstraintKinds , param : Any = None
288+ ) -> "Assembly" :
289+ ...
290+
392291 @overload
393292 def constrain (
394293 self ,
@@ -401,12 +300,25 @@ def constrain(
401300 ) -> "Assembly" :
402301 ...
403302
303+ @overload
304+ def constrain (
305+ self , id1 : str , s1 : Shape , kind : ConstraintKinds , param : Any = None ,
306+ ) -> "Assembly" :
307+ ...
308+
404309 def constrain (self , * args , param = None ):
405310 """
406311 Define a new constraint.
407312 """
408313
409- if len (args ) == 3 :
314+ # dispatch on arguments
315+ if len (args ) == 2 :
316+ q1 , kind = args
317+ id1 , s1 = self ._query (q1 )
318+ elif len (args ) == 3 and instance_of (args [1 ], UnaryConstraintKind ):
319+ q1 , kind , param = args
320+ id1 , s1 = self ._query (q1 )
321+ elif len (args ) == 3 :
410322 q1 , q2 , kind = args
411323 id1 , s1 = self ._query (q1 )
412324 id2 , s2 = self ._query (q2 )
@@ -421,11 +333,18 @@ def constrain(self, *args, param=None):
421333 else :
422334 raise ValueError (f"Incompatible arguments: { args } " )
423335
424- loc1 , id1_top = self ._subloc (id1 )
425- loc2 , id2_top = self ._subloc (id2 )
426- self .constraints .append (
427- Constraint ((id1_top , id2_top ), (s1 , s2 ), (loc1 , loc2 ), kind , param )
428- )
336+ # handle unary and binary constraints
337+ if instance_of (kind , UnaryConstraintKind ):
338+ loc1 , id1_top = self ._subloc (id1 )
339+ c = Constraint ((id1_top ,), (s1 ,), (loc1 ,), kind , param )
340+ elif instance_of (kind , BinaryConstraintKind ):
341+ loc1 , id1_top = self ._subloc (id1 )
342+ loc2 , id2_top = self ._subloc (id2 )
343+ c = Constraint ((id1_top , id2_top ), (s1 , s2 ), (loc1 , loc2 ), kind , param )
344+ else :
345+ raise ValueError (f"Unknown constraint: { kind } " )
346+
347+ self .constraints .append (c )
429348
430349 return self
431350
@@ -434,32 +353,53 @@ def solve(self) -> "Assembly":
434353 Solve the constraints.
435354 """
436355
437- # get all entities and number them
356+ # Get all entities and number them. First entity is marked as locked
438357 ents = {}
439358
440359 i = 0
441- lock_ix = 0
360+ locked = []
442361 for c in self .constraints :
443362 for name in c .objects :
444363 if name not in ents :
445364 ents [name ] = i
446- if name == self .name :
447- lock_ix = i
448365 i += 1
366+ if c .kind == "Fixed" or name == self .name :
367+ locked .append (ents [name ])
368+
369+ # Lock the first occuring entity if needed.
370+ if not locked :
371+ unary_objects = [
372+ c .objects [0 ]
373+ for c in self .constraints
374+ if instance_of (c .kind , UnaryConstraintKind )
375+ ]
376+ binary_objects = [
377+ c .objects [0 ]
378+ for c in self .constraints
379+ if instance_of (c .kind , BinaryConstraintKind )
380+ ]
381+ for b in binary_objects :
382+ if b not in unary_objects :
383+ locked .append (ents [b ])
384+ break
449385
450386 locs = [self .objects [n ].loc for n in ents ]
451387
452388 # construct the constraint mapping
453389 constraints = []
454390 for c in self .constraints :
455- constraints .append (((ents [c .objects [0 ]], ents [c .objects [1 ]]), c .toPOD ()))
391+ ixs = tuple (ents [obj ] for obj in c .objects )
392+ pods = c .toPODs ()
393+
394+ for pod in pods :
395+ constraints .append ((ixs , pod ))
456396
457397 # check if any constraints were specified
458398 if not constraints :
459399 raise ValueError ("At least one constraint required" )
460400
461401 # instantiate the solver
462- solver = ConstraintSolver (locs , constraints , locked = [ lock_ix ] )
402+ solver = ConstraintSolver (locs , constraints , locked = locked )
463403
464404 # solve
465405 locs_new , self ._solve_result = solver .solve ()
0 commit comments