cmflow 0.1.1.dev57__tar.gz → 0.3.2__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.
Files changed (30) hide show
  1. cmflow-0.3.2/.github/workflows/pypi.yml +31 -0
  2. {cmflow-0.1.1.dev57 → cmflow-0.3.2}/.github/workflows/unittest.yml +2 -7
  3. cmflow-0.3.2/.gitignore +33 -0
  4. cmflow-0.3.2/PKG-INFO +152 -0
  5. {cmflow-0.1.1.dev57 → cmflow-0.3.2}/README.md +16 -8
  6. {cmflow-0.1.1.dev57 → cmflow-0.3.2}/cmflow/conceptual_models.py +11 -145
  7. {cmflow-0.1.1.dev57 → cmflow-0.3.2}/cmflow/geom_3dface_utils.py +34 -34
  8. {cmflow-0.1.1.dev57 → cmflow-0.3.2}/cmflow/geom_surface_utils.py +53 -42
  9. {cmflow-0.1.1.dev57 → cmflow-0.3.2}/cmflow/t2data_utils.py +43 -15
  10. cmflow-0.3.2/cmflow/test_conceptual_models.py +285 -0
  11. cmflow-0.3.2/pyproject.toml +45 -0
  12. cmflow-0.1.1.dev57/.github/workflows/pypi.yml +0 -32
  13. cmflow-0.1.1.dev57/AUTHORS +0 -1
  14. cmflow-0.1.1.dev57/ChangeLog +0 -91
  15. cmflow-0.1.1.dev57/PKG-INFO +0 -132
  16. cmflow-0.1.1.dev57/cmflow/schema_wet_surface_features.json +0 -27
  17. cmflow-0.1.1.dev57/cmflow/test_conceptual_models.py +0 -150
  18. cmflow-0.1.1.dev57/cmflow/test_geom_3dface_utils_geom.dat +0 -12770
  19. cmflow-0.1.1.dev57/cmflow.egg-info/PKG-INFO +0 -132
  20. cmflow-0.1.1.dev57/cmflow.egg-info/SOURCES.txt +0 -24
  21. cmflow-0.1.1.dev57/cmflow.egg-info/dependency_links.txt +0 -1
  22. cmflow-0.1.1.dev57/cmflow.egg-info/not-zip-safe +0 -1
  23. cmflow-0.1.1.dev57/cmflow.egg-info/pbr.json +0 -1
  24. cmflow-0.1.1.dev57/cmflow.egg-info/top_level.txt +0 -1
  25. cmflow-0.1.1.dev57/setup.cfg +0 -24
  26. cmflow-0.1.1.dev57/setup.py +0 -8
  27. {cmflow-0.1.1.dev57 → cmflow-0.3.2}/cmflow/__init__.py +0 -0
  28. {cmflow-0.1.1.dev57 → cmflow-0.3.2}/cmflow/test_geom_3dface_utils.py +0 -0
  29. {cmflow-0.1.1.dev57 → cmflow-0.3.2}/cmflow/test_geom_3dface_utils_ex.ts +0 -0
  30. {cmflow-0.1.1.dev57 → cmflow-0.3.2}/cmflow/test_geom_surface_utils.py +0 -0
