wolfhece 2.2.7__py3-none-any.whl → 2.2.9__py3-none-any.whl

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.
wolfhece/PyDraw.py CHANGED
@@ -4305,7 +4305,7 @@ class WolfMapViewer(wx.Frame):
4305
4305
  if self.active_cloud is not None and self.active_array is not None:
4306
4306
 
4307
4307
  keyvalue='z'
4308
- if self.active_cloud.header:
4308
+ if self.active_cloud.has_values:
4309
4309
  choices = list(self.active_cloud.myvertices[0].keys())
4310
4310
  dlg = wx.SingleChoiceDialog(None, "Pick the value to interpolate", "Choices", choices)
4311
4311
  ret = dlg.ShowModal()
wolfhece/PyVertex.py CHANGED
@@ -549,7 +549,7 @@ class cloud_vertices(Element_To_Draw):
549
549
 
550
550
  self.loaded = False
551
551
 
552
- self.header = header
552
+ self._header = header
553
553
 
554
554
  self.gllist = 0
555
555
  self.forceupdategl = False
@@ -570,7 +570,7 @@ class cloud_vertices(Element_To_Draw):
570
570
  if Path(fname).suffix.lower() == '.dxf':
571
571
  self.import_from_dxf(self.filename, imported_elts=dxf_imported_elts)
572
572
  elif Path(fname).suffix.lower() == '.shp':
573
- self.import_shapefile(self.filename, bbox=bbox)
573
+ self.import_from_shapefile(self.filename, bbox=bbox)
574
574
  else:
575
575
  self.readfile(self.filename, header)
576
576
 
@@ -586,24 +586,53 @@ class cloud_vertices(Element_To_Draw):
586
586
  xyz = self.get_xyz()
587
587
  self.mytree = KDTree(xyz)
588
588
 
589
- def find_nearest(self, xy:np.ndarray, nb:int =1):
589
+ def find_nearest(self, xyz:np.ndarray | list, nb:int =1):
590
590
  """
591
- Find nearest neighbors from Scipy KDTree structure based on a copy of the vertices
591
+ Find nearest neighbors from Scipy KDTree structure based on a copy of the vertices.
592
592
 
593
- Return :
594
- - list of distances
595
- - list of "Wolfvertex"
596
- - list of elements stored in self.myvertices
593
+ See : https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.KDTree.query.html
594
+
595
+ :param xyz: coordinates to find nearest neighbors -- shape (n, m) - where m is the number of coordinates (2 or 3)
596
+ :param nb: number of nearest neighbors to find
597
+ :return: list of distances, list of "Wolfvertex", list of elements stored in self.myvertices - or list of lists if xyz is a list of coordinates
597
598
  """
598
599
 
599
- keys = self.myvertices.keys()
600
- if self.mytree is None:
601
- self.create_kdtree()
600
+ if isinstance(xyz, list):
601
+ if len(xyz) > 0:
602
+ if isinstance(xyz[0], float | int):
603
+ logging.warning(_('xyz is a list of floats -- converting to a list of lists'))
604
+ xyz = [xyz]
605
+
606
+ xyz = np.array(xyz)
607
+
608
+ try:
609
+ keys = list(self.myvertices.keys())
610
+ if self.mytree is None:
611
+ self.create_kdtree()
612
+
613
+ if xyz.shape[1] != self.mytree.m:
614
+ logging.error(_('Error in find_nearest -- xyz must be a list or 2D array with {} columns').format(self.mytree.m))
615
+ return None, None, None
616
+
617
+ dist, ii = self.mytree.query(xyz, k=nb)
602
618
 
603
- dist, ii = self.mytree.query(xy, k=nb)
604
- return dist, [self.myvertices[keys[curi]]['vertex'] for curi in ii], [self.myvertices[keys[curi]] for curi in ii]
619
+ if xyz.shape[0] == 1:
620
+ if nb == 1:
621
+ return dist[0], self.myvertices[keys[ii[0]]]['vertex'], self.myvertices[keys[ii[0]]]
622
+ else:
623
+ return dist[0], [self.myvertices[keys[curi]]['vertex'] for curi in ii[0]], [self.myvertices[keys[curi]] for curi in ii[0]]
624
+ else:
625
+ if nb == 1:
626
+ return dist, [self.myvertices[keys[curi]]['vertex'] for curi in ii], [self.myvertices[keys[curi]] for curi in ii]
627
+ else:
628
+ return dist, [[self.myvertices[keys[curi]]['vertex'] for curi in curii] for curii in ii], [[self.myvertices[keys[curi]] for curi in curii] for curii in ii]
605
629
 
