From 6ebbe8a730f8919b0071616c87de3485690e82a1 Mon Sep 17 00:00:00 2001 From: craig-warren Date: Mon, 18 May 2020 15:53:51 +0100 Subject: [PATCH] Work on rotate method for geometry objects. --- gprMax/__init__.py | 8 +- gprMax/_version.py | 2 +- gprMax/cmds_geometry/box.py | 7 ++ gprMax/cmds_geometry/cmds_geometry.py | 114 ++++++++++-------- gprMax/cmds_geometry/cylindrical_sector.py | 3 + gprMax/cmds_geometry/geometry_objects_read.py | 3 + gprMax/cmds_geometry/sphere.py | 3 + gprMax/cmds_geometry/triangle.py | 4 + gprMax/{cmds_multiple.py => cmds_multiuse.py} | 71 ++++++++++- .../{cmds_single_use.py => cmds_singleuse.py} | 2 +- gprMax/geometry_outputs.py | 19 ++- gprMax/hash_cmds_multiuse.py | 4 +- gprMax/hash_cmds_singleuse.py | 8 +- gprMax/scene.py | 6 +- gprMax/subgrids/base.py | 3 +- gprMax/subgrids/subgrid_hsg.py | 2 +- gprMax/subgrids/user_objects.py | 6 +- 17 files changed, 188 insertions(+), 77 deletions(-) rename gprMax/{cmds_multiple.py => cmds_multiuse.py} (93%) rename gprMax/{cmds_single_use.py => cmds_singleuse.py} (96%) diff --git a/gprMax/__init__.py b/gprMax/__init__.py index 1580cb9b..cffc9724 100644 --- a/gprMax/__init__.py +++ b/gprMax/__init__.py @@ -22,14 +22,14 @@ from .cmds_geometry.geometry_objects_read import GeometryObjectsRead from .cmds_geometry.plate import Plate from .cmds_geometry.sphere import Sphere from .cmds_geometry.triangle import Triangle -from .cmds_multiple import (PMLCFS, AddDebyeDispersion, AddDrudeDispersion, +from .cmds_multiuse import (PMLCFS, AddDebyeDispersion, AddDrudeDispersion, AddLorentzDispersion, GeometryObjectsWrite, GeometryView, HertzianDipole, MagneticDipole, Material, Rx, RxArray, Snapshot, SoilPeplinski, TransmissionLine, VoltageSource, Waveform) -from .cmds_single_use import (Discretisation, Domain, ExcitationFile, - NumThreads, PMLCells, RxSteps, SrcSteps, - TimeStepStabilityFactor, TimeWindow, Title) +from .cmds_singleuse import (Discretisation, Domain, ExcitationFile, + NumThreads, PMLCells, RxSteps, SrcSteps, + TimeStepStabilityFactor, TimeWindow, Title) from .gprMax import run as run from .hash_cmds_file import user_libs_fn_to_scene_obj from .scene import Scene diff --git a/gprMax/_version.py b/gprMax/_version.py index e2efb1c7..f6041d97 100644 --- a/gprMax/_version.py +++ b/gprMax/_version.py @@ -1,4 +1,4 @@ # This is where the version number is set and read by setup.py and conf.py (for the docs) -__version__ = '3.2.0b0' +__version__ = '4.0.0b0' codename = 'Johns whisky choice!' diff --git a/gprMax/cmds_geometry/box.py b/gprMax/cmds_geometry/box.py index 91a7e9ac..11654f26 100644 --- a/gprMax/cmds_geometry/box.py +++ b/gprMax/cmds_geometry/box.py @@ -46,6 +46,13 @@ class Box(UserObjectGeometry): super().__init__(**kwargs) self.hash = '#box' + def rotate(self, pts, axis, angle, origin=None): + pts = np.array([[self.kwargs['p1']], [self.kwargs['p1']]]) + rotation = UserObjectGeometry.rotate_2point_object + rot_pts = rotation(self, pts, axis, angle, origin) + self.kwargs['p1'] = tuple(rot_pts[0, :]) + self.kwargs['p1'] = tuple(rot_pts[1, :]) + def create(self, grid, uip): try: p1 = self.kwargs['p1'] diff --git a/gprMax/cmds_geometry/cmds_geometry.py b/gprMax/cmds_geometry/cmds_geometry.py index 177d3db0..2c9d17f6 100644 --- a/gprMax/cmds_geometry/cmds_geometry.py +++ b/gprMax/cmds_geometry/cmds_geometry.py @@ -47,20 +47,54 @@ class UserObjectGeometry: """Create the object and add it to the grid.""" pass - def rotate(self, axis, angle): - """Rotate geometry object. + def rotate_point(self, p, axis, angle, origin=(0, 0, 0)): + """Rotate a point. Args: + p (array): coordinates of point (x, y, z) axis (str): axis about which to perform rotation (x, y, or z) angle (int): angle of rotation (degrees) + origin (tuple): point about which to perform rotation (x, y, z) + + Returns: + p (array): coordinates of rotated point (x, y, z) """ - orig_p1 = self.kwargs['p1'] - orig_p2 = self.kwargs['p2'] - p1 = np.array([self.kwargs['p1']]) - p2 = np.array([self.kwargs['p2']]) + origin = np.array([origin]) + + # Move point to axis of rotation + p -= origin + + # Calculate rotation matrix + r = R.from_euler(axis, angle, degrees=True) + + # Apply rotation + p = r.apply(p) + + # Move object back to original axis + p += origin + + return p + + def rotate_2point_object(self, pts, axis, angle, origin=None): + """Rotate a geometry object that is defined by 2 points. + + Args: + pts (array): coordinates of points of object to be rotated + axis (str): axis about which to perform rotation (x, y, or z) + angle (int): angle of rotation (degrees) + origin (tuple): point about which to perform rotation (x, y, z) + + Returns: + new_pts (array): coordinates of points of rotated object + """ + + # Use origin at centre of object if not given + if not origin: + origin = pts[0,:] + (pts[1,:] - pts[0,:]) / 2 # Check angle value is suitable + angle = int(angle) if angle < 0 or angle > 360: logger.exception(self.__str__() + ' angle of rotation must be between 0-360 degrees') raise ValueError @@ -68,59 +102,33 @@ class UserObjectGeometry: logger.exception(self.__str__() + ' angle of rotation must be a multiple of 90 degrees') raise ValueError + # Check axis is valid if axis != 'x' and axis != 'y' and axis != 'z': logger.exception(self.__str__() + ' axis of rotation must be x, y, or z') raise ValueError - # Coordinates for axis of rotation (centre of object) - offset = p1 + (p2 - p1) / 2 + # Save original points + orig_pts = pts - # Move object to axis of rotation - p1 -= offset - p2 -= offset - - # Calculate rotation matrix - r = R.from_euler(axis, angle, degrees=True) - - # Apply rotation - p1 = r.apply(p1) - p2 = r.apply(p2) - - # Move object back to original axis - p1 += offset - p2 += offset + # Rotate points that define object + pts[0, :] = self.rotate_point(pts[0, :], axis, angle, origin) + pts[1, :] = self.rotate_point(pts[1, :], axis, angle, origin) # Get lower left and upper right coordinates to define new object - tmp = np.concatenate((p1, p2), axis=0) - p1 = np.min(tmp, axis=0) - p2 = np.max(tmp, axis=0) + new_pts = np.zeros(pts.shape) + new_pts[0, :] = np.min(pts, axis=0) + new_pts[1, :] = np.max(pts, axis=0) - # For 2D modes check axis of rotation against mode - # and correct invariant coordinate - # mode = config.get_model_config().mode - mode = 'TMz' - if mode == 'TMx': - if axis == 'y' or axis =='z': - logger.exception(self.__str__() + - ' axis of rotation must be x for TMx mode models') - raise ValueError - p1[2] = orig_p1[0] - p2[2] = orig_p2[0] - elif mode == 'TMy': - if axis == 'x' or axis == 'z': - logger.exception(self.__str__() + - ' axis of rotation must be x for TMy mode models') - raise ValueError - p1[2] = orig_p1[1] - p2[2] = orig_p2[1] - elif mode == 'TMz': - if axis == 'x' or axis == 'y': - logger.exception(self.__str__() + - ' axis of rotation must be x for TMz mode models') - raise ValueError - p1[2] = orig_p1[2] - p2[2] = orig_p2[2] + # Reset coordinates of invariant direction + # - only needed for 2D models, has no effect on 3D models. + if axis =='x': + new_pts[0, 0] = orig_pts[0, 0] + new_pts[1, 0] = orig_pts[1, 0] + elif axis == 'y': + new_pts[0, 1] = orig_pts[0, 1] + new_pts[1, 1] = orig_pts[1, 1] + elif axis == 'z': + new_pts[0, 2] = orig_pts[0, 2] + new_pts[1, 2] = orig_pts[1, 2] - # Write points back to original tuple - self.kwargs['p1'] = tuple(p1) - self.kwargs['p2'] = tuple(p2) + return new_pts diff --git a/gprMax/cmds_geometry/cylindrical_sector.py b/gprMax/cmds_geometry/cylindrical_sector.py index 08db5fda..abd185dc 100644 --- a/gprMax/cmds_geometry/cylindrical_sector.py +++ b/gprMax/cmds_geometry/cylindrical_sector.py @@ -58,6 +58,9 @@ class CylindricalSector(UserObjectGeometry): super().__init__(**kwargs) self.hash = '#cylindrical_sector' + def rotate(self, axis, angle, origin=None): + pass + def create(self, grid, uip): try: diff --git a/gprMax/cmds_geometry/geometry_objects_read.py b/gprMax/cmds_geometry/geometry_objects_read.py index 9d334227..7abd9f82 100644 --- a/gprMax/cmds_geometry/geometry_objects_read.py +++ b/gprMax/cmds_geometry/geometry_objects_read.py @@ -36,6 +36,9 @@ class GeometryObjectsRead(UserObjectGeometry): super().__init__(**kwargs) self.hash = '#geometry_objects_read' + def rotate(self, axis, angle, origin=None): + pass + def create(self, G, uip): """Create the object and add it to the grid.""" try: diff --git a/gprMax/cmds_geometry/sphere.py b/gprMax/cmds_geometry/sphere.py index 7a126a11..fb112ed8 100644 --- a/gprMax/cmds_geometry/sphere.py +++ b/gprMax/cmds_geometry/sphere.py @@ -46,6 +46,9 @@ class Sphere(UserObjectGeometry): super().__init__(**kwargs) self.hash = '#sphere' + def rotate(self, axis, angle, origin=None): + pass + def create(self, grid, uip): try: p1 = self.kwargs['p1'] diff --git a/gprMax/cmds_geometry/triangle.py b/gprMax/cmds_geometry/triangle.py index abf1caf5..8e17f130 100644 --- a/gprMax/cmds_geometry/triangle.py +++ b/gprMax/cmds_geometry/triangle.py @@ -50,6 +50,10 @@ class Triangle(UserObjectGeometry): super().__init__(**kwargs) self.hash = '#triangle' + def rotate(self, axis, angle, origin=None): + logger.debug('Add rotate method.') + pass + def create(self, grid, uip): try: up1 = self.kwargs['p1'] diff --git a/gprMax/cmds_multiple.py b/gprMax/cmds_multiuse.py similarity index 93% rename from gprMax/cmds_multiple.py rename to gprMax/cmds_multiuse.py index ff239bcb..f70597a5 100644 --- a/gprMax/cmds_multiple.py +++ b/gprMax/cmds_multiuse.py @@ -48,6 +48,7 @@ class UserObjectMulti: self.order = None self.hash = '#example' self.autotranslate = True + self.rotate_point = UserObjectGeometry.rotate_point def __str__(self): """Readable user string as per hash commands.""" @@ -63,11 +64,11 @@ class UserObjectMulti: """Create the object and add it to the grid.""" pass - def rotate(self, axis, angle): + def rotate(self, axis, angle, origin=None): pass def params_str(self): - """Readble string of parameters given to object.""" + """Readable string of parameters given to object.""" return self.hash + ': ' + str(self.kwargs) @@ -140,6 +141,70 @@ class VoltageSource(UserObjectMulti): self.order = 2 self.hash = '#voltage_source' + def rotate(self, axis, angle, origin=(0, 0, 0)): + """Rotate geometry object. + + Args: + axis (str): axis about which to perform rotation (x, y, or z) + angle (int): angle of rotation (degrees) + origin (tuple): point about which to perform rotation (x, y, z) + """ + + # Check angle value is suitable + angle = int(angle) + if angle < 0 or angle > 360: + logger.exception( + self.__str__() + ' angle of rotation must be between 0-360 degrees') + raise ValueError + if angle % 90 != 0: + logger.exception( + self.__str__() + ' angle of rotation must be a multiple of 90 degrees') + raise ValueError + + # Check axis is valid + if axis != 'x' and axis != 'y' and axis != 'z': + logger.exception(self.__str__() + + ' axis of rotation must be x, y, or z') + raise ValueError + + # Save original point + origp = self.kwargs['p1'] + + # Rotate point + p = self.rotate_point(self, origp, axis, angle, origin) + p = np.array([p]) + + # Reset coordinates of invariant direction + # - only needed for 2D models, has no effect on 3D models. + # Set polarisation depending on rotation angle + if axis == 'x': + p[0] = origp[0] + if self.kwargs['polarisation'].lower() == 'y': + if angle == 90 or angle == 270: + self.kwargs['polarisation'] = 'z' + elif self.kwargs['polarisation'].lower() =='z': + if angle == 90 or angle == 270: + self.kwargs['polarisation'] = 'y' + elif axis == 'y': + p[1] = origp[1] + if self.kwargs['polarisation'].lower() == 'x': + if angle == 90 or angle == 270: + self.kwargs['polarisation'] = 'z' + elif self.kwargs['polarisation'].lower() == 'z': + if angle == 90 or angle == 270: + self.kwargs['polarisation'] = 'x' + elif axis == 'z': + p[2] = origp[2] + if self.kwargs['polarisation'].lower() == 'x': + if angle == 90 or angle == 270: + self.kwargs['polarisation'] = 'y' + elif self.kwargs['polarisation'].lower() == 'y': + if angle == 90 or angle == 270: + self.kwargs['polarisation'] = 'x' + + # Write point back to original tuple + self.kwargs['p1'] = tuple(p) + def create(self, grid, uip): try: p1 = self.kwargs['p1'] @@ -552,7 +617,7 @@ class Rx(UserObjectMulti): try: r.ID = self.kwargs['id'] - outputs = self.kwargs['outputs'] + outputs = [self.kwargs['outputs']] except KeyError: # If no ID or outputs are specified, use default r.ID = r.__class__.__name__ + '(' + str(r.xcoord) + ',' + str(r.ycoord) + ',' + str(r.zcoord) + ')' diff --git a/gprMax/cmds_single_use.py b/gprMax/cmds_singleuse.py similarity index 96% rename from gprMax/cmds_single_use.py rename to gprMax/cmds_singleuse.py index 7ff05f9d..540318d4 100644 --- a/gprMax/cmds_single_use.py +++ b/gprMax/cmds_singleuse.py @@ -55,7 +55,7 @@ class UserObjectSingle: def create(self, grid, uip): pass - def rotate(self, axis, angle): + def rotate(self, axis, angle, origin=None): pass diff --git a/gprMax/geometry_outputs.py b/gprMax/geometry_outputs.py index 45310152..b90e48c5 100644 --- a/gprMax/geometry_outputs.py +++ b/gprMax/geometry_outputs.py @@ -285,7 +285,24 @@ class GeometryView: if not materialsonly: # Information on PML thickness if G.pmls: - root.set('PMLthickness', list(G.pmlthickness.values())) + # Only render PMLs if they are in the geometry view + pmlstorender = dict.fromkeys(G.pmlthickness, 0) + xmax = G.nx - self.vtk_xfcells + ymax = G.ny - self.vtk_yfcells + zmax = G.nz - self.vtk_zfcells + if G.pmlthickness['x0'] - self.vtk_xscells > 0: + pmlstorender['x0'] = G.pmlthickness['x0'] + if G.pmlthickness['y0'] - self.vtk_yscells > 0: + pmlstorender['y0'] = G.pmlthickness['y0'] + if G.pmlthickness['z0'] - self.vtk_zscells > 0: + pmlstorender['z0'] = G.pmlthickness['z0'] + if self.vtk_xfcells > G.nx - G.pmlthickness['xmax']: + pmlstorender['xmax'] = G.pmlthickness['xmax'] + if self.vtk_yfcells > G.ny - G.pmlthickness['ymax']: + pmlstorender['ymax'] = G.pmlthickness['ymax'] + if self.vtk_zfcells > G.nz - G.pmlthickness['zmax']: + pmlstorender['zmax'] = G.pmlthickness['zmax'] + root.set('PMLthickness', list(pmlstorender.values())) # Location of sources and receivers srcs = G.hertziandipoles + G.magneticdipoles + G.voltagesources + G.transmissionlines if srcs: diff --git a/gprMax/hash_cmds_multiuse.py b/gprMax/hash_cmds_multiuse.py index f30713dd..31baf94b 100644 --- a/gprMax/hash_cmds_multiuse.py +++ b/gprMax/hash_cmds_multiuse.py @@ -18,7 +18,7 @@ import logging -from .cmds_multiple import (PMLCFS, AddDebyeDispersion, AddDrudeDispersion, +from .cmds_multiuse import (PMLCFS, AddDebyeDispersion, AddDrudeDispersion, AddLorentzDispersion, GeometryObjectsWrite, GeometryView, HertzianDipole, MagneticDipole, Material, Rx, RxArray, Snapshot, SoilPeplinski, @@ -128,7 +128,7 @@ def process_multicmds(multicmds): if len(tmp) == 3: rx = Rx(p1=(float(tmp[0]), float(tmp[1]), float(tmp[2]))) else: - rx = Rx(p1=(float(tmp[0]), float(tmp[1]), float(tmp[2])), id=tmp[3], outputs=tmp[4:]) + rx = Rx(p1=(float(tmp[0]), float(tmp[1]), float(tmp[2])), id=tmp[3], outputs=' '.join(tmp[4:])) scene_objects.append(rx) diff --git a/gprMax/hash_cmds_singleuse.py b/gprMax/hash_cmds_singleuse.py index 282c95ff..3d6fbb04 100644 --- a/gprMax/hash_cmds_singleuse.py +++ b/gprMax/hash_cmds_singleuse.py @@ -18,10 +18,10 @@ import logging -from .cmds_single_use import (Discretisation, Domain, ExcitationFile, - NumThreads, OutputDir, PMLCells, RxSteps, - SrcSteps, TimeStepStabilityFactor, TimeWindow, - Title) +from .cmds_singleuse import (Discretisation, Domain, ExcitationFile, + NumThreads, OutputDir, PMLCells, RxSteps, + SrcSteps, TimeStepStabilityFactor, TimeWindow, + Title) logger = logging.getLogger(__name__) diff --git a/gprMax/scene.py b/gprMax/scene.py index a4bc3353..8e1ec363 100644 --- a/gprMax/scene.py +++ b/gprMax/scene.py @@ -20,9 +20,9 @@ import logging from .cmds_geometry.cmds_geometry import UserObjectGeometry from .cmds_geometry.fractal_box_builder import FractalBoxBuilder -from .cmds_multiple import UserObjectMulti -from .cmds_single_use import (Discretisation, Domain, TimeWindow, - UserObjectSingle) +from .cmds_multiuse import UserObjectMulti +from .cmds_singleuse import (Discretisation, Domain, TimeWindow, + UserObjectSingle) from .materials import create_built_in_materials from .subgrids.user_objects import SubGridBase as SubGridUserBase from .user_inputs import create_user_input_points diff --git a/gprMax/subgrids/base.py b/gprMax/subgrids/base.py index af66ce18..bfbdfc55 100644 --- a/gprMax/subgrids/base.py +++ b/gprMax/subgrids/base.py @@ -32,7 +32,8 @@ class SubGridBase(FDTDGrid): self.ratio = kwargs['ratio'] if self.ratio % 2 == 0: - raise ValueError('Subgrid Error: Only odd ratios are supported') + logger.exception('Subgrid Error: Only odd ratios are supported') + raise ValueError # Name of the grid self.name = kwargs['id'] diff --git a/gprMax/subgrids/subgrid_hsg.py b/gprMax/subgrids/subgrid_hsg.py index cb9741fe..18605360 100644 --- a/gprMax/subgrids/subgrid_hsg.py +++ b/gprMax/subgrids/subgrid_hsg.py @@ -151,7 +151,7 @@ class SubGridHSG(SubGridBase): s += f' Working region cells: {self.nwx} x {self.nwy} x {self.nwz}\n' for h in self.hertziandipoles: - s += f' Hertizian dipole at {h.xcoord} {h.ycoord} {h.zcoord}\n' + s += f' Hertzian dipole at {h.xcoord} {h.ycoord} {h.zcoord}\n' s += ' ' + str([x for x in self.waveforms if x.ID == h.waveformID][0]) + '\n' for r in self.rxs: diff --git a/gprMax/subgrids/user_objects.py b/gprMax/subgrids/user_objects.py index f669c60e..ee636f1f 100644 --- a/gprMax/subgrids/user_objects.py +++ b/gprMax/subgrids/user_objects.py @@ -23,7 +23,7 @@ import numpy as np from gprMax import config from ..cmds_geometry.cmds_geometry import UserObjectGeometry -from ..cmds_multiple import Rx, UserObjectMulti +from ..cmds_multiuse import Rx, UserObjectMulti from .multi import ReferenceRx as ReferenceRxUser from .subgrid_hsg import SubGridHSG as SubGridHSGUser @@ -129,13 +129,13 @@ class SubGridBase(UserObjectMulti): # Copy over built in materials sg.materials = [copy(m) for m in grid.materials if m.numID in range(0, grid.n_built_in_materials + 1)] - # Dont mix and match different subgrids + # Don't mix and match different subgrid types for sg_made in grid.subgrids: if type(sg) != type(sg_made): logger.exception(self.__str__() + ' please only use one type of subgrid') raise ValueError - # Reference the sub grid under the main grid to which it belongs. + # Reference the subgrid under the main grid to which it belongs grid.subgrids.append(sg)