@@ -0,0 +1,31 @@
1
+ name: Publish on PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ deploy:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: [3.13]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ with:
17
+ fetch-depth: 0
18
+ - name: Set up Python 3.13
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: '3.13'
22
+ - name: Install Hatch
23
+ run: |
24
+ python -m pip install --upgrade pip hatch
25
+ - name: Build and publish on PyPI
26
+ env:
27
+ HATCH_INDEX_USER: __token__
28
+ HATCH_INDEX_AUTH: ${{ secrets.PYPI_PASSWORD }}
29
+ run: |
30
+ hatch build
31
+ hatch publish --no-prompt dist/*
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- python-version: [2.7]
14
+ python-version: [3.13]
15
15
  steps:
16
16
  - uses: actions/checkout@v1
17
17
  - name: Set up Python ${{ matrix.python-version }}
@@ -31,12 +31,7 @@ jobs:
31
31
  pip install matplotlib
32
32
  pip install shapely
33
33
  pip install rtree
34
- pip install descartes
35
- mkdir pytough
36
- cd pytough
37
- git clone https://github.com/acroucher/PyTOUGH.git .
38
- pip install .
39
- cd ..
34
+ pip install pytough
40
35
  - name: Unit tests
41
36
  env:
42
37
  TWINE_USERNAME: __token__
@@ -0,0 +1,33 @@
1
+ *.pyc
2
+ *.pyo
3
+ *.npy
4
+ *.dat
5
+ *.json
6
+ *.incon
7
+ *.png
8
+ *.csv
9
+ *.xyz
10
+
11
+ # Sphinx
12
+ doc/source/reference/api/
13
+
14
+ # Files created by releasenotes build
15
+ releasenotes/build
16
+
17
+ # Packages/installer info
18
+ *.eggs
19
+ *.egg
20
+ *.egg-info
21
+ dist
22
+ build
23
+
24
+ # Other
25
+ AUTHORS
26
+ ChangeLog
27
+
28
+ # Editor files
29
+ *~
30
+ *.swp
31
+
32
+ # virtualenv
33
+ env_*/
cmflow-0.3.2/PKG-INFO ADDED
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: cmflow
3
+ Version: 0.3.2
4
+ Summary: Building TOUGH2/Waiwera models from layers of conceptual models
5
+ Project-URL: Homepage, https://github.com/cyeh015/cmflow
6
+ Project-URL: Source Code, https://github.com/cyeh015/cmflow
7
+ Author-email: Angus Yeh <a.yeh@auckland.ac.nz>
8
+ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Python: >=3.8
19
+ Requires-Dist: pytough
20
+ Requires-Dist: rtree
21
+ Requires-Dist: shapely
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Install
25
+
26
+ pip install -U cmflow
27
+
28
+ # Install Dependency
29
+
30
+ On most platforms, you should be able to install these packages by:
31
+
32
+ pip install shapely
33
+ pip install rtree
34
+ pip install pytough
35
+
36
+ On Linux (Ubuntu shown here) these can be installed via apt-get:
37
+
38
+ sudo apt-get install -y python-shapely
39
+ sudo apt-get install -y python-rtree
40
+
41
+ # Example
42
+
43
+ Creates BMStats that can be used later, from Leapfrog Geology:
44
+
45
+ # (ONLY ONCE) geo used to get geology from Leapfrog geological model
46
+ cmgeo = mulgrid('g_very_fine.dat')
47
+
48
+ # CSV file created by Leapfrog using cmgeo above
49
+ leapfrog = LeapfrogGM()
50
+ leapfrog.import_leapfrog_csv('grid_gtmp_ay2017_03_6_fit.csv')
51
+
52
+ cm_geology = CM_Blocky(cmgeo, leapfrog)
53
+
54
+ # whatever active model we are working on
55
+ bmgeo = mulgrid('gwaixx_yy.dat')
56
+
57
+ bms_geology = cm_geology.calc_bmstats(bm_geo)
58
+ bms_geology.save('a.json')
59
+
60
+ A BMStats object can be reused (very fast) to eg.
61
+
62
+ bms_geology = BMStats('a.json')
63
+
64
+ # get a cell's stats
65
+ cs = bms_geology.cellstats['abc12']
66
+
67
+ # rock that occupies most in cell 'abc12'
68
+ rock_name = bms_geology.zones[np.argmax(cs)]
69
+
70
+ # how many rock in cell 'abc12'
71
+ n_rock = len(np.nonzero(cs))
72
+
73
+ # list all rocks in cell 'abc12'
74
+ rocks = [bm_geology.zones[i] for i in np.nonzero(cs)]
75
+
76
+ # find all blocks intersect with the zone
77
+ blocks, ratios = bm_geology.blocks_in_zone('BASE1')
78
+ block_idx, ratios = bm_geology.blocks_in_zone('BASE1', indices=True)
79
+
80
+
81
+ # BMStats
82
+
83
+ This is the object that we keep for later use. It is associated to a certain
84
+ "geometry" file. So each cell has information on zones. Usually this is
85
+ generated by cm.populate_model(), which can be expensive.
86
+
87
+ - ? should I call it CMStats?
88
+ - ? TODO, .cellstats access by cell index
89
+ - ? TODO, .
90
+
91
+ Base Model Stats, mainly numpy arrays with rows corresponding to mulgrid
92
+ blocks, and columns corresponding to zones. Each is a value, usually
93
+ between 0.0 and 1.0. Often 1.0 is indicating that particular block is fully
94
+ within the zone.
95
+
96
+ .stats numpy array (n,m), n = num of model blocks, m = num of zones
97
+ .zones list of zone names (str)
98
+ .zonestats dict of stats column by zone names
99
+ .cellstats dict of stats row by block name
100
+
101
+ 6 elements, 3 zones
102
+ A B C
103
+ 0.0, 0.7, 0.3, -> row sum to 1.0, element 0, 0.7 rock B, 0.3 rock C
104
+ 1.0, 0.0, 0.0,
105
+ 1.0, 0.0, 0.0,
106
+ 0.0, 0.5, 0.5,
107
+ 0.1, 0.2, 0.7,
108
+ 0.0, 1.0, 0.0,
109
+ (this is only one way of using it, such as a rocktype)
110
+
111
+ .stats, numpy array (n * m), n number of geometry cells, m number of zones
112
+ .zones, a list of zone name, eg. geology rock names, fault names etc
113
+ .zonestats, a dict keyed by zone name, an array of size number of cells, each cell is between
114
+ .cellstats, a dict of stats by cell name
115
+
116
+ .save()
117
+ .load()
118
+ .add_stats() add another bmstat, merge stats
119
+ .add_cm() calls cm.populate_model, and merge stats
120
+
121
+ # CM
122
+ # CM_Blocky
123
+ # CM_Prism
124
+ # CM_Faults
125
+
126
+ These are the objects that can be created in order to create the final BMStats
127
+ objects. The common method .populate_model(bm_geo) is called to create BMStats
128
+ objects. It means the conceptual model is "applied" onto the bm_geo.
129
+
130
+ - TODO, .populate_model() should return BMStats instead
131
+ - ? TODO, .populate_model() should be called something else?
132
+
133
+ .populate_model(bm_geo) takes a target geometry, and return/creates BMStats
134
+
135
+
136
+ # LeapfrogGM
137
+
138
+
139
+
140
+
141
+ # Build and Publish
142
+
143
+ To bump version, create a tag, eg. `v0.1.0`.
144
+
145
+ If upload for the first time, create a PyPI account token, then use it for the publish step. The PyPI project will be created on first upload. Then revoke the account token and create a project token for later publishes.
146
+
147
+ Publish to PyPI:
148
+
149
+ ```console
150
+ hatch build
151
+ hatch publish dist/*
152
+ ```
@@ -4,20 +4,16 @@
4
4
 