606
- def init_from_nparray(self, array):
630
+ except Exception as e:
631
+ logging.error(_('Error in find_nearest -- {}').format(e))
632
+ logging.error(_('Check your input data -- it must be a list or 2D array with 3 columns'))
633
+ return None, None, None
634
+
635
+ def init_from_nparray(self, array:np.ndarray):
607
636
  """
608
637
  Fill-in from nparray -- shape (n,3)
609
638
  """
@@ -624,7 +653,7 @@ class cloud_vertices(Element_To_Draw):
624
653
 
625
654
  self.plotted = True
626
655
  if not self.loaded:
627
- self.readfile(self.filename, self.header)
656
+ self.readfile(self.filename, self._header)
628
657
  self.loaded = True
629
658
 
630
659
  def uncheck_plot(self, unload=True):
@@ -703,11 +732,11 @@ class cloud_vertices(Element_To_Draw):
703
732
  nbcols = len(curval)
704
733
 
705
734
  if nbcols < 2:
706
- print('Not enough values on one line -- Retry !!')
735
+ logging.warning(_('Not enough values on one line -- Retry !!'))
707
736
  return
708
737
  elif nbcols > 3:
709
738
  if headers is None:
710
- print('No headers -- Retry !!')
739
+ logging.warning(_('No headers -- Retry !!'))
711
740
  return
712
741
  else:
713
742
  if headers[2].lower() == 'z':
@@ -847,7 +876,7 @@ class cloud_vertices(Element_To_Draw):
847
876
 
848
877
  return k
849
878
 
850
- def import_shapefile(self, fn:str='', targetcolumn:str = 'X1_Y1_Z1', bbox:Polygon = None):
879
+ def import_from_shapefile(self, fn:str='', targetcolumn:str = 'X1_Y1_Z1', bbox:Polygon = None):
851
880
  """
852
881
  Importing Shapefile using geopandas library
853
882
 
@@ -1235,7 +1264,7 @@ class cloud_vertices(Element_To_Draw):
1235
1264
 
1236
1265
  """
1237
1266
 
1238
- if self.header:
1267
+ if self._header:
1239
1268
  if key=='vertex':
1240
1269
  xyz = [[curvert['vertex'].x, curvert['vertex'].y, curvert['vertex'].z] for curvert in self.myvertices.values()]
1241
1270
  else:
@@ -1244,6 +1273,63 @@ class cloud_vertices(Element_To_Draw):
1244
1273
  xyz = [[curvert['vertex'].x, curvert['vertex'].y, curvert['vertex'].z] for curvert in self.myvertices.values()]
1245
1274
  return np.array(xyz)
1246
1275
 
1276
+ @property
1277
+ def has_values(self) -> bool:
1278
+ """
1279
+ Check if the cloud has values (other than X,Y,Z)
1280
+ """
1281
+
1282
+ if self._header:
1283
+ return len(self.myvertices[0]) > 1
1284
+ else:
1285
+ return False
1286
+
1287
+ @property
1288
+ def header(self) -> list[str]:
1289
+ """
1290
+ Return the headers of the cloud
1291
+ """
1292
+
1293
+ if self._header:
1294
+ return list(self.myvertices[0].keys())
1295
+ else:
1296
+ return []
1297
+
1298
+ def add_values_by_id_list(self, id:str, values:list[float]):
1299
+ """
1300
+ Add values to the cloud
1301
+
1302
+ :param id: use as key for the values
1303
+ :param values: list of values to be added - must be the same length as number of vertices
1304
+
1305
+ """
1306
+
1307
+ if len(values) != len(self.myvertices):
1308
+ logging.warning(_('Number of values does not match the number of vertices -- Retry !!'))
1309
+ logging.info(_(('Number of vertices : ')+str(len(self.myvertices))))
1310
+ return
1311
+
1312
+ for item, val in zip(self.myvertices.values(), values):
1313
+ item[id] = val
1314
+
1315
+ self._header = True
1316
+
1317
+ @property
1318
+ def xyz(self) -> np.ndarray:
1319
+ """
1320
+ Alias for get_xyz method
1321
+ """
1322
+
1323
+ return self.get_xyz(key='vertex')
1324
+
1325
+ @property
1326
+ def nbvertices(self) -> int:
1327
+ """
1328
+ Number of vertices in the cloud
1329
+ """
1330
+
1331
+ return len(self.myvertices)
1332
+
1247
1333
  def interp_on_array(self, myarray, key:str='vertex', method:Literal['linear', 'nearest', 'cubic'] = 'linear'):
