Merge branch 'result_custom_path_export' into 'development'
added custom path export option to Result.export_* functions Closes #172 See merge request damask/DAMASK!599
This commit is contained in:
commit
1fae90b9b5
|
@ -276,7 +276,7 @@ class Result:
|
||||||
Increment number of all increments within the given bounds.
|
Increment number of all increments within the given bounds.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
s,e = map(lambda x: int(x[10:] if isinstance(x,str) and x.startswith(prefix_inc) else x),
|
s,e = map(lambda x: int(x.split(prefix_inc)[-1] if isinstance(x,str) and x.startswith(prefix_inc) else x),
|
||||||
(self.incs[ 0] if start is None else start,
|
(self.incs[ 0] if start is None else start,
|
||||||
self.incs[-1] if end is None else end))
|
self.incs[-1] if end is None else end))
|
||||||
return [i for i in self.incs if s <= i <= e]
|
return [i for i in self.incs if s <= i <= e]
|
||||||
|
@ -1516,7 +1516,9 @@ class Result:
|
||||||
|
|
||||||
|
|
||||||
def export_XDMF(self,
|
def export_XDMF(self,
|
||||||
output: Union[str, List[str]] = '*'):
|
output: Union[str, List[str]] = '*',
|
||||||
|
target_dir: Union[str, Path] = None,
|
||||||
|
absolute_path: bool = False):
|
||||||
"""
|
"""
|
||||||
Write XDMF file to directly visualize data from DADF5 file.
|
Write XDMF file to directly visualize data from DADF5 file.
|
||||||
|
|
||||||
|
@ -1529,12 +1531,17 @@ class Result:
|
||||||
output : (list of) str
|
output : (list of) str
|
||||||
Names of the datasets included in the XDMF file.
|
Names of the datasets included in the XDMF file.
|
||||||
Defaults to '*', in which case all datasets are considered.
|
Defaults to '*', in which case all datasets are considered.
|
||||||
|
target_dir : str or pathlib.Path, optional
|
||||||
|
Directory to save XDMF file. Will be created if non-existent.
|
||||||
|
absolute_path : bool, optional
|
||||||
|
Store absolute (instead of relative) path to DADF5 file.
|
||||||
|
Defaults to False, i.e. the XDMF file expects the
|
||||||
|
DADF5 file at a stable relative path.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.N_constituents != 1 or len(self.phases) != 1 or not self.structured:
|
if self.N_constituents != 1 or len(self.phases) != 1 or not self.structured:
|
||||||
raise TypeError('XDMF output requires structured grid with single phase and single constituent.')
|
raise TypeError('XDMF output requires structured grid with single phase and single constituent.')
|
||||||
|
|
||||||
|
|
||||||
attribute_type_map = defaultdict(lambda:'Matrix', ( ((),'Scalar'), ((3,),'Vector'), ((3,3),'Tensor')) )
|
attribute_type_map = defaultdict(lambda:'Matrix', ( ((),'Scalar'), ((3,),'Vector'), ((3,3),'Tensor')) )
|
||||||
|
|
||||||
def number_type_map(dtype):
|
def number_type_map(dtype):
|
||||||
|
@ -1567,6 +1574,11 @@ class Result:
|
||||||
attributes = []
|
attributes = []
|
||||||
data_items = []
|
data_items = []
|
||||||
|
|
||||||
|
hdf5_name = self.fname.name
|
||||||
|
hdf5_dir = self.fname.parent
|
||||||
|
xdmf_dir = Path.cwd() if target_dir is None else Path(target_dir)
|
||||||
|
hdf5_link = (hdf5_dir if absolute_path else Path(os.path.relpath(hdf5_dir,xdmf_dir.resolve())))/hdf5_name
|
||||||
|
|
||||||
with h5py.File(self.fname,'r') as f:
|
with h5py.File(self.fname,'r') as f:
|
||||||
for inc in self.visible['increments']:
|
for inc in self.visible['increments']:
|
||||||
|
|
||||||
|
@ -1601,8 +1613,7 @@ class Result:
|
||||||
data_items[-1].attrib = {'Format': 'HDF',
|
data_items[-1].attrib = {'Format': 'HDF',
|
||||||
'Precision': '8',
|
'Precision': '8',
|
||||||
'Dimensions': '{} {} {} 3'.format(*(self.cells[::-1]+1))}
|
'Dimensions': '{} {} {} 3'.format(*(self.cells[::-1]+1))}
|
||||||
data_items[-1].text = f'{os.path.split(self.fname)[1]}:/{inc}/geometry/u_n'
|
data_items[-1].text = f'{hdf5_link}:/{inc}/geometry/u_n'
|
||||||
|
|
||||||
for ty in ['phase','homogenization']:
|
for ty in ['phase','homogenization']:
|
||||||
for label in self.visible[ty+'s']:
|
for label in self.visible[ty+'s']:
|
||||||
for field in _match(self.visible['fields'],f['/'.join([inc,ty,label])].keys()):
|
for field in _match(self.visible['fields'],f['/'.join([inc,ty,label])].keys()):
|
||||||
|
@ -1624,9 +1635,10 @@ class Result:
|
||||||
'Precision': f'{dtype.itemsize}',
|
'Precision': f'{dtype.itemsize}',
|
||||||
'Dimensions': '{} {} {} {}'.format(*self.cells[::-1],1 if shape == () else
|
'Dimensions': '{} {} {} {}'.format(*self.cells[::-1],1 if shape == () else
|
||||||
np.prod(shape))}
|
np.prod(shape))}
|
||||||
data_items[-1].text = f'{os.path.split(self.fname)[1]}:{name}'
|
data_items[-1].text = f'{hdf5_link}:{name}'
|
||||||
|
|
||||||
with util.open_text(self.fname.with_suffix('.xdmf').name,'w') as f:
|
xdmf_dir.mkdir(parents=True,exist_ok=True)
|
||||||
|
with util.open_text((xdmf_dir/hdf5_name).with_suffix('.xdmf'),'w') as f:
|
||||||
f.write(xml.dom.minidom.parseString(ET.tostring(xdmf).decode()).toprettyxml())
|
f.write(xml.dom.minidom.parseString(ET.tostring(xdmf).decode()).toprettyxml())
|
||||||
|
|
||||||
|
|
||||||
|
@ -1654,6 +1666,7 @@ class Result:
|
||||||
output: Union[str,list] = '*',
|
output: Union[str,list] = '*',
|
||||||
mode: str = 'cell',
|
mode: str = 'cell',
|
||||||
constituents: IntSequence = None,
|
constituents: IntSequence = None,
|
||||||
|
target_dir: Union[str, Path] = None,
|
||||||
fill_float: float = np.nan,
|
fill_float: float = np.nan,
|
||||||
fill_int: int = 0,
|
fill_int: int = 0,
|
||||||
parallel: bool = True):
|
parallel: bool = True):
|
||||||
|
@ -1676,6 +1689,8 @@ class Result:
|
||||||
constituents : (list of) int, optional
|
constituents : (list of) int, optional
|
||||||
Constituents to consider.
|
Constituents to consider.
|
||||||
Defaults to None, in which case all constituents are considered.
|
Defaults to None, in which case all constituents are considered.
|
||||||
|
target_dir : str or pathlib.Path, optional
|
||||||
|
Directory to save VTK files. Will be created if non-existent.
|
||||||
fill_float : float
|
fill_float : float
|
||||||
Fill value for non-existent entries of floating point type.
|
Fill value for non-existent entries of floating point type.
|
||||||
Defaults to NaN.
|
Defaults to NaN.
|
||||||
|
@ -1706,6 +1721,9 @@ class Result:
|
||||||
|
|
||||||
at_cell_ph,in_data_ph,at_cell_ho,in_data_ho = self._mappings()
|
at_cell_ph,in_data_ph,at_cell_ho,in_data_ho = self._mappings()
|
||||||
|
|
||||||
|
vtk_dir = Path.cwd() if target_dir is None else Path(target_dir)
|
||||||
|
vtk_dir.mkdir(parents=True,exist_ok=True)
|
||||||
|
|
||||||
with h5py.File(self.fname,'r') as f:
|
with h5py.File(self.fname,'r') as f:
|
||||||
if self.version_minor >= 13:
|
if self.version_minor >= 13:
|
||||||
creator = f.attrs['creator'] if h5py3 else f.attrs['creator'].decode()
|
creator = f.attrs['creator'] if h5py3 else f.attrs['creator'].decode()
|
||||||
|
@ -1744,8 +1762,9 @@ class Result:
|
||||||
for label,dataset in outs.items():
|
for label,dataset in outs.items():
|
||||||
v = v.set(' / '.join(['/'.join([ty,field,label]),dataset.dtype.metadata['unit']]),dataset)
|
v = v.set(' / '.join(['/'.join([ty,field,label]),dataset.dtype.metadata['unit']]),dataset)
|
||||||
|
|
||||||
v.save(f'{self.fname.stem}_inc{inc[10:].zfill(N_digits)}',parallel=parallel)
|
|
||||||
|
|
||||||
|
v.save(vtk_dir/f'{self.fname.stem}_inc{inc.split(prefix_inc)[-1].zfill(N_digits)}',
|
||||||
|
parallel=parallel)
|
||||||
|
|
||||||
def get(self,
|
def get(self,
|
||||||
output: Union[str, List[str]] = '*',
|
output: Union[str, List[str]] = '*',
|
||||||
|
@ -1890,7 +1909,9 @@ class Result:
|
||||||
|
|
||||||
def export_setup(self,
|
def export_setup(self,
|
||||||
output: Union[str, List[str]] = '*',
|
output: Union[str, List[str]] = '*',
|
||||||
overwrite: bool = False):
|
target_dir: Union[str, Path] = None,
|
||||||
|
overwrite: bool = False,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Export configuration files.
|
Export configuration files.
|
||||||
|
|
||||||
|
@ -1899,21 +1920,35 @@ class Result:
|
||||||
output : (list of) str, optional
|
output : (list of) str, optional
|
||||||
Names of the datasets to export to the file.
|
Names of the datasets to export to the file.
|
||||||
Defaults to '*', in which case all datasets are exported.
|
Defaults to '*', in which case all datasets are exported.
|
||||||
|
target_dir : str or pathlib.Path, optional
|
||||||
|
Directory to save configuration files. Will be created if non-existent.
|
||||||
overwrite : bool, optional
|
overwrite : bool, optional
|
||||||
Overwrite existing configuration files.
|
Overwrite existing configuration files.
|
||||||
Defaults to False.
|
Defaults to False.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def export(name: str, obj: Union[h5py.Dataset,h5py.Group], output: Union[str,List[str]], overwrite: bool):
|
def export(name: str,
|
||||||
|
obj: Union[h5py.Dataset,h5py.Group],
|
||||||
|
output: Union[str,List[str]],
|
||||||
|
cfg_dir: Path,
|
||||||
|
overwrite: bool):
|
||||||
|
|
||||||
|
cfg = cfg_dir/name
|
||||||
|
|
||||||
if type(obj) == h5py.Dataset and _match(output,[name]):
|
if type(obj) == h5py.Dataset and _match(output,[name]):
|
||||||
d = obj.attrs['description'] if h5py3 else obj.attrs['description'].decode()
|
d = obj.attrs['description'] if h5py3 else obj.attrs['description'].decode()
|
||||||
if not Path(name).exists() or overwrite:
|
if overwrite or not cfg.exists():
|
||||||
with util.open_text(name,'w') as f_out: f_out.write(obj[0].decode())
|
with util.open_text(cfg,'w') as f_out: f_out.write(obj[0].decode())
|
||||||
print(f'Exported {d} to "{name}".')
|
print(f'Exported {d} to "{cfg}".')
|
||||||
else:
|
else:
|
||||||
print(f'"{name}" exists, {d} not exported.')
|
print(f'"{cfg}" exists, {d} not exported.')
|
||||||
elif type(obj) == h5py.Group:
|
elif type(obj) == h5py.Group:
|
||||||
os.makedirs(name, exist_ok=True)
|
cfg.mkdir(parents=True,exist_ok=True)
|
||||||
|
|
||||||
|
cfg_dir = (Path.cwd() if target_dir is None else Path(target_dir))
|
||||||
|
cfg_dir.mkdir(parents=True,exist_ok=True)
|
||||||
with h5py.File(self.fname,'r') as f_in:
|
with h5py.File(self.fname,'r') as f_in:
|
||||||
f_in['setup'].visititems(partial(export,output=output,overwrite=overwrite))
|
f_in['setup'].visititems(partial(export,
|
||||||
|
output=output,
|
||||||
|
cfg_dir=cfg_dir,
|
||||||
|
overwrite=overwrite))
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -323,12 +323,9 @@ class TestResult:
|
||||||
created_first = last.place('sigma').dtype.metadata['created']
|
created_first = last.place('sigma').dtype.metadata['created']
|
||||||
created_first = datetime.strptime(created_first,'%Y-%m-%d %H:%M:%S%z')
|
created_first = datetime.strptime(created_first,'%Y-%m-%d %H:%M:%S%z')
|
||||||
|
|
||||||
if overwrite == 'on':
|
last = last.view(protected=overwrite != 'on')
|
||||||
last = last.view(protected=False)
|
|
||||||
else:
|
|
||||||
last = last.view(protected=True)
|
|
||||||
|
|
||||||
time.sleep(2.)
|
time.sleep(2)
|
||||||
try:
|
try:
|
||||||
last.add_calculation('#sigma#*0.0+311.','sigma','not the Cauchy stress')
|
last.add_calculation('#sigma#*0.0+311.','sigma','not the Cauchy stress')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -380,13 +377,12 @@ class TestResult:
|
||||||
@pytest.mark.xfail(int(vtk.vtkVersion.GetVTKVersion().split('.')[0])<9, reason='missing "Direction" attribute')
|
@pytest.mark.xfail(int(vtk.vtkVersion.GetVTKVersion().split('.')[0])<9, reason='missing "Direction" attribute')
|
||||||
def test_vtk(self,request,tmp_path,ref_path,update,patch_execution_stamp,patch_datetime_now,output,fname,inc):
|
def test_vtk(self,request,tmp_path,ref_path,update,patch_execution_stamp,patch_datetime_now,output,fname,inc):
|
||||||
result = Result(ref_path/fname).view(increments=inc)
|
result = Result(ref_path/fname).view(increments=inc)
|
||||||
os.chdir(tmp_path)
|
result.export_VTK(output,target_dir=tmp_path,parallel=False)
|
||||||
result.export_VTK(output,parallel=False)
|
|
||||||
fname = fname.split('.')[0]+f'_inc{(inc if type(inc) == int else inc[0]):0>2}.vti'
|
fname = fname.split('.')[0]+f'_inc{(inc if type(inc) == int else inc[0]):0>2}.vti'
|
||||||
v = VTK.load(tmp_path/fname)
|
v = VTK.load(tmp_path/fname)
|
||||||
v.comments = 'n/a'
|
v.comments = 'n/a'
|
||||||
v.save(tmp_path/fname,parallel=False)
|
v.save(tmp_path/fname,parallel=False)
|
||||||
with open(fname) as f:
|
with open(tmp_path/fname) as f:
|
||||||
cur = hashlib.md5(f.read().encode()).hexdigest()
|
cur = hashlib.md5(f.read().encode()).hexdigest()
|
||||||
if update:
|
if update:
|
||||||
with open((ref_path/'export_VTK'/request.node.name).with_suffix('.md5'),'w') as f:
|
with open((ref_path/'export_VTK'/request.node.name).with_suffix('.md5'),'w') as f:
|
||||||
|
@ -416,34 +412,34 @@ class TestResult:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
single_phase.export_VTK(mode='invalid')
|
single_phase.export_VTK(mode='invalid')
|
||||||
|
|
||||||
|
def test_vtk_custom_path(self,tmp_path,single_phase):
|
||||||
|
export_dir = tmp_path/'export_dir'
|
||||||
|
single_phase.export_VTK(mode='point',target_dir=export_dir,parallel=False)
|
||||||
|
assert set(os.listdir(export_dir))==set([f'{single_phase.fname.stem}_inc{i:02}.vtp' for i in range(0,40+1,4)])
|
||||||
|
|
||||||
def test_XDMF_datatypes(self,tmp_path,single_phase,update,ref_path):
|
def test_XDMF_datatypes(self,tmp_path,single_phase,update,ref_path):
|
||||||
for shape in [('scalar',()),('vector',(3,)),('tensor',(3,3)),('matrix',(12,))]:
|
for what,shape in {'scalar':(),'vector':(3,),'tensor':(3,3),'matrix':(12,)}.items():
|
||||||
for dtype in ['f4','f8','i1','i2','i4','i8','u1','u2','u4','u8']:
|
for dtype in ['f4','f8','i1','i2','i4','i8','u1','u2','u4','u8']:
|
||||||
single_phase.add_calculation(f"np.ones(np.shape(#F#)[0:1]+{shape[1]},'{dtype}')",f'{shape[0]}_{dtype}')
|
single_phase.add_calculation(f"np.ones(np.shape(#F#)[0:1]+{shape},'{dtype}')",f'{what}_{dtype}')
|
||||||
fname = os.path.splitext(os.path.basename(single_phase.fname))[0]+'.xdmf'
|
xdmf_path = tmp_path/single_phase.fname.with_suffix('.xdmf').name
|
||||||
os.chdir(tmp_path)
|
single_phase.export_XDMF(target_dir=tmp_path)
|
||||||
single_phase.export_XDMF()
|
|
||||||
if update:
|
if update:
|
||||||
shutil.copy(tmp_path/fname,ref_path/fname)
|
shutil.copy(xdmf_path,ref_path/xdmf_path.name)
|
||||||
|
assert sorted(open(xdmf_path).read()) == sorted(open(ref_path/xdmf_path.name).read())
|
||||||
assert sorted(open(tmp_path/fname).read()) == sorted(open(ref_path/fname).read()) # XML is not ordered
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not (hasattr(vtk,'vtkXdmfReader') and hasattr(vtk.vtkXdmfReader(),'GetOutput')),
|
@pytest.mark.skipif(not (hasattr(vtk,'vtkXdmfReader') and hasattr(vtk.vtkXdmfReader(),'GetOutput')),
|
||||||
reason='https://discourse.vtk.org/t/2450')
|
reason='https://discourse.vtk.org/t/2450')
|
||||||
def test_XDMF_shape(self,tmp_path,single_phase):
|
def test_XDMF_shape(self,tmp_path,single_phase):
|
||||||
os.chdir(tmp_path)
|
single_phase.export_XDMF(target_dir=single_phase.fname.parent)
|
||||||
|
fname = single_phase.fname.with_suffix('.xdmf')
|
||||||
single_phase.export_XDMF()
|
|
||||||
fname = os.path.splitext(os.path.basename(single_phase.fname))[0]+'.xdmf'
|
|
||||||
reader_xdmf = vtk.vtkXdmfReader()
|
reader_xdmf = vtk.vtkXdmfReader()
|
||||||
reader_xdmf.SetFileName(fname)
|
reader_xdmf.SetFileName(fname)
|
||||||
reader_xdmf.Update()
|
reader_xdmf.Update()
|
||||||
dim_xdmf = reader_xdmf.GetOutput().GetDimensions()
|
dim_xdmf = reader_xdmf.GetOutput().GetDimensions()
|
||||||
bounds_xdmf = reader_xdmf.GetOutput().GetBounds()
|
bounds_xdmf = reader_xdmf.GetOutput().GetBounds()
|
||||||
|
|
||||||
single_phase.view(increments=0).export_VTK(parallel=False)
|
single_phase.view(increments=0).export_VTK(target_dir=single_phase.fname.parent,parallel=False)
|
||||||
fname = os.path.splitext(os.path.basename(single_phase.fname))[0]+'_inc00.vti'
|
fname = single_phase.fname.with_name(single_phase.fname.stem+'_inc00.vti')
|
||||||
reader_vti = vtk.vtkXMLImageDataReader()
|
reader_vti = vtk.vtkXMLImageDataReader()
|
||||||
reader_vti.SetFileName(fname)
|
reader_vti.SetFileName(fname)
|
||||||
reader_vti.Update()
|
reader_vti.Update()
|
||||||
|
@ -455,6 +451,40 @@ class TestResult:
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
default.export_XDMF()
|
default.export_XDMF()
|
||||||
|
|
||||||
|
def test_XDMF_custom_path(self,single_phase,tmp_path):
|
||||||
|
os.chdir(tmp_path)
|
||||||
|
single_phase.export_XDMF()
|
||||||
|
assert single_phase.fname.with_suffix('.xdmf').name in os.listdir(tmp_path)
|
||||||
|
export_dir = tmp_path/'export_dir'
|
||||||
|
single_phase.export_XDMF(target_dir=export_dir)
|
||||||
|
assert single_phase.fname.with_suffix('.xdmf').name in os.listdir(export_dir)
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not (hasattr(vtk,'vtkXdmfReader') and hasattr(vtk.vtkXdmfReader(),'GetOutput')),
|
||||||
|
reason='https://discourse.vtk.org/t/2450')
|
||||||
|
def test_XDMF_relabs_path(self,single_phase,tmp_path):
|
||||||
|
def dims(xdmf):
|
||||||
|
reader_xdmf = vtk.vtkXdmfReader()
|
||||||
|
reader_xdmf.SetFileName(xdmf)
|
||||||
|
reader_xdmf.Update()
|
||||||
|
return reader_xdmf.GetOutput().GetDimensions()
|
||||||
|
|
||||||
|
single_phase.export_XDMF(target_dir=tmp_path)
|
||||||
|
xdmfname = single_phase.fname.with_suffix('.xdmf').name
|
||||||
|
ref_dims = dims(tmp_path/xdmfname)
|
||||||
|
|
||||||
|
for (d,info) in {
|
||||||
|
'A': dict(absolute_path=True,
|
||||||
|
mv='..',
|
||||||
|
),
|
||||||
|
'B': dict(absolute_path=False,
|
||||||
|
mv='../A',
|
||||||
|
),
|
||||||
|
}.items():
|
||||||
|
sub = tmp_path/d; sub.mkdir(exist_ok=True)
|
||||||
|
single_phase.export_XDMF(target_dir=sub,absolute_path=info['absolute_path'])
|
||||||
|
os.replace(sub/xdmfname,sub/info['mv']/xdmfname)
|
||||||
|
assert ref_dims == dims(sub/info['mv']/xdmfname)
|
||||||
|
|
||||||
@pytest.mark.parametrize('view,output,flatten,prune',
|
@pytest.mark.parametrize('view,output,flatten,prune',
|
||||||
[({},['F','P','F','L_p','F_e','F_p'],True,True),
|
[({},['F','P','F','L_p','F_e','F_p'],True,True),
|
||||||
({'increments':3},'F',True,True),
|
({'increments':3},'F',True,True),
|
||||||
|
@ -511,7 +541,17 @@ class TestResult:
|
||||||
@pytest.mark.parametrize('output',['material.yaml','*'])
|
@pytest.mark.parametrize('output',['material.yaml','*'])
|
||||||
@pytest.mark.parametrize('overwrite',[True,False])
|
@pytest.mark.parametrize('overwrite',[True,False])
|
||||||
def test_export_setup(self,ref_path,tmp_path,fname,output,overwrite):
|
def test_export_setup(self,ref_path,tmp_path,fname,output,overwrite):
|
||||||
os.chdir(tmp_path)
|
|
||||||
r = Result(ref_path/fname)
|
r = Result(ref_path/fname)
|
||||||
r.export_setup(output,overwrite)
|
r.export_setup(output,target_dir=tmp_path,overwrite=overwrite)
|
||||||
r.export_setup(output,overwrite)
|
|
||||||
|
def test_export_setup_custom_path(self,ref_path,tmp_path):
|
||||||
|
src = ref_path/'4grains2x4x3_compressionY.hdf5'
|
||||||
|
subdir = 'export_dir'
|
||||||
|
absdir = tmp_path/subdir
|
||||||
|
absdir.mkdir()
|
||||||
|
|
||||||
|
r = Result(src)
|
||||||
|
for t,cwd in zip([absdir,subdir,None],[tmp_path,tmp_path,absdir]):
|
||||||
|
os.chdir(cwd)
|
||||||
|
r.export_setup('material.yaml',target_dir=t)
|
||||||
|
assert 'material.yaml' in os.listdir(absdir); (absdir/'material.yaml').unlink()
|
||||||
|
|
Loading…
Reference in New Issue