5
5
  # Install Dependency
6
6
 
7
- On Windows, `Shapely` and `Rtree` are easier to be installed by using Christoph Gohlke's non-official build:
7
+ On most platforms, you should be able to install these packages by:
8
8
 
9
- - [https://www.lfd.uci.edu/~gohlke/pythonlibs/#shapely](https://www.lfd.uci.edu/~gohlke/pythonlibs/#shapely)
10
- - [https://www.lfd.uci.edu/~gohlke/pythonlibs/#rtree](https://www.lfd.uci.edu/~gohlke/pythonlibs/#rtree)
11
-
12
- `descartes` can be installed on all platform by:
13
-
14
- pip install descartes
9
+ pip install shapely
10
+ pip install rtree
11
+ pip install pytough
15
12
 
16
13
  On Linux (Ubuntu shown here) these can be installed via apt-get:
17
14
 
18
15
  sudo apt-get install -y python-shapely
19
16
  sudo apt-get install -y python-rtree
20
- sudo apt-get install -y python-descartes
21
17
 
22
18
  # Example
23
19
 
@@ -119,3 +115,15 @@ objects. It means the conceptual model is "applied" onto the bm_geo.
119
115
 
120
116
 
121
117
 
118
+ # Build and Publish
119
+
120
+ To bump version, create a tag, eg. `v0.1.0`.
121
+
122
+ If upload for the first time, create a PyPI account token, then use it for the publish step. The PyPI project will be created on first upload. Then revoke the account token and create a project token for later publishes.
123
+
124
+ Publish to PyPI:
125
+
126
+ ```console
127
+ hatch build
128
+ hatch publish dist/*
129
+ ```
@@ -8,10 +8,9 @@ from shapely.geometry import Polygon
8
8
  from shapely.geometry import LineString
9
9
 
10
10
  # for faults CM
11
- from geom_3dface_utils import Face3D
12
- #
13
- from geom_surface_utils import get_columns_intersect_polygon
14
- from geom_surface_utils import geo_column_polygon
11
+ from cmflow.geom_3dface_utils import Face3D
12
+ from cmflow.geom_surface_utils import get_columns_intersect_polygon
13
+ from cmflow.geom_surface_utils import geo_column_polygon
15
14
 
16
15
  import json
17
16
  import time
@@ -20,9 +19,9 @@ START = [time.time()]
20
19
  def print_wall_time(msg, loop_stop=False, total=False):
21
20
  t = time.time()
22
21
  if total:
23
- print msg, ': %f seconds' % (t - START[0])
22
+ print(msg, ': %f seconds' % (t - START[0]))
24
23
  else:
25
- print msg, ': %f seconds' % (t - START[-1])
24
+ print(msg, ': %f seconds' % (t - START[-1]))
26
25
  if loop_stop:
27
26
  START.append(t)
28
27
 
@@ -32,7 +31,7 @@ class LeapfrogGM(object):
32
31
  self.litholist = []
33
32
  self.blocklitho = {}
34
33
  self.import_from = '' # optional, if imported from leapfrog
35
- self.geometry = '' # optional, matching mulgrid geometry file
34
+ self.geometry = geometry # optional, matching mulgrid geometry file
36
35
 
37
36
  def import_leapfrog_csv(self, filename, report=False):
38
37
  """ load geology info from Leapfrog's 'Generate rock types' feature. The
@@ -62,13 +61,13 @@ class LeapfrogGM(object):
62
61
  self.litholist.append(row[2])
63
62
  lithocodes[int(row[1])] = len(self.litholist)-1
64
63
  if read_block_litho:
65
- if len(row) <> 2: break
64
+ if len(row) != 2: break
66
65
  self.blocklitho[row[0]] = lithocodes[int(row[1])]
67
66
  f.close()
68
67
  self.import_from = filename
69
68
  if report:
70
- print '%8s Lithology found.' % len(self.litholist)
71
- print '%8s Blocks allocated.' % len(self.blocklitho)
69
+ print('%8s Lithology found.' % len(self.litholist))
70
+ print('%8s Blocks allocated.' % len(self.blocklitho))
72
71
 
73
72
  def write(self, filename):
74
73
  with open(filename, 'w') as f:
@@ -365,7 +364,7 @@ class CM_Faults(CM):
365
364
  if isinstance(dilation, float):
366
365
  if dilation > 0.0:
367
366
  self.dilation = dilation
368
- print 'Fault uses dilation'
367
+ print('Fault uses dilation')
369
368
  if faults is None:
370
369
  pass
371
370
  elif isinstance(faults, list):
@@ -446,7 +445,7 @@ class CM_Faults(CM):
446
445
  if len(pts) < 2:
447
446
  # not enough points to construct LineString, layer not
448
447
  # cutting through the fault Face
449
- print " skipping layer %i '%s' with fault '%s'" % (jj, lay.name, fname)
448
+ print(" skipping layer %i '%s' with fault '%s'" % (jj, lay.name, fname))
450
449
  continue
451
450
  line = LineString([tuple(pt[:2]) for pt in pts])
452
451
  if self.dilation is not None:
@@ -623,139 +622,6 @@ class BMStats(object):
623
622
  else:
624
623
  return nbs, nrs
625
624
 
626
- def test_zonestats_small():
627
- from t2data_utils import create_basic_t2data, update_block_geology
628
- START = [time.time()]
629
- geo = mulgrid().rectangular([1000]*20, [1000]*20, [100]*5, origin=[0,0,0],
630
- atmos_type=0)
631
- stats = BMStats(geo)
632
- poly = Polygon([
633
- np.array([1147.7 , 14125.4]),
634
- np.array([487.0 , 13041.7]),
635
- np.array([302.0 , 11799.4]),
636
- np.array([592.7 , 10372.2]),
637
- np.array([1412.1 , 8389.8]),
638
- np.array([1993.5 , 7808.4]),
639
- np.array([2865.8 , 7729.1]),
640
- np.array([3685.1 , 7544.1]),
641
- np.array([3975.9 , 6460.4]),
642
- np.array([4002.3 , 3262.2]),
643
- np.array([3923.0 , 2020.0]),
644
- np.array([3685.1 , 1094.9]),
645
- np.array([5720.3 , 275.5]),
646
- np.array([6645.4 , 381.3]),
647
- np.array([7914.1 , 698.4]),
648
- np.array([9156.3 , 857.0]),
649
- np.array([9922.8 , 804.2]),
650
- np.array([11693.7, 1676.4]),
651
- np.array([12486.6, 2786.5]),
652
- np.array([12539.5, 4927.4]),
653
- np.array([13913.9, 7121.2]),
654
- np.array([13543.9, 8680.6]),
655
- np.array([11376.5, 8574.9]),
656
- np.array([9473.5 , 8522.0]),
657
- np.array([8072.7 , 8944.9]),
658
- np.array([7121.1 , 10160.7]),
659
- np.array([6989.0 , 11244.4]),
660
- np.array([6856.8 , 11905.2]),
661
- np.array([5641.0 , 13041.7]),
662
- np.array([4980.2 , 13358.9]),
663
- np.array([3711.6 , 14204.7]),
664
- np.array([3262.2 , 14733.3]),
665
- np.array([1967.1 , 14812.6]),
666
- np.array([1359.2 , 14389.7]),
667
- ])
668
- ztop, zbot = 0.0, -100.0
669
- cm = CM_Prism('resis', poly, ztop, zbot)
670
- stats.add_cm(cm)
671
- ztop, zbot = -200.0, -300.0
672
- cm = CM_Prism('resis', poly, ztop, zbot)
673
- stats.add_cm(cm)
674
- stats.save('tmp_cm.json')
675
- stats_2 = BMStats(geo)
676
- stats_2.load('tmp_cm.json')
677
- # check if the same
678
- assert stats.zones == stats_2.zones
679
- assert np.array_equal(stats.stats, stats_2.stats)
680
- dat = create_basic_t2data(geo)
681
-
682
- # starting with single rock deflt
683
- orig_name = dat.grid.rocktypelist[0].name
684
- dat.grid.rename_rocktype(orig_name, 'NA ') # not in geological model
685
- # ATMOS
686
- for i in range(dat.grid.num_atmosphere_blocks):
687
- update_block_geology(dat, dat.grid.blocklist[i].name, 'ATMOS')
688
- # others
689
- for i in range(dat.grid.num_atmosphere_blocks, dat.grid.num_blocks):
690
- zi = np.argmax(stats.stats[i,:])
691
- if stats.stats[i,zi] > 0.0:
692
- # print CM_0_stats[i,zi], CM_0_stats[i,:]
693
- new_rname = update_block_geology(dat, dat.grid.blocklist[i].name, stats.zones[0])
694
- geo.write('gtmp.dat')
695
- dat.write('tmp.dat')
696
-
697
- def test_cm_blocky_full():
698
- START = [time.time()]
699
-
700
- leapfrog = LeapfrogGM(geometry='gtmp_ay2017_03_6')
701
- leapfrog.import_leapfrog_csv('grid_gtmp_ay2017_03_6_fit.csv')
702
- leapfrog.write('_leapfrog.json')
703
- print_wall_time('loaded leapfrog GM', loop_stop=True)
704
-
705
- geo = mulgrid('gtmp_ay2017_03_6_fit.dat')
706
- print_wall_time('loaded CM geo', loop_stop=True)
707
-
708
- bm_geo = mulgrid('gtmp_ay2017_05_5a.dat')
709
- print_wall_time('loaded CM geo and BM geo', loop_stop=True)
710
-
711
- print ' CM has %i blocks: %s' % (geo.num_blocks, geo.filename)
712
- print ' BM has %i blocks: %s' % (bm_geo.num_blocks, bm_geo.filename)
713
-
714
- cm = CM_Blocky(geo, leapfrog)
715
- stats, zones = cm.populate_model(bm_geo)
716
-
717
- print_wall_time('Finished all, total wall time:', total=True)
718
-
719
- print np.nonzero(stats)
720
- np.save('_CM_results.npy', stats)
721
-
722
- with open('_CM_results.json', 'w') as f:
723
- json.dump({
724
- "comments": [
725
- "CM (leapfrog): " + leapfrog.import_from,
726
- "CM geometry: " + geo.filename,
727
- "BM geometry: " + bm_geo.filename,
728
- ],
729
- "zones": zones,
730
- "stats": stats.tolist(),
731
- }, f, indent=4, sort_keys=True)
732
-
733
-
734
- def test_cm_fault_full():
735
- import glob
736
- START = [time.time()]
737
-
738
- bm_geo = mulgrid('gtmp_ay2017_05_5a.dat')
739
- print_wall_time('loaded BM geo', loop_stop=True)
740
- print ' BM has %i blocks: %s' % (bm_geo.num_blocks, bm_geo.filename)
741
-
742
- cm_f = CM_Faults(sorted(glob.glob('*.ts')))
743
- print_wall_time('loaded faults CM w/ %i faults' % cm_f.num_zones, loop_stop=True)
744
-
745
- stats, zones = cm_f.populate_model(bm_geo)
746
- print_wall_time('Finished all, total wall time:', total=True)
747
-
748
- save_as = '_CM_faults_results'
749
- print np.nonzero(stats)
750
- np.save(save_as + '.npy', stats)
751
- with open(save_as + '.json', 'w') as f:
752
- json.dump({
753
- "comments": [
754
- "BM geometry: " + bm_geo.filename,
755
- ],
756
- "zones": zones,
757
- "stats": stats.tolist(),
758
- }, f, indent=4, sort_keys=True)
759
625
 
760
626
 
761
627
  if __name__ == '__main__':
@@ -163,38 +163,38 @@ class Face3D(object):
163
163
 
164
164
  def read(self, filename):
165
165
  # TODO: implement full support, see http://paulbourke.net/dataformats/gocad/gocad.pdf
166
- f = open(filename, 'r')
167
- if not f.next().startswith('GOCAD TSurf 1'):
168
- raise Exception("File type not supported, need .ts file starts with 'GOCAD TSurf 1'.")
169
- done = False
170
- for line in f:
171
- if line.startswith('HEADER'):
172
- line = f.next()
173
- while '}' not in line:
174
- c = line.strip().split(':')
175
- self.header[c[0]] = c[1]
176
- line = f.next()
177
- elif line.startswith('TFACE'):
178
- print 'Reading TFACE...',
179
- for line in f:
180
- if line.startswith('PVRTX'):
181
- c = line.strip().split()
182
- self.points[c[1]] = Point(c[1], [float(x) for x in c[2:]])
183
- if line.startswith('TRGL'):
184
- c = line.strip().split()
185
- self.triangles.append(Triangle([self.points[p] for p in c[1:]]))
186
- # add back references
187
- self.triangles[-1].index = len(self.triangles) - 1
188
- for p in c[1:]:
189
- self.points[p].in_triangles.append(self.triangles[-1])
190
- if line.startswith('END'):
191
- done = True
192
- break
193
- else:
194
- pass
195
- f.close()
166
+ with open(filename, 'r') as f:
167
+ first_line = f.readline()
168
+ if not first_line.startswith("GOCAD TSurf 1"):
169
+ raise ValueError("File type not supported, need .ts file starts with 'GOCAD TSurf 1'.")
170
+ done = False
171
+ for line in f:
172
+ if line.startswith('HEADER'):
173
+ line = next(f)
174
+ while '}' not in line:
175
+ c = line.strip().split(':')
176
+ self.header[c[0]] = c[1]
177
+ line = next(f)
178
+ elif line.startswith('TFACE'):
179
+ print('Reading TFACE...', end=' ')
180
+ for line in f:
181
+ if line.startswith('PVRTX'):
182
+ c = line.strip().split()
183
+ self.points[c[1]] = Point(c[1], [float(x) for x in c[2:]])
184
+ if line.startswith('TRGL'):
185
+ c = line.strip().split()
186
+ self.triangles.append(Triangle([self.points[p] for p in c[1:]]))
187
+ # add back references
188
+ self.triangles[-1].index = len(self.triangles) - 1
189
+ for p in c[1:]:
190
+ self.points[p].in_triangles.append(self.triangles[-1])
191
+ if line.startswith('END'):
192
+ done = True
193
+ break
194
+ else:
195
+ pass
196
196
  if done:
197
- print 'Done.'
197
+ print('Done.')
198
198
 
199
199
  def __repr__(self):
200
200
  if 'name' in self.header:
@@ -244,14 +244,14 @@ class Face3D(object):
244
244
  # cur_ep can be either a segment or a point
245
245
  if isect['itype'] == (0,2):
246
246
  # two edges crosses plane
247
- ks = isect['crosses'].keys()
247
+ ks = list(isect['crosses'].keys())
248
248
  cur_ep, other_end_ep = ks[0], ks[1]
249
249
  elif isect['itype'] == (2,0):
250
250
  # two corners on plane
251
251
  cur_ep, other_end_ep = isect['corners'][0], isect['corners'][1]
252
252
  else:
253
253
  # one corner on plane, one edge crosses plane
254
- ks = isect['crosses'].keys()
254
+ ks = list(isect['crosses'].keys())
255
255
  cur_ep, other_end_ep = isect['corners'][0], ks[0]
256
256
 
257
257
  line = [self.get_intersection_co(other_end_ep, isect), self.get_intersection_co(cur_ep, isect)]
@@ -297,7 +297,7 @@ class Face3D(object):
297
297
 
298
298
  isect = self.cached_tri_intersections[nbs[0].index]
299
299
  exits = []
300
- for ex in isect['crosses'].keys() + isect['corners']:
300
+ for ex in list(isect['crosses'].keys()) + isect['corners']:
301
301
  if isinstance(cur_ep, tuple):
302
302
  if ex in [cur_ep, cur_ep[::-1]]:
303
303
  continue