1
1
from functools import reduce
2
2
from typing import Union , Optional , List , Dict , Any , overload , Tuple , Iterator , cast
3
3
from typing_extensions import Literal
4
+ from typish import instance_of
4
5
from uuid import uuid1 as uuid
5
6
6
7
from .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
9
10
from .occ_impl .assembly import Color
10
11
from .occ_impl .solver import (
11
12
ConstraintSolver ,
12
- ConstraintMarker ,
13
- Constraint as ConstraintPOD ,
13
+ ConstraintSpec as Constraint ,
14
+ UnaryConstraintKind ,
15
+ BinaryConstraintKind ,
14
16
)
15
17
from .occ_impl .exporters .assembly import (
16
18
exportAssembly ,
21
23
)
22
24
23
25
from .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
27
26
28
27
# type definitions
29
28
AssemblyObjects = Union [Shape , Workplane , None ]
@@ -67,112 +66,6 @@ def _define_grammar():
67
66
_grammar = _define_grammar ()
68
67
69
68
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
-
176
69
class Assembly (object ):
177
70
"""Nested assembly of Workplane and Shape objects defining their relative positions."""
178
71
@@ -389,6 +282,12 @@ def constrain(
389
282
) -> "Assembly" :
390
283
...
391
284
285
+ @overload
286
+ def constrain (
287
+ self , q1 : str , kind : ConstraintKinds , param : Any = None
288
+ ) -> "Assembly" :
289
+ ...
290
+
392
291
@overload
393
292
def constrain (
394
293
self ,
@@ -401,12 +300,25 @@ def constrain(
401
300
) -> "Assembly" :
402
301
...
403
302
303
+ @overload
304
+ def constrain (
305
+ self , id1 : str , s1 : Shape , kind : ConstraintKinds , param : Any = None ,
306
+ ) -> "Assembly" :
307
+ ...
308
+
404
309
def constrain (self , * args , param = None ):
405
310
"""
406
311
Define a new constraint.
407
312
"""
408
313
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 :
410
322
q1 , q2 , kind = args
411
323
id1 , s1 = self ._query (q1 )
412
324
id2 , s2 = self ._query (q2 )
@@ -421,11 +333,18 @@ def constrain(self, *args, param=None):
421
333
else :
422
334
raise ValueError (f"Incompatible arguments: { args } " )
423
335
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 )
429
348
430
349
return self
431
350
@@ -434,32 +353,53 @@ def solve(self) -> "Assembly":
434
353
Solve the constraints.
435
354
"""
436
355
437
- # get all entities and number them
356
+ # Get all entities and number them. First entity is marked as locked
438
357
ents = {}
439
358
440
359
i = 0
441
- lock_ix = 0
360
+ locked = []
442
361
for c in self .constraints :
443
362
for name in c .objects :
444
363
if name not in ents :
445
364
ents [name ] = i
446
- if name == self .name :
447
- lock_ix = i
448
365
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
449
385
450
386
locs = [self .objects [n ].loc for n in ents ]
451
387
452
388
# construct the constraint mapping
453
389
constraints = []
454
390
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 ))
456
396
457
397
# check if any constraints were specified
458
398
if not constraints :
459
399
raise ValueError ("At least one constraint required" )
460
400
461
401
# instantiate the solver
462
- solver = ConstraintSolver (locs , constraints , locked = [ lock_ix ] )
402
+ solver = ConstraintSolver (locs , constraints , locked = locked )
463
403
464
404
# solve
465
405
locs_new , self ._solve_result = solver .solve ()
0 commit comments