1248
1334
  """
1249
1335
  Interpolation of the cloud on a 2D array
@@ -53,6 +53,9 @@ from .matplotlib_fig import Matplotlib_Figure as MplFig
53
53
  from .PyPalette import wolfpalette
54
54
 
55
55
  class Triangulation(Element_To_Draw):
56
+ """ Triangulation based on a listof vertices
57
+ and triangles enumerated by their vertex indices """
58
+
56
59
  def __init__(self, fn='', pts=[], tri=[], idx: str = '', plotted: bool = True, mapviewer=None, need_for_wx: bool = False) -> None:
57
60
  super().__init__(idx, plotted, mapviewer, need_for_wx)
58
61
 
@@ -74,20 +77,24 @@ class Triangulation(Element_To_Draw):
74
77
  self.filename=fn
75
78
  self.read(fn)
76
79
  else:
77
- self.valid_format()
80
+ self.validate_format()
78
81
  pass
79
82
 
80
- def valid_format(self):
83
+ def validate_format(self):
84
+ """ Force the format of the data """
85
+
81
86
  if isinstance(self.pts,list):
82
87
  self.pts = np.asarray(self.pts)
83
88
  if isinstance(self.tri,list):
84
89
  self.tri = np.asarray(self.tri)
85
90
 
86
91
  def as_polydata(self) -> pv.PolyData:
92
+ """ Convert the triangulation to a PyVista PolyData object """
87
93
 
88
94
  return pv.PolyData(np.asarray(self.pts),np.column_stack([[3]*self.nb_tri,self.tri]), self.nb_tri)
89
95
 
90
- def from_polydata(self,poly:pv.PolyData):
96
+ def from_polydata(self, poly:pv.PolyData):
97
+ """ Convert a PyVista PolyData object to the triangulation format """
91
98
 
92
99
  self.pts = np.asarray(poly.points.copy())
93
100
  self.tri = np.asarray(poly.faces.reshape([int(len(poly.faces)/4),4])[:,1:4])
@@ -95,7 +102,8 @@ class Triangulation(Element_To_Draw):
95
102
  self.nb_pts = len(self.pts)
96
103
  self.nb_tri = len(self.tri)
97
104
 
98
- def clip_surface(self,other,invert=True,subdivide=0):
105
+ def clip_surface(self, other:"Triangulation", invert=True, subdivide=0):
106
+ """ Clip the triangulation with another one """
99
107
 
100
108
  if subdivide==0:
101
109
  mypoly = self.as_polydata()
@@ -104,14 +112,12 @@ class Triangulation(Element_To_Draw):
104
112
  mypoly = self.as_polydata().subdivide(subdivide)
105
113
  mycrop = other.as_polydata().subdivide(subdivide)
106
114
 
107
- res = mypoly.clip_surface(mycrop,invert=invert)
115
+ res = mypoly.clip_surface(mycrop, invert=invert)
108
116
 
109
117
  if len(res.faces)>0:
110
118
  self.from_polydata(res)
111
- else:
112
- return None
113
119
 
114
- def get_mask(self,eps=1e-10):
120
+ def get_mask(self, eps:float= 1e-10):
115
121
  """
116
122
  Teste si la surface de tous les triangles est positive
117
123
 
@@ -122,7 +128,7 @@ class Triangulation(Element_To_Draw):
122
128
  v1 = [self.pts[curtri[1]][:2] - self.pts[curtri[0]][:2] for curtri in self.tri]
123
129
  v2 = [self.pts[curtri[2]][:2] - self.pts[curtri[0]][:2] for curtri in self.tri]
124
130
  self.areas = np.cross(v2,v1,)/2
125
- return self.areas<=eps
131
+ return self.areas <= eps
126
132
 
127
133
  # invalid_tri = np.sort(np.where(self.areas<=eps)[0])
128
134
  # for curinv in invalid_tri[::-1]:
@@ -131,8 +137,11 @@ class Triangulation(Element_To_Draw):
131
137
  # self.nb_tri = len(self.tri)
132
138
 
133
139
  def import_from_gltf(self, fn=''):
140
+ """ Import a GLTF file and convert it to the triangulation format """
134
141
 
