-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpymeshmaker.py
More file actions
1578 lines (1295 loc) · 66.6 KB
/
pymeshmaker.py
File metadata and controls
1578 lines (1295 loc) · 66.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
""" Shapefile to .Geo file converter
Module to convert a shapefile '.shp' to a geometry file of gmsh '.geo'.
!!! IMPORTANT !!! It can handle inner polygons BUT it can't place boundaries in the inner polygons!
author: Ignace Pelckmans
(University of Antwerp, Belgium)
"""
import os
import pickle
import time
import warnings
warnings.filterwarnings("ignore")
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from shapely.geometry import Point
import pandas as pd
import geopandas as gpd
matplotlib.use('Agg')
from shp2mpol import *
from pol2pol import *
from remove_vertices import *
from remove_duplicates import *
from reverse_polygon import *
from polplot import *
from np2bgfield import *
class pymesh:
"""
Class which describes an a domain (geometry) and evenually a mesh (.msh) generated by gmsh
author: Ignace Pelckmans
(University of Antwerp)
"""
def __init__(self, name = ''):
# attribute with the name of the mesh
self.name = name
# automatically set the directory to the default directory
self.setDirectory()
# automatically set the defaultb boundary and break lists (empty)
self.setBoundariesAndBreaks()
def setDirectory(self, dir = '/home/ignace/Documents/Ph D/TELEMAC/Playing_Around/First_Steps/Test_Cases/'):
"""
Method to set a directory to save all files
Args:
dir: (Optional, defaults to .../Test_Cases/input/v1/) string with the directory to save all generated files into
"""
self.dir = dir
def loadShp(self, fn, proj = 32717, remove_vertices = True, remove_duplicates = True, reverse_polygon = False):
"""
Method to load a shapefile (.shp) and tranform to shapely Polygon, A Numpy array with dimension n x 2 with the
exterior coordinates and a list of Numpy arrays with dimensions m x 3 with all coordinates of the inner rings.
Args:
fn: (Required) string with path to shapefile
proj: (Optional, defaults to 32717) epsg code of the desired projection (hint: use metric projections, for instane, avoid epsg:4326)
Default is set at 32717 which is the epsg code for WGS 84 / UTM 17S.
remove_vertices: (Optional, defaults to True) True/False boolean to indicate whether excessive vertices should be removed
Excessive is defined as vertices where there is no change of direction.
remove_duplicates: (Optional, defaults to True) True/False boolean to indicate whether duplica vertices should be removed
"""
# load .shp and reproject
pol, epsg = shp2Mpol(fn, return_coordinate_system = True)
if epsg != proj:
print('reprojecting...')
pol = pol2Pol(pol, epsg, proj).geoms[0]
print('reprojected!')
if isinstance(pol, MultiPolygon):
pol = pol.geoms[0]
# remove excessive vertices
if remove_vertices: pol = removeVertices(pol)
# Remove duplicates (highly recommended!)
if remove_duplicates: pol = pol.simplify(0)
if reverse_polygon:
pol = reversePolygon(pol)
# convert to Numpy Array and rotate 90° do the shape becoms n x 2, remove z-coordinates
exter = np.rot90(pol.exterior.xy)[:-1,0:2]
inter = [np.rot90(i.xy)[:-1,0:2] for i in pol.interiors]
# number of exterior vertices
n = len(exter)
# set attributes
self.pol = pol
self.exter = exter
self.inter = inter
self.n = n
self.n_inner_rings = len(inter)
self.proj = proj
def loadGPKG(self, fn, proj = 0, remove_vertices = True, remove_duplicates = True, reverse_polygon = False):
"""
Method to load a geopackage (.gpkg) and tranform to shapely Polygon, A Numpy array with dimension n x 2 with the
exterior coordinates and a list of Numpy arrays with dimensions m x 3 with all coordinates of the inner rings.
Args:
fn: (Required) string with path to shapefile
proj: (Optional, defaults to 32717) epsg code of the desired projection (hint: use metric projections, for instane, avoid epsg:4326)
Default is set at 32717 which is the epsg code for WGS 84 / UTM 17S.
remove_vertices: (Optional, defaults to True) True/False boolean to indicate whether excessive vertices should be removed
Excessive is defined as vertices where there is no change of direction.
remove_duplicates: (Optional, defaults to True) True/False boolean to indicate whether duplica vertices should be removed
"""
# load .gpkg
gdf = gpd.read_file(fn, engine='pyogrio')
epsg = int(gdf.crs.srs.split(':')[-1])
# if need, reproject
if epsg != proj and proj != 0:
gdf = gdf.to_crs(epsg = proj)
print('reprojected!')
roi = gdf.iloc[0]
pol = roi.geometry
if isinstance(pol, MultiPolygon):
pol = pol.geoms[0]
# remove excessive vertices
if remove_vertices: pol = removeVertices(pol)
# Remove duplicates (highly recommended!)
if remove_duplicates: pol = pol.simplify(0)
if reverse_polygon:
pol = reversePolygon(pol)
# convert to Numpy Array and rotate 90° do the shape becoms n x 2, remove z-coordinates
exter = np.rot90(pol.exterior.xy)[:-1,0:2]
inter = [np.rot90(i.xy)[:-1,0:2] for i in pol.interiors]
# number of exterior vertices
n = len(exter)
# set attributes
self.pol = pol
self.exter = exter
self.inter = inter
self.n = n
self.n_inner_rings = len(inter)
self.proj = proj
def loadPolygon(self, pol, proj = 32717, remove_duplicates = True):
"""
Method to load a Shapely polygon.
Args:
pol: (Required) Shapely polygon
remove_duplicates: (Optional, defaults to True) True/False boolean to indicate whether duplica vertices should be removed
"""
if isinstance(pol, MultiPolygon):
pol = pol.geoms[0]
# Remove duplicates (highly recommended!)
if remove_duplicates: pol = pol.simplify(0)
# convert to Numpy Array and rotate 90° do the shape becoms n x 2, remove z-coordinates
exter = np.rot90(pol.exterior.xy)[:-1,0:2]
inter = [np.rot90(i.xy)[:-1,0:2] for i in pol.interiors]
# number of exterior vertices
n = len(exter)
# set attributes
self.pol = pol
self.exter = exter
self.inter = inter
self.n = n
self.n_inner_rings = len(inter)
self.proj = proj
def findBoundaries(self, boundaries = []):
"""
Method to find boundaries based on x,y pairs
:param boundaries: list of boundaries where each boundary follows the format: [[x1,y1],[x2,y2]]
:return:
"""
bs = []
for boundary in boundaries:
b0x, b0y = boundary[0]
b1x, b1y = boundary[1]
i0 = np.argmin((self.exter[:,0]-b0x)**2 + (self.exter[:,1]-b0y)**2)+1
i1 = np.argmin((self.exter[:,0]-b1x)**2 + (self.exter[:,1]-b1y)**2)+1
i0,i1 = np.sort([i0,i1])
bs.append([i0,i1])
boundaries = sorted(bs, key=lambda x: x[0])
self.setBoundariesAndBreaks(boundaries = boundaries)
def setBoundariesAndBreaks(self, boundaries = [], breaks = []):
"""
Method to set the boundaries and breaks as attributes:
Args:
boundaries: (Optional, defaults to an empty list) List of pairs of node id's (starting & ending node) of the boundaries
Node id's can be looked up through the plotting method
breaks: (Optional, defaults to an empty list) List of node id's to force on the geometry
"""
self.boundaries = boundaries
self.breaks = breaks
def vert2shp(self, fn):
"""
Method to create a shapefile containing points with their labels
Args:
fn: (Required) filename/directory path string to store the shapefile
"""
# put all coordinates in one array
coor = np.asarray(self.exter)
for i in self.inter:
coor = np.vstack((coor, np.asarray(i)))
# create a CRS object
source = osr.SpatialReference()
source.ImportFromEPSG(self.proj)
# Now convert it to a shapefile with OGR
driver = ogr.GetDriverByName('Esri Shapefile')
if fn[-4:] != '.shp': fn += '.shp'
ds = driver.CreateDataSource(fn)
layer = ds.CreateLayer('', source, geom_type = ogr.wkbPoint)
# Add one attribute
layer.CreateField(ogr.FieldDefn('id', ogr.OFTInteger))
defn = layer.GetLayerDefn()
for i in range(coor.shape[0]):
# Create a new feature (attribute and geometry)
feat = ogr.Feature(defn)
feat.SetField('id', i+1)
# Make a geometry, from Shapely object
p = Point(coor[i,0], coor[i,1])
geom = ogr.CreateGeometryFromWkb(p.wkb)
geom.AssignSpatialReference(source)
feat.SetGeometry(geom)
layer.CreateFeature(feat)
feat = geom = None # destroy these
# Save and close everything
ds = layer = feat = geom = None
def plotShp(self, figfile=None, plot_inner=True, plot_vert=True, plot_vertlabels=False,
plot_each_x_labels=10, figure_size=(50, 50), font_size=10, set_bounds=False, axis=None):
"""
Method to plot the original shapefile (after reprojecting)
Args:
figfile: (Optional) Path (string) to save the figure. Required if no axis is provided.
plot_inner: (Optional) True/False to plot inner rings
plot_vert: (Optional) True/False to show vertices
plot_vertlabels: (Optional) True/False to show vertex labels
plot_each_x_labels: (Optional) Int for label interval
figure_size: (Optional) Tuple for figure size
font_size: (Optional) Font size for vertex labels
set_bounds: (Optional) False or list of bounds [min_lat, max_lat, min_lon, max_lon]
axis: (Optional) Matplotlib axis to plot into; if None, creates a new figure
"""
is_external_axis = axis is not None
if not is_external_axis:
fig, axis = plt.subplots(figsize=figure_size)
axis.set_aspect('equal')
# plot polygon
if plot_inner:
polPlot(self.exter, XY_inner=self.inter, show_vertices=plot_vert, empty=False, plot_on_axis=axis,
show_vertices_labels=plot_vertlabels, show_vertices_labels_interval=plot_each_x_labels,
vertices_color='maroon', font_size=font_size)
else:
polPlot(self.exter, show_vertices=plot_vert, empty=False, plot_on_axis=axis,
show_vertices_labels=plot_vertlabels, show_vertices_labels_interval=plot_each_x_labels,
vertices_color='maroon', font_size=font_size)
# indicate boundaries
for b in self.boundaries:
X, Y = [], []
for i in range(b[0], b[1] + 1):
x, y = self.exter[i - 1]
X.append(x)
Y.append(y)
axis.plot(X, Y, color='purple', label='boundaries')
# indicate break points
for b in self.breaks:
x, y = self.exter[b - 1]
axis.scatter(x, y, c='pink', label='breaks')
if set_bounds and isinstance(set_bounds, (list, tuple)) and len(set_bounds) == 4:
axis.set_xlim(set_bounds[2], set_bounds[3])
axis.set_ylim(set_bounds[0], set_bounds[1])
# Save figure only if we created one
if not is_external_axis and figfile:
fig.savefig(figfile)
plt.close(fig)
def writeGeoFile(self, filename, cell_size = 250, spline_max_len = False, inner_rings = True, include_inner_rings = False,
XY_field = ' ', background_file = '', multiplier = 1, minres = 25, maxres = 1e9,
zerovalues = 250, outside_mesh_size = 250, plot_arr = False,
buffer_interpolation = 50, algo = 'Frontal-Delaunay', line_type = 'Splines', no_mesh_def = False,
prevent_add_nodes_to_edge = False, create_background_file = True, add_physical_lines = True,
physical_line_labels = ['closed', 'TidalEntrance', 'ClosedInner'], inner_rings_splines = True,):
"""
Method to write a .geo file
Args:
filename: (Required) Directory path (string) to store the .geo file
cell_size: (Optional, defaults to 250) Int or string 'hetero' to indicate the type of meshing, if number is given the mesh will be homogeneous
spline_max_len: (Optional, defaults to False) False/int to indicate the max length of a spline (if False, there is no limit)
inner_rings: (Optional, defaults to True) True/False boolean to add the points and splines of the inner rings
include_inner_rings: (Optional, defaults to False) True/False to include the inner rings in the domain.
!!! different combinations of the parameters inner_rings and include_inner_rings lead to different results:
- inner_rings = True and include_inner_rings = False:
Inner rings will be holes (or islands) in the final mesh
- inner_rings = True and include_inner_rings = True:
Inner rings won't be holes but the mesh will be forced on the edges of the inner ring so
that the mesh is completely the same + mesh in the holes as it would have been with the holes
- inner_rings = False and include_inner_rings = True/False:
Inner rings will be ignored.
XY_field: (Required if cell_size = 'hetero') directory path string to a pickle which hold a list with the following elements:
- 2d numpy array representing a spatial raster with values for the mesh size
- coordinate pair with the coordinates of the left bottom corner of the 2d numpy array
- x resolution
- y resolution
multiplier: (Optional, defaults 1) value to multiply the original array with
minres: (Optional, defaults to 25) minimum mesh size
zerovalues: (Optional, defaults to 250) the mesh size in areas with value 0
outside_mesh_size: (Optional, defaults to 250) the mesh size outside the background field
plot_arr: (Optional, defaults to False) False/file directory to store a plot of the original raster
buffer_interpolation: (Optional, defaults to False) False/float to indicate the size of buffer around the channels to interpolate
algo: (Optional, defaults to Frontal-Delaunay) String (Frontal-Delaunay or Delaunay) to set the 2D meshing algoritm
line_type: (Optional, defaults to Splines) 'Splines' or 'Lines' to indicate the type lines used !!! only works if there are no boundaries, otherwise it's splines !!!
no_mesh_def: (Optional, defaults to False) True/False boolean which if True, ignores all mesh size fields
prevent_add_nodes_to_edge: (Optional, defaults to False) True/False boolean which if True, prevents adding nodes to the domain edge
create_background_file: (Optional, defaults to True) True/False boolean which indicates whether to create a background file
add_physical_lines: (Optional, defaults to True) True/False boolean which indicates whether to create physical lines
physical_line_labels: (Optional, defaults to ['closed', 'TidalEntrance', 'ClosedInner']) dictionary of label names for the physical lines.
inner_rings_splines: (Optional, defaults to True) True/False boolean to indicate wheter to use splines (True) or lines (False) to create the inner rings
"""
# set attributes
self.algo = algo
if type(cell_size) == int: self.holescellsize = cell_size
else: self.holescellsize = zerovalues
self.inner = inner_rings
# Open txt file and allow both writing and reading
f = open(filename, "w+")
self.geofile = filename
# write header to indicate the right engine
f.write("SetFactory(\"OpenCASCADE\");\n")
f.write("Geometry.OCCParallel = 1;\n")
f.write("General.NumThreads = 6;\n//+\n")
# initialize counters for each features
self.n_Point = 1
self.n_Spline = 1
self.n_LineLoop = 1
self.n_Surface = 1
# attribute if there are inner rings
self.inner_rings = inner_rings
# reorganize the boundary node id's, exterior node id's and break id's to put make sure the first boundary node has id 1
self.reorganize()
# write the point lines
f = self.writeGeoFileAddPoints(f)
# write the spline/line lines
if line_type == 'Splines':
f = self.writeGeoFileAddSplines(f, spline_max_len = spline_max_len, line_type = line_type)
else:
f = self.writeGeoFileAddLines(f)
# write points and inner rings
if inner_rings:
f = self.writeGeoFileInnerRings(f, inner_rings_splines = inner_rings_splines)
# write physical boundaries
if add_physical_lines:
f = self.writeGeoFilePhysicalLines(f, physical_line_labels = physical_line_labels)
# A priori resolution
f = self.writeGeoFileAPrioriResolution(f)
# if indicated precent adding nodes to the domain edges
if prevent_add_nodes_to_edge:
f.write('Transfinite Line "*" = 2;\n')
# Line loop and surface of exterior ring
f = self.writeGeoFileLineLoopsSurface(f, inner_rings = inner_rings)
print('background file: ', background_file)
# set mesh cell size
if not no_mesh_def:
if type(cell_size) == str:
if cell_size == 'hetero':
if len(background_file) > 0: create_background_file = False;
f = self.writeGeoFileHeterogeneousMesh(f, XY_field, multiplier = multiplier, minres = minres, maxres = maxres,
zerovalues = zerovalues, outside_mesh_size = outside_mesh_size, plot_arr = plot_arr,
buffer_interpolation = buffer_interpolation, create_background_file = create_background_file, background_file = background_file)
elif type(cell_size) == float or type(cell_size) == int:
f = self.writeGeoFileHomogeneousMesh(f, mesh_size = cell_size)
# set meshing algorithm
f = self.writeGeoFileDefineAlgorithm(f, algorithm = algo)
# close the .geo file
f.close()
def reorganize(self):
"""
Method to reorganize XY list, break list and boundaries to put boundary nodes first
There is a spline-break at the first vertex anyway, so if there is a boundary, place the first vertex of that boundary
as the first vertex so the break will be at a boundary. To do so, we go over all vertices id's and store them in a list
when we pass the vertex id which is the first vertex of the first boundary, we start storing the vertex id's in a new list.
The first list will now be stiched after the second one.
For example, if we have an exterior of 10 vertices and there is one boundary between 4 - 5:
original exterior vertex id's list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
new exterior vertex id's list: [4, 5, 6, 7, 8, 10, 1, 2, 3]
Note that the inner rings does not have to be reorganized since they cannot hold a boundary per definition.
Args:
-
"""
# only proceed if there are boundaries
if len(self.boundaries) > 0:
# id of the first vertex of the first boundary
b0 = self.boundaries[0][0]
# initiate two lists
exter_re1, exter_re2 = [], []
# loop over all vertices in the exterior
for i in range(len(self.exter)):
# if i + i (vertex id's start at 1) is smaller than the first vertex of the first boundary ...
if i + 1 < b0:
# ... append that vertex to the first list
exter_re1.append(self.exter[i])
else:
# otherwise, append to the other list
exter_re2.append(self.exter[i])
# rearange order by placing the second list first
exter_re2.extend(exter_re1)
# exter is now the updated exter list
self.exter = exter_re2
# this means that the id's are reorganized and so, also the boundaries and breaks should be reorganized
boundaries_upd = []
# reorganize all boundary id's
#
# example: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] with boundaries at 4 - 5 and 8 - 10
# Here, the new boundaries will be: 1 - 2 and 5 - 7
for b_l, b_r in self.boundaries:
boundaries_upd.append([b_l - b0 + 1, b_r - b0+1])
self.boundaries = boundaries_upd
# similar for all break points
# however, now break id's can be smaller than 0 and thus - b0 + 1 can result in updated breaks equal to 0 or 1
# in that case, add the total number of exterior breaks
#
# example: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# * ^''^ * ^'''''^
# with boundaries at 4 - 5 and 8 - 10 and breaks at 2 and 6
#
# updated example: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# ^''^ * ^'''''^ *
# Here, the new boundaries will be: 1 - 2 and 5 - 7
# and the new breaks will be: 3
breaks_upd = []
breaks = [b-b0+1 for b in self.breaks]
self.breaks = [b+n if b < 1 else b for b in breaks ]
def writeGeoFileAddPoints(self, f):
"""
Method to add all points of the exterior points to the .geo file
Args:
f: (Required) open text file representing the .geo file
"""
# add a header
f.write('//+\n//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n'
'//+ Points\n//++++++++++++++++++++++++++++++++++++++++++++++++++++'
'++++++++++++++++++\n')
# each exterior vertex should be added as a Point in the format:
# Point(1) = {x, y, 0}
# Point(2) = {x, y, 0}
# ...
# loop over all points in the exterior
for i in range(np.shape(self.exter)[0]):
f.write("Point(%d) = {%f, %f, 0};\n" % (self.n_Point, self.exter[i][0], self.exter[i][1]))
# keep track of the number of points
self.n_Point += 1
return f
def writeGeoFileAddSplines(self, f, spline_max_len = False, line_type = 'Splines'):
"""
Method to write the splines and lins of the exterior in the .geo file
Args:
f: (Required) open text file representing the .geo file
spline_max_len: (Optional, defaults to False) False/int to indicate the max length of a spline (if False, there is no limit)
line_type: (Optional, defaults to Splines) 'Splines' or 'Lines' to indicate the type lines used !!! only works if there are no boundaries, otherwise it's splines !!!
"""
# add a header
f.write('//+\n//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n'
'//+ Lines & Splines\n//++++++++++++++++++++++++++++++++++++++++++++++++++++'
'++++++++++++++++++\n')
# initate local variables
boundaries = self.boundaries
breaks = self.breaks
n_Spline = self.n_Spline
n_Point = self.n_Point
# if there are boundaries
if len(boundaries) > 0:
# initialize list to store the id's of the boundaries (or 'physical lines')
physical_lines = []
# start with first boundary since the first vertices will always be boundary vertices in case of boundaries
# first boundary
bound1 = boundaries[0]
# length of first boundary
bound1diff = bound1[1]-bound1[0]
# write a line object in the .geofile
# format example:
# Line(1) = {1, 2}
# where the numbers inbetween curly brackets refer to .geo point id's
f.write('Line(%d) = {1, ' % n_Spline)
# add this id to the list of physical boundaries
physical_lines.append(n_Spline)
# add a line per segment of the first boundary
# for instance if there is a boundary 1 - 3:
# Line(1) = {1, 2}
# Line(2) = {2, 3}
for i in range(0,bound1diff-1):
f.write('%d};\n' % (i+2))
# keep track of number of lines and splines
n_Spline += 1
f.write('Line(%d) = {%d,' % (n_Spline, i+2))
# add all these lines to the list of physical boundaries
physical_lines.append(n_Spline)
# finish the open Line definition
f.write('%d};\n' % (bound1diff+1))
n_Spline += 1
# write the last line of the first boundary
f.write('Spline(%d) = {%d, ' % (n_Spline, bound1diff+1))
n_Spline += 1
# variable to keep track if the loop is inside a boundary or not
b = False
# initiate a variable to count the number of segments in a spline
spline_counter = 0
# loop over all points except for the points in the first boundary
for i in range(bound1diff+2,n_Point):
# if the point is the starting vertex of a boundary, close whatever was open en start a new line
# Example:
#
# before: Spline(n) = {1, 2, 3 .
# newly added:
# , 4};
# Line(n + 1) = Line{4,
#
if list(np.asarray(boundaries)[:,0]).__contains__(i):
# start a new line
f.write("%d};\nLine(%d) = {%d, " % (i, n_Spline, i))
physical_lines.append(n_Spline)
n_Spline += 1
# the loop is inside a boundary
b = True
# if the boundary only exist out of one vertex
if list(np.asarray(boundaries)[:, 1]).__contains__(i+1):
b = False
# if the vertex is part of a boundary
elif b:
# close the upper line and start a new one
# Example:
#
# before:
# Line(n + 1) = Line{4,
# new:
# Line(n + 1) = Line{4, 5};
# Line(n + 2) = Line{5,
f.write('%d};\nLine(%d) = {%d,' % (i, n_Spline, i))
physical_lines.append(n_Spline)
n_Spline += 1
# close the boundary flag if this vertex is the second last vertex of a boundary
if list(np.asarray(boundaries)[:, 1]).__contains__(i+1):
b = False
# if the vertex is the closing vertex of a boundary
elif list(np.asarray(boundaries)[:, 1]).__contains__(i):
# close the upper line and start a new one
# Example:
#
# before:
# Line(n + 2) = Line{5,
# new:
# Line(n + 2) = Line{5, 6};
# Line(n + 3) = Line{6,
f.write("%d};\nSpline(%d) = {%d," % (i, n_Spline,i))
n_Spline += 1
# if this point is also the last point
# Example:
#
# before:
# Line(n + 3) = Line{6,
# new:
# Line(n + 3) = Line{6, 7};
if i == n_Point-1:
f.write("%d, 1};\n" % (n_Point-1))
# if this point is also the last point (and no part of a boundary)
# Example:
#
# before:
# Line(n + 3) = Line{6,
# new:
# Line(n + 3) = Line{6, 7};
elif i == n_Point-1:
f.write("%d, 1};\n" % (n_Point-1))
# if the vertex is just a regular vertex and no part of a boundary
else:
# count the number of vertices in a spline
spline_counter += 1
# if there is a max spline length indicated
if spline_max_len:
# if the number of vertices in the spline is lower than than the allowed number
if spline_counter <= spline_max_len:
# if the vertex is a break
if breaks.__contains__(i):
# close the upper spline and start a new one
f.write("%d};\nSpline(%d) = {%d, " % (i, n_Spline, i))
n_Spline += 1
else:
# if the vertex is not a break and the number of vertices is lower than the allowed number, just add the point to the open spline
f.write("%d, " % (i))
else:
# if the number of vertices in this spline would exceed the allowed number, start a new spline
f.write("%d};\nSpline(%d) = {%d, " % (i, n_Spline, i))
n_Spline += 1
spline_counter = 0
# if there is not a maximum number of vertices in a spline indicated, check if it is a break vertex
elif breaks.__contains__(i):
f.write("%d};\nSpline(%d) = {%d, " % (i, n_Spline, i))
n_Spline += 1
# if there is not a maximum number of vertices in a spline and if it is not a break vertex, just add it to the open spline
else:
f.write("%d, " % (i))
# if there are no boundaries indicates
else:
if line_type == 'Splines':
# create first spline
f.write('Spline(1) = {')
# add all points to this spline, except for breaks. In that case, start a new spline
for i in range(1,n_Point):
# in case of breaker vertices
if breaks.__contains__(i):
f.write("%d};\nSpline(%d) = {%d, " % (i, n_Spline, i))
n_Spline += 1
f.write('%d,' % (i))
f.write('%d};\n' % (1))
n_Spline += 1
elif line_type == 'Lines':
# create first Line
f.write('Line(1) = {1,')
n_Spline += 1
# add all points to this spline, except for breaks. In that case, start a new spline
for i in range(2,n_Point-1):
f.write("%d};\nLine(%d) = {%d, " % (i, n_Spline, i))
n_Spline += 1
f.write('%d};\n' % (n_Point-1))
f.write('Line(%d) = {%d,1};\n' % (n_Spline, n_Point-1))
n_Spline += 1
# globalize local variables
self.n_Spline = n_Spline
if len(boundaries) > 0:
self.physical_lines = physical_lines
return f
def writeGeoFileAddLines(self, f):
"""
Method to write and lines of the exterior in the .geo file
Args:
f: (Required) open text file representing the .geo file
"""
# add a header
f.write('//+\n//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n'
'//+ Lines & Splines\n//++++++++++++++++++++++++++++++++++++++++++++++++++++'
'++++++++++++++++++\n')
physical_lines = []
for i in range(1, self.n_Point-1):
f.write('Line(%d) = {%d, %d};\n' % (i,i,i+1))
for boundary in self.boundaries:
if i >= boundary[0] and i+1 <= boundary[1]:
physical_lines.append(i)
f.write('Line(%d) = {%d, 1};\n' % (i+1,i+1))
# globalize local variables
self.n_Spline = i+2
if len(self.boundaries) > 0:
self.physical_lines = physical_lines
return f
def writeGeoFileInnerRings(self, f, inner_rings_splines = True):
"""
Method to include inner rings (holes) in the .geo file
Args:
f: (Required) open text file representing the .geo file
inner_rings_splines: (Optional, defaults to True) True/False boolean to indicate to use splines for the inner ring, the alternative are lines
"""
# initate local variables
boundaries = self.boundaries
breaks = self.breaks
n_Spline = self.n_Spline
n_Point = self.n_Point
# check if there are inner rings
if self.inner:
# write a header
f.write('//+\n//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n'
'//+ Holes Points\n//++++++++++++++++++++++++++++++++++++++++++++++++++++'
'++++++++++++++++++\n')
# save all inner points in a list per ring
Inner_Points = []
# loop over all inter rings
for I in self.inter:
Inner_Points.append([])
# loop over all vertices in an inner ring
for i in range(0,len(I)):
# add as a point
f.write("Point(%d) = {%f, %f, 0};\n" % (n_Point, I[i][0], I[i][1]))
# save the point id's in a list of list per inner ring
Inner_Points[-1].append(n_Point)
n_Point += 1
# start a new counter to include all inner rings
n_Spline_inter = n_Spline
if self.inner:
# create header
f.write('//+\n//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n'
'//+ Holes Splines\n//++++++++++++++++++++++++++++++++++++++++++++++++++++'
'++++++++++++++++++\n')
# initiate a list to store all inner spline ids
Inner_Splines_ids = []
Inner_Splines_ids_perLL = []
# create a spline per inner ring (no boundaries are allowed at this point)
# create splines if indicated
if inner_rings_splines:
for I in Inner_Points:
# open a spline
f.write('Spline(%d) = {' % (n_Spline_inter))
# add the id to the list of inner splines
Inner_Splines_ids.append(n_Spline_inter)
Inner_Splines_ids_perLL.append([n_Spline_inter])
# count
n_Spline_inter += 1
# add all points to this spline
for i in I:
if not i in self.breaks or i == I[0]:
f.write('%d,' % (i))
else:
f.write('%d};\n' % (i))
f.write('Spline(%d) = {%d,' % (n_Spline_inter, i))
# count
Inner_Splines_ids.append(n_Spline_inter)
Inner_Splines_ids_perLL[-1].append(n_Spline_inter)
n_Spline_inter += 1
# end with the first point of the ring
f.write('%d};\n' % (I[0]))
# otherwise, create rings of lines
elif not inner_rings_splines:
for I in Inner_Points:
# open a spline
f.write('Line(%d) = {%d,' % (n_Spline_inter, I[0]))
Inner_Splines_ids.append(n_Spline_inter)
Inner_Splines_ids_perLL.append([n_Spline_inter])
n_Spline_inter += 1
# add all points to this spline
for n in range(1,len(I)):
i = I[n]
f.write('%d};\nLine(%d) = {%d,' % (i, n_Spline_inter, i))
Inner_Splines_ids.append(n_Spline_inter)
Inner_Splines_ids_perLL[-1].append(n_Spline_inter)
n_Spline_inter += 1
# end with the first point of the ring
f.write('%d};\n' % (I[0]))
# globalize local variables
if self.inner:
self.Inner_Points = Inner_Points
self.Inner_Splines_ids = Inner_Splines_ids
self.n_Spline_inter = n_Spline_inter
self.Inner_Splines_ids_perLL = Inner_Splines_ids_perLL
self.n_Spline = n_Spline
self.n_Point = n_Point
return f
def writeGeoFilePhysicalLines(self, f, physical_line_labels = ['closed', 'TidalEntrance', 'ClosedInner']):
"""
Method to set the physical properties of the boundaries
Args:
f: (Required) open text file representing the .geo file
physical_line_labels: (Optional, defaults to {'closed', 'TidalEntrance', 'ClosedInner'}) dictionary of label names for the physical lines.
"""
# set local variables based on global variables
boundaries = self.boundaries
n_Spline = self.n_Spline
if self.inner:
Inner_Splines_ids = self.Inner_Splines_ids
physical_lines = self.physical_lines
# add a header
f.write('//+\n//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n'
'//+ Boundaries\n//++++++++++++++++++++++++++++++++++++++++++++++++++++'
'++++++++++++++++++\n')
# if there are no boundaries
if len(boundaries)==0:
f.write('Physical Line("%s") = {' % physical_line_labels[0])
for i in range(n_Spline-2):
f.write(' %d, ' % (i+1))
f.write('%d};\n' % (n_Spline-1))
# if there are boundaries
else:
# initialize a list to store all id's of what should be closed lines
closed_lines = []
# loop over all
for i in range(1, n_Spline):
# if it is not a physical line
if not physical_lines.__contains__(i):
closed_lines.append(i)
# add all boundary lines to the physical line
if len(boundaries) > 0:
j = 1
f.write('Physical Line("%s") = {1' % physical_line_labels[j])
for i in range(1,len(physical_lines)):
if physical_lines[i] - physical_lines[i-1] == 1:
# add each pysical line
f.write(', %d' % (physical_lines[i]))
else:
f.write('};\n')
j += 1
f.write('Physical Line("%s") = {%d' % (physical_line_labels[j], physical_lines[i]))
# close the physical line object
f.write('};\n')
# non-boundary lines are 'closed' boundaries
f.write('Physical Line("%s") = {' % physical_line_labels[0])
for i in range(len(closed_lines)-1):
f.write(' %d,' % (closed_lines[i]))
f.write(' %d};\n' % (closed_lines[-1]))
if self.inner_rings:
# all inner holes are ClosedInner boundaries
f.write('Physical Line("%s") = {' % physical_line_labels[-1])
for i in range(len(Inner_Splines_ids)-1):
f.write(' %d,' % (Inner_Splines_ids[i]))
f.write(' %d};\n' % (Inner_Splines_ids[-1]))
return f
def writeGeoFileAPrioriResolution(self, f):
"""
Method to set a a priori resolution
Args:
f: (Required) open text file representing the .geo file
autocoherence: (Optional, defaults to False) True/False boolean to indicate autocoherence
"""
# add header
f.write('//+\n//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n'
'//+ A Priori Resolution\n//++++++++++++++++++++++++++++++++++++++++++++++++++++'
'++++++++++++++++++\n')
# Accuracy of evaluation of the LC field for 1D mesh generation
# Prohibit boundary mesh size interpolation (suggested if the cell size is indicated by a field)
f.write('Mesh.LcIntegrationPrecision = 1e-3;\nMesh.CharacteristicLengthExtendFromBoundary = 0;\n')
return f
def writeGeoFileLineLoopsSurface(self, f, inner_rings = True):
"""
Method to write the lines indicating the line loop and surface of the exterior ring
Args:
f: (Required) open text file representing the .geo file
inner_rings: (Optional, defaults to True) True/False boolean to add the points and splines of the inner rings
"""
# set global parameters as local
n_LineLoop = self.n_LineLoop
n_Surface = self.n_Surface
n_Point = self.n_Point
n_Spline = self.n_Spline
if inner_rings:
Inner_Splines_ids_perLL = self.Inner_Splines_ids_perLL
# create a header
f.write('//+\n//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n'
'//+ Create Line Loop and Surface\n//++++++++++++++++++++++++++++++++++++++++++++++++++++'
'++++++++++++++++++\n')
# create a line loop with all splines of the exterior
# open the line loop
f.write('Line Loop(%d) = {' % (n_LineLoop))
n_LineLoop += 1
# add all the splines
for i in range(1,n_Spline-1):
f.write('%d, '% (i))
# close the line loop
f.write('%d};\n' % (n_Spline-1))
# check if there are inner rings
if inner_rings:
# create header
f.write('//+ Inner loops\n')
# counter
t = 0
# go over all inner rings ( Inner_Points is a list of list and so, its length is equal to the number of rings)
for I in self.Inner_Points:
# per ring, write a line loop, plane and physical surface
f.write('Line Loop(%d) = {%d' % (n_LineLoop, Inner_Splines_ids_perLL[t][0]))
if len(Inner_Splines_ids_perLL[t]) > 1:
for i in range(1, len(Inner_Splines_ids_perLL[t])):
f.write(', %d' % Inner_Splines_ids_perLL[t][i])
f.write('};\n')