forgekernel 0.9.4__tar.gz → 0.9.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {forgekernel-0.9.4/src/forgekernel.egg-info → forgekernel-0.9.5}/PKG-INFO +1 -1
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/__init__.py +1 -1
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/stepio.py +109 -9
- {forgekernel-0.9.4 → forgekernel-0.9.5/src/forgekernel.egg-info}/PKG-INFO +1 -1
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel.egg-info/SOURCES.txt +2 -1
- forgekernel-0.9.5/tests/test_step_drilled.py +112 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/LICENSE +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/README.md +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/pyproject.toml +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/setup.cfg +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/brep.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/bsolid.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/csg.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/curve.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/exact.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/hlr.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/interval.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/io.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/kernel.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/loft.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/mesh2d.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/nurbs.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/profile2d.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/quadric.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/ssi.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/surd.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/surfacing.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/tess.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel/trim.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel.egg-info/dependency_links.txt +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel.egg-info/requires.txt +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/src/forgekernel.egg-info/top_level.txt +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_boss.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_hlr.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_k1_exact.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_k37_loft.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_k38_profile.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_k3_curves.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_k3_ssi.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_k3_step.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_k6_surfacing.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_k7_bsolid.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_k7_stepexport.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_k7_trim.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_k7_trimflux.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_rotate.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_rust_oracle.py +0 -0
- {forgekernel-0.9.4 → forgekernel-0.9.5}/tests/test_scaffold.py +0 -0
|
@@ -359,10 +359,32 @@ def _perp(n):
|
|
|
359
359
|
return (p[0] / m, p[1] / m, p[2] / m)
|
|
360
360
|
|
|
361
361
|
|
|
362
|
-
def
|
|
363
|
-
"""
|
|
364
|
-
|
|
365
|
-
|
|
362
|
+
def _poly_contains_xy(verts, px, py) -> bool:
|
|
363
|
+
"""Ray parity in xy for a face polygon (used to decide which cap fragment
|
|
364
|
+
a bore breaks through)."""
|
|
365
|
+
inside = False
|
|
366
|
+
n = len(verts)
|
|
367
|
+
for i in range(n):
|
|
368
|
+
(x1, y1) = verts[i][0], verts[i][1]
|
|
369
|
+
(x2, y2) = verts[(i + 1) % n][0], verts[(i + 1) % n][1]
|
|
370
|
+
if (y1 > py) != (y2 > py):
|
|
371
|
+
if px < x1 + (py - y1) * (x2 - x1) / (y2 - y1):
|
|
372
|
+
inside = not inside
|
|
373
|
+
return inside
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def write_step_planar_solid(solid, *, name: str = "gitcad_part",
|
|
377
|
+
bores=()) -> str:
|
|
378
|
+
"""Emit a forge solid as a valid AP214 STEP file (full product structure +
|
|
379
|
+
MANIFOLD_SOLID_BREP with shared edges).
|
|
380
|
+
|
|
381
|
+
``solid`` supplies the planar faces; ``bores`` is an optional list of z-axis
|
|
382
|
+
``Cyl`` bores drilled through it. A bore contributes a CYLINDRICAL_SURFACE
|
|
383
|
+
wall (as two half-faces, so no periodic seam is needed), a circular inner
|
|
384
|
+
bound on each cap it breaks through, and a flat disk face where it ends
|
|
385
|
+
blind. Circles and cylinders are emitted as EXACT analytic surfaces, not
|
|
386
|
+
faceted — a drilled plate exports as a real cylindrical hole that any CAD
|
|
387
|
+
system reads back as a hole."""
|
|
366
388
|
lines: list[str] = []
|
|
367
389
|
nid = [0]
|
|
368
390
|
|
|
@@ -424,6 +446,36 @@ def write_step_planar_solid(solid, *, name: str = "gitcad_part") -> str:
|
|
|
424
446
|
ka, kb)
|
|
425
447
|
return edges[key]
|
|
426
448
|
|
|
449
|
+
def zplacement(cx, cy, z, up=1):
|
|
450
|
+
"""AXIS2_PLACEMENT_3D on the +z (or −z) axis through (cx, cy, z),
|
|
451
|
+
ref direction +x — the frame every circle and cylinder here uses."""
|
|
452
|
+
o = emit(f"CARTESIAN_POINT('',({_dec(cx)},{_dec(cy)},{_dec(z)}))")
|
|
453
|
+
ax = emit(f"DIRECTION('',(0.,0.,{'1.' if up > 0 else '-1.'}))")
|
|
454
|
+
rd = emit(f"DIRECTION('',(1.,0.,0.))")
|
|
455
|
+
return emit(f"AXIS2_PLACEMENT_3D('',#{o},#{ax},#{rd})")
|
|
456
|
+
|
|
457
|
+
def rim_edges(c, z):
|
|
458
|
+
"""The two half-circle EDGE_CURVEs of a bore's rim at height z, split at
|
|
459
|
+
angles 0 and π so the cylindrical face needs no periodic seam. Cached,
|
|
460
|
+
so the cap's inner bound and the wall share the very same edges."""
|
|
461
|
+
key = ("rim", c.cx, c.cy, c.r, z)
|
|
462
|
+
if key in edges:
|
|
463
|
+
return edges[key]
|
|
464
|
+
p0 = (c.cx + c.r, c.cy, z)
|
|
465
|
+
p1 = (c.cx - c.r, c.cy, z)
|
|
466
|
+
circ = emit(f"CIRCLE('',#{zplacement(c.cx, c.cy, z)},{_dec(c.r)})")
|
|
467
|
+
e_a = emit(f"EDGE_CURVE('',#{vertex(p0)},#{vertex(p1)},#{circ},.T.)")
|
|
468
|
+
e_b = emit(f"EDGE_CURVE('',#{vertex(p1)},#{vertex(p0)},#{circ},.T.)")
|
|
469
|
+
edges[key] = (e_a, e_b, p0, p1)
|
|
470
|
+
return edges[key]
|
|
471
|
+
|
|
472
|
+
def oriented(eid, fwd):
|
|
473
|
+
return emit(f"ORIENTED_EDGE('',*,*,#{eid},{'.T.' if fwd else '.F.'})")
|
|
474
|
+
|
|
475
|
+
def loop_of(oes):
|
|
476
|
+
return emit(f"EDGE_LOOP('',({','.join('#' + str(x) for x in oes)}))")
|
|
477
|
+
|
|
478
|
+
bores = list(bores)
|
|
427
479
|
face_ids = []
|
|
428
480
|
for poly in solid.polys:
|
|
429
481
|
vs = [tuple(v) for v in poly.verts]
|
|
@@ -432,10 +484,24 @@ def write_step_planar_solid(solid, *, name: str = "gitcad_part") -> str:
|
|
|
432
484
|
a, b = vs[i], vs[(i + 1) % len(vs)]
|
|
433
485
|
ec, ka, kb = edge_curve(a, b)
|
|
434
486
|
fwd = (a[0], a[1], a[2]) == ka
|
|
435
|
-
oe.append(
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
bound
|
|
487
|
+
oe.append(oriented(ec, fwd))
|
|
488
|
+
bound = emit(f"FACE_OUTER_BOUND('',#{loop_of(oe)},.T.)")
|
|
489
|
+
bounds = [bound]
|
|
490
|
+
# a z-cap gets a circular INNER bound for every bore that breaks it
|
|
491
|
+
n = poly.plane.n
|
|
492
|
+
if n[0] == 0 and n[1] == 0:
|
|
493
|
+
zc = vs[0][2]
|
|
494
|
+
for c in bores:
|
|
495
|
+
if (c.z0 < zc < c.z1 or c.z0 == zc or c.z1 == zc) and \
|
|
496
|
+
_poly_contains_xy(vs, c.cx, c.cy):
|
|
497
|
+
a_e, b_e, _, _ = rim_edges(c, zc)
|
|
498
|
+
# each rim half-circle is shared with the bore wall, which
|
|
499
|
+
# traverses the LOWER rim forward and the UPPER rim
|
|
500
|
+
# backward; the cap must take the opposite sense so every
|
|
501
|
+
# edge is used once .T. and once .F. (manifold closure).
|
|
502
|
+
fwd = n[2] > 0 # top cap vs bottom cap
|
|
503
|
+
hole = loop_of([oriented(a_e, fwd), oriented(b_e, fwd)])
|
|
504
|
+
bounds.append(emit(f"FACE_BOUND('',#{hole},.T.)"))
|
|
439
505
|
(nx, ny, nz), _ = _unit3(poly.plane.n)
|
|
440
506
|
rx, ry, rz = _perp((nx, ny, nz))
|
|
441
507
|
origin = emit(f"CARTESIAN_POINT('',({_dec(vs[0][0])},{_dec(vs[0][1])},"
|
|
@@ -444,7 +510,41 @@ def write_step_planar_solid(solid, *, name: str = "gitcad_part") -> str:
|
|
|
444
510
|
rdir = emit(f"DIRECTION('',({fnum(rx)},{fnum(ry)},{fnum(rz)}))")
|
|
445
511
|
place = emit(f"AXIS2_PLACEMENT_3D('',#{origin},#{axis},#{rdir})")
|
|
446
512
|
plane = emit(f"PLANE('',#{place})")
|
|
447
|
-
|
|
513
|
+
blist = ",".join("#" + str(b) for b in bounds)
|
|
514
|
+
face_ids.append(emit(f"ADVANCED_FACE('',({blist}),#{plane},.T.)"))
|
|
515
|
+
|
|
516
|
+
# -- bore walls (two half-cylinder faces) + blind end caps ----------------
|
|
517
|
+
(_, _, sz0), (_, _, sz1) = solid.bbox()
|
|
518
|
+
for c in bores:
|
|
519
|
+
z0, z1 = max(c.z0, sz0), min(c.z1, sz1)
|
|
520
|
+
if z1 <= z0:
|
|
521
|
+
continue
|
|
522
|
+
lo_a, lo_b, lp0, lp1 = rim_edges(c, z0)
|
|
523
|
+
hi_a, hi_b, hp0, hp1 = rim_edges(c, z1)
|
|
524
|
+
seam0 = edge_curve(lp0, hp0)[0] # the two vertical seam edges
|
|
525
|
+
seam1 = edge_curve(lp1, hp1)[0]
|
|
526
|
+
cyl = emit(f"CYLINDRICAL_SURFACE('',#{zplacement(c.cx, c.cy, z0)},"
|
|
527
|
+
f"{_dec(c.r)})")
|
|
528
|
+
for lo_e, hi_e, s_from, s_to in ((lo_a, hi_a, seam1, seam0),
|
|
529
|
+
(lo_b, hi_b, seam0, seam1)):
|
|
530
|
+
oes = [oriented(lo_e, True), oriented(s_from, True),
|
|
531
|
+
oriented(hi_e, False), oriented(s_to, False)]
|
|
532
|
+
b = emit(f"FACE_OUTER_BOUND('',#{loop_of(oes)},.T.)")
|
|
533
|
+
# material lies OUTSIDE a bore, so the solid's outward normal points
|
|
534
|
+
# toward the axis — opposite the cylinder's own normal: .F.
|
|
535
|
+
face_ids.append(emit(f"ADVANCED_FACE('',(#{b}),#{cyl},.F.)"))
|
|
536
|
+
# A blind end gets a flat disk. Its outward normal points INTO the bore
|
|
537
|
+
# (material is on the far side): +z at the floor, −z at the ceiling.
|
|
538
|
+
# The loop sense is independent of that — it mirrors the wall, which
|
|
539
|
+
# takes the lower rim forward and the upper rim backward.
|
|
540
|
+
for zb, axis_up, fwd in ((z0, 1, False), (z1, -1, True)):
|
|
541
|
+
if (zb == sz0 and c.z0 <= sz0) or (zb == sz1 and c.z1 >= sz1):
|
|
542
|
+
continue # breaks through: no disk here
|
|
543
|
+
a_e, b_e, _, _ = rim_edges(c, zb)
|
|
544
|
+
disk = loop_of([oriented(a_e, fwd), oriented(b_e, fwd)])
|
|
545
|
+
b = emit(f"FACE_OUTER_BOUND('',#{disk},.T.)")
|
|
546
|
+
pl = emit(f"PLANE('',#{zplacement(c.cx, c.cy, zb, axis_up)})")
|
|
547
|
+
face_ids.append(emit(f"ADVANCED_FACE('',(#{b}),#{pl},.T.)"))
|
|
448
548
|
|
|
449
549
|
shell = emit(f"CLOSED_SHELL('',({','.join('#' + str(x) for x in face_ids)}))")
|
|
450
550
|
brep = emit(f"MANIFOLD_SOLID_BREP('{name}',#{shell})")
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""STEP export of drilled solids — exact cylindrical holes, not facets.
|
|
2
|
+
|
|
3
|
+
A hole is emitted as a real CYLINDRICAL_SURFACE with CIRCLE edges, so any CAD
|
|
4
|
+
system reads it back as a hole (with a diameter you can query) rather than a
|
|
5
|
+
polygon soup. The oracle here is topological: in a valid MANIFOLD_SOLID_BREP
|
|
6
|
+
every edge is used exactly twice, once in each direction — an orientation slip
|
|
7
|
+
anywhere in the caps, walls or blind disks breaks that immediately.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from collections import defaultdict
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from forgekernel.brep import Solid
|
|
18
|
+
from forgekernel.quadric import Cyl, DrilledSolid
|
|
19
|
+
from forgekernel.stepio import write_step_planar_solid
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _entities(text):
|
|
23
|
+
ent = {}
|
|
24
|
+
for m in re.finditer(r"^#(\d+) = ([A-Z0-9_]+)\((.*)\);$", text, re.M):
|
|
25
|
+
ent[int(m.group(1))] = (m.group(2), m.group(3))
|
|
26
|
+
return ent
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _refs(s):
|
|
30
|
+
return [int(x) for x in re.findall(r"#(\d+)", s)]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _edge_uses(text):
|
|
34
|
+
"""edge id -> list of traversal senses across every face loop."""
|
|
35
|
+
ent = _entities(text)
|
|
36
|
+
use = defaultdict(list)
|
|
37
|
+
for fid, (t, args) in ent.items():
|
|
38
|
+
if t != "ADVANCED_FACE":
|
|
39
|
+
continue
|
|
40
|
+
for b in _refs(args):
|
|
41
|
+
if ent.get(b, ("", ""))[0] not in ("FACE_OUTER_BOUND", "FACE_BOUND"):
|
|
42
|
+
continue
|
|
43
|
+
for lp in _refs(ent[b][1]):
|
|
44
|
+
if ent.get(lp, ("", ""))[0] != "EDGE_LOOP":
|
|
45
|
+
continue
|
|
46
|
+
for oe in _refs(ent[lp][1]):
|
|
47
|
+
if ent.get(oe, ("", ""))[0] != "ORIENTED_EDGE":
|
|
48
|
+
continue
|
|
49
|
+
use[_refs(ent[oe][1])[0]].append(
|
|
50
|
+
".T." in ent[oe][1].split(",")[-1])
|
|
51
|
+
return use
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _assert_manifold(text):
|
|
55
|
+
use = _edge_uses(text)
|
|
56
|
+
assert use, "no edges found"
|
|
57
|
+
for eid, senses in use.items():
|
|
58
|
+
assert len(senses) == 2, f"edge #{eid} used {len(senses)}x, want 2"
|
|
59
|
+
assert senses[0] != senses[1], f"edge #{eid} traversed the same way twice"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _assert_refs_resolve(text):
|
|
63
|
+
ids = {int(m) for m in re.findall(r"^#(\d+) =", text, re.M)}
|
|
64
|
+
used = {int(m) for m in re.findall(r"#(\d+)", text)}
|
|
65
|
+
assert not (used - ids), f"dangling refs: {sorted(used - ids)[:5]}"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
CASES = {
|
|
69
|
+
"through hole": DrilledSolid(Solid.box(40, 20, 5), [Cyl(20, 10, 4, 0, 5)]),
|
|
70
|
+
"blind from top": DrilledSolid(Solid.box(30, 30, 10), [Cyl(15, 15, 3, 4, 10)]),
|
|
71
|
+
"blind from bottom": DrilledSolid(Solid.box(30, 30, 10), [Cyl(15, 15, 3, 0, 6)]),
|
|
72
|
+
"two holes": DrilledSolid(Solid.box(60, 30, 5),
|
|
73
|
+
[Cyl(15, 15, 3, 0, 5), Cyl(45, 15, 3, 0, 5)]),
|
|
74
|
+
"counterbore": DrilledSolid(Solid.box(30, 30, 10),
|
|
75
|
+
[Cyl(15, 15, 2, 0, 10), Cyl(15, 15, 4, 7, 10)]),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@pytest.mark.parametrize("label", sorted(CASES))
|
|
80
|
+
def test_drilled_step_is_a_closed_oriented_brep(label) -> None:
|
|
81
|
+
solid = CASES[label]
|
|
82
|
+
text = write_step_planar_solid(solid.base, bores=solid.bores)
|
|
83
|
+
assert text.startswith("ISO-10303-21;")
|
|
84
|
+
assert text.rstrip().endswith("END-ISO-10303-21;")
|
|
85
|
+
_assert_refs_resolve(text)
|
|
86
|
+
_assert_manifold(text)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_a_hole_is_an_exact_cylinder_not_facets() -> None:
|
|
90
|
+
solid = DrilledSolid(Solid.box(40, 20, 5), [Cyl(20, 10, 4, 0, 5)])
|
|
91
|
+
text = write_step_planar_solid(solid.base, bores=solid.bores)
|
|
92
|
+
# one analytic cylinder of the right radius on the right axis...
|
|
93
|
+
cyls = re.findall(r"CYLINDRICAL_SURFACE\('',#(\d+),([0-9.]+)\)", text)
|
|
94
|
+
assert len(cyls) == 1 and float(cyls[0][1]) == pytest.approx(4.0)
|
|
95
|
+
# ...two rim circles, same radius
|
|
96
|
+
circles = re.findall(r"CIRCLE\('',#(\d+),([0-9.]+)\)", text)
|
|
97
|
+
assert len(circles) == 2
|
|
98
|
+
assert all(float(r) == pytest.approx(4.0) for _, r in circles)
|
|
99
|
+
# ...and the axis really is at the bore centre
|
|
100
|
+
ent = _entities(text)
|
|
101
|
+
place = ent[int(cyls[0][0])][1]
|
|
102
|
+
origin = ent[_refs(place)[0]][1]
|
|
103
|
+
xs = [float(v) for v in re.findall(r"-?[0-9.]+", origin)]
|
|
104
|
+
assert xs[0] == pytest.approx(20.0) and xs[1] == pytest.approx(10.0)
|
|
105
|
+
# the caps carry the hole as an INNER bound (FACE_BOUND), not an outer one
|
|
106
|
+
assert len(re.findall(r"= FACE_BOUND\(", text)) == 2
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_planar_export_is_unchanged_by_the_bore_support() -> None:
|
|
110
|
+
box = Solid.box(20, 20, 5)
|
|
111
|
+
assert write_step_planar_solid(box) == write_step_planar_solid(box, bores=[])
|
|
112
|
+
_assert_manifold(write_step_planar_solid(box))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|