135
- if fn =='':
142
+ wx_exists = wx.GetApp() is not None
143
+
144
+ if fn =='' and wx_exists:
136
145
  dlg=wx.FileDialog(None,_('Choose filename'),wildcard='binary gltf2 (*.glb)|*.glb|gltf2 (*.gltf)|*.gltf|All (*.*)|*.*',style=wx.FD_OPEN)
137
146
  ret=dlg.ShowModal()
138
147
  if ret==wx.ID_CANCEL:
@@ -141,6 +150,8 @@ class Triangulation(Element_To_Draw):
141
150
 
142
151
  fn=dlg.GetPath()
143
152
  dlg.Destroy()
153
+ else:
154
+ fn = str(fn)
144
155
 
145
156
  gltf = pygltflib.GLTF2().load(fn)
146
157
 
@@ -216,6 +227,7 @@ class Triangulation(Element_To_Draw):
216
227
  self.nb_tri = len(self.tri)
217
228
 
218
229
  def export_to_gltf(self,fn=''):
230
+ """ Export the triangulation to a GLTF file """
219
231
 
220
232
  #on force les types de variables
221
233
  triangles = np.asarray(self.tri).astype(np.uint32)
@@ -302,7 +314,7 @@ class Triangulation(Element_To_Draw):
302
314
  return
303
315
 
304
316
  if fn!='':
305
- self.filename=fn
317
+ self.filename = fn
306
318
 
307
319
  triangles = np.asarray(self.tri).astype(np.uint32)
308
320
  points = np.asarray(self.pts) #.astype(np.float64)
@@ -343,11 +355,13 @@ class Triangulation(Element_To_Draw):
343
355
  buf = np.frombuffer(f.read(4 * self.nb_tri * 3), dtype=np.uint32)
344
356
  self.tri = np.array(buf.copy(), dtype=np.uint32).reshape([self.nb_tri,3]).astype(np.int32)
345
357
 
346
- self.valid_format()
358
+ self.validate_format()
347
359
  self.find_minmax(True)
348
360
  self.reset_plot()
349
361
 
350
362
  def reset_plot(self):
363
+ """ Reset the OpenGL plot """
364
+
351
365
  try:
352
366
  if self.id_list!=-99999:
353
367
  glDeleteLists(self.id_list)
@@ -357,6 +371,7 @@ class Triangulation(Element_To_Draw):
357
371
  self.id_list = -99999
358
372
 
359
373
  def plot(self, sx=None, sy=None, xmin=None, ymin=None, xmax=None, ymax=None, size=None ):
374
+ """ Plot the triangulation in OpenGL """
360
375
 
361
376
  if self.id_list == -99999:
362
377
  try:
@@ -387,7 +402,25 @@ class Triangulation(Element_To_Draw):
387
402
  else:
388
403
  glCallList(self.id_list)
389
404
 
405
+ def plot_matplotlib(self, ax:Axes, color='black', alpha=1., lw=1.5, **kwargs):
406
+ """ Plot the triangulation in Matplotlib
407
+ """
408
+
409
+ if self.nb_tri>0:
410
+ for curtri in self.tri:
411
+ x = [self.pts[curtri[0]][0], self.pts[curtri[1]][0], self.pts[curtri[2]][0], self.pts[curtri[0]][0]]
412
+ y = [self.pts[curtri[0]][1], self.pts[curtri[1]][1], self.pts[curtri[2]][1], self.pts[curtri[0]][1]]
413
+ ax.plot(x, y, color=color, alpha=alpha, lw=lw, **kwargs)
414
+ else:
415
+ logging.warning('No triangles to plot')
416
+
417
+
390
418
  def find_minmax(self,force):
419
+ """ Find the min and max of the triangulation
420
+
421
+ :param force: force the min and max to be calculated
422
+ """
423
+
391
424
  if force:
392
425
  if self.nb_pts>0:
393
426
  self.xmin=np.min(self.pts[:,0])
@@ -396,6 +429,8 @@ class Triangulation(Element_To_Draw):
396
429
  self.ymax=np.max(self.pts[:,1])
397
430
 
398
431
  def import_dxf(self,fn):
432
+ """ Import a DXF file and convert it to the triangulation format """
433
+
399
434
  import ezdxf
400
435
 
401
436
  if not path.exists(fn):
@@ -431,7 +466,7 @@ class Triangulation(Element_To_Draw):
431
466
  self.pts = xyz_u
432
467
  self.nb_pts = len(self.pts)
433
468
  self.nb_tri = len(self.tri)
434
- self.valid_format()
469
+ self.validate_format()
435
470
 
436
471
  def set_cache(self):
437
472
  """ Set the cache for the vertices """
@@ -4038,7 +4073,7 @@ class zone:
4038
4073
  self.add_vector(mypl,0)
4039
4074
  self.add_vector(mypr,2)
4040
4075
 
4041
- def createmultibin(self, nb=None, nb2=0) -> Triangulation:
4076
+ def create_multibin(self, nb:int = None, nb2:int = 0) -> Triangulation:
4042
4077
  """
4043
4078
  Création d'une triangulation sur base des vecteurs
4044
4079
  Tient compte de l'ordre
@@ -4075,7 +4110,11 @@ class zone:
4075
4110
  nb=int(dlg.GetValue())
4076
4111
  dlg.Destroy()
4077
4112
  else:
4078
- logging.warning( _('Bad parameter nb'))
4113
+ try:
4114
+ nb=int(nb)
4115
+ except:
4116
+ logging.warning( _('Bad parameter nb'))
4117
+ return None
4079
4118
 
4080
4119
  # redécoupage des polylines
4081
4120
  s = np.linspace(0.,1.,num=nb,endpoint=True)
@@ -4095,10 +4134,16 @@ class zone:
4095
4134
  ret=dlg.ShowModal()
4096
4135
  if ret==wx.ID_CANCEL:
4097
4136
  dlg.Destroy()
4098
- return
4137
+ return None
4099
4138
 
4100
4139
  nb2=int(dlg.GetValue())
4101
4140
  dlg.Destroy()
4141
+ else:
4142
+ try:
4143
+ nb2=int(nb2)
4144
+ except:
4145
+ logging.warning( _('Bad parameter nb2'))
4146
+ return None
4102
4147
 
4103
4148
  if nb2>0:
4104
4149
  finalls = []
@@ -4187,9 +4232,12 @@ class zone:
4187
4232
 
4188
4233
  return interp
4189
4234
 
4190
- def create_constrainedDelaunay(self, nb=None) -> Triangulation:
4235
+ def create_constrainedDelaunay(self, nb:int = None) -> Triangulation:
4191
4236
  """
4192
- Création d'une triangulation Delaunay contrainte sur base des vecteurs
4237
+ Création d'une triangulation Delaunay contrainte sur base des vecteurs de la zone.
4238
+
4239
+ Il est nécessaire de définir au moins un polygone définissant la zone de triangulation.
4240
+ Les autres vecteurs seront utilisés comme contraintes de triangulation.
4193
4241
 
4194
4242
  Utilisation de la librairie "triangle" (https://www.cs.cmu.edu/~quake/triangle.delaunay.html)
4195
4243
 
@@ -4223,14 +4271,18 @@ class zone:
4223
4271
  nb=int(dlg.GetValue())
4224
4272
  dlg.Destroy()
4225
4273
  else:
4226
- logging.warning( _('Bad parameter nb'))
4274
+ try:
4275
+ nb=int(nb)
4276
+ except:
4277
+ logging.warning( _('Bad parameter nb'))
4278
+ return None
4227
4279
 
4228
4280
  if nb==0:
4229
4281
  # no decimation
4230
4282
  newls = myls
4231
4283
  else:
4232
4284
  # redécoupage des polylines
4233
- s = np.linspace(0.,1.,num=nb,endpoint=True)
4285
+ s = np.linspace(0., 1., num=nb, endpoint=True)
4234
4286
 
4235
4287
  newls = [LineString([curls.interpolate(curs,True) for curs in s]) for curls in myls if curls.length>0.]
4236
4288
 
@@ -7690,7 +7742,7 @@ class Zones(wx.Frame, Element_To_Draw):
7690
7742
  dlg.Destroy()
7691
7743
  return
7692
7744
 
7693
- mytri = myzone.createmultibin()
7745
+ mytri = myzone.create_multibin()
7694
7746
 
7695
7747
  self.mapviewer.add_object('triangulation',newobj=mytri)
7696
7748
  self.mapviewer.Refresh()
wolfhece/apps/version.py CHANGED
@@ -5,7 +5,7 @@ class WolfVersion():
5
5
 
6
6
  self.major = 2
7
7
  self.minor = 2
8
- self.patch = 7
8
+ self.patch = 9
9
9
 
10
10
  def __str__(self):
11
11